From 32af6cb0cff91b4eade5eb9f7cdaaff60c92484c Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:18:15 +0300 Subject: [PATCH 01/75] Added support for Command/Meta key --- src/c_bind.cpp | 4 ++++ src/c_console.cpp | 4 ++++ src/ct_chat.cpp | 8 ++++++++ src/d_gui.h | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/c_bind.cpp b/src/c_bind.cpp index 0744efeee..66ed14ca5 100644 --- a/src/c_bind.cpp +++ b/src/c_bind.cpp @@ -230,7 +230,11 @@ const char *KeyNames[NUM_KEYS] = NULL, NULL, NULL, NULL, NULL, "pause", NULL, "home", //C0 "uparrow", "pgup", NULL, "leftarrow",NULL, "rightarrow",NULL, "end", //C8 "downarrow","pgdn", "ins", "del", NULL, NULL, NULL, NULL, //D0 +#ifdef __APPLE__ + NULL, NULL, NULL, "command", NULL, "apps", "power", "sleep", //D8 +#else // !__APPLE__ NULL, NULL, NULL, "lwin", "rwin", "apps", "power", "sleep", //D8 +#endif // __APPLE__ NULL, NULL, NULL, "wake", NULL, "search", "favorites","refresh", //E0 "webstop", "webforward","webback", "mycomputer","mail", "mediaselect",NULL, NULL, //E8 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, //F0 diff --git a/src/c_console.cpp b/src/c_console.cpp index e7c7dc30b..873cf5d7b 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -1421,7 +1421,11 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len) case 'V': TabbedLast = false; TabbedList = false; +#ifdef __APPLE__ + if (ev->data3 & GKM_META) +#else // !__APPLE__ if (ev->data3 & GKM_CTRL) +#endif // __APPLE__ { if (data1 == 'C') { // copy to clipboard diff --git a/src/ct_chat.cpp b/src/ct_chat.cpp index c0c1f13f0..29edf3bcc 100644 --- a/src/ct_chat.cpp +++ b/src/ct_chat.cpp @@ -145,12 +145,20 @@ bool CT_Responder (event_t *ev) CT_BackSpace (); return true; } +#ifdef __APPLE__ + else if (ev->data1 == 'C' && (ev->data3 & GKM_META)) +#else // !__APPLE__ else if (ev->data1 == 'C' && (ev->data3 & GKM_CTRL)) +#endif // __APPLE__ { I_PutInClipboard ((char *)ChatQueue); return true; } +#ifdef __APPLE__ + else if (ev->data1 == 'V' && (ev->data3 & GKM_META)) +#else // !__APPLE__ else if (ev->data1 == 'V' && (ev->data3 & GKM_CTRL)) +#endif // __APPLE__ { CT_PasteChat(I_GetFromClipboard(false)); } diff --git a/src/d_gui.h b/src/d_gui.h index b88041771..86c3761ed 100644 --- a/src/d_gui.h +++ b/src/d_gui.h @@ -70,7 +70,8 @@ enum GUIKeyModifiers GKM_SHIFT = 1, GKM_CTRL = 2, GKM_ALT = 4, - GKM_LBUTTON = 8 + GKM_META = 8, + GKM_LBUTTON = 16 }; // Special codes for some GUI keys, including a few real ASCII codes. From 39e2ebe42502f674c1ea8d386355c6c47a606a11 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:19:07 +0300 Subject: [PATCH 02/75] Fixed GNU inline assembly for Clang --- src/gccinlines.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gccinlines.h b/src/gccinlines.h index b905449de..ecdf45df6 100644 --- a/src/gccinlines.h +++ b/src/gccinlines.h @@ -41,7 +41,7 @@ static inline SDWORD Scale (SDWORD a, SDWORD b, SDWORD c) : "a,a,a,a,a,a" (a), "m,r,m,r,d,d" (b), "r,r,m,m,r,m" (c) - : "%cc" + : "cc" ); return result; @@ -59,7 +59,7 @@ static inline SDWORD MulScale (SDWORD a, SDWORD b, SDWORD c) : "a,a,a,a" (a), "m,r,m,r" (b), "c,c,I,I" (c) - : "%cc" + : "cc" ); return result; } @@ -210,7 +210,7 @@ static inline SDWORD DivScale (SDWORD a, SDWORD b, SDWORD c) : "a" (lo), "d" (hi), "r" (b) - : "%cc"); + : "cc"); return result; } @@ -226,7 +226,7 @@ static inline SDWORD DivScale1 (SDWORD a, SDWORD b) "=&d,d" (dummy) : "a,a" (a), "r,m" (b) - : "%cc"); + : "cc"); return result; } @@ -241,7 +241,7 @@ static inline SDWORD DivScale1 (SDWORD a, SDWORD b) : "a,a" (a<>(32-s)), \ "r,m" (b) \ - : "%cc"); \ + : "cc"); \ return result; \ } @@ -287,7 +287,7 @@ static inline SDWORD DivScale32 (SDWORD a, SDWORD b) "=d,d" (dummy) : "d,d" (a), "r,m" (b) - : "%cc"); + : "cc"); return result; } @@ -313,7 +313,7 @@ static inline void clearbufshort (void *buff, unsigned int count, WORD clear) "rep stosw" :"=D" (buff), "=c" (count) :"D" (buff), "c" (count), "a" (clear|(clear<<16)) - :"%cc"); + :"cc"); } static inline SDWORD ksgn (SDWORD a) @@ -327,6 +327,6 @@ static inline SDWORD ksgn (SDWORD a) "adc $0,%1" :"=r" (dummy), "=r" (result) :"0" (a) - :"%cc"); + :"cc"); return result; } From 0a5dd940728ed9f94a74ddd92b394aabc23f20c9 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:20:30 +0300 Subject: [PATCH 03/75] Fixed whole program (link-time) optimization build with Clang --- src/g_level.h | 2 +- src/thingdef/thingdef.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/g_level.h b/src/g_level.h index 0cd366798..ec867c226 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -52,7 +52,7 @@ class FScanner; #define GCC_YSEG #else #define MSVC_YSEG -#define GCC_YSEG __attribute__((section(SECTION_YREG))) +#define GCC_YSEG __attribute__((section(SECTION_YREG))) __attribute__((used)) #endif struct FIntermissionDescriptor; diff --git a/src/thingdef/thingdef.h b/src/thingdef/thingdef.h index c388a8544..5225b787a 100644 --- a/src/thingdef/thingdef.h +++ b/src/thingdef/thingdef.h @@ -266,11 +266,11 @@ enum EDefinitionType #define GCC_MSEG #else #define MSVC_ASEG -#define GCC_ASEG __attribute__((section(SECTION_AREG))) +#define GCC_ASEG __attribute__((section(SECTION_AREG))) __attribute__((used)) #define MSVC_PSEG -#define GCC_PSEG __attribute__((section(SECTION_GREG))) +#define GCC_PSEG __attribute__((section(SECTION_GREG))) __attribute__((used)) #define MSVC_MSEG -#define GCC_MSEG __attribute__((section(SECTION_MREG))) +#define GCC_MSEG __attribute__((section(SECTION_MREG))) __attribute__((used)) #endif From 2efb62e8ef75e53229ad277fdbae57dba139e09a Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:21:00 +0300 Subject: [PATCH 04/75] Added work-around for vectorization issue in Apple's GCC 4.x --- src/nodebuild_utility.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/nodebuild_utility.cpp b/src/nodebuild_utility.cpp index e7d5865d5..14ab7be58 100644 --- a/src/nodebuild_utility.cpp +++ b/src/nodebuild_utility.cpp @@ -69,7 +69,13 @@ static const int PO_LINE_EXPLICIT = 5; angle_t FNodeBuilder::PointToAngle (fixed_t x, fixed_t y) { const double rad2bam = double(1<<30) / M_PI; +#if defined __APPLE__ && !defined __llvm__ + // Work-around for vectorization issue in Apple's GCC 4.x + // See https://gcc.gnu.org/wiki/Math_Optimization_Flags for details + long double ang = atan2l (double(y), double(x)); +#else // !__APPLE__ || __llvm__ double ang = atan2 (double(y), double(x)); +#endif // __APPLE__ && !__llvm__ return angle_t(ang * rad2bam) << 1; } From 906102c3b6112e83514dc0a58a8b1bf1026da796 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:23:08 +0300 Subject: [PATCH 05/75] Added HID Utilities source code https://developer.apple.com/library/mac/samplecode/HID_Utilities/Introduction/Intro.html https://developer.apple.com/library/mac/samplecode/HID_Utilities/HID_Utilities.zip --- src/cocoa/HID_Config_Utilities.c | 919 +++++++++++++++++++++ src/cocoa/HID_Error_Handler.c | 101 +++ src/cocoa/HID_Name_Lookup.c | 1203 ++++++++++++++++++++++++++++ src/cocoa/HID_Queue_Utilities.c | 354 ++++++++ src/cocoa/HID_Utilities.c | 1061 ++++++++++++++++++++++++ src/cocoa/HID_Utilities_External.h | 417 ++++++++++ src/cocoa/IOHIDDevice_.c | 544 +++++++++++++ src/cocoa/IOHIDDevice_.h | 422 ++++++++++ src/cocoa/IOHIDElement_.c | 502 ++++++++++++ src/cocoa/IOHIDElement_.h | 339 ++++++++ src/cocoa/IOHIDLib_.h | 111 +++ src/cocoa/ImmrHIDUtilAddOn.c | 101 +++ src/cocoa/ImmrHIDUtilAddOn.h | 50 ++ 13 files changed, 6124 insertions(+) create mode 100755 src/cocoa/HID_Config_Utilities.c create mode 100755 src/cocoa/HID_Error_Handler.c create mode 100755 src/cocoa/HID_Name_Lookup.c create mode 100755 src/cocoa/HID_Queue_Utilities.c create mode 100755 src/cocoa/HID_Utilities.c create mode 100755 src/cocoa/HID_Utilities_External.h create mode 100755 src/cocoa/IOHIDDevice_.c create mode 100755 src/cocoa/IOHIDDevice_.h create mode 100755 src/cocoa/IOHIDElement_.c create mode 100755 src/cocoa/IOHIDElement_.h create mode 100755 src/cocoa/IOHIDLib_.h create mode 100755 src/cocoa/ImmrHIDUtilAddOn.c create mode 100755 src/cocoa/ImmrHIDUtilAddOn.h diff --git a/src/cocoa/HID_Config_Utilities.c b/src/cocoa/HID_Config_Utilities.c new file mode 100755 index 000000000..6cd1ad56a --- /dev/null +++ b/src/cocoa/HID_Config_Utilities.c @@ -0,0 +1,919 @@ +// File: HID_Config_Utilities.c +// Abstract: Implementation of the HID configuration utilities +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#define LOG_SCORING 0 + +#include // malloc +#include // clock + +#include + +#include "HID_Utilities_External.h" + +// --------------------------------- + +// polls single device's elements for a change greater than kPercentMove. Times out after given time +// returns 1 and pointer to element if found +// returns 0 and NULL for both parameters if not found + +unsigned char HIDConfigureSingleDeviceAction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef, float timeout) { + if ( !inIOHIDDeviceRef ) { + return (0); + } + if ( 0 == HIDHaveDeviceList() ) { // if we do not have a device list + return (0); // return 0 + } + + Boolean found = FALSE; + + // build list of device and elements to save current values + int maxElements = HIDCountDeviceElements(inIOHIDDeviceRef, kHIDElementTypeInput); + int *saveValueArray = (int *) calloc( maxElements, sizeof(int) ); // 2D array to save values + + // store initial values on first pass / compare to initial value on subsequent passes + Boolean first = TRUE; + + // get all the elements from this device + CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + // if that worked... + if ( elementCFArrayRef ) { + clock_t start = clock(), end; + + // poll all devices and elements + while ( !found ) { + int currElementIndex = 0; + CFIndex idx, cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + *outIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !*outIOHIDElementRef ) { + continue; + } + + // is this an input element? + IOHIDElementType type = IOHIDElementGetType(*outIOHIDElementRef); + + switch ( type ) { + // these types are inputs + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + default: + { + break; + } + case kIOHIDElementTypeOutput: + case kIOHIDElementTypeFeature: + case kIOHIDElementTypeCollection: + { + *outIOHIDElementRef = NULL; // these types are not ( Skip them ) + break; + } + } /* switch */ + if ( !*outIOHIDElementRef ) { + continue; // skip this element + } + + // get this elements current value + int value = 0; // default value is zero + IOHIDValueRef tIOHIDValueRef; + IOReturn ioReturn = IOHIDDeviceGetValue(inIOHIDDeviceRef, *outIOHIDElementRef, &tIOHIDValueRef); + if ( kIOReturnSuccess == ioReturn ) { + value = IOHIDValueGetScaledValue(tIOHIDValueRef, kIOHIDValueScaleTypePhysical); + } + if ( first ) { + saveValueArray[currElementIndex] = value; + } else { + CFIndex min = IOHIDElementGetLogicalMin(*outIOHIDElementRef); + CFIndex max = IOHIDElementGetLogicalMax(*outIOHIDElementRef); + + int initialValue = saveValueArray[currElementIndex]; + int delta = (float)(max - min) * kPercentMove * 0.01; + // is the new value within +/- delta of the initial value? + if ( ( (initialValue + delta) < value ) || ( (initialValue - delta) > value ) ) { + found = 1; // ( yes! ) mark as found + break; + } + } // if ( first ) + + currElementIndex++; // bump element index + } // next idx + + first = FALSE; // no longer the first pass + + // are we done? + end = clock(); + double secs = (double)(end - start) / CLOCKS_PER_SEC; + if ( secs > timeout ) { + break; // ( yes ) timeout + } + } // while ( !found ) + + CFRelease(elementCFArrayRef); + } // if ( elementCFArrayRef ) + // return device and element moved + if ( found ) { + return (1); + } else { + *outIOHIDElementRef = NULL; + return (0); + } +} // HIDConfigureSingleDeviceAction + +//************************************************************************* +// +// HIDConfigureAction( outIOHIDDeviceRef, outIOHIDElementRef, inTimeout ) +// +// Purpose: polls all devices and elements for a change greater than kPercentMove. +// Times out after given time returns 1 and pointer to device and element +// if found; returns 0 and NULL for both parameters if not found +// +// Inputs: outIOHIDDeviceRef - address where to store the device +// outIOHIDElementRef - address where to store the element +// inTimeout - the timeout +// Returns: Boolean - if successful +// outIOHIDDeviceRef - the device +// outIOHIDElementRef - the element +// + +Boolean HIDConfigureAction(IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef, float inTimeout) { + // param error? + if ( !outIOHIDDeviceRef || !outIOHIDElementRef ) { + return (0); + } + if ( !gDeviceCFArrayRef ) { // if we do not have a device list + // and we can't build another list + if ( !HIDBuildDeviceList(0, 0) || !gDeviceCFArrayRef ) { + return (FALSE); // bail + } + } + + IOHIDDeviceRef tIOHIDDeviceRef; + IOHIDElementRef tIOHIDElementRef; + + // remember when we start; used to calculate timeout + clock_t start = clock(), end; + + // determine the maximum number of elements + CFIndex maxElements = 0; + CFIndex devIndex, devCount = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIndex = 0; devIndex < devCount; devIndex++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); + if ( !tIOHIDDeviceRef ) { + continue; // skip this one + } + + CFIndex count = HIDCountDeviceElements(tIOHIDDeviceRef, kHIDElementTypeInput); + if ( count > maxElements ) { + maxElements = count; + } + } + + // allocate an array of int's in which to store devCount * maxElements values + double *saveValueArray = (double *) calloc( devCount * maxElements, sizeof(double) ); // clear 2D array to save values + + // on first pass store initial values / compare current values to initial values on subsequent passes + Boolean found = FALSE, first = TRUE; + + while ( !found ) { + double maxDeltaPercent = 0; // we want to find the one that moves the most ( percentage wise ) + for ( devIndex = 0; devIndex < devCount; devIndex++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); + if ( !tIOHIDDeviceRef ) { + continue; // skip this one + } + +#ifdef DEBUG + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); +#endif + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex eleIndex, eleCount = CFArrayGetCount(gElementCFArrayRef); + for ( eleIndex = 0; eleIndex < eleCount; eleIndex++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, eleIndex); + if ( !tIOHIDElementRef ) { + continue; + } + + IOHIDElementType tIOHIDElementType = IOHIDElementGetType(tIOHIDElementRef); + // only care about inputs (no outputs or features) + if ( tIOHIDElementType <= kIOHIDElementTypeInput_ScanCodes ) { + if ( IOHIDElementIsArray(tIOHIDElementRef) ) { + //printf( "ARRAY!\n" ); + continue; // skip array elements + } + + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + uint32_t reportCount = IOHIDElementGetReportCount(tIOHIDElementRef); +#ifdef DEBUG + if ( first ) { + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + printf("%s, dev: {ref:%p, ven: 0x%08lX, pro: 0x%08lX}, ele: {ref:%p, cookie: %p, usage:%04lX:%08lX}\n", + __PRETTY_FUNCTION__, + tIOHIDDeviceRef, + vendorID, + productID, + tIOHIDElementRef, + cookie, + (long unsigned int) usagePage, + (long unsigned int) usage); + fflush(stdout); + if ( (0x054C == vendorID) && (0x0268 == productID) && (0x001E == (UInt32) cookie) ) { + //printf( "DING!\n" ); + } + } + +#endif +#if 1 // work-around for IOHIDValueGetScaledValue crash (when element report count > 1) + if ( reportCount > 1 ) { + //printf( "REPORT!\n" ); + continue; // skip reports + } + +#endif + // ignore PID elements and arrays + if ( (kHIDPage_PID != usagePage) && (((uint32_t)-1) != usage) ) { + // get this elements current value + double value = 0.0; // default value is zero + IOHIDValueRef tIOHIDValueRef; + IOReturn ioReturn = IOHIDDeviceGetValue(tIOHIDDeviceRef, tIOHIDElementRef, &tIOHIDValueRef); + if ( kIOReturnSuccess == ioReturn ) { + value = IOHIDValueGetScaledValue(tIOHIDValueRef, kIOHIDValueScaleTypePhysical); + } + if ( first ) { + saveValueArray[(devIndex * maxElements) + eleIndex] = value; + } else { + double initialValue = saveValueArray[(devIndex * maxElements) + eleIndex]; + + CFIndex valueMin = IOHIDElementGetPhysicalMin(tIOHIDElementRef); + CFIndex valueMax = IOHIDElementGetPhysicalMax(tIOHIDElementRef); + + double deltaPercent = fabs( (initialValue - value) * 100.0 / (valueMax - valueMin) ); +#if 0 // debug code useful to dump out value info for specific (vendorID, productID, usagePage and usage) device + if ( !first ) { + // Device: 0x13b6a0 = { Logitech Inc. - WingMan Force 3D, vendorID: 0x046D, productID: 0xC283, + // usage: 0x0001:0x0004, "Generic Desktop Joystick" + if ( (vendorID == 0x046D) && (productID == 0xC283) ) { + if ( (kHIDPage_GenericDesktop == usagePage) && (kHIDUsage_GD_Rz == usage) ) { + printf("initial: %6.2f, value: %6.2f, diff: %6.2f, delta percent: %6.2f!\n", + initialValue, + value, + fabs(initialValue - value), + deltaPercent); + } + } + } + + deltaPercent = 0.0; +#endif + if ( deltaPercent >= kPercentMove ) { + found = TRUE; + if ( deltaPercent > maxDeltaPercent ) { + maxDeltaPercent = deltaPercent; + + *outIOHIDDeviceRef = tIOHIDDeviceRef; + *outIOHIDElementRef = tIOHIDElementRef; + } + + break; + } + } // if first + + } // if usage + + } // if type + + } // for elements... + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + if ( found ) { + // HIDDumpElementInfo( tIOHIDElementRef ); + break; // DONE! + } + } // for devices + + first = FALSE; // no longer the first pass + + // are we done? + end = clock(); + double secs = (double)(end - start) / CLOCKS_PER_SEC; + if ( secs > inTimeout ) { + break; // ( yes ) timeout + } + } // while ( !found ) + // return device and element moved + if ( !found ) { + *outIOHIDDeviceRef = NULL; + *outIOHIDElementRef = NULL; + } + + return (found); +} // HIDConfigureAction + +//************************************************************************* +// +// HIDSaveElementPref( inKeyCFStringRef, inAppCFStringRef, inIOHIDDeviceRef, inIOHIDElementRef ) +// +// Purpose: Save the device & element values into the specified key in the specified applications preferences +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// inIOHIDDeviceRef - the device +// inIOHIDElementRef - the element +// Returns: Boolean - if successful +// + +Boolean HIDSaveElementPref(const CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef inIOHIDElementRef) { + Boolean success = FALSE; + if ( inKeyCFStringRef && inAppCFStringRef && inIOHIDDeviceRef && inIOHIDElementRef ) { + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + require(vendorID, Oops); + + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + require(productID, Oops); + + long locID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef); + require(locID, Oops); + + uint32_t usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef); + uint32_t usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef); + if ( !usagePage || !usage ) { + usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef); + usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef); + } + + require(usagePage && usage, Oops); + + uint32_t usagePageE = IOHIDElementGetUsagePage(inIOHIDElementRef); + uint32_t usageE = IOHIDElementGetUsage(inIOHIDElementRef); + IOHIDElementCookie eleCookie = IOHIDElementGetCookie(inIOHIDElementRef); + + CFStringRef prefCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, + CFSTR("d:{v:%ld, p:%ld, l:%ld, p:%ld, u:%ld}, e:{p:%ld, u:%ld, c:%ld}"), + vendorID, productID, locID, usagePage, usage, + usagePageE, usageE, eleCookie); + if ( prefCFStringRef ) { + CFPreferencesSetAppValue(inKeyCFStringRef, prefCFStringRef, inAppCFStringRef); + CFRelease(prefCFStringRef); + success = TRUE; + } + } + +Oops: ; + return (success); +} // HIDSaveElementPref + +//************************************************************************* +// +// HIDRestoreElementPref( inKeyCFStringRef, inAppCFStringRef, outIOHIDDeviceRef, outIOHIDElementRef ) +// +// Purpose: Find the specified preference in the specified application +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// outIOHIDDeviceRef - address where to restore the device +// outIOHIDElementRef - address where to restore the element +// Returns: Boolean - if successful +// outIOHIDDeviceRef - the device +// outIOHIDElementRef - the element +// + +Boolean HIDRestoreElementPref(CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef * outIOHIDDeviceRef, + IOHIDElementRef *outIOHIDElementRef) { + Boolean found = FALSE; + if ( inKeyCFStringRef && inAppCFStringRef && outIOHIDDeviceRef && outIOHIDElementRef ) { + CFPropertyListRef prefCFPropertyListRef = CFPreferencesCopyAppValue(inKeyCFStringRef, inAppCFStringRef); + if ( prefCFPropertyListRef ) { + if ( CFStringGetTypeID() == CFGetTypeID(prefCFPropertyListRef) ) { + char buffer[256]; + if ( CFStringGetCString( (CFStringRef) prefCFPropertyListRef, buffer, sizeof(buffer), + kCFStringEncodingUTF8 ) ) + { + HID_info_rec searchHIDInfo; + + int count = sscanf(buffer, + "d:{v:%d, p:%d, l:%d, p:%d, u:%d}, e:{p:%d, u:%d, c:%ld}", + &searchHIDInfo.device.vendorID, + &searchHIDInfo.device.productID, + &searchHIDInfo.device.locID, + &searchHIDInfo.device.usagePage, + &searchHIDInfo.device.usage, + &searchHIDInfo.element.usagePage, + &searchHIDInfo.element.usage, + (long *) &searchHIDInfo.element.cookie); + if ( 8 == count ) { // if we found all eight parametersā€¦ + // and can find a device & element that matches theseā€¦ + if ( HIDFindDeviceAndElement(&searchHIDInfo, outIOHIDDeviceRef, outIOHIDElementRef) ) { + found = TRUE; + } + } + } + } else { + // We found the entry with this key but it's the wrong type; delete it. + CFPreferencesSetAppValue(inKeyCFStringRef, NULL, inAppCFStringRef); + (void) CFPreferencesAppSynchronize(inAppCFStringRef); + } + + CFRelease(prefCFPropertyListRef); + } + } + + return (found); +} // HIDRestoreElementPref + +//************************************************************************* +// +// HIDFindDeviceAndElement( inSearchInfo, outFoundDevice, outFoundElement ) +// +// Purpose: find the closest matching device and element for this action +// +// Notes: matches device: serial, vendorID, productID, location, inUsagePage, usage +// matches element: cookie, inUsagePage, usage, +// +// Inputs: inSearchInfo - the device & element info we searching for +// outFoundDevice - the address of the best matching device +// outFoundElement - the address of the best matching element +// +// Returns: Boolean - TRUE if we find a match +// outFoundDevice - the best matching device +// outFoundElement - the best matching element +// + +Boolean HIDFindDeviceAndElement(const HID_info_rec *inSearchInfo, IOHIDDeviceRef *outFoundDevice, IOHIDElementRef *outFoundElement) { + Boolean result = FALSE; + + IOHIDDeviceRef bestIOHIDDeviceRef = NULL; + IOHIDElementRef bestIOHIDElementRef = NULL; + long bestScore = 0; + + CFIndex devIndex, devCount = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIndex = 0; devIndex < devCount; devIndex++ ) { + long deviceScore = 1; + + IOHIDDeviceRef tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); + if ( !tIOHIDDeviceRef ) { + continue; + } + // match vendorID, productID (+10, +8) + if ( inSearchInfo->device.vendorID ) { + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + if ( vendorID ) { + if ( inSearchInfo->device.vendorID == vendorID ) { + deviceScore += 10; + if ( inSearchInfo->device.productID ) { + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); + if ( productID ) { + if ( inSearchInfo->device.productID == productID ) { + deviceScore += 8; + } // if ( inSearchInfo->device.productID == productID ) + + } // if ( productID ) + + } // if ( inSearchInfo->device.productID ) + + } // if (inSearchInfo->device.vendorID == vendorID) + + } // if vendorID + + } // if search->device.vendorID + // match usagePage & usage (+9) + if ( inSearchInfo->device.usagePage && inSearchInfo->device.usage ) { + uint32_t usagePage = IOHIDDevice_GetUsagePage(tIOHIDDeviceRef) ; + uint32_t usage = IOHIDDevice_GetUsage(tIOHIDDeviceRef); + if ( !usagePage || !usage ) { + usagePage = IOHIDDevice_GetPrimaryUsagePage(tIOHIDDeviceRef); + usage = IOHIDDevice_GetPrimaryUsage(tIOHIDDeviceRef); + } + if ( usagePage ) { + if ( inSearchInfo->device.usagePage == usagePage ) { + if ( usage ) { + if ( inSearchInfo->device.usage == usage ) { + deviceScore += 9; + } // if ( inSearchInfo->usage == usage ) + + } // if ( usage ) + + } // if ( inSearchInfo->usagePage == usagePage ) + + } // if ( usagePage ) + + } // if ( inSearchInfo->usagePage && inSearchInfo->usage ) + // match location ID (+5) + if ( inSearchInfo->device.locID ) { + long locID = IOHIDDevice_GetLocationID(tIOHIDDeviceRef); + if ( locID ) { + if ( inSearchInfo->device.locID == locID ) { + deviceScore += 5; + } + } + } + + // iterate over all elements of this device + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, NULL, 0); + if ( gElementCFArrayRef ) { + CFIndex eleIndex, eleCount = CFArrayGetCount(gElementCFArrayRef); + for ( eleIndex = 0; eleIndex < eleCount; eleIndex++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, eleIndex); + if ( !tIOHIDElementRef ) { + continue; + } + + long score = deviceScore; + // match usage page, usage & cookie + if ( inSearchInfo->element.usagePage && inSearchInfo->element.usage ) { + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + if ( inSearchInfo->element.usagePage == usagePage ) { + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + if ( inSearchInfo->element.usage == usage ) { + score += 5; + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + if ( inSearchInfo->element.cookie == cookie ) { + score += 4; + } // cookies match + + } else { + score = 0; + } // usages match + + } else { + score = 0; + } // usage pages match + + } // if ( search usage page & usage ) + +#if LOG_SCORING + if ( kHIDPage_KeyboardOrKeypad != tElementRef->usagePage ) { // skip keyboards here + printf("%s: ( %ld:%ld )-I-Debug, score: %ld\t", + __PRETTY_FUNCTION__, + inSearchInfo->element.usagePage, + inSearchInfo->element.usage, + score); + HIDPrintElement(tIOHIDElementRef); + } + +#endif // LOG_SCORING + if ( score > bestScore ) { + bestIOHIDDeviceRef = tIOHIDDeviceRef; + bestIOHIDElementRef = tIOHIDElementRef; + bestScore = score; +#if LOG_SCORING + printf("%s: ( %ld:%ld )-I-Debug, better score: %ld\t", + __PRETTY_FUNCTION__, + inSearchInfo->element.usagePage, + inSearchInfo->element.usage, + score); + HIDPrintElement(bestIOHIDElementRef); +#endif // LOG_SCORING + } + } // for elements... + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // for ( devIndex = 0; devIndex < devCount; devIndex++ ) + if ( bestIOHIDDeviceRef || bestIOHIDElementRef ) { + *outFoundDevice = bestIOHIDDeviceRef; + *outFoundElement = bestIOHIDElementRef; +#if LOG_SCORING + printf("%s: ( %ld:%ld )-I-Debug, best score: %ld\t", + __PRETTY_FUNCTION__, + inSearchInfo->element.usagePage, + inSearchInfo->element.usage, + bestScore); + HIDPrintElement(bestIOHIDElementRef); +#endif // LOG_SCORING + result = TRUE; + } + + return (result); +} // HIDFindDeviceAndElement + +// --------------------------------- + +// takes input records, save required info +// assume file is open and at correct position. +// will always write to file (if file exists) size of HID_info_rec, even if device and or element is bad + +void HIDSaveElementConfig(FILE *fileRef, IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef, int actionCookie) { + // must save: + // actionCookie + // Device: serial,vendorID, productID, location, usagePage, usage + // Element: cookie, usagePage, usage, + HID_info_rec hidInfoRec; + HIDSetElementConfig(&hidInfoRec, inIOHIDDeviceRef, inIOHIDElementRef, actionCookie); + // write to file + if ( fileRef ) { + fwrite( (void *)&hidInfoRec, sizeof(HID_info_rec), 1, fileRef ); + } +} // HIDSaveElementConfig + +// --------------------------------- + +// take file, read one record (assume file position is correct and file is open) +// search for matching device +// return pDevice, pElement and cookie for action + +int HIDRestoreElementConfig(FILE *fileRef, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef) { + // Device: serial,vendorID, productID, location, usagePage, usage + // Element: cookie, usagePage, usage, + + HID_info_rec hidInfoRec; + fread( (void *) &hidInfoRec, 1, sizeof(HID_info_rec), fileRef ); + return ( HIDGetElementConfig(&hidInfoRec, outIOHIDDeviceRef, outIOHIDElementRef) ); +} // HIDRestoreElementConfig + +// --------------------------------- + +// Set up a config record for saving +// takes an input records, returns record user can save as they want +// Note: the save rec must be pre-allocated by the calling app and will be filled out +void HIDSetElementConfig(HID_info_ptr inHIDInfoPtr, + IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef inIOHIDElementRef, + int actionCookie) { + // must save: + // actionCookie + // Device: serial,vendorID, productID, location, usagePage, usage + // Element: cookie, usagePage, usage, + inHIDInfoPtr->actionCookie = actionCookie; + // device + // need to add serial number when I have a test case + if ( inIOHIDDeviceRef && inIOHIDElementRef ) { + inHIDInfoPtr->device.vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + inHIDInfoPtr->device.productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + inHIDInfoPtr->device.locID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef); + inHIDInfoPtr->device.usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef); + inHIDInfoPtr->device.usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef); + + inHIDInfoPtr->element.usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef); + inHIDInfoPtr->element.usage = IOHIDElementGetUsage(inIOHIDElementRef); + inHIDInfoPtr->element.minReport = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef); + inHIDInfoPtr->element.maxReport = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef); + inHIDInfoPtr->element.cookie = IOHIDElementGetCookie(inIOHIDElementRef); + } else { + inHIDInfoPtr->device.vendorID = 0; + inHIDInfoPtr->device.productID = 0; + inHIDInfoPtr->device.locID = 0; + inHIDInfoPtr->device.usage = 0; + inHIDInfoPtr->device.usagePage = 0; + + inHIDInfoPtr->element.usagePage = 0; + inHIDInfoPtr->element.usage = 0; + inHIDInfoPtr->element.minReport = 0; + inHIDInfoPtr->element.maxReport = 0; + inHIDInfoPtr->element.cookie = 0; + } +} // HIDSetElementConfig + +// --------------------------------- +#if 0 // debug utility function to dump config record +void HIDDumpConfig(HID_info_ptr inHIDInfoPtr) { + printf( + "Config Record for action: %d\n\t vendor: %d product: %d location: %d\n\t usage: %d usagePage: %d\n\t element.usagePage: %d element.usage: %d\n\t minReport: %d maxReport: %d\n\t cookie: %d\n", + inHIDInfoPtr->actionCookie, + inHIDInfoPtr->device.vendorID, + inHIDInfoPtr->device.productID, + inHIDInfoPtr->locID, + inHIDInfoPtr->usage, + inHIDInfoPtr->usagePage, + inHIDInfoPtr->element.usagePage, + inHIDInfoPtr->element.usage, + inHIDInfoPtr->minReport, + inHIDInfoPtr->maxReport, + inHIDInfoPtr->cookie); +} // HIDDumpConfig +#endif // 0 +// --------------------------------- + +// Get matching element from config record +// takes a pre-allocated and filled out config record +// search for matching device +// return pDevice, pElement and cookie for action +int HIDGetElementConfig(HID_info_ptr inHIDInfoPtr, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef) { + if ( !inHIDInfoPtr->device.locID && !inHIDInfoPtr->device.vendorID && !inHIDInfoPtr->device.productID && !inHIDInfoPtr->device.usage + && !inHIDInfoPtr->device.usagePage ) // + { // + // early out + *outIOHIDDeviceRef = NULL; + *outIOHIDElementRef = NULL; + return (inHIDInfoPtr->actionCookie); + } + + IOHIDDeviceRef tIOHIDDeviceRef, foundIOHIDDeviceRef = NULL; + IOHIDElementRef tIOHIDElementRef, foundIOHIDElementRef = NULL; + + CFIndex devIdx, devCnt, idx, cnt; + // compare to current device list for matches + // look for device + if ( inHIDInfoPtr->device.locID && inHIDInfoPtr->device.vendorID && inHIDInfoPtr->device.productID ) { // look for specific device + // type plug in to same port + devCnt = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIdx = 0; devIdx < devCnt; devIdx++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIdx); + if ( !tIOHIDDeviceRef ) { + continue; // skip this device + } + + long locID = IOHIDDevice_GetLocationID(tIOHIDDeviceRef); + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); + if ( (inHIDInfoPtr->device.locID == locID) + && (inHIDInfoPtr->device.vendorID == vendorID) + && (inHIDInfoPtr->device.productID == productID) ) + { + foundIOHIDDeviceRef = tIOHIDDeviceRef; + } + if ( foundIOHIDDeviceRef ) { + break; + } + } // next devIdx + if ( foundIOHIDDeviceRef ) { + CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(foundIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( elementCFArrayRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + if ( inHIDInfoPtr->element.cookie == cookie ) { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } + if ( !foundIOHIDElementRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + if ( (inHIDInfoPtr->element.usage == usage) && (inHIDInfoPtr->element.usagePage == usagePage) ) { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } // next idx + + } // if ( !foundIOHIDElementRef ) + if ( foundIOHIDElementRef ) { // if same device + // setup the calibration + IOHIDElement_SetupCalibration(tIOHIDElementRef); + + IOHIDElement_SetCalibrationSaturationMin(tIOHIDElementRef, inHIDInfoPtr->element.minReport); + IOHIDElement_SetCalibrationSaturationMax(tIOHIDElementRef, inHIDInfoPtr->element.maxReport); + } + + CFRelease(elementCFArrayRef); + } // if ( elementCFArrayRef ) + + } // if ( foundIOHIDDeviceRef ) + // if we have not found a match, look at just vendor and product + if ( (!foundIOHIDDeviceRef) && (inHIDInfoPtr->device.vendorID && inHIDInfoPtr->device.productID) ) { + devCnt = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIdx = 0; devIdx < devCnt; devIdx++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIdx); + if ( !tIOHIDDeviceRef ) { + continue; // skip this device + } + + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); + if ( (inHIDInfoPtr->device.vendorID == vendorID) + && (inHIDInfoPtr->device.productID == productID) ) + { + foundIOHIDDeviceRef = tIOHIDDeviceRef; + } + if ( foundIOHIDDeviceRef ) { + break; + } + } + // match elements by cookie since same device type + if ( foundIOHIDDeviceRef ) { + CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(foundIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( elementCFArrayRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + if ( inHIDInfoPtr->element.cookie == cookie ) { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } + // if no cookie match (should NOT occur) match on usage + if ( !foundIOHIDElementRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + if ( (inHIDInfoPtr->element.usage == usage) + && (inHIDInfoPtr->element.usagePage == usagePage) ) + { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } // next idx + + } // if ( !foundIOHIDElementRef ) + if ( foundIOHIDElementRef ) { // if same device + // setup the calibration + IOHIDElement_SetupCalibration(tIOHIDElementRef); + IOHIDElement_SetCalibrationSaturationMin(tIOHIDElementRef, inHIDInfoPtr->element.minReport); + IOHIDElement_SetCalibrationSaturationMax(tIOHIDElementRef, inHIDInfoPtr->element.maxReport); + } + + CFRelease(elementCFArrayRef); + } // if ( elementCFArrayRef ) + + } // if ( foundIOHIDDeviceRef ) + + } // if ( device not found & vendorID & productID ) + + } // if ( inHIDInfoPtr->locID && inHIDInfoPtr->device.vendorID && inHIDInfoPtr->device.productID ) + // can't find matching device return NULL, do not return first device + if ( (!foundIOHIDDeviceRef) || (!foundIOHIDElementRef) ) { + // no HID device + *outIOHIDDeviceRef = NULL; + *outIOHIDElementRef = NULL; + return (inHIDInfoPtr->actionCookie); + } else { + // HID device + *outIOHIDDeviceRef = foundIOHIDDeviceRef; + *outIOHIDElementRef = foundIOHIDElementRef; + return (inHIDInfoPtr->actionCookie); + } +} // HIDGetElementConfig diff --git a/src/cocoa/HID_Error_Handler.c b/src/cocoa/HID_Error_Handler.c new file mode 100755 index 000000000..8aaca7f36 --- /dev/null +++ b/src/cocoa/HID_Error_Handler.c @@ -0,0 +1,101 @@ +// File: HID_Error_Handler.c +// Abstract: Implementation of the HID utilities error handlers +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifdef DEBUG // not used in release +#if !defined (kBuildingLibrary) +#define kVerboseErrors + +// system includes ---------------------------------------------------------- + +#ifdef kVerboseErrors +//#include +#endif +#endif // not kBuildingLibrary +#endif // DEBUG + +#include + +// project includes --------------------------------------------------------- + +#include "HID_Utilities_External.h" + +// globals (internal/private) ----------------------------------------------- + +// prototypes (internal/private) -------------------------------------------- + +// functions (internal/private) --------------------------------------------- + +#pragma mark - +// ------------------------------------- + +// central error reporting + +void HIDReportErrorNum(const char *strError, int numError) { + char errMsgCStr[256]; + + sprintf(errMsgCStr, "%s #%d (0x%x)", strError, numError, numError); + + // out as debug string +#ifdef kVerboseErrors + { + fputs(errMsgCStr, stderr); + } +#endif // kVerboseErrors +} // HIDReportErrorNum + +// ------------------------------------- + +void HIDReportError(const char *strError) { + char errMsgCStr[256]; + + sprintf(errMsgCStr, "%s", strError); + + // out as debug string +#ifdef kVerboseErrors + { + fputs(errMsgCStr, stderr); + } +#endif // kVerboseErrors +} // HIDReportError diff --git a/src/cocoa/HID_Name_Lookup.c b/src/cocoa/HID_Name_Lookup.c new file mode 100755 index 000000000..4e4b023bd --- /dev/null +++ b/src/cocoa/HID_Name_Lookup.c @@ -0,0 +1,1203 @@ +// File: HID_Name_Lookup.c +// Abstract: HID Name Lookup Utilities. +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#pragma mark - includes & imports +//***************************************************** +#include "HID_Utilities_External.h" +//***************************************************** +#pragma mark - typedefs, enums, defines, etc. +//***************************************************** +#define FAKE_MISSING_NAMES 0 // for debugging; returns the vendor, product & cookie ( or usage info ) as numbers. +#define VERBOSE_ELEMENT_NAMES 0 // set TRUE to include vender & product names in element names ( useful for debugging ) + +#define kNameKeyCFStringRef CFSTR("Name") +//***************************************************** +#pragma mark - local ( static ) function prototypes +//***************************************************** + +#if 0 // currently unused +static SInt32 hu_SaveToXMLFile(CFPropertyListRef inCFPRef, CFURLRef inCFURLRef); +static SInt32 hu_XMLSave(CFPropertyListRef inCFPropertyListRef, CFStringRef inResourceName, CFStringRef inResourceExtension); +#endif +static CFPropertyListRef hu_LoadFromXMLFile(CFURLRef inCFURLRef); +static CFPropertyListRef hu_XMLLoad(CFStringRef inResourceName, CFStringRef inResourceExtension); + +static Boolean hu_XMLSearchForElementNameByCookie(long inVendorID, long inProductID, IOHIDElementCookie inCookie, char *outCStr); +static Boolean hu_XMLSearchForElementNameByUsage(long inVendorID, long inProductID, long inUsagePage, long inUsage, char *outCStr); + +static Boolean hu_XMLSearchForVendorNameByVendorID(long inVendorID, char *outCStr); +static Boolean hu_XMLSearchForProductNameByVendorProductID(long inVendorID, long inProductID, char *outCStr); + +#if 0 // currently unused +static Boolean hu_AddVendorProductToCFDict(CFMutableDictionaryRef inCFMutableDictionaryRef, + long inVendorID, + CFStringRef inVendorCFStringRef, + long inProductID, + CFStringRef inProductCFStringRef); +static Boolean hu_AddDeviceElementToUsageXML(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef); +#endif +//***************************************************** +#pragma mark - exported globals +//***************************************************** +#pragma mark - local ( static ) globals +//***************************************************** +static CFPropertyListRef gCookieCFPropertyListRef = NULL; +static CFPropertyListRef gUsageCFPropertyListRef = NULL; + +//***************************************************** +#pragma mark - exported function implementations +//***************************************************** + +/************************************************************************* + * + * HIDGetVendorNameFromVendorID( inVendorID, inProductID, inCookie, outCStrName ) + * + * Purpose: Uses an devices vendor ID to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetVendorNameFromVendorID(long inVendorID, char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + if ( hu_XMLSearchForVendorNameByVendorID(inVendorID, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld}#", inVendorID); + result = TRUE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetVendorNameFromVendorID + +/************************************************************************* + * + * HIDGetProductNameFromVendorProductID( inVendorID, inProductID, outCStrName ) + * + * Purpose: Uses an elements vendor, product & usage info to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inUsagePage - the elements usage page + * inUsage - the elements usage + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetProductNameFromVendorProductID(long inVendorID, long inProductID, char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + if ( hu_XMLSearchForProductNameByVendorProductID(inVendorID, inProductID, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld, P: %ld, U: %ld: %ld}#", inVendorID, inProductID, inUsagePage, inUsage); + result = TRUE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetProductNameFromVendorProductID + +/************************************************************************* + * + * HIDGetElementNameFromVendorProductCookie( inVendorID, inProductID, inCookie, outCStrName ) + * + * Purpose: Uses an elements vendor, product & cookie to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inCookie - the elements cookie + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetElementNameFromVendorProductCookie(int inVendorID, int inProductID, IOHIDElementCookie inCookie, char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + // Look in the XML file first + if ( hu_XMLSearchForElementNameByCookie(inVendorID, inProductID, inCookie, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld, P: %ld, C: %ld}#", inVendorID, inProductID, inCookie); +#else + result = FALSE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetElementNameFromVendorProductCookie + +/************************************************************************* + * + * HIDGetElementNameFromVendorProductUsage( inVendorID, inProductID, inUsagePage, inUsage, outCStrName ) + * + * Purpose: Uses an elements vendor, product & usage info to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inUsagePage - the elements usage page + * inUsage - the elements usage + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetElementNameFromVendorProductUsage(long inVendorID, + long inProductID, + long inUsagePage, + long inUsage, + char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + if ( hu_XMLSearchForElementNameByUsage(inVendorID, inProductID, inUsagePage, inUsage, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld, P: %ld, U: %ld: %ld}#", inVendorID, inProductID, inUsagePage, inUsage); + result = TRUE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetElementNameFromVendorProductUsage + +#if 0 // currently unused +/************************************************************************* + * + * HIDAddDeviceToXML( inDevice ) + * + * Purpose: Adds a devices info to the HID_device_usage_strings.plist( XML ) file + * + * Inputs: inDevice - the device + * Returns: Boolean - if successful + */ +static Boolean HIDAddDeviceToXML(IOHIDDeviceRef inIOHIDDeviceRef) { + Boolean result = FALSE; + if ( HIDIsValidDevice(inIOHIDDeviceRef) ) { + CFStringRef vendorCFStringRef = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef); + CFStringRef productCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef); + if ( vendorCFStringRef && productCFStringRef ) { +#if 0 // don't update the cookie xml file + gCookieCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_cookie_strings"), CFSTR("plist") ); + if ( gCookieCFPropertyListRef ) { + CFMutableDictionaryRef tCFMutableDictionaryRef = + CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, + 0, + gCookieCFPropertyListRef); + if ( tCFMutableDictionaryRef ) { + if ( hu_AddVendorProductToCFDict(tCFMutableDictionaryRef, vendorID, vendorCFStringRef, productID, + productCFStringRef) ) + { + hu_XMLSave( tCFMutableDictionaryRef, + CFSTR( + "HID_cookie_strings"), CFSTR("plist") ); + result = TRUE; + } + + CFRelease(tCFMutableDictionaryRef); + } + } + +#endif + if ( gUsageCFPropertyListRef ) { + CFRelease(gUsageCFPropertyListRef); + } + + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + if ( gUsageCFPropertyListRef ) { + CFMutableDictionaryRef tCFMutableDictionaryRef = + CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, + 0, + gUsageCFPropertyListRef); + if ( tCFMutableDictionaryRef ) { + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + if ( hu_AddVendorProductToCFDict(tCFMutableDictionaryRef, vendorID, vendorCFStringRef, productID, + productCFStringRef) ) + { + hu_XMLSave( tCFMutableDictionaryRef, + CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + result = TRUE; + } + + CFRelease(tCFMutableDictionaryRef); + } + } + } + } + + return (result); +} // HIDAddDeviceToXML + +/************************************************************************* + * + * HIDAddDeviceElementToXML( inDevice, inElement ) + * + * Purpose: Adds a devices info to the HID_device_usage_strings.plist( XML ) file + * + * Inputs: inDevice - the device + * inElement - the element + * + * Returns: Boolean - if successful + */ +Boolean HIDAddDeviceElementToXML(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + Boolean result = FALSE; + if ( HIDIsValidElement(inIOHIDElementRef) ) { + if ( HIDAddDeviceToXML(inIOHIDDeviceRef) ) { + result = TRUE; + } + if ( hu_AddDeviceElementToUsageXML(inIOHIDDeviceRef, inIOHIDElementRef) ) { + result = TRUE; + } + } + + return (result); +} // HIDAddDeviceElementToXML +#endif +/************************************************************************* + * + * HIDGetTypeName( inIOHIDElementType, outCStrName ) + * + * Purpose: return a C string for a given element type( see IOHIDKeys.h ) + * Notes: returns "Unknown Type" for invalid types + * + * Inputs: inIOHIDElementType - type element type + * outCStrName - address where to store element type string + * + * Returns: outCStrName - the element type string + */ + +void HIDGetTypeName(IOHIDElementType inIOHIDElementType, char *outCStrName) { + switch ( inIOHIDElementType ) { + case kIOHIDElementTypeInput_Misc: + { + sprintf(outCStrName, "Miscellaneous Input"); + break; + } + + case kIOHIDElementTypeInput_Button: + { + sprintf(outCStrName, "Button Input"); + break; + } + + case kIOHIDElementTypeInput_Axis: + { + sprintf(outCStrName, "Axis Input"); + break; + } + + case kIOHIDElementTypeInput_ScanCodes: + { + sprintf(outCStrName, "Scan Code Input"); + break; + } + + case kIOHIDElementTypeOutput: + { + sprintf(outCStrName, "Output"); + break; + } + + case kIOHIDElementTypeFeature: + { + sprintf(outCStrName, "Feature"); + break; + } + + case kIOHIDElementTypeCollection: + { + sprintf(outCStrName, "Collection"); + break; + } + + default: + { + sprintf(outCStrName, "Unknown Type"); + break; + } + } // switch + +} // HIDGetTypeName + +//************************************************************************* +// +// HIDCopyUsageName( inUsagePage, inUsage ) +// +// Purpose: return a CFStringRef string for a given usage page & usage( see IOUSBHIDParser.h ) +// +// Notes: returns usage page and usage values in CFString form for unknown values +// +// Inputs: inUsagePage - the usage page +// inUsage - the usage +// +// Returns: CFStringRef - the resultant string +// + +CFStringRef HIDCopyUsageName(long inUsagePage, long inUsage) { + static CFPropertyListRef tCFPropertyListRef = NULL; + CFStringRef result = NULL; + if ( !tCFPropertyListRef ) { + tCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_usage_strings"), CFSTR("plist") ); + } + if ( tCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(tCFPropertyListRef) ) + { + CFStringRef pageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("0x%4.4lX"), inUsagePage); + if ( pageKeyCFStringRef ) { + CFDictionaryRef pageCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(tCFPropertyListRef, pageKeyCFStringRef, + (const void **) &pageCFDictionaryRef) ) + { + CFStringRef pageCFStringRef; + if ( CFDictionaryGetValueIfPresent(pageCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &pageCFStringRef) ) + { + CFStringRef usageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "0x%4.4lX"), inUsage); + if ( usageKeyCFStringRef ) { + CFStringRef usageCFStringRef; + if ( CFDictionaryGetValueIfPresent(pageCFDictionaryRef, usageKeyCFStringRef, + (const void **) &usageCFStringRef) ) + { + result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@"), pageCFStringRef, usageCFStringRef); + } + +#if FAKE_MISSING_NAMES + else { + result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ #%@"), pageCFStringRef, usageKeyCFStringRef); + } +#endif + CFRelease(usageKeyCFStringRef); + } + } else { + // no name data for this page + } + } else { + // no data for this page + } + + CFRelease(pageKeyCFStringRef); + } + } + + // CFRelease( tCFPropertyListRef ); // Leak this! + // tCFPropertyListRef = NULL; + } + + return (result); +} // HIDCopyUsageName + +//***************************************************** +#pragma mark - local ( static ) function implementations +//***************************************************** +#if 0 // currently unused +/************************************************************************* + * + * hu_SaveToXMLFile( inCFPRef, inCFURLRef ) + * + * Purpose: save a property list into an XML file + * + * Inputs: inCFPRef - the data + * inCFURLRef - URL for the file + * + * Returns: SInt32 - error code ( if any ) + */ +static SInt32 hu_SaveToXMLFile(CFPropertyListRef inCFPRef, CFURLRef inCFURLRef) { + CFDataRef xmlCFDataRef; + SInt32 error = coreFoundationUnknownErr; + + // Convert the property list into XML data. + xmlCFDataRef = CFPropertyListCreateXMLData(kCFAllocatorDefault, inCFPRef); + if ( xmlCFDataRef ) { + // Write the XML data to the file. + (void) CFURLWriteDataAndPropertiesToResource(inCFURLRef, xmlCFDataRef, NULL, &error); + + // Release the XML data + CFRelease(xmlCFDataRef); + } + + return (error); +} // hu_SaveToXMLFile +#endif +/************************************************************************* + * + * hu_LoadFromXMLFile( inCFURLRef ) + * + * Purpose: load a property list from an XML file + * + * Inputs: inCFURLRef - URL for the file + * + * Returns: CFPropertyListRef - the data + */ +static CFPropertyListRef hu_LoadFromXMLFile(CFURLRef inCFURLRef) { + CFDataRef xmlCFDataRef; + CFPropertyListRef myCFPropertyListRef = NULL; + + // Read the XML file. + SInt32 error; + if ( CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, inCFURLRef, &xmlCFDataRef, NULL, NULL, &error) ) { + CFStringRef errorString; + // Reconstitute the dictionary using the XML data. + myCFPropertyListRef = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, + xmlCFDataRef, + kCFPropertyListImmutable, + &errorString); + // Release the XML data + CFRelease(xmlCFDataRef); + } + + return (myCFPropertyListRef); +} // hu_LoadFromXMLFile + +#if 0 // currently unused +/************************************************************************* + * + * hu_XMLSave( inCFPropertyListRef, inResourceName, inResourceExtension ) + * + * Purpose: save a CFPropertyListRef into a resource( XML ) file + * + * Inputs: inCFPropertyListRef - the data + * inResourceName - name of the resource file + * inResourceExtension - extension of the resource file + * + * Returns: SInt32 - error code ( if any ) + */ +static SInt32 hu_XMLSave(CFPropertyListRef inCFPropertyListRef, CFStringRef inResourceName, CFStringRef inResourceExtension) { + CFURLRef resFileCFURLRef; + SInt32 error = -1; + + // check the main (application) bundle + resFileCFURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), inResourceName, inResourceExtension, NULL); + + if ( !resFileCFURLRef ) { + // check this specific (HID_Utilities framework) bundle + CFBundleRef tCFBundleRef = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HID_Utilities")); + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + } + } + if ( !resFileCFURLRef ) { + // check bundles already loaded or otherwise known to the current process + CFArrayRef tCFArrayRef = CFBundleGetAllBundles(); + CFIndex idx, cnt = CFArrayGetCount(tCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + CFBundleRef tCFBundleRef = (CFBundleRef) CFArrayGetValueAtIndex(tCFArrayRef, idx) ; + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + if ( resFileCFURLRef ) { + break; + } + } + } + } + if ( resFileCFURLRef ) { + error = hu_SaveToXMLFile(inCFPropertyListRef, resFileCFURLRef); + CFRelease(resFileCFURLRef); + } + + return (error); +} // hu_XMLSave +#endif +/************************************************************************* + * + * hu_XMLLoad( inResourceName, inResourceExtension ) + * + * Purpose: Load a resource( XML ) file into a CFPropertyListRef + * + * Inputs: inResourceName - name of the resource file + * inResourceExtension - extension of the resource file + * + * Returns: CFPropertyListRef - the data + */ +static CFPropertyListRef hu_XMLLoad(CFStringRef inResourceName, CFStringRef inResourceExtension) { + CFURLRef resFileCFURLRef; + CFPropertyListRef tCFPropertyListRef = NULL; + + // check the main (application) bundle + resFileCFURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), inResourceName, inResourceExtension, NULL); + + if ( !resFileCFURLRef ) { + // check this specific (HID_Utilities framework) bundle + CFBundleRef tCFBundleRef = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HID_Utilities")); + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + } + } + if ( !resFileCFURLRef ) { + // check bundles already loaded or otherwise known to the current process + CFArrayRef tCFArrayRef = CFBundleGetAllBundles(); + CFIndex idx, cnt = CFArrayGetCount(tCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + CFBundleRef tCFBundleRef = (CFBundleRef) CFArrayGetValueAtIndex(tCFArrayRef, idx) ; + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + if ( resFileCFURLRef ) { + break; + } + } + } + } + if ( resFileCFURLRef ) { + tCFPropertyListRef = hu_LoadFromXMLFile(resFileCFURLRef); + CFRelease(resFileCFURLRef); + } + + return (tCFPropertyListRef); +} // hu_XMLLoad + +/************************************************************************* + * + * hu_XMLSearchForVendorNameByVendorID( inVendorID, outCStr ) + * + * Purpose: Find a vendor string in the resource ( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForVendorNameByVendorID(long inVendorID, char *outCStr) { + Boolean results = FALSE; + if ( !gUsageCFPropertyListRef ) { + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + if ( gUsageCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gUsageCFPropertyListRef) ) + { + CFDictionaryRef vendorCFDictionaryRef; + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + if ( CFDictionaryGetValueIfPresent(gUsageCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + CFStringRef vendorCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) && vendorCFStringRef ) + { + // CFShow( vendorCFStringRef ); + results = + CFStringGetCString(vendorCFStringRef, outCStr, CFStringGetLength( + vendorCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + } + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gUsageCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForVendorNameByVendorID + +/************************************************************************* + * + * hu_XMLSearchForProductNameByVendorProductID( inVendorID, inProductID, outCStr ) + * + * Purpose: Find an product string in the resource ( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForProductNameByVendorProductID(long inVendorID, long inProductID, char *outCStr) { + Boolean results = FALSE; + if ( !gUsageCFPropertyListRef ) { + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + if ( gUsageCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gUsageCFPropertyListRef) ) + { + // first we make our vendor ID key + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + // and use it to look up our vendor dictionary + CFDictionaryRef vendorCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(gUsageCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + // pull our vendor name our of that dictionary + CFStringRef vendorCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) ) + { +#if FAKE_MISSING_NAMES + CFRetain(vendorCFStringRef); // so we can CFRelease it later + } else { + vendorCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "V: %@"), vendorKeyCFStringRef); +#endif + } + + // now we make our product ID key + CFStringRef productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%ld"), inProductID); + if ( productKeyCFStringRef ) { + // and use that key to look up our product dictionary in the vendor dictionary + CFDictionaryRef productCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, productKeyCFStringRef, + (const void **) &productCFDictionaryRef) ) + { + // pull our product name our of the product dictionary + CFStringRef productCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &productCFStringRef) ) + { +#if FAKE_MISSING_NAMES + CFRetain(productCFStringRef); // so we can CFRelease it later + } else { + productCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "P: %@"), kNameKeyCFStringRef); +#endif + } + + CFStringRef fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@"), vendorCFStringRef, + productCFStringRef); + if ( fullCFStringRef ) { + // CFShow( fullCFStringRef ); + results = + CFStringGetCString(fullCFStringRef, outCStr, CFStringGetLength( + fullCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + CFRelease(fullCFStringRef); + } + +#if FAKE_MISSING_NAMES + if ( productCFStringRef ) { + CFRelease(productCFStringRef); + } + +#endif + } + + CFRelease(productKeyCFStringRef); + } + +#if FAKE_MISSING_NAMES + if ( vendorCFStringRef ) { + CFRelease(vendorCFStringRef); + } + +#endif + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gUsageCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForProductNameByVendorProductID + +/************************************************************************* + * + * hu_XMLSearchForElementNameByCookie( inVendorID, inProductID, inCookie, outCStr ) + * + * Purpose: Find an element string in the resource( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inCookie - the elements cookie + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForElementNameByCookie(long inVendorID, long inProductID, IOHIDElementCookie inCookie, char *outCStr) { + Boolean results = FALSE; + if ( !gCookieCFPropertyListRef ) { + gCookieCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_cookie_strings"), CFSTR("plist") ); + } + if ( gCookieCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gCookieCFPropertyListRef) ) + { + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + CFDictionaryRef vendorCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(gCookieCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + CFDictionaryRef productCFDictionaryRef; + CFStringRef productKeyCFStringRef; + CFStringRef vendorCFStringRef; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) ) + { + // CFShow( vendorCFStringRef ); + } + + productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inProductID); + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, productKeyCFStringRef, + (const void **) &productCFDictionaryRef) ) + { + CFStringRef fullCFStringRef = NULL; + CFStringRef cookieKeyCFStringRef; + CFStringRef productCFStringRef; + CFStringRef cookieCFStringRef; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &productCFStringRef) ) + { + // CFShow( productCFStringRef ); + } + + cookieKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inCookie); + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, cookieKeyCFStringRef, + (const void **) &cookieCFStringRef) ) + { +#if VERBOSE_ELEMENT_NAMES + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ %@"), vendorCFStringRef, productCFStringRef, + cookieCFStringRef); +#else + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), cookieCFStringRef); +#endif // VERBOSE_ELEMENT_NAMES + // CFShow( cookieCFStringRef ); + } + +#if FAKE_MISSING_NAMES + else { + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ # %@"), vendorCFStringRef, productCFStringRef, + cookieKeyCFStringRef); + } +#endif // FAKE_MISSING_NAMES + if ( fullCFStringRef ) { + // CFShow( fullCFStringRef ); + results = + CFStringGetCString(fullCFStringRef, outCStr, CFStringGetLength( + fullCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + CFRelease(fullCFStringRef); + } + + CFRelease(cookieKeyCFStringRef); + } + + CFRelease(productKeyCFStringRef); + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gCookieCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForElementNameByCookie + +/************************************************************************* + * + * hu_XMLSearchForElementNameByUsage( inVendorID, inProductID, inUsagePage, inUsage, outCStr ) + * + * Purpose: Find an element string in the resource( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inUsagePage - the elements usage page + * inUsage - the elements usage + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForElementNameByUsage(long inVendorID, long inProductID, long inUsagePage, long inUsage, + char *outCStr) { + Boolean results = FALSE; + if ( !gUsageCFPropertyListRef ) { + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + if ( gUsageCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gUsageCFPropertyListRef) ) + { + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + CFDictionaryRef vendorCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(gUsageCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + CFStringRef vendorCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) ) + { + vendorCFStringRef = CFStringCreateCopy(kCFAllocatorDefault, vendorCFStringRef); + } else { + vendorCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("v: %ld"), inVendorID); + // CFShow( vendorCFStringRef ); + } + + CFStringRef productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%ld"), inProductID); + + CFDictionaryRef productCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, productKeyCFStringRef, + (const void **) &productCFDictionaryRef) ) + { + CFStringRef fullCFStringRef = NULL; + + CFStringRef productCFStringRef; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &productCFStringRef) ) + { + // CFShow( productCFStringRef ); + } + + CFStringRef usageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%ld:%ld"), inUsagePage, inUsage); + CFStringRef usageCFStringRef; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, usageKeyCFStringRef, + (const void **) &usageCFStringRef) ) + { +#if VERBOSE_ELEMENT_NAMES + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ %@"), vendorCFStringRef, productCFStringRef, + usageCFStringRef); +#else + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), usageCFStringRef); +#endif // VERBOSE_ELEMENT_NAMES + // CFShow( usageCFStringRef ); + } + +#if FAKE_MISSING_NAMES + else { + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ # %@"), vendorCFStringRef, productCFStringRef, + usageKeyCFStringRef); + } +#endif // FAKE_MISSING_NAMES + if ( fullCFStringRef ) { + // CFShow( fullCFStringRef ); + results = + CFStringGetCString(fullCFStringRef, outCStr, CFStringGetLength( + fullCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + CFRelease(fullCFStringRef); + } + + CFRelease(usageKeyCFStringRef); + } + if ( vendorCFStringRef ) { + CFRelease(vendorCFStringRef); + } + + CFRelease(productKeyCFStringRef); + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gUsageCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForElementNameByUsage + +#if 0 // currently unused +/************************************************************************* + * + * hu_AddVendorProductToCFDict( inCFMutableDictionaryRef, inVendorID, inVendorCFStringRef, inProductID, inProductCFStringRef ) + * + * Purpose: add a vendor & product to a dictionary + * + * Inputs: inCFMutableDictionaryRef - the dictionary + * inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inProductCFStringRef - the string to be added + * + * Returns: Boolean - if successful + */ +static Boolean hu_AddVendorProductToCFDict(CFMutableDictionaryRef inCFMutableDictionaryRef, + long inVendorID, + CFStringRef inVendorCFStringRef, + long inProductID, + CFStringRef inProductCFStringRef) { + Boolean results = FALSE; + if ( inCFMutableDictionaryRef && ( CFDictionaryGetTypeID() == CFGetTypeID(inCFMutableDictionaryRef) ) ) { + CFMutableDictionaryRef vendorCFMutableDictionaryRef; + CFStringRef vendorKeyCFStringRef; + + CFMutableDictionaryRef productCFMutableDictionaryRef; + CFStringRef productKeyCFStringRef; + + // if the vendor dictionary doesn't exist + vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( CFDictionaryGetValueIfPresent(inCFMutableDictionaryRef, vendorKeyCFStringRef, + (const void **) &vendorCFMutableDictionaryRef) ) + { + // copy it. + vendorCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, vendorCFMutableDictionaryRef); + } else { // ...otherwise... + // create it. + vendorCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the vendor name key doesn't exist + if ( !CFDictionaryContainsKey(vendorCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + // create it. + CFDictionaryAddValue(vendorCFMutableDictionaryRef, kNameKeyCFStringRef, inVendorCFStringRef); + results = TRUE; + } + + // if the product key exists in the vendor dictionary + productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inProductID); + if ( CFDictionaryGetValueIfPresent(vendorCFMutableDictionaryRef, productKeyCFStringRef, + (const void **) &productCFMutableDictionaryRef) ) + { + // copy it. + productCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, productCFMutableDictionaryRef); + } else { // ...otherwise... + // create it. + productCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the product name key doesn't exist + if ( !CFDictionaryContainsKey(productCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + // create it. + CFDictionaryAddValue(productCFMutableDictionaryRef, kNameKeyCFStringRef, inProductCFStringRef); + results = TRUE; + } + if ( vendorCFMutableDictionaryRef ) { + if ( productCFMutableDictionaryRef ) { + if ( results ) { + CFDictionarySetValue(vendorCFMutableDictionaryRef, productKeyCFStringRef, productCFMutableDictionaryRef); + } + + CFRelease(productCFMutableDictionaryRef); + } + if ( results ) { + CFDictionarySetValue(inCFMutableDictionaryRef, vendorKeyCFStringRef, vendorCFMutableDictionaryRef); + } + + CFRelease(vendorCFMutableDictionaryRef); + } + if ( productKeyCFStringRef ) { + CFRelease(productKeyCFStringRef); + } + if ( vendorKeyCFStringRef ) { + CFRelease(vendorKeyCFStringRef); + } + } + + return (results); +} // hu_AddVendorProductToCFDict + +/************************************************************************* + * + * hu_AddDeviceElementToUsageXML( inDevice, inElement ) + * + * Purpose: add a device and it's elements to our usage( XML ) file + * + * Inputs: inDevice - the device + * inElement - the element + * + * Returns: Boolean - if successful + */ +static Boolean hu_AddDeviceElementToUsageXML(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + Boolean results = FALSE; + if ( gUsageCFPropertyListRef ) { + CFRelease(gUsageCFPropertyListRef); + } + + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + if ( gUsageCFPropertyListRef ) { + CFMutableDictionaryRef tCFMutableDictionaryRef = + CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, + 0, + gUsageCFPropertyListRef); + if ( tCFMutableDictionaryRef ) { + CFMutableDictionaryRef vendorCFMutableDictionaryRef; + + CFMutableDictionaryRef productCFMutableDictionaryRef; + CFStringRef productKeyCFStringRef; + + CFStringRef usageKeyCFStringRef; + + // if the vendor dictionary exists... + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), vendorID); + if ( vendorKeyCFStringRef ) { + if ( CFDictionaryGetValueIfPresent(tCFMutableDictionaryRef, vendorKeyCFStringRef, + (const void **) &vendorCFMutableDictionaryRef) ) + { + // ...copy it... + vendorCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, + 0, + vendorCFMutableDictionaryRef); + } else { // ...otherwise... + // ...create it. + vendorCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the vendor name key doesn't exist... + if ( !CFDictionaryContainsKey(vendorCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + CFStringRef manCFStringRef = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef); + // ...create it. + CFDictionaryAddValue(vendorCFMutableDictionaryRef, kNameKeyCFStringRef, manCFStringRef); + results = TRUE; + } + + // if the product key exists in the vendor dictionary... + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), productID); + if ( CFDictionaryGetValueIfPresent(vendorCFMutableDictionaryRef, productKeyCFStringRef, + (const void **) &productCFMutableDictionaryRef) ) + { + // ...copy it... + productCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, + 0, + productCFMutableDictionaryRef); + } else { // ...otherwise... + // ...create it. + productCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the product name key doesn't exist... + if ( !CFDictionaryContainsKey(productCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + CFStringRef productCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef); + // ...create it. + CFDictionaryAddValue(productCFMutableDictionaryRef, kNameKeyCFStringRef, productCFStringRef); + results = TRUE; + } + + // if the usage key doesn't exist in the product dictionary... + uint32_t usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsagePage(inIOHIDElementRef); + usageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld:%ld"), usagePage, usage); + if ( usageKeyCFStringRef ) { + if ( !CFDictionaryContainsKey(productCFMutableDictionaryRef, usageKeyCFStringRef) ) { + // find it's generic name + CFStringRef usageCFStringRef = HIDCopyUsageName(usagePage, usage); + if ( usageCFStringRef ) { + // and add that. + CFDictionaryAddValue(productCFMutableDictionaryRef, usageKeyCFStringRef, usageCFStringRef); + results = TRUE; + CFRelease(usageCFStringRef); + } + } + + CFRelease(usageKeyCFStringRef); + } + if ( vendorCFMutableDictionaryRef ) { + if ( productCFMutableDictionaryRef ) { + if ( results ) { + CFDictionarySetValue(vendorCFMutableDictionaryRef, productKeyCFStringRef, productCFMutableDictionaryRef); + } + + CFRelease(productCFMutableDictionaryRef); + } + if ( results ) { + CFDictionarySetValue(tCFMutableDictionaryRef, vendorKeyCFStringRef, vendorCFMutableDictionaryRef); + } + + CFRelease(vendorCFMutableDictionaryRef); + } + + CFRelease(vendorKeyCFStringRef); + } + if ( productKeyCFStringRef ) { + CFRelease(productKeyCFStringRef); + } + if ( results ) { + hu_XMLSave( tCFMutableDictionaryRef, + CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + + CFRelease( + tCFMutableDictionaryRef); + } + } + + return (results); +} // hu_AddDeviceElementToUsageXML +#endif diff --git a/src/cocoa/HID_Queue_Utilities.c b/src/cocoa/HID_Queue_Utilities.c new file mode 100755 index 000000000..586609d7f --- /dev/null +++ b/src/cocoa/HID_Queue_Utilities.c @@ -0,0 +1,354 @@ +// File: HID_Queue_Utilities.c +// Abstract: HID Queue Utilities. +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#include "HID_Utilities_External.h" + +// ================================== +// private functions + +// creates a queue for a device, creates and opens device interface if required +static IOReturn HIDCreateQueue(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + // do we already have a queue? + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { // (yes) + assert( IOHIDQueueGetTypeID() == CFGetTypeID(tIOHIDQueueRef) ); + } else { + tIOHIDQueueRef = IOHIDQueueCreate(kCFAllocatorDefault, inIOHIDDeviceRef, kDeviceQueueSize, kIOHIDOptionsTypeNone); + if ( tIOHIDQueueRef ) { // did that work + IOHIDDevice_SetQueue(inIOHIDDeviceRef, tIOHIDQueueRef); + result = kIOReturnSuccess; + } else { + HIDReportErrorNum("Failed to create queue via create", result); + } + } + } else { + HIDReportErrorNum("HID device ref does not exist for queue creation", result); + } + + return (result); +} /* HIDCreateQueue */ + +// --------------------------------- +// returns true if queue is empty false otherwise +// error if no device, empty if no queue +static unsigned char HIDIsDeviceQueueEmpty(IOHIDDeviceRef inIOHIDDeviceRef) { + if ( inIOHIDDeviceRef ) { // need device and queue + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO); + + while ( tIOHIDElementRef ) { + if ( IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) { + return (false); + } + + tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO); + } + } else { + HIDReportError("NULL device passed to HIDIsDeviceQueueEmpty."); + } + } else { + HIDReportError("NULL device passed to HIDIsDeviceQueueEmpty."); + } + + return (true); +} /* HIDIsDeviceQueueEmpty */ + +// --------------------------------- + +// disposes and releases queue, sets queue to NULL,. +// Note: will have no effect if device or queue do not exist +static IOReturn HIDDisposeReleaseQueue(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + + // release the queue + CFRelease(tIOHIDQueueRef); + } + } else { + HIDReportError("NULL device passed to HIDDisposeReleaseQueue."); + } + + return (result); +} /* HIDDisposeReleaseQueue */ + +// ================================== +// public functions +// ---------------------------------- + +// queues specific element, performing any device queue set up required +// queue is started and ready to return events on exit from this function +int HIDQueueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + if ( inIOHIDElementRef ) { + assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) ); + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( !tIOHIDQueueRef ) { // if no queue create queue + result = HIDCreateQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess == result ) { + tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + } + } + if ( tIOHIDQueueRef ) { + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + // queue element + if ( !IOHIDQueueContainsElement(tIOHIDQueueRef, inIOHIDElementRef) ) { + IOHIDQueueAddElement(tIOHIDQueueRef, inIOHIDElementRef); + } + + // restart queue + IOHIDQueueStart(tIOHIDQueueRef); + } else { + HIDReportError("No queue for device passed to HIDQueueElement."); + if ( kIOReturnSuccess == result ) { + result = kIOReturnError; + } + } + } else { + HIDReportError("NULL element passed to HIDQueueElement."); + result = kIOReturnBadArgument; + } + } else { + HIDReportError("NULL device passed to HIDQueueElement."); + result = kIOReturnBadArgument; + } + + return (result); +} /* HIDQueueElement */ +// --------------------------------- + +// adds all elements to queue, performing any device queue set up required +// queue is started and ready to return events on exit from this function +int HIDQueueDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + // error checking + if ( !inIOHIDDeviceRef ) { + HIDReportError("Device does not exist, cannot queue device."); + return (kIOReturnBadArgument); + } + if ( !inIOHIDDeviceRef ) { // must have interface + HIDReportError("Device does not have hid device ref, cannot queue device."); + return (kIOReturnError); + } + + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( !tIOHIDQueueRef ) { // if no queue create queue + result = HIDCreateQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess == result ) { + tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + } + } + if ( (kIOReturnSuccess != result) || (!tIOHIDQueueRef) ) { + HIDReportErrorNum("Could not queue device due to problem creating queue.", result); + if ( kIOReturnSuccess != result ) { + return (result); + } else { + return (kIOReturnError); + } + } + + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + + // queue element + IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO); + + while ( tIOHIDElementRef ) { + if ( !IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) { + IOHIDQueueAddElement(tIOHIDQueueRef, tIOHIDElementRef); + } + + tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO); + } + + // restart queue + IOHIDQueueStart(tIOHIDQueueRef); + + return (result); +} /* HIDQueueDevice */ + +// --------------------------------- +// removes element for queue, if last element in queue will release queue and closes device interface +int HIDDequeueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + if ( inIOHIDElementRef ) { + assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) ); + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + // de-queue element + if ( IOHIDQueueContainsElement(tIOHIDQueueRef, inIOHIDElementRef) ) { + IOHIDQueueRemoveElement(tIOHIDQueueRef, inIOHIDElementRef); + } + // release device queue and close interface if queue empty + if ( HIDIsDeviceQueueEmpty(inIOHIDDeviceRef) ) { + result = HIDDisposeReleaseQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess != result ) { + HIDReportErrorNum("Failed to dispose and release queue.", result); + } + } else { // not empty so restart queue + IOHIDQueueStart(tIOHIDQueueRef); + } + } else { + HIDReportError("No queue for device passed to HIDDequeueElement."); + if ( kIOReturnSuccess == result ) { + result = kIOReturnError; + } + } + } else { + HIDReportError("NULL element passed to HIDDequeueElement."); + result = kIOReturnBadArgument; + } + } else { + HIDReportError("NULL device passed to HIDDequeueElement."); + result = kIOReturnBadArgument; + } + + return (result); +} /* HIDDequeueElement */ + +// --------------------------------- +// completely removes all elements from queue and releases queue and closes device interface +// does not release device interfaces, application must call ReleaseHIDDeviceList on exit +int HIDDequeueDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + // error checking + if ( !inIOHIDDeviceRef ) { + HIDReportError("Device does not exist, cannot queue device."); + return (kIOReturnBadArgument); + } + if ( !inIOHIDDeviceRef ) { // must have interface + HIDReportError("Device does not have hid device ref, cannot queue device."); + return (kIOReturnError); + } + + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + // iterate through elements and if queued, remove + IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO); + + while ( tIOHIDElementRef ) { + // de-queue element + if ( IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) { + IOHIDQueueRemoveElement(tIOHIDQueueRef, tIOHIDElementRef); + } + + tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO); + } + + // ensure queue is disposed and released + result = HIDDisposeReleaseQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess != result ) { + HIDReportErrorNum("Failed to dispose and release queue.", result); + } + } else { + HIDReportError("No queue for device passed to HIDDequeueElement."); + if ( kIOReturnSuccess == result ) { + result = kIOReturnError; + } + } + + return (result); +} /* HIDDequeueDevice */ +// --------------------------------- + +// releases all device queues for quit or rebuild (must be called) +// does not release device interfaces, application must call ReleaseHIDDeviceList on exit +IOReturn HIDReleaseAllDeviceQueues(void) { + IOReturn result = kIOReturnSuccess; + IOHIDDeviceRef tIOHIDDeviceRef = HIDGetFirstDevice(); + + while ( tIOHIDDeviceRef ) { + result = HIDDequeueDevice(tIOHIDDeviceRef); + if ( kIOReturnSuccess != result ) { + HIDReportErrorNum("Could not dequeue device.", result); + } + + tIOHIDDeviceRef = HIDGetNextDevice(tIOHIDDeviceRef); + } + + return (result); +} /* HIDReleaseAllDeviceQueues */ + +// --------------------------------- +// Get the next event in the queue for a device +// elements or entire device should be queued prior to calling this with HIDQueueElement or HIDQueueDevice +// returns true if an event is avialable for the element and fills out *pHIDEvent structure, returns false otherwise +// Note: kIOReturnUnderrun returned from getNextEvent indicates an empty queue not an error condition +// Note: application should pass in a pointer to a IOHIDEventStruct cast to a void (for CFM compatibility) +unsigned char HIDGetEvent(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDValueRef *pIOHIDValueRef) { + if ( inIOHIDDeviceRef ) { + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + if ( pIOHIDValueRef ) { + *pIOHIDValueRef = IOHIDQueueCopyNextValueWithTimeout(tIOHIDQueueRef, 0.0); + if ( *pIOHIDValueRef ) { + return (true); + } + } + } else { + HIDReportError("Could not get HID event, hid queue reference does not exist."); + } + } else { + HIDReportError("Could not get HID event, device does not exist."); + } + + return (false); // did not get event +} /* HIDGetEvent */ diff --git a/src/cocoa/HID_Utilities.c b/src/cocoa/HID_Utilities.c new file mode 100755 index 000000000..2beb6ca02 --- /dev/null +++ b/src/cocoa/HID_Utilities.c @@ -0,0 +1,1061 @@ +// File: HID_Utilities.c +// Abstract: Implementation of the HID utilities +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//*************************************************** +#pragma mark - includes & imports +//----------------------------------------------------- + +#include + +#include "HID_Utilities_External.h" + +//*************************************************** +#pragma mark - typedefs, enums, defines, etc. +//----------------------------------------------------- +#define FAKE_MISSING_NAMES 1 // set this to true while debuging to get more explicit element names; false for +// the +// generic ones + +#define kPercentMove 10 // precent of overall range a element must move to register +#define kNameKeyCFStringRef CFSTR("Name") // dictionary key + +//*************************************************** +#pragma mark - local ( static ) function prototypes +//----------------------------------------------------- + +static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context); +static CFComparisonResult CFDeviceArrayComparatorFunction(const void *val1, const void *val2, void *context); +static CFMutableDictionaryRef hu_SetUpMatchingDictionary(UInt32 inUsagePage, UInt32 inUsage); + +//*************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +IOHIDManagerRef gIOHIDManagerRef = NULL; +CFMutableArrayRef gDeviceCFArrayRef = NULL; +CFIndex gDeviceIndex; +CFArrayRef gElementCFArrayRef = NULL; + +//*************************************************** +#pragma mark - local ( static ) globals +//----------------------------------------------------- + +//*************************************************** +#pragma mark - exported function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// HIDBuildMultiDeviceList( inUsagePages, inUsages, inNumDeviceTypes ) +// +// Purpose: builds list of devices with elements +// +// Inputs: inUsagePages - inNumDeviceTypes sized array of matching usage pages +// inUsages - inNumDeviceTypes sized array of matching usages +// inNumDeviceTypes - number of usage pages & usages +// +// Returns: Boolean - if successful +// +Boolean HIDBuildMultiDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes) { + Boolean result = FALSE; // assume failure ( pessimist! ) + Boolean first = (!gIOHIDManagerRef); // not yet created? + if ( first ) { + // create the manager + gIOHIDManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + } + if ( gIOHIDManagerRef ) { + CFMutableArrayRef hidMatchingCFMutableArrayRef = NULL; + if ( inUsages && inUsagePages && inNumDeviceTypes ) { + hidMatchingCFMutableArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if ( hidMatchingCFMutableArrayRef ) { + int idx; + for ( idx = 0; idx < inNumDeviceTypes; idx++ ) { // for all usage and usage page types + // Set up matching dictionary. returns NULL on error. + CFMutableDictionaryRef hidMatchingCFDictRef = hu_SetUpMatchingDictionary(inUsagePages[idx], inUsages[idx]); + if ( hidMatchingCFDictRef ) { + CFArrayAppendValue(hidMatchingCFMutableArrayRef, (void *) hidMatchingCFDictRef); + CFRelease(hidMatchingCFDictRef); + } else { + fprintf(stderr, "%s: Couldnā€™t create a matching dictionary.", __PRETTY_FUNCTION__); + } + } + } else { + fprintf(stderr, "%s: Couldnā€™t create a matching array.", __PRETTY_FUNCTION__); + } + } + + // set it for IOHIDManager to use to match against + IOHIDManagerSetDeviceMatchingMultiple(gIOHIDManagerRef, hidMatchingCFMutableArrayRef); + if ( hidMatchingCFMutableArrayRef ) { + CFRelease(hidMatchingCFMutableArrayRef); + } + if ( first ) { + // open it + IOReturn tIOReturn = IOHIDManagerOpen(gIOHIDManagerRef, kIOHIDOptionsTypeNone); + if ( kIOReturnSuccess != tIOReturn ) { + fprintf(stderr, "%s: Couldnā€™t open IOHIDManager.", __PRETTY_FUNCTION__); + goto Oops; + } + } + + HIDRebuildDevices(); + result = TRUE; + } else { + fprintf(stderr, "%s: Couldnā€™t create a IOHIDManager.", __PRETTY_FUNCTION__); + } + +Oops: ; + return (result); +} // HIDBuildMultiDeviceList + +/************************************************************************* + * + * HIDBuildDeviceList( inUsagePage, inUsage ) + * + * Purpose: builds list of devices with elements + * + * Notes: same as above but this uses a single inUsagePage and usage + * allocates memory and captures devices + * list is allocated internally within HID Utilites and can be accessed via accessor functions + * structures within list are considered flat and user accessable, but not user modifiable + * can be called again to rebuild list to account for new devices + * ( will do the right thing in case of disposing existing list ) + * + * Inputs: inUsagePage - usage page + * inUsage - usages + * + * Returns: Boolean - if successful + */ + +Boolean HIDBuildDeviceList(UInt32 inUsagePage, UInt32 inUsage) { + return ( HIDBuildMultiDeviceList(&inUsagePage, &inUsage, 1) ); // call HIDBuildMultiDeviceList with a single usage +} + +/************************************************************************* + * + * HIDUpdateDeviceList( inUsagePages, inUsages, inNumDeviceTypes ) + * + * Purpose: updates the current device list for any new/removed devices + * + * Notes: if this is called before HIDBuildDeviceList then it functions like HIDBuildMultiDeviceList + * inUsagePage & inUsage are each a inNumDeviceTypes sized array of matching usage and usage pages + * + * Inputs: inUsagePages - inNumDeviceTypes sized array of matching usage pages + * inUsages - inNumDeviceTypes sized array of matching usages + * inNumDeviceTypes - number of usage pages & usages + * + * Returns: Boolean - TRUE if the device config changed + */ + +Boolean HIDUpdateDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes) { + return ( HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes) ); +} + +/************************************************************************* + * + * HIDReleaseDeviceList( void ) + * + * Purpose: release list built by above functions + * + * Notes: MUST be called prior to application exit to properly release devices + * if not called( or app crashes ) devices can be recovered by pluging into different location in USB chain + * + * Inputs: none + * + * Returns: none + */ + +void HIDReleaseDeviceList(void) { + if ( gDeviceCFArrayRef ) { + CFRelease(gDeviceCFArrayRef); + gDeviceCFArrayRef = NULL; + } +} // HIDReleaseDeviceList + +/************************************************************************* + * + * HIDHaveDeviceList( void ) + * + * Purpose: does a device list exist? + * + * Inputs: none + * + * Returns: Boolean - TRUE if we have previously built a device list + */ + +Boolean HIDHaveDeviceList(void) { + return (NULL != gDeviceCFArrayRef); +} + +//************************************************************************* +// +// HIDRebuildDevices( ) +// +// Purpose: rebuilds the (internal) list of IOHIDDevices +// +// Inputs: none +// +// Returns: none +// + +void HIDRebuildDevices(void) { + // get the set of devices from the IOHID manager + CFSetRef devCFSetRef = IOHIDManagerCopyDevices(gIOHIDManagerRef); + if ( devCFSetRef ) { + // if the existing array isn't empty... + if ( gDeviceCFArrayRef ) { + // release it + CFRelease(gDeviceCFArrayRef); + } + + // create an empty array + gDeviceCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + // now copy the set to the array + CFSetApplyFunction(devCFSetRef, CFSetApplierFunctionCopyToCFArray, (void *) gDeviceCFArrayRef); + // now sort the array by location ID's + CFIndex cnt = CFArrayGetCount(gDeviceCFArrayRef); + CFArraySortValues(gDeviceCFArrayRef, CFRangeMake(0, cnt), CFDeviceArrayComparatorFunction, NULL); + + // and release the set we copied from the IOHID manager + CFRelease(devCFSetRef); + } +} // HIDRebuildDevices + +// --------------------------------- + +// how many HID devices have been found +// returns 0 if no device list exist + +UInt32 HIDCountDevices(void) { + return ( CFArrayGetCount(gDeviceCFArrayRef) ); +} + +// --------------------------------- + +// how many elements does a specific device have +// returns 0 if device is invlaid or NULL + +UInt32 HIDCountDeviceElements(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask) { + int count = 0; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; + } + + IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef); + + switch ( type ) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + { + if ( typeMask & kHIDElementTypeInput ) { + count++; + } + + break; + } + + case kIOHIDElementTypeOutput: + { + if ( typeMask & kHIDElementTypeOutput ) { + count++; + } + + break; + } + + case kIOHIDElementTypeFeature: + { + if ( typeMask & kHIDElementTypeFeature ) { + count++; + } + + break; + } + + case kIOHIDElementTypeCollection: + { + if ( typeMask & kHIDElementTypeCollection ) { + count++; + } + + break; + } + default: { + break; + } + } // switch ( type ) + + } // next idx + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // if ( inIOHIDDeviceRef ) + + return (count); +} /* HIDCountDeviceElements */ + +// --------------------------------- + +// get the first device in the device list +// returns NULL if no list exists or it's empty + +IOHIDDeviceRef HIDGetFirstDevice(void) { + IOHIDDeviceRef result = NULL; + + gDeviceIndex = 0; + if ( gDeviceCFArrayRef ) { + CFIndex count = CFArrayGetCount(gDeviceCFArrayRef); + if ( (gDeviceIndex >= 0) && (gDeviceIndex < count) ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex); + } + } + + return (result); +} /* HIDGetFirstDevice */ + +// --------------------------------- + +// get next device in list given current device as parameter +// returns NULL if end of list + +IOHIDDeviceRef HIDGetNextDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + IOHIDDeviceRef result = NULL; + if ( gDeviceCFArrayRef && inIOHIDDeviceRef ) { + CFIndex idx, cnt = CFArrayGetCount(gDeviceCFArrayRef); + // quick case to verify the current device index is valid for current device + if ( (gDeviceIndex >= 0) && (gDeviceIndex < cnt) ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex); + if ( result && (result == inIOHIDDeviceRef) ) { + result = NULL; + gDeviceIndex++; // bump index + } else { + // previous index was invalid; + gDeviceIndex = -1; + // search for current device's index + for ( idx = 0; idx < cnt; idx++ ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, idx); + if ( (result) && (result == inIOHIDDeviceRef) ) { + gDeviceIndex = idx + 1; // found valid index; bump to next one + break; + } + } + + result = NULL; + } + if ( (gDeviceIndex >= 0) && (gDeviceIndex < cnt) ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex); + } + } // if valid index + + } // if ( gDeviceCFArrayRef && inIOHIDDeviceRef ) + + return (result); +} /* HIDGetNextDevice */ + +// --------------------------------- + +// get the first element of device passed in as parameter +// returns NULL if no list exists or device does not exists or is NULL +IOHIDElementRef HIDGetFirstDeviceElement(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask) { + IOHIDElementRef result = NULL; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; + } + + IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef); + + switch ( type ) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + { + if ( typeMask & kHIDElementTypeInput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeOutput: + { + if ( typeMask & kHIDElementTypeOutput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeFeature: + { + if ( typeMask & kHIDElementTypeFeature ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeCollection: + { + if ( typeMask & kHIDElementTypeCollection ) { + result = tIOHIDElementRef; + } + + break; + } + default: { + break; + } + } // switch ( type ) + if ( result ) { + break; // DONE! + } + } // next idx + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // if ( inIOHIDDeviceRef ) + + return (result); +} /* HIDGetFirstDeviceElement */ + +// --------------------------------- + +// get next element of given device in list given current element as parameter +// will walk down each collection then to next element or collection (depthwise traverse) +// returns NULL if end of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get previous HIDGetNextDeviceElement functionality +IOHIDElementRef HIDGetNextDeviceElement(IOHIDElementRef inIOHIDElementRef, HIDElementTypeMask typeMask) { + IOHIDElementRef result = NULL; + if ( inIOHIDElementRef ) { + assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) ); + + IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef); + if ( tIOHIDDeviceRef ) { + Boolean found = FALSE; + + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; + } + if ( !found ) { + if ( inIOHIDElementRef == tIOHIDElementRef ) { + found = TRUE; + } + + continue; // next element + } else { + // we've found the current element; now find the next one of the right type + IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef); + + switch ( type ) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + { + if ( typeMask & kHIDElementTypeInput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeOutput: + { + if ( typeMask & kHIDElementTypeOutput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeFeature: + { + if ( typeMask & kHIDElementTypeFeature ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeCollection: + { + if ( typeMask & kHIDElementTypeCollection ) { + result = tIOHIDElementRef; + } + + break; + } + default: { + break; + } + } // switch ( type ) + if ( result ) { + break; // DONE! + } + } // if ( !found ) + + } // next idx + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // if ( inIOHIDDeviceRef ) + + } // if ( inIOHIDElementRef ) + + return (result); +} /* HIDGetNextDeviceElement */ + +#if 0 +// --------------------------------- +// get previous element of given device in list given current element as parameter +// this wlaks directly up the tree to the top element and does not search at each level +// returns NULL if beginning of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get non-collection elements +IOHIDElementRef HIDGetPreviousDeviceElement(IOHIDElementRef pElement, HIDElementTypeMask typeMask) { + IOHIDElementRef pPreviousElement = pElement->pPrevious; + + // walk back up tree to element prior + while ( pPreviousElement && !HIDMatchElementTypeMask(pPreviousElement->type, typeMask) ) { + pElement = pPreviousElement; // look at previous element + pPreviousElement = pElement->pPrevious; + } + + return (pPreviousElement); // return this element +} /* HIDGetPreviousDeviceElement */ + +#endif + +// utility routine to dump device info +void HIDDumpDeviceInfo(IOHIDDeviceRef inIOHIDDeviceRef) { + char cstring[256]; + + printf("Device: %p = { ", inIOHIDDeviceRef); + + char manufacturer[256] = ""; // name of manufacturer + CFStringRef tCFStringRef = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, manufacturer, sizeof(manufacturer), kCFStringEncodingUTF8) ); + } + + char product[256] = ""; // name of product + tCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, product, sizeof(product), kCFStringEncodingUTF8) ); + } + + printf("%s - %s, ", manufacturer, product); + + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + if ( vendorID ) { +#if 1 + printf(" vendorID: 0x%04lX, ", vendorID); +#else + if ( HIDGetVendorNameFromVendorID(vendorID, cstring) ) { + printf(" vendorID: 0x%04lX (\"%s\"), ", vendorID, cstring); + } else { + printf(" vendorID: 0x%04lX, ", vendorID); + } + +#endif + } + + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + if ( productID ) { +#if 1 + printf(" productID: 0x%04lX, ", productID); +#else + if ( HIDGetProductNameFromVendorProductID(vendorID, productID, cstring) ) { + printf(" productID: 0x%04lX (\"%s\"), ", productID, cstring); + } else { + printf(" productID: 0x%04lX, ", productID); + } + +#endif + } + + uint32_t usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef); + uint32_t usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef); + if ( !usagePage || !usage ) { + usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef); + usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef); + } + + printf("usage: 0x%04lX:0x%04lX, ", (long unsigned int) usagePage, (long unsigned int) usage); + +#if 1 + tCFStringRef = HIDCopyUsageName(usagePage, usage); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8) ); + printf("\"%s\", ", cstring); + CFRelease(tCFStringRef); + } + +#endif + +#if 1 + tCFStringRef = IOHIDDevice_GetTransport(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8) ); + printf("Transport: \"%s\", ", cstring); + } + + long vendorIDSource = IOHIDDevice_GetVendorIDSource(inIOHIDDeviceRef); + if ( vendorIDSource ) { + printf("VendorIDSource: %ld, ", vendorIDSource); + } + + long version = IOHIDDevice_GetVersionNumber(inIOHIDDeviceRef); + if ( version ) { + printf("version: %ld, ", version); + } + + tCFStringRef = IOHIDDevice_GetSerialNumber(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8) ); + printf("SerialNumber: \"%s\", ", cstring); + } + + long country = IOHIDDevice_GetCountryCode(inIOHIDDeviceRef); + if ( country ) { + printf("CountryCode: %ld, ", country); + } + + long locationID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef); + if ( locationID ) { + printf("locationID: 0x%08lX, ", locationID); + } + +#if 0 + CFArrayRef pairs = IOHIDDevice_GetUsagePairs(inIOHIDDeviceRef); + if ( pairs ) { + CFIndex idx, cnt = CFArrayGetCount(pairs); + for ( idx = 0; idx < cnt; idx++ ) { + const void *pair = CFArrayGetValueAtIndex(pairs, idx); + CFShow(pair); + } + } + +#endif + long maxInputReportSize = IOHIDDevice_GetMaxInputReportSize(inIOHIDDeviceRef); + if ( maxInputReportSize ) { + printf("MaxInputReportSize: %ld, ", maxInputReportSize); + } + + long maxOutputReportSize = IOHIDDevice_GetMaxOutputReportSize(inIOHIDDeviceRef); + if ( maxOutputReportSize ) { + printf("MaxOutputReportSize: %ld, ", maxOutputReportSize); + } + + long maxFeatureReportSize = IOHIDDevice_GetMaxFeatureReportSize(inIOHIDDeviceRef); + if ( maxFeatureReportSize ) { + printf("MaxFeatureReportSize: %ld, ", maxOutputReportSize); + } + + long reportInterval = IOHIDDevice_GetReportInterval(inIOHIDDeviceRef); + if ( reportInterval ) { + printf("ReportInterval: %ld, ", reportInterval); + } + + IOHIDQueueRef queueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( queueRef ) { + printf("queue: %p, ", queueRef); + } + + IOHIDTransactionRef transactionRef = IOHIDDevice_GetTransaction(inIOHIDDeviceRef); + if ( transactionRef ) { + printf("transaction: %p, ", transactionRef); + } + +#endif + printf("}\n"); + fflush(stdout); +} // HIDDumpDeviceInfo + +// utility routine to dump element info +void HIDDumpElementInfo(IOHIDElementRef inIOHIDElementRef) { + if ( inIOHIDElementRef ) { + printf(" Element: %p = { ", inIOHIDElementRef); +#if 0 + IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef); + printf("Device: %p, ", tIOHIDDeviceRef); +#endif + IOHIDElementRef parentIOHIDElementRef = IOHIDElementGetParent(inIOHIDElementRef); + printf("parent: %p, ", parentIOHIDElementRef); +#if 0 + CFArrayRef childrenCFArrayRef = IOHIDElementGetChildren(inIOHIDElementRef); + printf("children: %p: { ", childrenCFArrayRef); + fflush(stdout); + CFShow(childrenCFArrayRef); + fflush(stdout); + printf(" }, "); +#endif + IOHIDElementCookie tIOHIDElementCookie = IOHIDElementGetCookie(inIOHIDElementRef); + printf("cookie: %p, ", tIOHIDElementCookie); + + IOHIDElementType tIOHIDElementType = IOHIDElementGetType(inIOHIDElementRef); + + switch ( tIOHIDElementType ) { + case kIOHIDElementTypeInput_Misc: + { + printf("type: Misc, "); + break; + } + + case kIOHIDElementTypeInput_Button: + { + printf("type: Button, "); + break; + } + + case kIOHIDElementTypeInput_Axis: + { + printf("type: Axis, "); + break; + } + + case kIOHIDElementTypeInput_ScanCodes: + { + printf("type: ScanCodes, "); + break; + } + + case kIOHIDElementTypeOutput: + { + printf("type: Output, "); + break; + } + + case kIOHIDElementTypeFeature: + { + printf("type: Feature, "); + break; + } + + case kIOHIDElementTypeCollection: + { + IOHIDElementCollectionType tIOHIDElementCollectionType = IOHIDElementGetCollectionType(inIOHIDElementRef); + + switch ( tIOHIDElementCollectionType ) { + case kIOHIDElementCollectionTypePhysical: + { + printf("type: Physical Collection, "); + break; + } + + case kIOHIDElementCollectionTypeApplication: + { + printf("type: Application Collection, "); + break; + } + + case kIOHIDElementCollectionTypeLogical: + { + printf("type: Logical Collection, "); + break; + } + + case kIOHIDElementCollectionTypeReport: + { + printf("type: Report Collection, "); + break; + } + + case kIOHIDElementCollectionTypeNamedArray: + { + printf("type: Named Array Collection, "); + break; + } + + case kIOHIDElementCollectionTypeUsageSwitch: + { + printf("type: Usage Switch Collection, "); + break; + } + + case kIOHIDElementCollectionTypeUsageModifier: + { + printf("type: Usage Modifier Collection, "); + break; + } + + default: + { + printf("type: %p Collection, ", (void *) tIOHIDElementCollectionType); + break; + } + } // switch + + break; + } + + default: + { + printf("type: %p, ", (void *) tIOHIDElementType); + break; + } + } /* switch */ + + uint32_t usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(inIOHIDElementRef); + printf("usage: 0x%04lX:0x%04lX, ", (long unsigned int) usagePage, (long unsigned int) usage); +#if 1 + CFStringRef tCFStringRef = HIDCopyUsageName(usagePage, usage); + if ( tCFStringRef ) { + char usageString[256] = ""; + verify( CFStringGetCString(tCFStringRef, usageString, sizeof(usageString), kCFStringEncodingUTF8) ); + printf("\"%s\", ", usageString); + CFRelease(tCFStringRef); + } + +#endif + CFStringRef nameCFStringRef = IOHIDElementGetName(inIOHIDElementRef); + char buffer[256]; + if ( nameCFStringRef && CFStringGetCString(nameCFStringRef, buffer, sizeof(buffer), kCFStringEncodingUTF8) ) { + printf("name: %s, ", buffer); + } + + uint32_t reportID = IOHIDElementGetReportID(inIOHIDElementRef); + uint32_t reportSize = IOHIDElementGetReportSize(inIOHIDElementRef); + uint32_t reportCount = IOHIDElementGetReportCount(inIOHIDElementRef); + printf("report: { ID: %lu, Size: %lu, Count: %lu }, ", + (long unsigned int) reportID, (long unsigned int) reportSize, (long unsigned int) reportCount); + + uint32_t unit = IOHIDElementGetUnit(inIOHIDElementRef); + uint32_t unitExp = IOHIDElementGetUnitExponent(inIOHIDElementRef); + if ( unit || unitExp ) { + printf("unit: %lu * 10^%lu, ", (long unsigned int) unit, (long unsigned int) unitExp); + } + + CFIndex logicalMin = IOHIDElementGetLogicalMin(inIOHIDElementRef); + CFIndex logicalMax = IOHIDElementGetLogicalMax(inIOHIDElementRef); + if ( logicalMin != logicalMax ) { + printf("logical: {min: %ld, max: %ld}, ", logicalMin, logicalMax); + } + + CFIndex physicalMin = IOHIDElementGetPhysicalMin(inIOHIDElementRef); + CFIndex physicalMax = IOHIDElementGetPhysicalMax(inIOHIDElementRef); + if ( physicalMin != physicalMax ) { + printf("physical: {min: %ld, max: %ld}, ", physicalMin, physicalMax); + } + + Boolean isVirtual = IOHIDElementIsVirtual(inIOHIDElementRef); + if ( isVirtual ) { + printf("isVirtual, "); + } + + Boolean isRelative = IOHIDElementIsRelative(inIOHIDElementRef); + if ( isRelative ) { + printf("isRelative, "); + } + + Boolean isWrapping = IOHIDElementIsWrapping(inIOHIDElementRef); + if ( isWrapping ) { + printf("isWrapping, "); + } + + Boolean isArray = IOHIDElementIsArray(inIOHIDElementRef); + if ( isArray ) { + printf("isArray, "); + } + + Boolean isNonLinear = IOHIDElementIsNonLinear(inIOHIDElementRef); + if ( isNonLinear ) { + printf("isNonLinear, "); + } + + Boolean hasPreferredState = IOHIDElementHasPreferredState(inIOHIDElementRef); + if ( hasPreferredState ) { + printf("hasPreferredState, "); + } + + Boolean hasNullState = IOHIDElementHasNullState(inIOHIDElementRef); + if ( hasNullState ) { + printf("hasNullState, "); + } + + printf(" }\n"); + } +} // HIDDumpElementInfo + +void HIDDumpElementCalibrationInfo(IOHIDElementRef inIOHIDElementRef) { + printf(" Element: %p = { ", inIOHIDElementRef); + + CFIndex calMin = IOHIDElement_GetCalibrationMin(inIOHIDElementRef); + CFIndex calMax = IOHIDElement_GetCalibrationMax(inIOHIDElementRef); + printf("cal: {min: %ld, max: %ld}, ", calMin, calMax); + + CFIndex satMin = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef); + CFIndex satMax = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef); + printf("sat: {min: %ld, max: %ld}, ", satMin, satMax); + + CFIndex deadMin = IOHIDElement_GetCalibrationDeadZoneMin(inIOHIDElementRef); + CFIndex deadMax = IOHIDElement_GetCalibrationDeadZoneMax(inIOHIDElementRef); + printf("dead: {min: %ld, max: %ld}, ", deadMin, deadMax); + + double_t granularity = IOHIDElement_GetCalibrationGranularity(inIOHIDElementRef); + printf("granularity: %6.2f }\n", granularity); +} // HIDDumpElementCalibrationInfo + +//*************************************************** +#pragma mark - local ( static ) function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// CFSetApplierFunctionCopyToCFArray( value, context ) +// +// Purpose: CFSetApplierFunction to copy the CFSet to a CFArray +// +// Notes: called one time for each item in the CFSet +// +// Inputs: value - the current element of the CFSet +// context - the CFMutableArrayRef we're adding the CFSet elements to +// +// Returns: nothing +// +static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context) { + // printf( "%s: 0x%08lX\n", __PRETTY_FUNCTION__, (long unsigned int) value ); + CFArrayAppendValue( (CFMutableArrayRef) context, value ); +} // CFSetApplierFunctionCopyToCFArray + +// --------------------------------- +// used to sort the CFDevice array after copying it from the (unordered) (CF)set. +// we compare based on the location ID's since they're consistant (across boots & launches). +// +static CFComparisonResult CFDeviceArrayComparatorFunction(const void *val1, const void *val2, void *context) { +#pragma unused( context ) + CFComparisonResult result = kCFCompareEqualTo; + + long loc1 = IOHIDDevice_GetLocationID( (IOHIDDeviceRef) val1 ); + long loc2 = IOHIDDevice_GetLocationID( (IOHIDDeviceRef) val2 ); + if ( loc1 < loc2 ) { + result = kCFCompareLessThan; + } else if ( loc1 > loc2 ) { + result = kCFCompareGreaterThan; + } + + return (result); +} // CFDeviceArrayComparatorFunction + +//************************************************************************* +// +// hu_SetUpMatchingDictionary( inUsagePage, inUsage ) +// +// Purpose: builds a matching dictionary based on usage page and usage +// +// Notes: Only called by HIDBuildMultiDeviceList +// +// Inputs: inUsagePage - usage page +// inUsage - usages +// +// Returns: CFMutableDictionaryRef - the matching dictionary +// + +static CFMutableDictionaryRef hu_SetUpMatchingDictionary(UInt32 inUsagePage, UInt32 inUsage) { + // create a dictionary to add usage page/usages to + CFMutableDictionaryRef refHIDMatchDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if ( refHIDMatchDictionary ) { + if ( inUsagePage ) { + // Add key for device type to refine the matching dictionary. + CFNumberRef pageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage); + if ( pageCFNumberRef ) { + CFDictionarySetValue(refHIDMatchDictionary, + CFSTR(kIOHIDPrimaryUsagePageKey), pageCFNumberRef); + CFRelease(pageCFNumberRef); + // note: the usage is only valid if the usage page is also defined + if ( inUsage ) { + CFNumberRef usageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage); + if ( usageCFNumberRef ) { + CFDictionarySetValue(refHIDMatchDictionary, + CFSTR(kIOHIDPrimaryUsageKey), usageCFNumberRef); + CFRelease(usageCFNumberRef); + } else { + fprintf(stderr, "%s: CFNumberCreate( usage ) failed.", __PRETTY_FUNCTION__); + } + } + } else { + fprintf(stderr, "%s: CFNumberCreate( usage page ) failed.", __PRETTY_FUNCTION__); + } + } + } else { + fprintf(stderr, "%s: CFDictionaryCreateMutable failed.", __PRETTY_FUNCTION__); + } + + return (refHIDMatchDictionary); +} // hu_SetUpMatchingDictionary diff --git a/src/cocoa/HID_Utilities_External.h b/src/cocoa/HID_Utilities_External.h new file mode 100755 index 000000000..bd498bc51 --- /dev/null +++ b/src/cocoa/HID_Utilities_External.h @@ -0,0 +1,417 @@ +// File: HID_Utilities_External.h +// Abstract: External interface for HID Utilities, can be used with either library or source. +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef _HID_Utilities_External_h_ +#define _HID_Utilities_External_h_ + +// ================================== + +#ifdef __cplusplus +extern "C" { +#endif + +// ================================== + +//includes + +#include +#include "IOHIDLib_.h" + +// ================================== + +#ifndef _IOKIT_HID_IOHIDKEYS_H_ +/*! + @typedef IOHIDElementCookie + @abstract Abstract data type used as a unique identifier for an element. + */ +#ifdef __LP64__ +typedef uint32_t IOHIDElementCookie; +#else +typedef void *IOHIDElementCookie; +#endif +#endif + +// Device and Element Interfaces + +enum HIDElementTypeMask { + kHIDElementTypeInput = 1 << 1, + kHIDElementTypeOutput = 1 << 2, + kHIDElementTypeFeature = 1 << 3, + kHIDElementTypeCollection = 1 << 4, + kHIDElementTypeIO = kHIDElementTypeInput | kHIDElementTypeOutput | kHIDElementTypeFeature, + kHIDElementTypeAll = kHIDElementTypeIO | kHIDElementTypeCollection +}; +typedef enum HIDElementTypeMask HIDElementTypeMask; + +// ================================== + +//***************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +extern IOHIDManagerRef gIOHIDManagerRef; +extern CFMutableArrayRef gDeviceCFArrayRef; +extern CFArrayRef gElementCFArrayRef; + +//************************************************************************* +// +// HIDBuildMultiDeviceList( inUsagePages, inUsages, inNumDeviceTypes ) +// +// Purpose: builds list of devices with elements (allocates memory and captures devices) in which +// the devices could be of different types/usages list is allocated internally within HID +// Utilites and can be accessed via accessor functions structures within list are considered +// flat and user accessable, but not user modifiable can be called again to rebuild list to +// account for new devices (will do the right thing in case of disposing existing list) +// usagePage, usage are each a numDeviceTypes sized array of matching usage and usage pages +// returns true if succesful +// +// Inputs: inUsagePages - inNumDeviceTypes sized array of matching usage pages +// inUsages - inNumDeviceTypes sized array of matching usages +// inNumDeviceTypes - number of usage pages & usages +// +// Returns: Boolean - if successful +// +extern Boolean HIDBuildMultiDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes); + +// same as above but this uses a single usagePage and usage +extern Boolean HIDBuildDeviceList(UInt32 usagePage, UInt32 usage); + +// updates the current device list for any new/removed devices +// if this is called before HIDBuildDeviceList the it functions like HIDBuildMultiDeviceList +// usagePage, usage are each a numDeviceTypes sized array of matching usage and usage pages +// returns true if successful which means if any device were added or removed (the device config changed) +extern Boolean HIDUpdateDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes); + +// release list built by above function +// MUST be called prior to application exit to properly release devices +// if not called (or app crashes) devices can be recovered by pluging into different location in USB chain +extern void HIDReleaseDeviceList(void); + +//************************************************************************* +// +// HIDRebuildDevices( ) +// +// Purpose: rebuilds the (internal) list of devices +// +// Inputs: none +// +// Returns: none +// + +extern void HIDRebuildDevices(void); + +// does a device list exist +extern unsigned char HIDHaveDeviceList(void); + +// how many HID devices have been found +// returns 0 if no device list exist +extern UInt32 HIDCountDevices(void); + +// how many elements does a specific device have +// returns 0 if device is invalid or NULL +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get non-collection elements +extern UInt32 HIDCountDeviceElements(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask); + +// get the first device in the device list +// returns NULL if no list exists +extern IOHIDDeviceRef HIDGetFirstDevice(void); + +// get next device in list given current device as parameter +// returns NULL if end of list +extern IOHIDDeviceRef HIDGetNextDevice(IOHIDDeviceRef inIOHIDDeviceRef); + +// get the first element of device passed in as parameter +// returns NULL if no list exists or device does not exists or is NULL +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get previous HIDGetFirstDeviceElement functionality +extern IOHIDElementRef HIDGetFirstDeviceElement(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask); + +// get next element of given device in list given current element as parameter +// will walk down each collection then to next element or collection (depthwise traverse) +// returns NULL if end of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get previous HIDGetNextDeviceElement functionality +extern IOHIDElementRef HIDGetNextDeviceElement(IOHIDElementRef inIOHidElementRef, HIDElementTypeMask typeMask); + +// get previous element of given device in list given current element as parameter +// this walks directly up the tree to the top element and does not search at each level +// returns NULL if beginning of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get non-collection elements +extern IOHIDElementRef HIDGetPreviousDeviceElement(IOHIDElementRef inIOHidElementRef, HIDElementTypeMask typeMask); + +// returns C string type name given a type enumeration passed in as parameter( see IOHIDKeys.h ) +// returns empty string for invalid types +extern void HIDGetTypeName(IOHIDElementType inIOHIDElementType, char *outCStrName); + +//************************************************************************* +// +// HIDCopyUsageName( inUsagePage, inUsage ) +// +// Purpose: return a CFStringRef string for a given usage page & usage( see IOUSBHIDParser.h ) +// +// Notes: returns usage page and usage values in CFString form for unknown values +// +// Inputs: inUsagePage - the usage page +// inUsage - the usage +// +// Returns: CFStringRef - the resultant string +// + +extern CFStringRef HIDCopyUsageName(long inUsagePage, long inUsage); + +// ================================== + +// Element Event Queue and Value Interfaces + +enum { + kDefaultUserMin = 0, // default user min and max used for scaling + kDefaultUserMax = 255 +}; + +enum { + kDeviceQueueSize = 50 // this is wired kernel memory so should be set to as small as possible + // but should account for the maximum possible events in the queue + // USB updates will likely occur at 100 Hz so one must account for this rate of + // if states change quickly (updates are only posted on state changes) +}; + +// ================================== + +// queues specific element, performing any device queue set up required +extern int HIDQueueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHidElementRef); + +// adds all elements to queue, performing any device queue set up required +extern int HIDQueueDevice(IOHIDDeviceRef inIOHIDDeviceRef); + +// removes element for queue, if last element in queue will release queue and device +extern int HIDDequeueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHidElementRef); + +// completely removes all elements from queue and releases queue and device +extern int HIDDequeueDevice(IOHIDDeviceRef inIOHIDDeviceRef); + +// releases all device queues for quit or rebuild (must be called) +extern int HIDReleaseAllDeviceQueues(void); + +// returns true if an event is avialable for the element and fills out *pHIDEvent structure, returns false otherwise +// pHIDEvent is a poiner to a IOHIDEventStruct, using void here for compatibility, users can cast a required +extern unsigned char HIDGetEvent(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDValueRef *pIOHIDValueRef); + +// ================================== + +// Conguration and Save Interfaces + +enum { + kPercentMove = 10 // precent of overall range a element must move to register +}; + +typedef struct HID_info_struct { + int actionCookie; + // device + // need to add serial number when I have a test case + struct { + int vendorID, productID; + int locID; + uint32_t usagePage, usage; + } device; + // elements + struct { + uint32_t usagePage, usage; + int minReport, maxReport; + IOHIDElementCookie cookie; // always 32 bits + } element; +}HID_info_rec, *HID_info_ptr; + +// get vendor name from vendor ID +extern Boolean HIDGetVendorNameFromVendorID(long inVendorID, char *outCStrName); + +// get product name from vendor/product ID +extern Boolean HIDGetProductNameFromVendorProductID(long inVendorID, long inProductID, char *outCStrName); + +// get element name from vendor id/product id look up ( using element cookie ) +extern Boolean HIDGetElementNameFromVendorProductCookie(int inVendorID, + int inProductID, + IOHIDElementCookie inCookie, + char * outCStrName); + +// get element name from vendor id/product id look up ( using element usage page & usage ) +extern Boolean HIDGetElementNameFromVendorProductUsage(long inVendorID, + long inProductID, + long inUsagePage, + long inUsage, + char *inCStrName); + +// utility routines to dump device or element info +extern void HIDDumpDeviceInfo(IOHIDDeviceRef inIOHIDDeviceRef); +extern void HIDDumpElementInfo(IOHIDElementRef inIOHIDElementRef); +extern void HIDDumpElementCalibrationInfo(IOHIDElementRef inIOHIDElementRef); + +// polls single device's elements for a change greater than kPercentMove. Times out after given time +// returns 1 and pointer to element if found +// returns 0 and NULL for both parameters if not found +extern unsigned char HIDConfigureSingleDeviceAction(IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef *outIOHIDElementRef, + float timeout); + +//************************************************************************* +// +// HIDConfigureAction( outDeviceRef, outElementRef, inTimeout ) +// +// Purpose: polls all devices and elements for a change greater than kPercentMove. +// Times out after given time returns 1 and pointer to device and element +// if found; returns 0 and NULL for both parameters if not found +// +// Inputs: outDeviceRef - address where to store the device +// outElementRef - address where to store the element +// inTimeout - the timeout +// Returns: Boolean - TRUE if successful +// outDeviceRef - the device +// outElementRef - the element +// + +extern Boolean HIDConfigureAction(IOHIDDeviceRef *outDeviceRef, IOHIDElementRef *outElementRef, float inTimeout); + +//************************************************************************* +// +// HIDSaveElementPref( inKeyCFStringRef, inAppCFStringRef, inDeviceRef, inElementRef ) +// +// Purpose: Save the device & element values into the specified key in the specified applications preferences +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// inDeviceRef - the device +// inElementRef - the element +// Returns: Boolean - if successful +// + +extern Boolean HIDSaveElementPref(const CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef inDeviceRef, + IOHIDElementRef inElementRef); + +//************************************************************************* +// +// HIDRestoreElementPref( inKeyCFStringRef, inAppCFStringRef, outDeviceRef, outElementRef ) +// +// Purpose: Find the specified preference in the specified application +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// outDeviceRef - address where to restore the device +// outElementRef - address where to restore the element +// Returns: Boolean - if successful +// outDeviceRef - the device +// outElementRef - the element +// + +extern Boolean HIDRestoreElementPref(CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef * outDeviceRef, + IOHIDElementRef *outElementRef); + +//************************************************************************* +// +// HIDFindDeviceAndElement( inSearchInfo, outFoundDevice, outFoundElement ) +// +// Purpose: find the closest matching device and element for this action +// +// Notes: matches device: serial, vendorID, productID, location, inUsagePage, usage +// matches element: cookie, inUsagePage, usage, +// +// Inputs: inSearchInfo - the device & element info we searching for +// outFoundDevice - the address of the best matching device +// outFoundElement - the address of the best matching element +// +// Returns: Boolean - TRUE if we find a match +// outFoundDevice - the best matching device +// outFoundElement - the best matching element +// + +extern Boolean HIDFindDeviceAndElement(const HID_info_rec *inSearchInfo, + IOHIDDeviceRef * outFoundDevice, + IOHIDElementRef * outFoundElement); + +// -- These are routines to use if the applcationwants HID Utilities to do the file handling -- +// Note: the FILE * is a MachO posix FILE and will not likely work directly with MW MSL FILE * type. + +// take input records, save required info +// assume file is open and at correct position. +void HIDSaveElementConfig(FILE *fileRef, IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHidElementRef, int actionCookie); + +// takes a file, reads one record (assume file position is correct and file is open) +// search for matching device +// return tIOHIDDeviceRef, tIOHIDElementRef and cookie for action +int HIDRestoreElementConfig(FILE *fileRef, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef); + +// -- These are routines to use if the client wants to use their own file handling -- + +// Set up a config record for saving +// takes an input records, returns record user can save as they want +// Note: the save rec must be pre-allocated by the calling app and will be filled out +void HIDSetElementConfig(HID_info_ptr inHIDInfoPtr, + IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef inIOHidElementRef, + int actionCookie); + +// Get matching element from config record +// takes a pre-allocated and filled out config record +// search for matching device +// return tIOHIDDeviceRef, tIOHIDElementRef and cookie for action +int HIDGetElementConfig(HID_info_ptr inHIDInfoPtr, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef); + +// ================================== + +// Error reporter, can be set to report however the application desires +extern void HIDReportError(const char *strError); + +// Error with numeric code reporter, can be set to report however the application desires +extern void HIDReportErrorNum(const char *strError, int numError); + +#ifdef __cplusplus +} +#endif + +#endif // _HID_Utilities_External_h_ diff --git a/src/cocoa/IOHIDDevice_.c b/src/cocoa/IOHIDDevice_.c new file mode 100755 index 000000000..c1e2d12cf --- /dev/null +++ b/src/cocoa/IOHIDDevice_.c @@ -0,0 +1,544 @@ +// File: IOHIDDevice_.c +// Abstract: convieance functions for IOHIDDeviceGetProperty +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#pragma mark - includes & imports +//----------------------------------------------------- + +#include "IOHIDDevice_.h" + +//***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. +//----------------------------------------------------- + +#define kIOHIDDevice_TransactionKey "DeviceTransactionRef" +#define kIOHIDDevice_QueueKey "DeviceQueueRef" + +//***************************************************** +#pragma mark - local (static) function prototypes +//----------------------------------------------------- + +static Boolean IOHIDDevice_GetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long *outValue); +static void IOHIDDevice_SetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long inValue); + +//***************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - local (static) globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - exported function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// HIDIsValidDevice( inIOHIDDeviceRef ) +// +// Purpose: validate this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: Boolean - TRUE if we find the device in our( internal ) device list +// + +Boolean HIDIsValidDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + Boolean result = FALSE; // assume failure (pessimist!) + if ( inIOHIDDeviceRef ) { + if ( CFGetTypeID(inIOHIDDeviceRef) ==IOHIDDeviceGetTypeID() ) { + result = TRUE; + } + } + + return (result); +} // HIDIsValidDevice + +//************************************************************************* +// +// IOHIDDevice_GetTransport( inIOHIDDeviceRef ) +// +// Purpose: get the Transport CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the Transport for this device +// + +CFStringRef IOHIDDevice_GetTransport(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDTransportKey) ) ); +} + +//************************************************************************* +// +// IOHIDDevice_GetVendorID( inIOHIDDeviceRef ) +// +// Purpose: get the vendor ID for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the vendor ID for this device +// + +long IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey), &result); + return (result); +} // IOHIDDevice_GetVendorID + +//************************************************************************* +// +// IOHIDDevice_GetVendorIDSource( inIOHIDDeviceRef ) +// +// Purpose: get the VendorIDSource for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the VendorIDSource for this device +// + +long IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDSourceKey), &result); + return (result); +} // IOHIDDevice_GetVendorIDSource + +//************************************************************************* +// +// IOHIDDevice_GetProductID( inIOHIDDeviceRef ) +// +// Purpose: get the product ID for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the product ID for this device +// + +long IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey), &result); + return (result); +} // IOHIDDevice_GetProductID + +//************************************************************************* +// +// IOHIDDevice_GetVersionNumber( inIOHIDDeviceRef ) +// +// Purpose: get the VersionNumber CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the VersionNumber for this device +// + +long IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVersionNumberKey), &result); + return (result); +} // IOHIDDevice_GetVersionNumber + +//************************************************************************* +// +// IOHIDDevice_GetManufacturer( inIOHIDDeviceRef ) +// +// Purpose: get the Manufacturer CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the Manufacturer for this device +// + +CFStringRef IOHIDDevice_GetManufacturer(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDManufacturerKey) ) ); +} // IOHIDDevice_GetManufacturer + +//************************************************************************* +// +// IOHIDDevice_GetProduct( inIOHIDDeviceRef ) +// +// Purpose: get the Product CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the Product for this device +// + +CFStringRef IOHIDDevice_GetProduct(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDProductKey) ) ); +} // IOHIDDevice_GetProduct + +//************************************************************************* +// +// IOHIDDevice_GetSerialNumber( inIOHIDDeviceRef ) +// +// Purpose: get the SerialNumber CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the SerialNumber for this device +// + +CFStringRef IOHIDDevice_GetSerialNumber(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDSerialNumberKey) ) ); +} + +//************************************************************************* +// +// IOHIDDevice_GetCountryCode( inIOHIDDeviceRef ) +// +// Purpose: get the CountryCode CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the CountryCode for this device +// + +long IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDCountryCodeKey), &result); + return (result); +} // IOHIDDevice_GetCountryCode + +//************************************************************************* +// +// IOHIDDevice_GetLocationID( inIOHIDDeviceRef ) +// +// Purpose: get the location ID for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the location ID for this device +// + +long IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey), &result); + return (result); +} // IOHIDDevice_GetLocationID + +//************************************************************************* +// +// IOHIDDevice_GetUsage( inIOHIDDeviceRef ) +// +// Purpose: get the usage for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the usage for this device +// + +uint32_t IOHIDDevice_GetUsage(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsageKey), (long *) &result); + return (result); +} // IOHIDDevice_GetUsage + +//************************************************************************* +// +// IOHIDDevice_GetUsagePage( inIOHIDDeviceRef ) +// +// Purpose: get the usage page for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the usage page for this device +// + +uint32_t IOHIDDevice_GetUsagePage(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsagePageKey), &result); + return (result); +} // IOHIDDevice_GetUsagePage + +//************************************************************************* +// +// IOHIDDevice_GetUsagePairs( inIOHIDDeviceRef ) +// +// Purpose: get the UsagePairs CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFArrayRef - the UsagePairs for this device +// + +CFArrayRef IOHIDDevice_GetUsagePairs(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsagePairsKey) ) ); +} + +//************************************************************************* +// +// IOHIDDevice_GetPrimaryUsage( inIOHIDDeviceRef ) +// +// Purpose: get the PrimaryUsage CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the PrimaryUsage for this device +// + +uint32_t IOHIDDevice_GetPrimaryUsage(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey), &result); + return (result); +} // IOHIDDevice_GetPrimaryUsage + +//************************************************************************* +// +// IOHIDDevice_GetPrimaryUsagePage( inIOHIDDeviceRef ) +// +// Purpose: get the PrimaryUsagePage CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the PrimaryUsagePage for this device +// + +uint32_t IOHIDDevice_GetPrimaryUsagePage(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey), &result); + return (result); +} // IOHIDDevice_GetPrimaryUsagePage + +//************************************************************************* +// +// IOHIDDevice_GetMaxInputReportSize( inIOHIDDeviceRef ) +// +// Purpose: get the MaxInputReportSize CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the MaxInputReportSize for this device +// + +long IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDMaxInputReportSizeKey), &result); + return (result); +} // IOHIDDevice_GetMaxInputReportSize + +//************************************************************************* +// +// IOHIDDevice_GetMaxOutputReportSize( inIOHIDDeviceRef ) +// +// Purpose: get the MaxOutputReportSize for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the MaxOutput for this device +// + +long IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDMaxOutputReportSizeKey), &result); + return (result); +} // IOHIDDevice_GetMaxOutputReportSize + +//************************************************************************* +// +// IOHIDDevice_GetMaxFeatureReportSize( inIOHIDDeviceRef ) +// +// Purpose: get the MaxFeatureReportSize for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the MaxFeatureReportSize for this device +// + +long IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDMaxFeatureReportSizeKey), &result); + return (result); +} // IOHIDDevice_GetMaxFeatureReportSize + +//************************************************************************* +// +// IOHIDDevice_GetReportInterval( inIOHIDDeviceRef ) +// +// Purpose: get the ReportInterval for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: long - the ReportInterval for this device +// +#ifndef kIOHIDReportIntervalKey +#define kIOHIDReportIntervalKey "ReportInterval" +#endif +long IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef) { + long result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDReportIntervalKey), &result); + return (result); +} // IOHIDDevice_GetReportInterval + +//************************************************************************* +// +// IOHIDDevice_GetQueue( inIOHIDDeviceRef ) +// +// Purpose: get the Queue for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: IOHIDQueueRef - the Queue for this device +// + +IOHIDQueueRef IOHIDDevice_GetQueue(IOHIDDeviceRef inIOHIDDeviceRef) { + IOHIDQueueRef result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), (long *) &result); + if ( result ) { + assert( IOHIDQueueGetTypeID() == CFGetTypeID(result) ); + } + + return (result); +} // IOHIDDevice_GetQueue + +//************************************************************************* +// +// IOHIDDevice_SetQueue( inIOHIDDeviceRef, inQueueRef ) +// +// Purpose: Set the Queue for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// inQueueRef - the Queue reference +// +// Returns: nothing +// + +void IOHIDDevice_SetQueue(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDQueueRef inQueueRef) { + IOHIDDevice_SetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), (long) inQueueRef); +} + +//************************************************************************* +// +// IOHIDDevice_GetTransaction( inIOHIDDeviceRef ) +// +// Purpose: get the Transaction for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: IOHIDTransactionRef - the Transaction for this device +// + +IOHIDTransactionRef IOHIDDevice_GetTransaction(IOHIDDeviceRef inIOHIDDeviceRef) { + IOHIDTransactionRef result = 0; + (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), (long *) &result); + return (result); +} // IOHIDDevice_GetTransaction + +//************************************************************************* +// +// IOHIDDevice_SetTransaction( inIOHIDDeviceRef, inTransactionRef ) +// +// Purpose: Set the Transaction for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// inTransactionRef - the Transaction reference +// +// Returns: nothing +// + +void IOHIDDevice_SetTransaction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDTransactionRef inTransactionRef) { + IOHIDDevice_SetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), (long) inTransactionRef); +} + +//***************************************************** +#pragma mark - local (static) function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// IOHIDDevice_GetLongProperty( inIOHIDDeviceRef, inKey, outValue ) +// +// Purpose: convieance function to return a long property of a device +// +// Inputs: inIOHIDDeviceRef - the device +// inKey - CFString for the +// outValue - address where to restore the element +// Returns: the action cookie +// outValue - the device +// + +static Boolean IOHIDDevice_GetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long *outValue) { + Boolean result = FALSE; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, inKey); + if ( tCFTypeRef ) { + // if this is a number + if ( CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef) ) { + // get it's value + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, outValue ); + } + } + } + + return (result); +} // IOHIDDevice_GetLongProperty + +//************************************************************************* +// +// IOHIDDevice_SetLongProperty( inIOHIDDeviceRef, inKey, inValue ) +// +// Purpose: convieance function to set a long property of an Device +// +// Inputs: inIOHIDDeviceRef - the Device +// inKey - CFString for the key +// inValue - the value to set it to +// Returns: nothing +// + +static void IOHIDDevice_SetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long inValue) { + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); + if ( tCFNumberRef ) { + IOHIDDeviceSetProperty(inIOHIDDeviceRef, inKey, tCFNumberRef); + CFRelease(tCFNumberRef); + } +} // IOHIDDevice_SetLongProperty + +//***************************************************** diff --git a/src/cocoa/IOHIDDevice_.h b/src/cocoa/IOHIDDevice_.h new file mode 100755 index 000000000..739352844 --- /dev/null +++ b/src/cocoa/IOHIDDevice_.h @@ -0,0 +1,422 @@ +// File: IOHIDDevice_.h +// Abstract: convieance functions for IOHIDDeviceGetProperty +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef __IOHIDDevice__ +#define __IOHIDDevice__ + +//***************************************************** +#pragma mark - includes & imports + +#include + +#include "IOHIDLib_.h" + +//***************************************************** +#if PRAGMA_ONCE +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_IMPORT +#pragma import on +#endif + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=mac68k +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(push, 2) +#elif PRAGMA_STRUCT_PACK +#pragma pack(2) +#endif + + //***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported globals + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported function prototypes + //----------------------------------------------------- + + //************************************************************************* + // + // HIDIsValidDevice( inIOHIDDeviceRef ) + // + // Purpose: validate this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: Boolean - TRUE if we find the device in our( internal ) device list + // + + extern Boolean HIDIsValidDevice(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetTransport( inIOHIDDeviceRef ) + // + // Purpose: get the Transport CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the Transport CFString for this device + // + + extern CFStringRef IOHIDDevice_GetTransport(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetVendorID( inIOHIDDeviceRef ) + // + // Purpose: get the vendor ID for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the vendor ID for this device + // + + extern long IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetVendorIDSource( inIOHIDDeviceRef ) + // + // Purpose: get the VendorIDSource for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the VendorIDSource for this device + // + + extern long IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetProductID( inIOHIDDeviceRef ) + // + // Purpose: get the product ID for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the product ID for this device + // + + extern long IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetVersionNumber( inIOHIDDeviceRef ) + // + // Purpose: get the VersionNumber CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the VersionNumber for this device + // + + extern long IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetManufacturer( inIOHIDDeviceRef ) + // + // Purpose: get the Manufacturer CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the Manufacturer CFString for this device + // + + extern CFStringRef IOHIDDevice_GetManufacturer(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetProduct( inIOHIDDeviceRef ) + // + // Purpose: get the Product CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the Product CFString for this device + // + + extern CFStringRef IOHIDDevice_GetProduct(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetSerialNumber( inIOHIDDeviceRef ) + // + // Purpose: get the SerialNumber CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the SerialNumber CFString for this device + // + + extern CFStringRef IOHIDDevice_GetSerialNumber(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetCountryCode( inIOHIDDeviceRef ) + // + // Purpose: get the CountryCode CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the CountryCode for this device + // + + extern long IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetLocationID( inIOHIDDeviceRef ) + // + // Purpose: get the location ID for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the location ID for this device + // + + extern long IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetUsage( inIOHIDDeviceRef ) + // + // Purpose: get the usage for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the usage for this device + // + + extern uint32_t IOHIDDevice_GetUsage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetUsagePage( inIOHIDDeviceRef ) + // + // Purpose: get the usage page for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the usage page for this device + // + + extern uint32_t IOHIDDevice_GetUsagePage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetUsagePairs( inIOHIDDeviceRef ) + // + // Purpose: get the UsagePairs CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFArrayRef - the UsagePairs for this device + // + + extern CFArrayRef IOHIDDevice_GetUsagePairs(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetPrimaryUsage( inIOHIDDeviceRef ) + // + // Purpose: get the PrimaryUsage CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the PrimaryUsage CFString for this device + // + + extern uint32_t IOHIDDevice_GetPrimaryUsage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetPrimaryUsagePage( inIOHIDDeviceRef ) + // + // Purpose: get the PrimaryUsagePage CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the PrimaryUsagePage CFString for this device + // + + extern uint32_t IOHIDDevice_GetPrimaryUsagePage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetMaxInputReportSize( inIOHIDDeviceRef ) + // + // Purpose: get the MaxInputReportSize for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the MaxInputReportSize for this device + // + + extern long IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetMaxOutputReportSize( inIOHIDDeviceRef ) + // + // Purpose: get the MaxOutputReportSize for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the MaxOutputReportSize for this device + // + + extern long IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetMaxFeatureReportSize( inIOHIDDeviceRef ) + // + // Purpose: get the MaxFeatureReportSize for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the MaxFeatureReportSize for this device + // + + extern long IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetReportInterval( inIOHIDDeviceRef ) + // + // Purpose: get the ReportInterval for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: long - the ReportInterval for this device + // + + extern long IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetQueue( inIOHIDDeviceRef ) + // + // Purpose: get the Queue for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: IOHIDQueueRef - the Queue for this device + // + + extern IOHIDQueueRef IOHIDDevice_GetQueue(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_SetQueue( inIOHIDDeviceRef, inQueueRef ) + // + // Purpose: Set the Queue for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // inQueueRef - the Queue + // + // Returns: nothing + // + + extern void IOHIDDevice_SetQueue(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDQueueRef inQueueRef); + + //************************************************************************* + // + // IOHIDDevice_GetTransaction( inIOHIDDeviceRef ) + // + // Purpose: get the Transaction for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: IOHIDTransactionRef - the Transaction for this device + // + + extern IOHIDTransactionRef IOHIDDevice_GetTransaction(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_SetTransaction( inIOHIDDeviceRef, inTransactionRef ) + // + // Purpose: Set the Transaction for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // inTransactionRef - the Transaction + // + // Returns: nothing + // + + extern void IOHIDDevice_SetTransaction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDTransactionRef inTransactionRef); + + //***************************************************** +#if PRAGMA_STRUCT_ALIGN +#pragma options align=reset +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(pop) +#elif PRAGMA_STRUCT_PACK +#pragma pack() +#endif + +#ifdef PRAGMA_IMPORT_OFF +#pragma import off +#elif PRAGMA_IMPORT +#pragma import reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __IOHIDDevice__ // diff --git a/src/cocoa/IOHIDElement_.c b/src/cocoa/IOHIDElement_.c new file mode 100755 index 000000000..f4d8be9eb --- /dev/null +++ b/src/cocoa/IOHIDElement_.c @@ -0,0 +1,502 @@ +// File: IOHIDElement_.c +// Abstract: convieance functions for IOHIDElementGetProperty +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#pragma mark - includes & imports +//----------------------------------------------------- + +#include "IOHIDElement_.h" + +//***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. +//----------------------------------------------------- + +//***************************************************** +#pragma mark - local (static) function prototypes +//----------------------------------------------------- + +// static Boolean IOHIDElement_GetLongProperty( IOHIDElementRef inElementRef, CFStringRef inKey, long * outValue ); +// static void IOHIDElement_SetLongProperty( IOHIDElementRef inElementRef, CFStringRef inKey, long inValue ); + +//***************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - local (static) globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - exported function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// HIDIsValidElement( inIOHIDElementRef ) +// +// Purpose: validate this element +// +// Inputs: inIOHIDElementRef - the element +// +// Returns: Boolean - TRUE if this is a valid element ref +// +Boolean HIDIsValidElement(IOHIDElementRef inIOHIDElementRef) { + Boolean result = FALSE; // assume failure (pessimist!) + if ( inIOHIDElementRef ) { + if ( CFGetTypeID(inIOHIDElementRef) ==IOHIDElementGetTypeID() ) { + result = TRUE; + } + } + + return (result); +} // HIDIsValidElement + +//************************************************************************* +// +// IOHIDElement_GetValue( inElementRef, inIOHIDValueScaleType ) +// +// Purpose: returns the current value for an element( polling ) +// +// Notes: will return 0 on error conditions which should be accounted for by application +// +// Inputs: inElementRef - the element +// inIOHIDValueScaleType - scale type ( calibrated or physical ) +// +// Returns: double - current value for element +// +double IOHIDElement_GetValue(IOHIDElementRef inElementRef, IOHIDValueScaleType inIOHIDValueScaleType) { + long result = 0; + IOHIDValueRef tIOHIDValueRef; + if ( kIOReturnSuccess == IOHIDDeviceGetValue(IOHIDElementGetDevice(inElementRef), inElementRef, &tIOHIDValueRef) ) { + result = IOHIDValueGetScaledValue(tIOHIDValueRef, inIOHIDValueScaleType); + } + + return (result); +} // IOHIDElement_GetValue + +//************************************************************************* +// +// IOHIDElement_GetCalibrationMin( inElementRef ) +// +// Purpose: get the minimum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the minimum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationMin(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMinKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMaxKey), &result) ) { + result = 0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMinKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationMin + +//************************************************************************* +// +// IOHIDElement_SetCalibrationMin( inElementRef, inValue ) +// +// Purpose: set the minimum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the minimum bounds for a calibrated value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationMin(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMinKey), inValue); +} // IOHIDElement_SetCalibrationMin + +//************************************************************************* +// +// IOHIDElement_GetCalibrationMax( inElementRef ) +// +// Purpose: get the maximum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationMax(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMaxKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMaxKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationMax + +//************************************************************************* +// +// IOHIDElement_SetCalibrationMax( inElementRef, inValue ) +// +// Purpose: set the maximum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationMax(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMaxKey), inValue); +} // IOHIDElement_SetCalibrationMax + +//************************************************************************* +// +// IOHIDElement_GetCalibrationSaturationMin( inElementRef ) +// +// Purpose: get the mininum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationSaturationMin(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMinKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMinKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationSaturationMin + +//************************************************************************* +// +// IOHIDElement_SetCalibrationSaturationMin( inElementRef, inValue ) +// +// Purpose: set the mininum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationSaturationMin(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMinKey), inValue); +} // IOHIDElement_SetCalibrationSaturationMin + +//************************************************************************* +// +// IOHIDElement_GetCalibrationSaturationMax( inElementRef ) +// +// Purpose: get the maximum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationSaturationMax(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMaxKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMaxKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationSaturationMax + +//************************************************************************* +// +// IOHIDElement_SetCalibrationSaturationMax( inElementRef, inValue ) +// +// Purpose: set the maximum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationSaturationMax(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMaxKey), inValue); +} // IOHIDElement_SetCalibrationSaturationMax + +//************************************************************************* +// +// IOHIDElement_GetCalibrationDeadZoneMin( inElementRef ) +// +// Purpose: get the minimum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationDeadZoneMin(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMinKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMinKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationDeadZoneMin + +//************************************************************************* +// +// IOHIDElement_SetCalibrationDeadZoneMin( inElementRef, inValue ) +// +// Purpose: set the minimum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationDeadZoneMin(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMinKey), inValue); +} // IOHIDElement_SetCalibrationDeadZoneMin + +//************************************************************************* +// +// IOHIDElement_GetCalibrationDeadZoneMax( inElementRef ) +// +// Purpose: get the maximum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationDeadZoneMax(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMaxKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMaxKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationDeadZoneMax + +//************************************************************************* +// +// IOHIDElement_SetCalibrationDeadZoneMax( inElementRef, inValue ) +// +// Purpose: set the maximum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationDeadZoneMax(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMaxKey), inValue); +} // IOHIDElement_SetCalibrationDeadZoneMax + +//************************************************************************* +// +// IOHIDElement_GetCalibrationGranularity( inElementRef ) +// +// Purpose: get the level of detail returned for a calibrated element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: double_t - the maximum Calibration value for this element +// + +double_t IOHIDElement_GetCalibrationGranularity(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationGranularityKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationGranularityKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationGranularity + +//************************************************************************* +// +// IOHIDElement_SetCalibrationGranularity( inElementRef, inValue ) +// +// Purpose: set the level of detail returned for a calibrated element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the the level of detail for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationGranularity(IOHIDElementRef inElementRef, double_t inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationGranularityKey), inValue); +} // IOHIDElement_SetCalibrationGranularity + +//************************************************************************* +// +// IOHIDElement_SetupCalibration( inElementRef ) +// +// Purpose: set default values for the element calibration parameters +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: nothing +// +void IOHIDElement_SetupCalibration(IOHIDElementRef inIOHIDElementRef) { + // these are the min/max values returned by IOHIDValueGetScaledValue( v, kIOHIDValueScaleTypeCalibrated ); + IOHIDElement_SetCalibrationMin( inIOHIDElementRef, IOHIDElementGetLogicalMin(inIOHIDElementRef) ); + IOHIDElement_SetCalibrationMax( inIOHIDElementRef, IOHIDElementGetLogicalMax(inIOHIDElementRef) ); + + // this is the granularity of the values returned by IOHIDValueGetScaledValue( v, kIOHIDValueScaleTypeCalibrated ); + // for example if set to 0.1 the values returned will be multiples of 0.1 ( 0.1, 0.2, 0.3, etc. ) + IOHIDElement_SetCalibrationGranularity(inIOHIDElementRef, 0.); + + // these define the dead zone (like in the middel of joystick axis) + IOHIDElement_SetCalibrationDeadZoneMin(inIOHIDElementRef, 0); + IOHIDElement_SetCalibrationDeadZoneMax(inIOHIDElementRef, 0); +#if 1 + // get the current value of this element + double value = IOHIDElement_GetValue(inIOHIDElementRef, kIOHIDValueScaleTypePhysical); + // use it as our min/mas saturation + IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, value); + IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, value); +#else + // calculate the middle physical value we would expect from this element + CFIndex valueMin = IOHIDElementGetPhysicalMin(inIOHIDElementRef); + CFIndex valueMax = IOHIDElementGetPhysicalMax(inIOHIDElementRef); + CFIndex valueMid = (valueMin + valueMax) / 2; + + // use it as our min/mas saturation + // this value determines the min/max values that have been recieved from the device element + IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, valueMid); + IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, valueMid); + + // get the current value of this element + double value = IOHIDElement_GetValue(inIOHIDElementRef, kIOHIDValueScaleTypePhysical); + // and use it to adjust the current saturation values if it's outside their range + if ( value < IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef) ) { + IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, value); + } + if ( value > IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef) ) { + IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, value); + } + +#endif +} // IOHIDElement_SetupCalibration +//***************************************************** +#pragma mark - local (static) function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// IOHIDElement_GetLongProperty( inElementRef, inKey, outValue ) +// +// Purpose: convieance function to return a long property of an element +// +// Inputs: inElementRef - the element +// inKey - CFString for the key +// outValue - address where to store the value +// Returns: Boolean - TRUE if successful +// outValue - the long property's value +// + +Boolean IOHIDElement_GetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long *outValue) { + Boolean result = FALSE; + + CFTypeRef tCFTypeRef = IOHIDElementGetProperty(inElementRef, inKey); + if ( tCFTypeRef ) { + // if this is a number + if ( CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef) ) { + // get it's value + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, outValue ); + } + } + + return (result); +} /* IOHIDElement_GetLongProperty */ + +//************************************************************************* +// +// IOHIDElement_SetLongProperty( inElementRef, inKey, inValue ) +// +// Purpose: convieance function to set a long property of an element +// +// Inputs: inElementRef - the element +// inKey - CFString for the key +// inValue - the value to set it to +// +// Returns: nothing +// + +void IOHIDElement_SetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long inValue) { + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); + if ( tCFNumberRef ) { + IOHIDElementSetProperty(inElementRef, inKey, tCFNumberRef); + CFRelease(tCFNumberRef); + } +} // IOHIDElement_SetLongProperty + +//***************************************************** diff --git a/src/cocoa/IOHIDElement_.h b/src/cocoa/IOHIDElement_.h new file mode 100755 index 000000000..a8a631668 --- /dev/null +++ b/src/cocoa/IOHIDElement_.h @@ -0,0 +1,339 @@ +// File: IOHIDElement_.h +// Abstract: convieance functions for IOHIDElementGetProperty +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef __IOHIDElement___ +#define __IOHIDElement___ + +//***************************************************** +#pragma mark - includes & imports + +#include + +#include "IOHIDLib_.h" +//***************************************************** +#if PRAGMA_ONCE +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_IMPORT +#pragma import on +#endif + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=mac68k +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(push, 2) +#elif PRAGMA_STRUCT_PACK +#pragma pack(2) +#endif + + //***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported globals + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported function prototypes + //----------------------------------------------------- + + //************************************************************************* + // + // HIDIsValidElement( inIOHIDElementRef ) + // + // Purpose: validate this element + // + // Inputs: inIOHIDElementRef - the element + // + // Returns: Boolean - TRUE if this is a valid element ref + // + extern Boolean HIDIsValidElement(IOHIDElementRef inIOHIDElementRef); + + //************************************************************************* + // + // IOHIDElement_GetValue( inElementRef, inIOHIDValueScaleType ) + // + // Purpose: returns the current value for an element( polling ) + // + // Notes: will return 0 on error conditions which should be accounted for by application + // + // Inputs: inElementRef - the element + // inIOHIDValueScaleType - scale type ( calibrated or physical ) + // + // Returns: double - current value for element + // + extern double IOHIDElement_GetValue(IOHIDElementRef inElementRef, IOHIDValueScaleType inIOHIDValueScaleType); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationMin( inElementRef ) + // + // Purpose: get the minimum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the minimum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationMin(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationMin( inElementRef, inValue ) + // + // Purpose: set the minimum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the minimum bounds for a calibrated value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationMin(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationMax( inElementRef ) + // + // Purpose: get the maximum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationMax(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationMax( inElementRef, inValue ) + // + // Purpose: set the maximum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationMax(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationSaturationMin( inElementRef ) + // + // Purpose: get the mininum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationSaturationMin(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationSaturationMin( inElementRef, inValue ) + // + // Purpose: set the mininum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationSaturationMin(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationSaturationMax( inElementRef ) + // + // Purpose: get the maximum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationSaturationMax(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationSaturationMax( inElementRef, inValue ) + // + // Purpose: set the maximum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationSaturationMax(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationDeadZoneMin( inElementRef ) + // + // Purpose: get the minimum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationDeadZoneMin(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationDeadZoneMin( inElementRef, inValue ) + // + // Purpose: set the minimum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationDeadZoneMin(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationDeadZoneMax( inElementRef ) + // + // Purpose: get the maximum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationDeadZoneMax(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationDeadZoneMax( inElementRef, inValue ) + // + // Purpose: set the maximum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationDeadZoneMax(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationGranularity( inElementRef ) + // + // Purpose: get the level of detail returned for a calibrated element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: double_t - the maximum Calibration value for this element + // + + extern double_t IOHIDElement_GetCalibrationGranularity(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationGranularity( inElementRef, inValue ) + // + // Purpose: set the level of detail returned for a calibrated element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the the level of detail for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationGranularity(IOHIDElementRef inElementRef, double_t inValue); + + //************************************************************************* + // + // IOHIDElement_SetupCalibration( inElementRef ) + // + // Purpose: set default values for the element calibration parameters + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetupCalibration(IOHIDElementRef inIOHIDElementRef); + + extern Boolean IOHIDElement_GetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long *outValue); + extern void IOHIDElement_SetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long inValue); + + //***************************************************** +#if PRAGMA_STRUCT_ALIGN +#pragma options align=reset +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(pop) +#elif PRAGMA_STRUCT_PACK +#pragma pack() +#endif + +#ifdef PRAGMA_IMPORT_OFF +#pragma import off +#elif PRAGMA_IMPORT +#pragma import reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __IOHIDElement___ // diff --git a/src/cocoa/IOHIDLib_.h b/src/cocoa/IOHIDLib_.h new file mode 100755 index 000000000..38c6248ac --- /dev/null +++ b/src/cocoa/IOHIDLib_.h @@ -0,0 +1,111 @@ +// File: IOHIDLib_.h +// Abstract: Single include file for all header files of IOHIDLib +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef __IOHIDLib___ +#define __IOHIDLib___ + +//***************************************************** +#pragma mark - includes & imports +//----------------------------------------------------- +#include + +#include "IOHIDDevice_.h" +#include "IOHIDElement_.h" + +#include "ImmrHIDUtilAddOn.h" + +//***************************************************** +#if PRAGMA_ONCE +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_IMPORT +#pragma import on +#endif + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=mac68k +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(push, 2) +#elif PRAGMA_STRUCT_PACK +#pragma pack(2) +#endif + + //***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported globals + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported function prototypes + //----------------------------------------------------- + + //***************************************************** +#if PRAGMA_STRUCT_ALIGN +#pragma options align=reset +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(pop) +#elif PRAGMA_STRUCT_PACK +#pragma pack() +#endif + +#ifdef PRAGMA_IMPORT_OFF +#pragma import off +#elif PRAGMA_IMPORT +#pragma import reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __IOHIDLib___ diff --git a/src/cocoa/ImmrHIDUtilAddOn.c b/src/cocoa/ImmrHIDUtilAddOn.c new file mode 100755 index 000000000..07e6a31b1 --- /dev/null +++ b/src/cocoa/ImmrHIDUtilAddOn.c @@ -0,0 +1,101 @@ +// File: ImmrHIDUtilAddOn.c +// Abstract: Glue code to convert IOHIDDeviceRef's to (FFB) io_object_t's +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#include +#include + +#include "ImmrHIDUtilAddOn.h" + +//--------------------------------------------------------------------------------- +// +// AllocateHIDObjectFromIOHIDDeviceRef( ) +// +// returns: +// NULL, or acceptable io_object_t +// +//--------------------------------------------------------------------------------- +io_service_t AllocateHIDObjectFromIOHIDDeviceRef(IOHIDDeviceRef inIOHIDDeviceRef) { + io_service_t result = 0L; + if ( inIOHIDDeviceRef ) { + // Set up the matching criteria for the devices we're interested in. + // We are interested in instances of class IOHIDDevice. + // matchingDict is consumed below( in IOServiceGetMatchingService ) + // so we have no leak here. + CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOHIDDeviceKey); + if ( matchingDict ) { + // Add a key for locationID to our matching dictionary. This works for matching to + // IOHIDDevices, so we will only look for a device attached to that particular port + // on the machine. + CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey) ); + if ( tCFTypeRef ) { + CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDLocationIDKey), tCFTypeRef); + // CFRelease( tCFTypeRef ); // don't release objects that we "Get". + + // IOServiceGetMatchingService assumes that we already know that there is only one device + // that matches. This way we don't have to do the whole iteration dance to look at each + // device that matches. This is a new API in 10.2 + result = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict); + } + + // Note: We're not leaking the matchingDict. + // One reference is consumed by IOServiceGetMatchingServices + } + } + + return (result); +} // AllocateHIDObjectFromIOHIDDeviceRef + +//--------------------------------------------------------------------------------- +// +// FreeHIDObject( ) +// +//--------------------------------------------------------------------------------- +bool FreeHIDObject(io_service_t inHIDObject) { + kern_return_t kr; + + kr = IOObjectRelease(inHIDObject); + + return (kIOReturnSuccess == kr); +} // FreeHIDObject diff --git a/src/cocoa/ImmrHIDUtilAddOn.h b/src/cocoa/ImmrHIDUtilAddOn.h new file mode 100755 index 000000000..72de752e3 --- /dev/null +++ b/src/cocoa/ImmrHIDUtilAddOn.h @@ -0,0 +1,50 @@ +// File: ImmrHIDUtilAddOn.h +// Abstract: Glue code to convert IOHIDDeviceRef's to (FFB) io_object_t's +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#include +#include + +extern io_service_t AllocateHIDObjectFromIOHIDDeviceRef(IOHIDDeviceRef inIOHIDDeviceRef); +extern bool FreeHIDObject(io_object_t inHIDObject); From d6cc6ee452633ebfe7f587bf8d51edf38652ee54 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:26:17 +0300 Subject: [PATCH 06/75] Added notification dialog in case of fatal error --- src/sdl/i_main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 27324edf6..3e199a165 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -75,6 +75,10 @@ extern "C" int cc_install_handlers(int, char**, int, int*, const char*, int(*)(char*, char*)); +#ifdef __APPLE__ +void Mac_I_FatalError(const char* errortext); +#endif + // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- @@ -356,6 +360,11 @@ int main (int argc, char **argv) I_ShutdownJoysticks(); if (error.GetMessage ()) fprintf (stderr, "%s\n", error.GetMessage ()); + +#ifdef __APPLE__ + Mac_I_FatalError(error.GetMessage()); +#endif // __APPLE__ + exit (-1); } catch (...) From fecd1b640103640af70fa4135f5ffa6c515708fe Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:33:29 +0300 Subject: [PATCH 07/75] Moved cursor and timer implementations into separate files --- src/CMakeLists.txt | 2 + src/sdl/i_cursor.cpp | 73 +++++++++++++ src/sdl/i_system.cpp | 250 ++----------------------------------------- src/sdl/i_timer.cpp | 210 ++++++++++++++++++++++++++++++++++++ 4 files changed, 291 insertions(+), 244 deletions(-) create mode 100644 src/sdl/i_cursor.cpp create mode 100644 src/sdl/i_timer.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1444f1cf3..87c3f4dfa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -559,11 +559,13 @@ set( PLAT_SDL_SOURCES sdl/crashcatcher.c sdl/hardware.cpp sdl/i_cd.cpp + sdl/i_cursor.cpp sdl/i_input.cpp sdl/i_joystick.cpp sdl/i_main.cpp sdl/i_movie.cpp sdl/i_system.cpp + sdl/i_timer.cpp sdl/sdlvideo.cpp sdl/st_start.cpp ) set( PLAT_MAC_SOURCES diff --git a/src/sdl/i_cursor.cpp b/src/sdl/i_cursor.cpp new file mode 100644 index 000000000..c4202a6b3 --- /dev/null +++ b/src/sdl/i_cursor.cpp @@ -0,0 +1,73 @@ + +// Moved from sdl/i_system.cpp + +#include + +#include + +#include "bitmap.h" +#include "v_palette.h" +#include "textures.h" + + +extern SDL_Surface *cursorSurface; +extern SDL_Rect cursorBlit; + + +bool I_SetCursor(FTexture *cursorpic) +{ + if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null) + { + // Must be no larger than 32x32. + if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) + { + return false; + } + +#ifdef USE_XCURSOR + if (UseXCursor) + { + if (FirstCursor == NULL) + { + FirstCursor = SDL_GetCursor(); + } + X11Cursor = CreateColorCursor(cursorpic); + if (X11Cursor != NULL) + { + SDL_SetCursor(X11Cursor); + return true; + } + } +#endif + if (cursorSurface == NULL) + cursorSurface = SDL_CreateRGBSurface (0, 32, 32, 32, MAKEARGB(0,255,0,0), MAKEARGB(0,0,255,0), MAKEARGB(0,0,0,255), MAKEARGB(255,0,0,0)); + + SDL_ShowCursor(0); + SDL_LockSurface(cursorSurface); + BYTE buffer[32*32*4]; + memset(buffer, 0, 32*32*4); + FBitmap bmp(buffer, 32*4, 32, 32); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + memcpy(cursorSurface->pixels, bmp.GetPixels(), 32*32*4); + SDL_UnlockSurface(cursorSurface); + } + else + { + SDL_ShowCursor(1); + + if (cursorSurface != NULL) + { + SDL_FreeSurface(cursorSurface); + cursorSurface = NULL; + } +#ifdef USE_XCURSOR + if (X11Cursor != NULL) + { + SDL_SetCursor(FirstCursor); + SDL_FreeCursor(X11Cursor); + X11Cursor = NULL; + } +#endif + } + return true; +} diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index 9b3027a7d..ca5ffa2e2 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -122,190 +122,6 @@ void I_EndRead(void) } -static DWORD TicStart; -static DWORD TicNext; -static DWORD BaseTime; -static int TicFrozen; - -// Signal based timer. -static Semaphore timerWait; -static int tics; -static DWORD sig_start, sig_next; - -void I_SelectTimer(); - -// [RH] Returns time in milliseconds -unsigned int I_MSTime (void) -{ - unsigned int time = SDL_GetTicks (); - return time - BaseTime; -} - -// Exactly the same thing, but based does no modification to the time. -unsigned int I_FPSTime() -{ - return SDL_GetTicks(); -} - -// -// I_GetTime -// returns time in 1/35th second tics -// -int I_GetTimeSelect (bool saveMS) -{ - I_SelectTimer(); - return I_GetTime (saveMS); -} - -int I_GetTimePolled (bool saveMS) -{ - if (TicFrozen != 0) - { - return TicFrozen; - } - - DWORD tm = SDL_GetTicks(); - - if (saveMS) - { - TicStart = tm; - TicNext = Scale((Scale (tm, TICRATE, 1000) + 1), 1000, TICRATE); - } - return Scale(tm - BaseTime, TICRATE, 1000); -} - -int I_GetTimeSignaled (bool saveMS) -{ - if (saveMS) - { - TicStart = sig_start; - TicNext = sig_next; - } - return tics; -} - -int I_WaitForTicPolled (int prevtic) -{ - int time; - - assert (TicFrozen == 0); - while ((time = I_GetTimePolled(false)) <= prevtic) - ; - - return time; -} - -int I_WaitForTicSignaled (int prevtic) -{ - assert (TicFrozen == 0); - - while(tics <= prevtic) - { - SEMAPHORE_WAIT(timerWait) - } - - return tics; -} - -void I_FreezeTimeSelect (bool frozen) -{ - I_SelectTimer(); - return I_FreezeTime (frozen); -} - -void I_FreezeTimePolled (bool frozen) -{ - if (frozen) - { - assert(TicFrozen == 0); - TicFrozen = I_GetTimePolled(false); - } - else - { - assert(TicFrozen != 0); - int froze = TicFrozen; - TicFrozen = 0; - int now = I_GetTimePolled(false); - BaseTime += (now - froze) * 1000 / TICRATE; - } -} - -void I_FreezeTimeSignaled (bool frozen) -{ - TicFrozen = frozen; -} - -int I_WaitForTicSelect (int prevtic) -{ - I_SelectTimer(); - return I_WaitForTic (prevtic); -} - -// -// I_HandleAlarm -// Should be called every time there is an alarm. -// -void I_HandleAlarm (int sig) -{ - if(!TicFrozen) - tics++; - sig_start = SDL_GetTicks(); - sig_next = Scale((Scale (sig_start, TICRATE, 1000) + 1), 1000, TICRATE); - SEMAPHORE_SIGNAL(timerWait) -} - -// -// I_SelectTimer -// Sets up the timer function based on if we can use signals for efficent CPU -// usage. -// -void I_SelectTimer() -{ - SEMAPHORE_INIT(timerWait, 0, 0) -#ifndef __sun - signal(SIGALRM, I_HandleAlarm); -#else - struct sigaction alrmaction; - sigaction(SIGALRM, NULL, &alrmaction); - alrmaction.sa_handler = I_HandleAlarm; - sigaction(SIGALRM, &alrmaction, NULL); -#endif - - struct itimerval itv; - itv.it_interval.tv_sec = itv.it_value.tv_sec = 0; - itv.it_interval.tv_usec = itv.it_value.tv_usec = 1000000/TICRATE; - - if (setitimer(ITIMER_REAL, &itv, NULL) != 0) - { - I_GetTime = I_GetTimePolled; - I_FreezeTime = I_FreezeTimePolled; - I_WaitForTic = I_WaitForTicPolled; - } - else - { - I_GetTime = I_GetTimeSignaled; - I_FreezeTime = I_FreezeTimeSignaled; - I_WaitForTic = I_WaitForTicSignaled; - } -} - -// Returns the fractional amount of a tic passed since the most recent tic -fixed_t I_GetTimeFrac (uint32 *ms) -{ - DWORD now = SDL_GetTicks (); - if (ms) *ms = TicNext; - DWORD step = TicNext - TicStart; - if (step == 0) - { - return FRACUNIT; - } - else - { - fixed_t frac = clamp ((now - TicStart)*FRACUNIT/step, 0, FRACUNIT); - return frac; - } -} - void I_WaitVBL (int count) { // I_WaitVBL is never used to actually synchronize to the @@ -327,6 +143,9 @@ void SetLanguageIDs () LanguageIDs[3] = LanguageIDs[2] = LanguageIDs[1] = LanguageIDs[0] = lang; } +void I_InitTimer (); +void I_ShutdownTimer (); + // // I_Init // @@ -335,11 +154,9 @@ void I_Init (void) CheckCPUID (&CPU); DumpCPUInfo (&CPU); - I_GetTime = I_GetTimeSelect; - I_WaitForTic = I_WaitForTicSelect; - I_FreezeTime = I_FreezeTimeSelect; atterm (I_ShutdownSound); I_InitSound (); + I_InitTimer (); } // @@ -355,6 +172,8 @@ void I_Quit (void) G_CheckDemoStatus(); C_DeinitConsole(); + + I_ShutdownTimer(); } @@ -900,60 +719,3 @@ SDL_Cursor *CreateColorCursor(FTexture *cursorpic) SDL_Surface *cursorSurface = NULL; SDL_Rect cursorBlit = {0, 0, 32, 32}; -bool I_SetCursor(FTexture *cursorpic) -{ - if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null) - { - // Must be no larger than 32x32. - if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) - { - return false; - } - -#ifdef USE_XCURSOR - if (UseXCursor) - { - if (FirstCursor == NULL) - { - FirstCursor = SDL_GetCursor(); - } - X11Cursor = CreateColorCursor(cursorpic); - if (X11Cursor != NULL) - { - SDL_SetCursor(X11Cursor); - return true; - } - } -#endif - if (cursorSurface == NULL) - cursorSurface = SDL_CreateRGBSurface (0, 32, 32, 32, MAKEARGB(0,255,0,0), MAKEARGB(0,0,255,0), MAKEARGB(0,0,0,255), MAKEARGB(255,0,0,0)); - - SDL_ShowCursor(0); - SDL_LockSurface(cursorSurface); - BYTE buffer[32*32*4]; - memset(buffer, 0, 32*32*4); - FBitmap bmp(buffer, 32*4, 32, 32); - cursorpic->CopyTrueColorPixels(&bmp, 0, 0); - memcpy(cursorSurface->pixels, bmp.GetPixels(), 32*32*4); - SDL_UnlockSurface(cursorSurface); - } - else - { - SDL_ShowCursor(1); - - if (cursorSurface != NULL) - { - SDL_FreeSurface(cursorSurface); - cursorSurface = NULL; - } -#ifdef USE_XCURSOR - if (X11Cursor != NULL) - { - SDL_SetCursor(FirstCursor); - SDL_FreeCursor(X11Cursor); - X11Cursor = NULL; - } -#endif - } - return true; -} diff --git a/src/sdl/i_timer.cpp b/src/sdl/i_timer.cpp new file mode 100644 index 000000000..b36bfc840 --- /dev/null +++ b/src/sdl/i_timer.cpp @@ -0,0 +1,210 @@ + +// Moved from sdl/i_system.cpp + +#include +#include + +#include + +#include "basictypes.h" +#include "basicinlines.h" +#include "hardware.h" +#include "i_system.h" +#include "templates.h" + + +static DWORD TicStart; +static DWORD TicNext; +static DWORD BaseTime; +static int TicFrozen; + +// Signal based timer. +static Semaphore timerWait; +static int tics; +static DWORD sig_start, sig_next; + +void I_SelectTimer(); + +// [RH] Returns time in milliseconds +unsigned int I_MSTime (void) +{ + unsigned int time = SDL_GetTicks (); + return time - BaseTime; +} + +// Exactly the same thing, but based does no modification to the time. +unsigned int I_FPSTime() +{ + return SDL_GetTicks(); +} + +// +// I_GetTime +// returns time in 1/35th second tics +// +int I_GetTimeSelect (bool saveMS) +{ + I_SelectTimer(); + return I_GetTime (saveMS); +} + +int I_GetTimePolled (bool saveMS) +{ + if (TicFrozen != 0) + { + return TicFrozen; + } + + DWORD tm = SDL_GetTicks(); + + if (saveMS) + { + TicStart = tm; + TicNext = Scale((Scale (tm, TICRATE, 1000) + 1), 1000, TICRATE); + } + return Scale(tm - BaseTime, TICRATE, 1000); +} + +int I_GetTimeSignaled (bool saveMS) +{ + if (saveMS) + { + TicStart = sig_start; + TicNext = sig_next; + } + return tics; +} + +int I_WaitForTicPolled (int prevtic) +{ + int time; + + assert (TicFrozen == 0); + while ((time = I_GetTimePolled(false)) <= prevtic) + ; + + return time; +} + +int I_WaitForTicSignaled (int prevtic) +{ + assert (TicFrozen == 0); + + while(tics <= prevtic) + { + SEMAPHORE_WAIT(timerWait) + } + + return tics; +} + +void I_FreezeTimeSelect (bool frozen) +{ + I_SelectTimer(); + return I_FreezeTime (frozen); +} + +void I_FreezeTimePolled (bool frozen) +{ + if (frozen) + { + assert(TicFrozen == 0); + TicFrozen = I_GetTimePolled(false); + } + else + { + assert(TicFrozen != 0); + int froze = TicFrozen; + TicFrozen = 0; + int now = I_GetTimePolled(false); + BaseTime += (now - froze) * 1000 / TICRATE; + } +} + +void I_FreezeTimeSignaled (bool frozen) +{ + TicFrozen = frozen; +} + +int I_WaitForTicSelect (int prevtic) +{ + I_SelectTimer(); + return I_WaitForTic (prevtic); +} + +// +// I_HandleAlarm +// Should be called every time there is an alarm. +// +void I_HandleAlarm (int sig) +{ + if(!TicFrozen) + tics++; + sig_start = SDL_GetTicks(); + sig_next = Scale((Scale (sig_start, TICRATE, 1000) + 1), 1000, TICRATE); + SEMAPHORE_SIGNAL(timerWait) +} + +// +// I_SelectTimer +// Sets up the timer function based on if we can use signals for efficent CPU +// usage. +// +void I_SelectTimer() +{ + SEMAPHORE_INIT(timerWait, 0, 0) +#ifndef __sun + signal(SIGALRM, I_HandleAlarm); +#else + struct sigaction alrmaction; + sigaction(SIGALRM, NULL, &alrmaction); + alrmaction.sa_handler = I_HandleAlarm; + sigaction(SIGALRM, &alrmaction, NULL); +#endif + + struct itimerval itv; + itv.it_interval.tv_sec = itv.it_value.tv_sec = 0; + itv.it_interval.tv_usec = itv.it_value.tv_usec = 1000000/TICRATE; + + if (setitimer(ITIMER_REAL, &itv, NULL) != 0) + { + I_GetTime = I_GetTimePolled; + I_FreezeTime = I_FreezeTimePolled; + I_WaitForTic = I_WaitForTicPolled; + } + else + { + I_GetTime = I_GetTimeSignaled; + I_FreezeTime = I_FreezeTimeSignaled; + I_WaitForTic = I_WaitForTicSignaled; + } +} + +// Returns the fractional amount of a tic passed since the most recent tic +fixed_t I_GetTimeFrac (uint32 *ms) +{ + DWORD now = SDL_GetTicks (); + if (ms) *ms = TicNext; + DWORD step = TicNext - TicStart; + if (step == 0) + { + return FRACUNIT; + } + else + { + fixed_t frac = clamp ((now - TicStart)*FRACUNIT/step, 0, FRACUNIT); + return frac; + } +} + +void I_InitTimer () +{ + I_GetTime = I_GetTimeSelect; + I_WaitForTic = I_WaitForTicSelect; + I_FreezeTime = I_FreezeTimeSelect; +} + +void I_ShutdownTimer () +{ + +} From e12f860f1bdc63d5f4835b568cb4023f7d72e15c Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:36:02 +0300 Subject: [PATCH 08/75] Added native Cocoa back-end implementation --- src/cocoa/i_backend_cocoa.mm | 1744 ++++++++++++++++++++++++++++++++++ src/cocoa/i_joystick.cpp | 776 +++++++++++++++ src/cocoa/i_timer.cpp | 191 ++++ src/cocoa/zdoom-info.plist | 47 + src/cocoa/zdoom.icns | Bin 0 -> 196176 bytes src/cocoa/zdoom.xib | 874 +++++++++++++++++ 6 files changed, 3632 insertions(+) create mode 100644 src/cocoa/i_backend_cocoa.mm create mode 100644 src/cocoa/i_joystick.cpp create mode 100644 src/cocoa/i_timer.cpp create mode 100644 src/cocoa/zdoom-info.plist create mode 100644 src/cocoa/zdoom.icns create mode 100644 src/cocoa/zdoom.xib diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm new file mode 100644 index 000000000..d4c353edd --- /dev/null +++ b/src/cocoa/i_backend_cocoa.mm @@ -0,0 +1,1744 @@ +/* + ** i_backend_cocoa.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + +#include "basictypes.h" +#include "basicinlines.h" +#include "bitmap.h" +#include "c_console.h" +#include "c_dispatch.h" +#include "cmdlib.h" +#include "d_event.h" +#include "d_gui.h" +#include "dikeys.h" +#include "doomdef.h" +#include "doomstat.h" +#include "s_sound.h" +#include "textures.h" +#include "v_video.h" +#include "version.h" + +#undef Class + + +#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) + + +// --------------------------------------------------------------------------- + + +#ifndef NSAppKitVersionNumber10_7 + +@interface NSView(HiDPIStubs) +- (NSPoint)convertPointToBacking:(NSPoint)aPoint; +- (NSSize)convertSizeToBacking:(NSSize)aSize; +- (NSSize)convertSizeFromBacking:(NSSize)aSize; + +- (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag; +@end + +@interface NSScreen(HiDPIStubs) +- (NSRect)convertRectToBacking:(NSRect)aRect; +@end + +#endif // NSAppKitVersionNumber10_7 + + +// --------------------------------------------------------------------------- + + +CVAR(Bool, use_mouse, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_filter, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) +{ + if (self < 0) + { + self = 0; + } + else if (self > 2) + { + self = 2; + } +} + +bool GUICapture; + + +extern int paused, chatmodeon; +extern constate_e ConsoleState; + +EXTERN_CVAR(Int, m_use_mouse); + +void I_ShutdownJoysticks(); + + +namespace +{ + +struct FrameBufferParameters +{ + float pixelScale; + + float shiftX; + float shiftY; + + float width; + float height; +}; + +FrameBufferParameters s_frameBufferParameters; + + +const int ARGC_MAX = 64; + +int s_argc; +char* s_argv[ARGC_MAX]; + +TArray s_argvStorage; + + +bool s_nativeMouse = true; + +// TODO: remove this magic! +size_t s_skipMouseMoves; + +NSCursor* s_cursor; + + +void CheckGUICapture() +{ + const bool wantCapture = (MENU_Off == menuactive) + ? (c_down == ConsoleState || c_falling == ConsoleState || chatmodeon) + : (menuactive == MENU_On || menuactive == MENU_OnNoPause); + + if (wantCapture != GUICapture) + { + GUICapture = wantCapture; + + ResetButtonStates(); + } +} + +void CenterCursor() +{ + NSWindow* window = [NSApp keyWindow]; + if (nil == window) + { + return; + } + + const NSRect displayRect = [[window screen] frame]; + const NSRect windowRect = [window frame]; + const CGPoint centerPoint = CGPointMake(NSMidX(windowRect), displayRect.size.height - NSMidY(windowRect)); + + CGEventSourceRef eventSource = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); + + if (NULL != eventSource) + { + CGEventRef mouseMoveEvent = CGEventCreateMouseEvent(eventSource, + kCGEventMouseMoved, centerPoint, kCGMouseButtonLeft); + + if (NULL != mouseMoveEvent) + { + CGEventPost(kCGHIDEventTap, mouseMoveEvent); + CFRelease(mouseMoveEvent); + } + + CFRelease(eventSource); + } + + // TODO: remove this magic! + s_skipMouseMoves = 2; +} + + +bool IsInGame() +{ + switch (mouse_capturemode) + { + default: + case 0: + return gamestate == GS_LEVEL; + + case 1: + return gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_FINALE; + + case 2: + return true; + } +} + +void SetNativeMouse(bool wantNative) +{ + if (wantNative != s_nativeMouse) + { + s_nativeMouse = wantNative; + + if (!wantNative) + { + CenterCursor(); + } + + CGAssociateMouseAndMouseCursorPosition(wantNative); + + if (wantNative) + { + [NSCursor unhide]; + } + else + { + [NSCursor hide]; + } + } +} + +void CheckNativeMouse() +{ + bool windowed = (NULL == screen) || !screen->IsFullscreen(); + bool wantNative; + + if (windowed) + { + if (![NSApp isActive] || !use_mouse) + { + wantNative = true; + } + else if (MENU_WaitKey == menuactive) + { + wantNative = false; + } + else + { + wantNative = (!m_use_mouse || MENU_WaitKey != menuactive) + && (!IsInGame() || GUICapture || paused || demoplayback); + } + } + else + { + // ungrab mouse when in the menu with mouse control on. + wantNative = m_use_mouse + && (MENU_On == menuactive || MENU_OnNoPause == menuactive); + } + + SetNativeMouse(wantNative); +} + +} // unnamed namespace + + +// from iokit_joystick.cpp +void I_ProcessJoysticks(); + + +void I_GetEvent() +{ + [[NSRunLoop mainRunLoop] limitDateForMode:NSDefaultRunLoopMode]; +} + +void I_StartTic() +{ + CheckGUICapture(); + CheckNativeMouse(); + + I_ProcessJoysticks(); + I_GetEvent(); +} + +void I_StartFrame() +{ + +} + + +void I_SetMouseCapture() +{ + +} + +void I_ReleaseMouseCapture() +{ + +} + + +// --------------------------------------------------------------------------- + + +namespace +{ + +const size_t KEY_COUNT = 128; + + +// See Carbon -> HIToolbox -> Events.h for kVK_ constants + +const uint8_t KEYCODE_TO_DIK[KEY_COUNT] = +{ + DIK_A, DIK_S, DIK_D, DIK_F, DIK_H, DIK_G, DIK_Z, DIK_X, // 0x00 - 0x07 + DIK_C, DIK_V, 0, DIK_B, DIK_Q, DIK_W, DIK_E, DIK_R, // 0x08 - 0x0F + DIK_Y, DIK_T, DIK_1, DIK_2, DIK_3, DIK_4, DIK_6, DIK_5, // 0x10 - 0x17 + DIK_EQUALS, DIK_9, DIK_7, DIK_MINUS, DIK_8, DIK_0, DIK_RBRACKET, DIK_O, // 0x18 - 0x1F + DIK_U, DIK_LBRACKET, DIK_I, DIK_P, DIK_RETURN, DIK_L, DIK_J, DIK_APOSTROPHE, // 0x20 - 0x27 + DIK_K, DIK_SEMICOLON, DIK_BACKSLASH, DIK_COMMA, DIK_SLASH, DIK_N, DIK_M, DIK_PERIOD, // 0x28 - 0x2F + DIK_TAB, DIK_SPACE, DIK_GRAVE, DIK_BACK, 0, DIK_ESCAPE, 0, DIK_LWIN, // 0x30 - 0x37 + DIK_LSHIFT, DIK_CAPITAL, DIK_LMENU, DIK_LCONTROL, DIK_RSHIFT, DIK_RMENU, DIK_RCONTROL, 0, // 0x38 - 0x3F + 0, DIK_DECIMAL, 0, DIK_MULTIPLY, 0, DIK_ADD, 0, 0, // 0x40 - 0x47 + DIK_VOLUMEUP, DIK_VOLUMEDOWN, DIK_MUTE, DIK_SLASH, DIK_NUMPADENTER, 0, DIK_SUBTRACT, 0, // 0x48 - 0x4F + 0, DIK_NUMPAD_EQUALS, DIK_NUMPAD0, DIK_NUMPAD1, DIK_NUMPAD2, DIK_NUMPAD3, DIK_NUMPAD4, DIK_NUMPAD5, // 0x50 - 0x57 + DIK_NUMPAD6, DIK_NUMPAD7, 0, DIK_NUMPAD8, DIK_NUMPAD9, 0, 0, 0, // 0x58 - 0x5F + DIK_F5, DIK_F6, DIK_F7, DIK_F3, DIK_F8, DIK_F9, 0, DIK_F11, // 0x60 - 0x67 + 0, DIK_F13, 0, DIK_F14, 0, DIK_F10, 0, DIK_F12, // 0x68 - 0x6F + 0, DIK_F15, 0, DIK_HOME, 0, DIK_DELETE, DIK_F4, DIK_END, // 0x70 - 0x77 + DIK_F2, 0, DIK_F1, DIK_LEFT, DIK_RIGHT, DIK_DOWN, DIK_UP, 0, // 0x78 - 0x7F +}; + +const uint8_t KEYCODE_TO_ASCII[KEY_COUNT] = +{ + 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', // 0x00 - 0x07 + 'c', 'v', 0, 'b', 'q', 'w', 'e', 'r', // 0x08 - 0x0F + 'y', 't', '1', '2', '3', '4', '6', '5', // 0x10 - 0x17 + '=', '9', '7', '-', '8', '0', ']', 'o', // 0x18 - 0x1F + 'u', '[', 'i', 'p', 13, 'l', 'j', '\'', // 0x20 - 0x27 + 'k', ';', '\\', ',', '/', 'n', 'm', '.', // 0x28 - 0x2F + 9, ' ', '`', 12, 0, 27, 0, 0, // 0x30 - 0x37 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x38 - 0x3F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 - 0x47 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x48 - 0x4F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58 - 0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68 - 0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x77 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x78 - 0x7F +}; + + +uint8_t ModifierToDIK(const uint32_t modifier) +{ + switch (modifier) + { + case NSAlphaShiftKeyMask: return DIK_CAPITAL; + case NSShiftKeyMask: return DIK_LSHIFT; + case NSControlKeyMask: return DIK_LCONTROL; + case NSAlternateKeyMask: return DIK_LMENU; + case NSCommandKeyMask: return DIK_LWIN; + } + + return 0; +} + +SWORD ModifierFlagsToGUIKeyModifiers(NSEvent* theEvent) +{ + const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); + return ((modifiers & NSShiftKeyMask ) ? GKM_SHIFT : 0) + | ((modifiers & NSControlKeyMask ) ? GKM_CTRL : 0) + | ((modifiers & NSAlternateKeyMask) ? GKM_ALT : 0) + | ((modifiers & NSCommandKeyMask ) ? GKM_META : 0); +} + +bool ShouldGenerateGUICharEvent(NSEvent* theEvent) +{ + const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); + return !(modifiers & NSControlKeyMask) + && !(modifiers & NSAlternateKeyMask) + && !(modifiers & NSCommandKeyMask) + && !(modifiers & NSFunctionKeyMask); +} + +void ProcessKeyboardFlagsEvent(NSEvent* theEvent) +{ + static const uint32_t FLAGS_MASK = + NSDeviceIndependentModifierFlagsMask & ~NSNumericPadKeyMask; + + const uint32_t modifiers = [theEvent modifierFlags] & FLAGS_MASK; + static uint32_t oldModifiers = 0; + const uint32_t deltaModifiers = modifiers ^ oldModifiers; + + if (0 == deltaModifiers) + { + return; + } + + event_t event = {}; + + event.type = modifiers > oldModifiers ? EV_KeyDown : EV_KeyUp; + event.data1 = ModifierToDIK(deltaModifiers); + + oldModifiers = modifiers; + + // Caps Lock is a modifier key which generates one event per state change + // but not per actual key press or release. So treat any event as key down + // Also its event should be not be posted in menu and console + + if (DIK_CAPITAL == event.data1) + { + if (GUICapture) + { + return; + } + + event.type = EV_KeyDown; + } + + D_PostEvent(&event); +} + +NSStringEncoding GetEncodingForUnicodeCharacter(const unichar character) +{ + if (character >= L'\u0100' && character <= L'\u024F') + { + return NSWindowsCP1250StringEncoding; // Central and Eastern Europe + } + else if (character >= L'\u0370' && character <= L'\u03FF') + { + return NSWindowsCP1253StringEncoding; // Greek + } + else if (character >= L'\u0400' && character <= L'\u04FF') + { + return NSWindowsCP1251StringEncoding; // Cyrillic + } + + // TODO: add handling for other characters + // TODO: Turkish should use NSWindowsCP1254StringEncoding + + return NSWindowsCP1252StringEncoding; +} + +unsigned char GetCharacterFromNSEvent(NSEvent* theEvent) +{ + const NSString* unicodeCharacters = [theEvent characters]; + + if (0 == [unicodeCharacters length]) + { + return '\0'; + } + + const unichar unicodeCharacter = [unicodeCharacters characterAtIndex:0]; + const NSStringEncoding encoding = GetEncodingForUnicodeCharacter(unicodeCharacter); + + unsigned char character = '\0'; + + if (NSWindowsCP1252StringEncoding == encoding) + { + // TODO: make sure that the following is always correct + character = unicodeCharacter & 0xFF; + } + else + { + const NSData* const characters = + [[theEvent characters] dataUsingEncoding:encoding]; + + character = [characters length] > 0 + ? *static_cast([characters bytes]) + : '\0'; + } + + return character; +} + +void ProcessKeyboardEventInMenu(NSEvent* theEvent) +{ + event_t event = {}; + + event.type = EV_GUI_Event; + event.subtype = NSKeyDown == [theEvent type] ? EV_GUI_KeyDown : EV_GUI_KeyUp; + event.data2 = GetCharacterFromNSEvent(theEvent); + event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent); + + if (EV_GUI_KeyDown == event.subtype && [theEvent isARepeat]) + { + event.subtype = EV_GUI_KeyRepeat; + } + + const unsigned short keyCode = [theEvent keyCode]; + + switch (keyCode) + { + case kVK_Return: event.data1 = GK_RETURN; break; + case kVK_PageUp: event.data1 = GK_PGUP; break; + case kVK_PageDown: event.data1 = GK_PGDN; break; + case kVK_End: event.data1 = GK_END; break; + case kVK_Home: event.data1 = GK_HOME; break; + case kVK_LeftArrow: event.data1 = GK_LEFT; break; + case kVK_RightArrow: event.data1 = GK_RIGHT; break; + case kVK_UpArrow: event.data1 = GK_UP; break; + case kVK_DownArrow: event.data1 = GK_DOWN; break; + case kVK_Delete: event.data1 = GK_BACKSPACE; break; + case kVK_ForwardDelete: event.data1 = GK_DEL; break; + case kVK_Escape: event.data1 = GK_ESCAPE; break; + case kVK_F1: event.data1 = GK_F1; break; + case kVK_F2: event.data1 = GK_F2; break; + case kVK_F3: event.data1 = GK_F3; break; + case kVK_F4: event.data1 = GK_F4; break; + case kVK_F5: event.data1 = GK_F5; break; + case kVK_F6: event.data1 = GK_F6; break; + case kVK_F7: event.data1 = GK_F7; break; + case kVK_F8: event.data1 = GK_F8; break; + case kVK_F9: event.data1 = GK_F9; break; + case kVK_F10: event.data1 = GK_F10; break; + case kVK_F11: event.data1 = GK_F11; break; + case kVK_F12: event.data1 = GK_F12; break; + default: + event.data1 = KEYCODE_TO_ASCII[keyCode]; + break; + } + + if (event.data1 < 128) + { + event.data1 = toupper(event.data1); + + D_PostEvent(&event); + } + + if (!iscntrl(event.data2) + && EV_GUI_KeyUp != event.subtype + && ShouldGenerateGUICharEvent(theEvent)) + { + event.subtype = EV_GUI_Char; + event.data1 = event.data2; + event.data2 = event.data3 & GKM_ALT; + + D_PostEvent(&event); + } +} + +void ProcessKeyboardEvent(NSEvent* theEvent) +{ + const unsigned short keyCode = [theEvent keyCode]; + if (keyCode >= KEY_COUNT) + { + assert(!"Unknown keycode"); + return; + } + + if (GUICapture) + { + ProcessKeyboardEventInMenu(theEvent); + } + else + { + event_t event = {}; + + event.type = NSKeyDown == [theEvent type] ? EV_KeyDown : EV_KeyUp; + event.data1 = KEYCODE_TO_DIK[ keyCode ]; + + if (0 != event.data1) + { + event.data2 = KEYCODE_TO_ASCII[ keyCode ]; + + D_PostEvent(&event); + } + } +} + + +bool IsHiDPISupported() +{ +#ifdef NSAppKitVersionNumber10_7 + return NSAppKitVersionNumber >= NSAppKitVersionNumber10_7; +#else // !NSAppKitVersionNumber10_7 + return false; +#endif // NSAppKitVersionNumber10_7 +} + +NSSize GetRealContentViewSize(const NSWindow* const window) +{ + const NSView* view = [window contentView]; + const NSSize frameSize = [view frame].size; + + // TODO: figure out why [NSView frame] returns different values in "fullscreen" and in window + // In "fullscreen" the result is multiplied by [NSScreen backingScaleFactor], but not in window + + return (IsHiDPISupported() && NSNormalWindowLevel == [window level]) + ? [view convertSizeToBacking:frameSize] + : frameSize; +} + + +void NSEventToGameMousePosition(NSEvent* inEvent, event_t* outEvent) +{ + const NSWindow* window = [inEvent window]; + const NSView* view = [window contentView]; + + const NSPoint screenPos = [NSEvent mouseLocation]; + const NSPoint windowPos = [window convertScreenToBase:screenPos]; + + const NSPoint viewPos = IsHiDPISupported() + ? [view convertPointToBacking:windowPos] + : [view convertPointFromBase:windowPos]; + + const CGFloat frameHeight = GetRealContentViewSize(window).height; + + const CGFloat posX = ( viewPos.x - s_frameBufferParameters.shiftX) / s_frameBufferParameters.pixelScale; + const CGFloat posY = (frameHeight - viewPos.y - s_frameBufferParameters.shiftY) / s_frameBufferParameters.pixelScale; + + outEvent->data1 = static_cast< int >(posX); + outEvent->data2 = static_cast< int >(posY); +} + +void ProcessMouseButtonEvent(NSEvent* theEvent) +{ + event_t event = {}; + + const NSEventType cocoaEventType = [theEvent type]; + + if (GUICapture) + { + event.type = EV_GUI_Event; + + switch (cocoaEventType) + { + case NSLeftMouseDown: event.subtype = EV_GUI_LButtonDown; break; + case NSRightMouseDown: event.subtype = EV_GUI_RButtonDown; break; + case NSOtherMouseDown: event.subtype = EV_GUI_MButtonDown; break; + case NSLeftMouseUp: event.subtype = EV_GUI_LButtonUp; break; + case NSRightMouseUp: event.subtype = EV_GUI_RButtonUp; break; + case NSOtherMouseUp: event.subtype = EV_GUI_MButtonUp; break; + } + + NSEventToGameMousePosition(theEvent, &event); + + D_PostEvent(&event); + } + else + { + switch (cocoaEventType) + { + case NSLeftMouseDown: + case NSRightMouseDown: + case NSOtherMouseDown: + event.type = EV_KeyDown; + break; + + case NSLeftMouseUp: + case NSRightMouseUp: + case NSOtherMouseUp: + event.type = EV_KeyUp; + break; + } + + event.data1 = std::min(KEY_MOUSE1 + [theEvent buttonNumber], NSInteger(KEY_MOUSE8)); + + D_PostEvent(&event); + } +} + + +void ProcessMouseMoveInMenu(NSEvent* theEvent) +{ + event_t event = {}; + + event.type = EV_GUI_Event; + event.subtype = EV_GUI_MouseMove; + + NSEventToGameMousePosition(theEvent, &event); + + D_PostEvent(&event); +} + +void ProcessMouseMoveInGame(NSEvent* theEvent) +{ + if (!use_mouse) + { + return; + } + + // TODO: remove this magic! + + if (s_skipMouseMoves > 0) + { + --s_skipMouseMoves; + return; + } + + int x([theEvent deltaX]); + int y(-[theEvent deltaY]); + + if (0 == x && 0 == y) + { + return; + } + + if (!m_noprescale) + { + x *= 3; + y *= 2; + } + + event_t event = {}; + + static int lastX = 0, lastY = 0; + + if (m_filter) + { + event.x = (x + lastX) / 2; + event.y = (y + lastY) / 2; + } + else + { + event.x = x; + event.y = y; + } + + lastX = x; + lastY = y; + + if (0 != event.x | 0 != event.y) + { + event.type = EV_Mouse; + + D_PostEvent(&event); + } +} + +void ProcessMouseMoveEvent(NSEvent* theEvent) +{ + if (GUICapture) + { + ProcessMouseMoveInMenu(theEvent); + } + else + { + ProcessMouseMoveInGame(theEvent); + } +} + + +void ProcessMouseWheelEvent(NSEvent* theEvent) +{ + const CGFloat delta = [theEvent deltaY]; + const bool isZeroDelta = fabs(delta) < 1.0E-5; + + if (isZeroDelta && GUICapture) + { + return; + } + + event_t event = {}; + + if (GUICapture) + { + event.type = EV_GUI_Event; + event.subtype = delta > 0.0f ? EV_GUI_WheelUp : EV_GUI_WheelDown; + event.data3 = delta; + event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent); + } + else + { + event.type = isZeroDelta ? EV_KeyUp : EV_KeyDown; + event.data1 = delta > 0.0f ? KEY_MWHEELUP : KEY_MWHEELDOWN; + } + + D_PostEvent(&event); +} + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +@interface FullscreenWindow : NSWindow +{ + +} + +- (bool)canBecomeKeyWindow; + +- (void)close; + +@end + + +@implementation FullscreenWindow + +- (bool)canBecomeKeyWindow +{ + return true; +} + + +- (void)close +{ + [super close]; + + I_ShutdownJoysticks(); + + [NSApp terminate:self]; +} + +@end + + +// --------------------------------------------------------------------------- + + +@interface FullscreenView : NSOpenGLView +{ + +} + +- (void)resetCursorRects; + +@end + + +@implementation FullscreenView + +- (void)resetCursorRects +{ + [super resetCursorRects]; + [self addCursorRect: [self bounds] + cursor: s_cursor]; +} + +@end + + +// --------------------------------------------------------------------------- + + +@interface ApplicationDelegate : NSResponder +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + +#endif +{ +@private + FullscreenWindow* m_window; + + bool m_openGLInitialized; + int m_multisample; +} + +- (id)init; +- (void)dealloc; + +- (void)keyDown:(NSEvent*)theEvent; +- (void)keyUp:(NSEvent*)theEvent; + +- (void)applicationDidBecomeActive:(NSNotification*)aNotification; +- (void)applicationWillResignActive:(NSNotification*)aNotification; + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; + +- (void)applicationWillTerminate:(NSNotification*)aNotification; + +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename; + +- (int)multisample; +- (void)setMultisample:(int)multisample; + +- (void)initializeOpenGL; +- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height; + +- (void)processEvents:(NSTimer*)timer; + +- (void)invalidateCursorRects; + +- (void)setMainWindowVisible:(bool)visible; + +@end + + +static ApplicationDelegate* s_applicationDelegate; + + +@implementation ApplicationDelegate + +- (id)init +{ + self = [super init]; + + m_openGLInitialized = false; + m_multisample = 0; + + return self; +} + +- (void)dealloc +{ + [m_window release]; + + [super dealloc]; +} + + +- (void)keyDown:(NSEvent*)theEvent +{ + // Empty but present to avoid playing of 'beep' alert sound + + ZD_UNUSED(theEvent); +} + +- (void)keyUp:(NSEvent*)theEvent +{ + // Empty but present to avoid playing of 'beep' alert sound + + ZD_UNUSED(theEvent); +} + + +- (void)applicationDidBecomeActive:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + S_SetSoundPaused(1); +} + +- (void)applicationWillResignActive:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + S_SetSoundPaused(0); +} + + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification +{ + // When starting from command line with real executable path, e.g. ZDoom.app/Contents/MacOS/ZDoom + // application remains deactivated for an unknown reason. + // The following call resolves this issue + [NSApp activateIgnoringOtherApps:YES]; + + // Setup timer for custom event loop + + NSTimer* timer = [NSTimer timerWithTimeInterval:0 + target:self + selector:@selector(processEvents:) + userInfo:nil + repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:timer + forMode:NSDefaultRunLoopMode]; + + exit(SDL_main(s_argc, s_argv)); +} + + +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename +{ + ZD_UNUSED(theApplication); + + if (0 == [filename length] || s_argc + 2 >= ARGC_MAX) + { + return FALSE; + } + + // Some parameters from command line are passed to this function + // These parameters need to be skipped to avoid duplication + // Note: SDL has different approach to fix this issue, see the same method in SDLMain.m + + const char* const charFileName = [filename UTF8String]; + + for (int i = 0; i < s_argc; ++i) + { + if (0 == strcmp(s_argv[i], charFileName)) + { + return FALSE; + } + } + + s_argvStorage.Push("-file"); + s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + + s_argvStorage.Push([filename UTF8String]); + s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + + return TRUE; +} + + +- (void)applicationWillTerminate:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + // Hide window as nothing will be rendered at this point + [m_window orderOut:nil]; +} + + +- (int)multisample +{ + return m_multisample; +} + +- (void)setMultisample:(int)multisample +{ + m_multisample = multisample; +} + + +- (void)initializeOpenGL +{ + if (m_openGLInitialized) + { + return; + } + + // Create window + + m_window = [[FullscreenWindow alloc] initWithContentRect:NSMakeRect(0, 0, 640, 480) + styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + [m_window setOpaque:YES]; + [m_window makeFirstResponder:self]; + [m_window setAcceptsMouseMovedEvents:YES]; + + // Create OpenGL context and view + + NSOpenGLPixelFormatAttribute attributes[16]; + size_t i = 0; + + attributes[i++] = NSOpenGLPFADoubleBuffer; + attributes[i++] = NSOpenGLPFAColorSize; + attributes[i++] = 32; + attributes[i++] = NSOpenGLPFADepthSize; + attributes[i++] = 24; + attributes[i++] = NSOpenGLPFAStencilSize; + attributes[i++] = 8; + + if (m_multisample) + { + attributes[i++] = NSOpenGLPFAMultisample; + attributes[i++] = NSOpenGLPFASampleBuffers; + attributes[i++] = 1; + attributes[i++] = NSOpenGLPFASamples; + attributes[i++] = m_multisample; + } + + attributes[i] = 0; + + NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; + + const NSRect contentRect = [m_window contentRectForFrameRect:[m_window frame]]; + NSOpenGLView* glView = [[FullscreenView alloc] initWithFrame:contentRect + pixelFormat:pixelFormat]; + [[glView openGLContext] makeCurrentContext]; + + if (IsHiDPISupported()) + { + [glView setWantsBestResolutionOpenGLSurface:YES]; + } + + [m_window setContentView:glView]; + + m_openGLInitialized = true; +} + +- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height +{ + [self initializeOpenGL]; + + CGLContextObj context = CGLGetCurrentContext(); + NSView* view = [m_window contentView]; + + if (fullscreen) + { + NSScreen* screen = [m_window screen]; + const NSRect screenFrame = [screen frame]; + const NSRect displayRect = IsHiDPISupported() + ? [screen convertRectToBacking:screenFrame] + : screenFrame; + + const float displayWidth = displayRect.size.width; + const float displayHeight = displayRect.size.height; + + const float pixelScaleFactorX = displayWidth / static_cast< float >(width ); + const float pixelScaleFactorY = displayHeight / static_cast< float >(height); + + s_frameBufferParameters.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); + + s_frameBufferParameters.width = width * s_frameBufferParameters.pixelScale; + s_frameBufferParameters.height = height * s_frameBufferParameters.pixelScale; + + s_frameBufferParameters.shiftX = (displayWidth - s_frameBufferParameters.width ) / 2.0f; + s_frameBufferParameters.shiftY = (displayHeight - s_frameBufferParameters.height) / 2.0f; + + [m_window setLevel:NSMainMenuWindowLevel + 1]; + [m_window setStyleMask:NSBorderlessWindowMask]; + [m_window setHidesOnDeactivate:YES]; + [m_window setFrame:displayRect display:YES]; + [m_window setFrameOrigin:NSMakePoint(0.0f, 0.0f)]; + } + else + { + s_frameBufferParameters.pixelScale = 1.0f; + + s_frameBufferParameters.width = static_cast< float >(width ); + s_frameBufferParameters.height = static_cast< float >(height); + + s_frameBufferParameters.shiftX = 0.0f; + s_frameBufferParameters.shiftY = 0.0f; + + const NSSize windowPixelSize = NSMakeSize(width, height); + const NSSize windowSize = IsHiDPISupported() + ? [view convertSizeFromBacking:windowPixelSize] + : windowPixelSize; + + [m_window setLevel:NSNormalWindowLevel]; + [m_window setStyleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask]; + [m_window setHidesOnDeactivate:NO]; + [m_window setContentSize:windowSize]; + [m_window center]; + } + + const NSSize viewSize = GetRealContentViewSize(m_window); + + glViewport(0, 0, viewSize.width, viewSize.height); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + CGLFlushDrawable(context); + + static NSString* const TITLE_STRING = + [NSString stringWithFormat:@"%s %s", GAMESIG, GetVersionString()]; + [m_window setTitle:TITLE_STRING]; + + if (![m_window isKeyWindow]) + { + [m_window makeKeyAndOrderFront:nil]; + } +} + + +- (void)processEvents:(NSTimer*)timer +{ + ZD_UNUSED(timer); + + while (true) + { + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate dateWithTimeIntervalSinceNow:0] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (nil == event) + { + break; + } + + const NSEventType eventType = [event type]; + + switch (eventType) + { + case NSMouseMoved: + ProcessMouseMoveEvent(event); + break; + + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSOtherMouseDown: + case NSOtherMouseUp: + ProcessMouseButtonEvent(event); + break; + + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSOtherMouseDragged: + ProcessMouseButtonEvent(event); + ProcessMouseMoveEvent(event); + break; + + case NSScrollWheel: + ProcessMouseWheelEvent(event); + break; + + case NSKeyDown: + case NSKeyUp: + ProcessKeyboardEvent(event); + break; + + case NSFlagsChanged: + ProcessKeyboardFlagsEvent(event); + break; + } + + [NSApp sendEvent:event]; + } + + [NSApp updateWindows]; +} + + +- (void)invalidateCursorRects +{ + [m_window invalidateCursorRectsForView:[m_window contentView]]; +} + + +- (void)setMainWindowVisible:(bool)visible +{ + if (visible) + { + [m_window orderFront:nil]; + } + else + { + [m_window orderOut:nil]; + } +} + +@end + + +// --------------------------------------------------------------------------- + + +void I_SetMainWindowVisible(bool visible) +{ + [s_applicationDelegate setMainWindowVisible:visible]; + + SetNativeMouse(!visible); +} + + +// --------------------------------------------------------------------------- + + +bool I_SetCursor(FTexture* cursorpic) +{ + if (NULL == cursorpic || FTexture::TEX_Null == cursorpic->UseType) + { + s_cursor = [NSCursor arrowCursor]; + } + else + { + // Create bitmap image representation + + const NSInteger imageWidth = cursorpic->GetWidth(); + const NSInteger imageHeight = cursorpic->GetHeight(); + const NSInteger imagePitch = imageWidth * 4; + + NSBitmapImageRep* bitmapImageRep = [NSBitmapImageRep alloc]; + [bitmapImageRep initWithBitmapDataPlanes:NULL + pixelsWide:imageWidth + pixelsHigh:imageHeight + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:imagePitch + bitsPerPixel:0]; + + // Load bitmap data to representation + + BYTE* buffer = [bitmapImageRep bitmapData]; + FBitmap bitmap(buffer, imagePitch, imageWidth, imageHeight); + cursorpic->CopyTrueColorPixels(&bitmap, 0, 0); + + // Swap red and blue components in each pixel + + for (size_t i = 0; i < size_t(imageWidth * imageHeight); ++i) + { + const size_t offset = i * 4; + + const BYTE temp = buffer[offset ]; + buffer[offset ] = buffer[offset + 2]; + buffer[offset + 2] = temp; + } + + // Create image from representation and set it as cursor + + NSData* imageData = [bitmapImageRep representationUsingType:NSPNGFileType + properties:nil]; + NSImage* cursorImage = [[NSImage alloc] initWithData:imageData]; + + s_cursor = [[NSCursor alloc] initWithImage:cursorImage + hotSpot:NSMakePoint(0.0f, 0.0f)]; + } + + [s_applicationDelegate invalidateCursorRects]; + + return true; +} + + +// --------------------------------------------------------------------------- + + +extern "C" +{ + +struct SDL_mutex +{ + pthread_mutex_t mutex; +}; + + +SDL_mutex* SDL_CreateMutex() +{ + pthread_mutexattr_t attributes; + pthread_mutexattr_init(&attributes); + pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE); + + SDL_mutex* result = new SDL_mutex; + + if (0 != pthread_mutex_init(&result->mutex, &attributes)) + { + delete result; + result = NULL; + } + + pthread_mutexattr_destroy(&attributes); + + return result; +} + +int SDL_mutexP(SDL_mutex* mutex) +{ + return pthread_mutex_lock(&mutex->mutex); +} + +int SDL_mutexV(SDL_mutex* mutex) +{ + return pthread_mutex_unlock(&mutex->mutex); +} + +void SDL_DestroyMutex(SDL_mutex* mutex) +{ + pthread_mutex_destroy(&mutex->mutex); + delete mutex; +} + + +static timeval s_startTicks; + +uint32_t SDL_GetTicks() +{ + timeval now; + gettimeofday(&now, NULL); + + const uint32_t ticks = + (now.tv_sec - s_startTicks.tv_sec ) * 1000 + + (now.tv_usec - s_startTicks.tv_usec) / 1000; + + return ticks; +} + + +int SDL_Init(Uint32 flags) +{ + ZD_UNUSED(flags); + + return 0; +} + +void SDL_Quit() +{ + if (NULL != s_applicationDelegate) + { + [NSApp setDelegate:nil]; + [NSApp deactivate]; + + [s_applicationDelegate release]; + s_applicationDelegate = NULL; + } +} + + +char* SDL_GetError() +{ + static char empty[] = {0}; + return empty; +} + + +char* SDL_VideoDriverName(char* namebuf, int maxlen) +{ + return strncpy(namebuf, "Native OpenGL", maxlen); +} + +const SDL_VideoInfo* SDL_GetVideoInfo() +{ + // NOTE: Only required fields are assigned + + static SDL_PixelFormat pixelFormat; + memset(&pixelFormat, 0, sizeof(pixelFormat)); + + pixelFormat.BitsPerPixel = 32; + + static SDL_VideoInfo videoInfo; + memset(&videoInfo, 0, sizeof(videoInfo)); + + const NSRect displayRect = [[NSScreen mainScreen] frame]; + + videoInfo.current_w = displayRect.size.width; + videoInfo.current_h = displayRect.size.height; + videoInfo.vfmt = &pixelFormat; + + return &videoInfo; +} + +SDL_Rect** SDL_ListModes(SDL_PixelFormat* format, Uint32 flags) +{ + ZD_UNUSED(format); + ZD_UNUSED(flags); + + static std::vector< SDL_Rect* > resolutions; + + if (resolutions.empty()) + { +#define DEFINE_RESOLUTION(WIDTH, HEIGHT) \ + static SDL_Rect resolution_##WIDTH##_##HEIGHT = { 0, 0, WIDTH, HEIGHT }; \ + resolutions.push_back(&resolution_##WIDTH##_##HEIGHT); + + DEFINE_RESOLUTION(640, 480); + DEFINE_RESOLUTION(720, 480); + DEFINE_RESOLUTION(800, 600); + DEFINE_RESOLUTION(1024, 640); + DEFINE_RESOLUTION(1024, 768); + DEFINE_RESOLUTION(1152, 720); + DEFINE_RESOLUTION(1280, 720); + DEFINE_RESOLUTION(1280, 800); + DEFINE_RESOLUTION(1280, 960); + DEFINE_RESOLUTION(1280, 1024); + DEFINE_RESOLUTION(1366, 768); + DEFINE_RESOLUTION(1400, 1050); + DEFINE_RESOLUTION(1440, 900); + DEFINE_RESOLUTION(1600, 900); + DEFINE_RESOLUTION(1600, 1200); + DEFINE_RESOLUTION(1680, 1050); + DEFINE_RESOLUTION(1920, 1080); + DEFINE_RESOLUTION(1920, 1200); + DEFINE_RESOLUTION(2048, 1536); + DEFINE_RESOLUTION(2560, 1440); + DEFINE_RESOLUTION(2560, 1600); + DEFINE_RESOLUTION(2880, 1800); + +#undef DEFINE_RESOLUTION + + resolutions.push_back(NULL); + } + + return &resolutions[0]; +} + + +//static GLAuxilium::Texture2D* s_softwareTexture; +static GLuint s_frameBufferTexture = 0; + +static const Uint16 BYTES_PER_PIXEL = 4; + +static SDL_PixelFormat* GetPixelFormat() +{ + static SDL_PixelFormat result; + + result.palette = NULL; + result.BitsPerPixel = BYTES_PER_PIXEL * 8; + result.BytesPerPixel = BYTES_PER_PIXEL; + result.Rloss = 0; + result.Gloss = 0; + result.Bloss = 0; + result.Aloss = 8; + result.Rshift = 8; + result.Gshift = 16; + result.Bshift = 24; + result.Ashift = 0; + result.Rmask = 0x000000FF; + result.Gmask = 0x0000FF00; + result.Bmask = 0x00FF0000; + result.Amask = 0xFF000000; + result.colorkey = 0; + result.alpha = 0xFF; + + return &result; +} + + +SDL_Surface* SDL_SetVideoMode(int width, int height, int, Uint32 flags) +{ + [s_applicationDelegate changeVideoResolution:(SDL_FULLSCREEN & flags) width:width height:height]; + + static SDL_Surface result; + + const bool isSoftwareRenderer = !(SDL_OPENGL & flags); + + if (isSoftwareRenderer) + { + if (NULL != result.pixels) + { + free(result.pixels); + } + + if (0 != s_frameBufferTexture) + { + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &s_frameBufferTexture); + s_frameBufferTexture = 0; + } + } + + result.flags = flags; + result.format = GetPixelFormat(); + result.w = width; + result.h = height; + result.pitch = width * BYTES_PER_PIXEL; + result.pixels = isSoftwareRenderer ? malloc(width * height * BYTES_PER_PIXEL) : NULL; + result.refcount = 1; + + result.clip_rect.x = 0; + result.clip_rect.y = 0; + result.clip_rect.w = width; + result.clip_rect.h = height; + + return &result; +} + + +void SDL_WM_SetCaption(const char* title, const char* icon) +{ + ZD_UNUSED(title); + ZD_UNUSED(icon); + + // Window title is set in SDL_SetVideoMode() +} + +static void ResetSoftwareViewport() +{ + // For an unknown reason the following call to glClear() is needed + // to avoid drawing of garbage in fullscreen mode + // when game video resolution's aspect ratio is different from display one + + GLint viewport[2]; + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport); + + glViewport(0, 0, viewport[0], viewport[1]); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(s_frameBufferParameters.shiftX, s_frameBufferParameters.shiftY, + s_frameBufferParameters.width, s_frameBufferParameters.height); +} + +int SDL_WM_ToggleFullScreen(SDL_Surface* surface) +{ + if (surface->flags & SDL_FULLSCREEN) + { + surface->flags &= ~SDL_FULLSCREEN; + } + else + { + surface->flags |= SDL_FULLSCREEN; + } + + [s_applicationDelegate changeVideoResolution:(SDL_FULLSCREEN & surface->flags) + width:surface->w + height:surface->h]; + ResetSoftwareViewport(); + + return 1; +} + + +void SDL_GL_SwapBuffers() +{ + [[NSOpenGLContext currentContext] flushBuffer]; +} + +int SDL_LockSurface(SDL_Surface* surface) +{ + ZD_UNUSED(surface); + + return 0; +} + +void SDL_UnlockSurface(SDL_Surface* surface) +{ + ZD_UNUSED(surface); +} + +int SDL_BlitSurface(SDL_Surface* src, SDL_Rect* srcrect, SDL_Surface* dst, SDL_Rect* dstrect) +{ + ZD_UNUSED(src); + ZD_UNUSED(srcrect); + ZD_UNUSED(dst); + ZD_UNUSED(dstrect); + + return 0; +} + + +static void SetupSoftwareRendering(SDL_Surface* screen) +{ + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, screen->w, screen->h, 0.0, -1.0, 1.0); + + ResetSoftwareViewport(); + + glEnable(GL_TEXTURE_2D); + + glGenTextures(1, &s_frameBufferTexture); + glBindTexture(GL_TEXTURE_2D, s_frameBufferTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +int SDL_Flip(SDL_Surface* screen) +{ + assert(NULL != screen); + + if (0 == s_frameBufferTexture) + { + SetupSoftwareRendering(screen); + } + + const int width = screen->w; + const int height = screen->h; + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, + width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, screen->pixels); + + glBegin(GL_QUADS); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glTexCoord2f(0.0f, 0.0f); + glVertex2f(0.0f, 0.0f); + glTexCoord2f(1.0f, 0.0f); + glVertex2f(width, 0.0f); + glTexCoord2f(1.0f, 1.0f); + glVertex2f(width, height); + glTexCoord2f(0.0f, 1.0f); + glVertex2f(0.0f, height); + glEnd(); + + glFlush(); + + SDL_GL_SwapBuffers(); + + return 0; +} + +int SDL_SetPalette(SDL_Surface* surface, int flags, SDL_Color* colors, int firstcolor, int ncolors) +{ + ZD_UNUSED(surface); + ZD_UNUSED(flags); + ZD_UNUSED(colors); + ZD_UNUSED(firstcolor); + ZD_UNUSED(ncolors); + + return 0; +} + +} // extern "C" + +#ifdef main +#undef main +#endif // main + +static void CheckOSVersion() +{ + static const char* const PARAMETER_NAME = "kern.osrelease"; + + size_t size = 0; + + if (-1 == sysctlbyname(PARAMETER_NAME, NULL, &size, NULL, 0)) + { + return; + } + + char* version = static_cast(alloca(size)); + + if (-1 == sysctlbyname(PARAMETER_NAME, version, &size, NULL, 0)) + { + return; + } + + if (strcmp(version, "10.0") < 0) + { + CFOptionFlags responseFlags; + CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, + CFSTR("Unsupported version of OS X"), CFSTR("You need OS X 10.6 or higher running on Intel platform in order to play."), + NULL, NULL, NULL, &responseFlags); + + exit(EXIT_FAILURE); + } +} + +int main(int argc, char** argv) +{ + CheckOSVersion(); + + gettimeofday(&s_startTicks, NULL); + + for (int i = 0; i <= argc; ++i) + { + const char* const argument = argv[i]; + + if (NULL == argument || '\0' == argument[0]) + { + continue; + } + + s_argvStorage.Push(argument); + s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + } + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + [NSApplication sharedApplication]; + [NSBundle loadNibNamed:@"zdoom" owner:NSApp]; + + s_applicationDelegate = [ApplicationDelegate new]; + [NSApp setDelegate:s_applicationDelegate]; + + [NSApp run]; + + [pool release]; + + return EXIT_SUCCESS; +} diff --git a/src/cocoa/i_joystick.cpp b/src/cocoa/i_joystick.cpp new file mode 100644 index 000000000..add1b1bd7 --- /dev/null +++ b/src/cocoa/i_joystick.cpp @@ -0,0 +1,776 @@ +/* + ** i_joystick.cpp + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "m_joy.h" + +#include "HID_Utilities_External.h" + +#include "d_event.h" +#include "doomdef.h" +#include "templates.h" + + +namespace +{ + +FString ToFString( const CFStringRef string ) +{ + if ( NULL == string ) + { + return FString(); + } + + const CFIndex stringLength = CFStringGetLength( string ); + + if ( 0 == stringLength ) + { + return FString(); + } + + const size_t bufferSize = CFStringGetMaximumSizeForEncoding( stringLength, kCFStringEncodingUTF8 ) + 1; + + char buffer[ bufferSize ]; + memset( buffer, 0, bufferSize ); + + CFStringGetCString( string, buffer, bufferSize, kCFStringEncodingUTF8 ); + + return FString( buffer ); +} + + +class IOKitJoystick : public IJoystickConfig +{ +public: + explicit IOKitJoystick( IOHIDDeviceRef device ); + virtual ~IOKitJoystick(); + + virtual FString GetName(); + virtual float GetSensitivity(); + virtual void SetSensitivity( float scale ); + + virtual int GetNumAxes(); + virtual float GetAxisDeadZone( int axis ); + virtual EJoyAxis GetAxisMap( int axis ); + virtual const char* GetAxisName( int axis ); + virtual float GetAxisScale( int axis ); + + virtual void SetAxisDeadZone( int axis, float deadZone ); + virtual void SetAxisMap( int axis, EJoyAxis gameAxis ); + virtual void SetAxisScale( int axis, float scale ); + + virtual bool IsSensitivityDefault(); + virtual bool IsAxisDeadZoneDefault( int axis ); + virtual bool IsAxisMapDefault( int axis ); + virtual bool IsAxisScaleDefault( int axis ); + + virtual void SetDefaultConfig(); + virtual FString GetIdentifier(); + + void AddAxes( float axes[ NUM_JOYAXIS ] ) const; + + void Update(); + +private: + IOHIDDeviceRef m_device; + + float m_sensitivity; + + struct AxisInfo + { + char name[ 64 ]; + + float value; + + float deadZone; + float defaultDeadZone; + float sensitivity; + float defaultSensitivity; + + EJoyAxis gameAxis; + EJoyAxis defaultGameAxis; + + IOHIDElementRef element; + }; + + TArray< AxisInfo > m_axes; + + TArray< IOHIDElementRef > m_buttons; + TArray< IOHIDElementRef > m_POVs; + + + static const float DEFAULT_DEADZONE; + static const float DEFAULT_SENSITIVITY; + + + bool ProcessAxis ( const IOHIDValueRef value ); + bool ProcessButton( const IOHIDValueRef value ); + bool ProcessPOV ( const IOHIDValueRef value ); + +}; + + +const float IOKitJoystick::DEFAULT_DEADZONE = 0.25f; +const float IOKitJoystick::DEFAULT_SENSITIVITY = 1.0f; + + +IOKitJoystick::IOKitJoystick( IOHIDDeviceRef device ) +: m_device( device ) +, m_sensitivity( DEFAULT_SENSITIVITY ) +{ + IOHIDElementRef element = HIDGetFirstDeviceElement( device, kHIDElementTypeInput ); + + while ( NULL != element ) + { + const uint32_t usagePage = IOHIDElementGetUsagePage( element ); + + if ( kHIDPage_GenericDesktop == usagePage ) + { + const uint32_t usage = IOHIDElementGetUsage( element ); + + if ( kHIDUsage_GD_Slider == usage + || kHIDUsage_GD_X == usage || kHIDUsage_GD_Y == usage || kHIDUsage_GD_Z == usage + || kHIDUsage_GD_Rx == usage || kHIDUsage_GD_Ry == usage || kHIDUsage_GD_Rz == usage ) + { + AxisInfo axis; + memset( &axis, 0, sizeof( axis ) ); + + if ( const CFStringRef name = IOHIDElementGetName( element ) ) + { + CFStringGetCString( name, axis.name, sizeof( axis.name ) - 1, kCFStringEncodingUTF8 ); + } + else + { + snprintf( axis.name, sizeof( axis.name ), "Axis %i", m_axes.Size() + 1 ); + } + + axis.element = element; + + m_axes.Push( axis ); + + IOHIDElement_SetCalibrationMin( element, -1 ); + IOHIDElement_SetCalibrationMax( element, 1 ); + + HIDQueueElement( m_device, element ); + } + else if ( kHIDUsage_GD_Hatswitch == usage && m_POVs.Size() < 4 ) + { + m_POVs.Push( element ); + + HIDQueueElement( m_device, element ); + } + } + else if ( kHIDPage_Button == usagePage ) + { + m_buttons.Push( element ); + + HIDQueueElement( m_device, element ); + } + + element = HIDGetNextDeviceElement( element, kHIDElementTypeInput ); + } + + SetDefaultConfig(); +} + +IOKitJoystick::~IOKitJoystick() +{ + M_SaveJoystickConfig( this ); +} + + +FString IOKitJoystick::GetName() +{ + FString result; + + result += ToFString( IOHIDDevice_GetManufacturer( m_device ) ); + result += " "; + result += ToFString( IOHIDDevice_GetProduct( m_device ) ); + + return result; +} + + +float IOKitJoystick::GetSensitivity() +{ + return m_sensitivity; +} + +void IOKitJoystick::SetSensitivity( float scale ) +{ + m_sensitivity = scale; +} + + +int IOKitJoystick::GetNumAxes() +{ + return static_cast< int >( m_axes.Size() ); +} + +#define IS_AXIS_VALID ( static_cast< unsigned int >( axis ) < m_axes.Size() ) + +float IOKitJoystick::GetAxisDeadZone( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].deadZone : 0.0f; +} + +EJoyAxis IOKitJoystick::GetAxisMap( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].gameAxis : JOYAXIS_None; +} + +const char* IOKitJoystick::GetAxisName( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].name : "Invalid"; +} + +float IOKitJoystick::GetAxisScale( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].sensitivity : 0.0f; +} + +void IOKitJoystick::SetAxisDeadZone( int axis, float deadZone ) +{ + if ( IS_AXIS_VALID ) + { + m_axes[ axis ].deadZone = clamp( deadZone, 0.0f, 1.0f ); + } +} + +void IOKitJoystick::SetAxisMap( int axis, EJoyAxis gameAxis ) +{ + if ( IS_AXIS_VALID ) + { + m_axes[ axis ].gameAxis = ( gameAxis > JOYAXIS_None && gameAxis < NUM_JOYAXIS ) + ? gameAxis + : JOYAXIS_None; + } +} + +void IOKitJoystick::SetAxisScale( int axis, float scale ) +{ + if ( IS_AXIS_VALID ) + { + m_axes[ axis ].sensitivity = scale; + } +} + + +bool IOKitJoystick::IsSensitivityDefault() +{ + return DEFAULT_SENSITIVITY == m_sensitivity; +} + +bool IOKitJoystick::IsAxisDeadZoneDefault( int axis ) +{ + return IS_AXIS_VALID + ? ( m_axes[ axis ].deadZone == m_axes[ axis ].defaultDeadZone ) + : true; +} + +bool IOKitJoystick::IsAxisMapDefault( int axis ) +{ + return IS_AXIS_VALID + ? ( m_axes[ axis ].gameAxis == m_axes[ axis ].defaultGameAxis ) + : true; +} + +bool IOKitJoystick::IsAxisScaleDefault( int axis ) +{ + return IS_AXIS_VALID + ? ( m_axes[ axis ].sensitivity == m_axes[ axis ].defaultSensitivity ) + : true; +} + +#undef IS_AXIS_VALID + +void IOKitJoystick::SetDefaultConfig() +{ + m_sensitivity = DEFAULT_SENSITIVITY; + + const size_t axisCount = m_axes.Size(); + + for ( size_t i = 0; i < axisCount; ++i ) + { + m_axes[i].deadZone = DEFAULT_DEADZONE; + m_axes[i].sensitivity = DEFAULT_SENSITIVITY; + m_axes[i].gameAxis = JOYAXIS_None; + } + + // Two axes? Horizontal is yaw and vertical is forward. + + if ( 2 == axisCount) + { + m_axes[0].gameAxis = JOYAXIS_Yaw; + m_axes[1].gameAxis = JOYAXIS_Forward; + } + + // Three axes? First two are movement, third is yaw. + + else if ( axisCount >= 3 ) + { + m_axes[0].gameAxis = JOYAXIS_Side; + m_axes[1].gameAxis = JOYAXIS_Forward; + m_axes[2].gameAxis = JOYAXIS_Yaw; + + // Four axes? First two are movement, last two are looking around. + + if ( axisCount >= 4 ) + { + m_axes[3].gameAxis = JOYAXIS_Pitch; +// ??? m_axes[3].sensitivity = 0.75f; + + // Five axes? Use the fifth one for moving up and down. + + if ( axisCount >= 5 ) + { + m_axes[4].gameAxis = JOYAXIS_Up; + } + } + } + + // If there is only one axis, then we make no assumptions about how + // the user might want to use it. + + // Preserve defaults for config saving. + + for ( size_t i = 0; i < axisCount; ++i ) + { + m_axes[i].defaultDeadZone = m_axes[i].deadZone; + m_axes[i].defaultSensitivity = m_axes[i].sensitivity; + m_axes[i].defaultGameAxis = m_axes[i].gameAxis; + } +} + + +FString IOKitJoystick::GetIdentifier() +{ + char identifier[ 32 ] = {0}; + + snprintf( identifier, sizeof( identifier ), "VID_%04lx_PID_%04lx", + IOHIDDevice_GetVendorID( m_device ), IOHIDDevice_GetProductID( m_device ) ); + + return FString( identifier ); +} + + +void IOKitJoystick::AddAxes( float axes[ NUM_JOYAXIS ] ) const +{ + for ( size_t i = 0, count = m_axes.Size(); i < count; ++i ) + { + const EJoyAxis axis = m_axes[i].gameAxis; + + if ( JOYAXIS_None == axis ) + { + continue; + } + + axes[ axis ] -= m_axes[i].value; + } +} + + +void IOKitJoystick::Update() +{ + IOHIDValueRef value = NULL; + + while ( HIDGetEvent( m_device, &value ) && NULL != value ) + { + ProcessAxis( value ) || ProcessButton( value ) || ProcessPOV( value ); + + CFRelease( value ); + } +} + + +bool IOKitJoystick::ProcessAxis( const IOHIDValueRef value ) +{ + const IOHIDElementRef element = IOHIDValueGetElement( value ); + + if ( NULL == element ) + { + return false; + } + + for ( size_t i = 0, count = m_axes.Size(); i < count; ++i ) + { + if ( element != m_axes[i].element ) + { + continue; + } + + AxisInfo& axis = m_axes[i]; + + const double scaledValue = IOHIDValueGetScaledValue( value, kIOHIDValueScaleTypeCalibrated ); + const double filteredValue = Joy_RemoveDeadZone( scaledValue, axis.deadZone, NULL ); + + axis.value = static_cast< float >( filteredValue * m_sensitivity * axis.sensitivity ); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessButton( const IOHIDValueRef value ) +{ + const IOHIDElementRef element = IOHIDValueGetElement( value ); + + if ( NULL == element ) + { + return false; + } + + for ( size_t i = 0, count = m_buttons.Size(); i < count; ++i ) + { + if ( element != m_buttons[i] ) + { + continue; + } + + const int newButton = IOHIDValueGetIntegerValue( value ) & 1; + const int oldButton = ~newButton; + + Joy_GenerateButtonEvents( oldButton, newButton, 1, + static_cast< int >( KEY_FIRSTJOYBUTTON + i ) ); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessPOV( const IOHIDValueRef value ) +{ + const IOHIDElementRef element = IOHIDValueGetElement( value ); + + if ( NULL == element ) + { + return false; + } + + for ( size_t i = 0, count = m_POVs.Size(); i < count; ++i ) + { + if ( element != m_POVs[i] ) + { + continue; + } + + const CFIndex direction = IOHIDValueGetIntegerValue( value ); + + // Default values is for Up/North + int oldButtons = 0; + int newButtons = 1; + int numButtons = 1; + int baseButton = KEY_JOYPOV1_UP; + + switch ( direction ) + { + case 0: // N + break; + + case 1: // NE + newButtons = 3; + numButtons = 2; + break; + + case 2: // E + baseButton = KEY_JOYPOV1_RIGHT; + break; + + case 3: // SE + newButtons = 3; + numButtons = 2; + baseButton = KEY_JOYPOV1_RIGHT; + break; + + case 4: // S + baseButton = KEY_JOYPOV1_DOWN; + break; + + case 5: // SW + newButtons = 3; + numButtons = 2; + baseButton = KEY_JOYPOV1_DOWN; + break; + + case 6: // W + baseButton = KEY_JOYPOV1_LEFT; + break; + + case 7: // NW + newButtons = 9; // UP and LEFT + numButtons = 4; + break; + + default: + // release all four directions + oldButtons = 15; + newButtons = 0; + numButtons = 4; + break; + } + + Joy_GenerateButtonEvents( oldButtons, newButtons, numButtons, + static_cast< int >( baseButton + i * 4 ) ); + } + + return false; +} + + +// --------------------------------------------------------------------------- + + +class IOKitJoystickManager +{ +public: + IOKitJoystickManager(); + ~IOKitJoystickManager(); + + void GetJoysticks( TArray< IJoystickConfig* >& joysticks ) const; + + void AddAxes( float axes[ NUM_JOYAXIS ] ) const; + + // Updates axes/buttons states + void Update(); + + // Rebuilds device list + void Rescan(); + +private: + TArray< IOKitJoystick* > m_joysticks; + + static void OnDeviceChanged( void* context, IOReturn result, void* sender, IOHIDDeviceRef device ); + + void ReleaseJoysticks(); + + void EnableCallbacks(); + void DisableCallbacks(); + +}; + + +IOKitJoystickManager::IOKitJoystickManager() +{ + Rescan(); +} + +IOKitJoystickManager::~IOKitJoystickManager() +{ + ReleaseJoysticks(); + DisableCallbacks(); + + HIDReleaseDeviceList(); +} + + +void IOKitJoystickManager::GetJoysticks( TArray< IJoystickConfig* >& joysticks ) const +{ + const size_t joystickCount = m_joysticks.Size(); + + joysticks.Resize( joystickCount ); + + for ( size_t i = 0; i < joystickCount; ++i ) + { + M_LoadJoystickConfig( m_joysticks[i] ); + + joysticks[i] = m_joysticks[i]; + } +} + +void IOKitJoystickManager::AddAxes( float axes[ NUM_JOYAXIS ] ) const +{ + for ( size_t i = 0, count = m_joysticks.Size(); i < count; ++i ) + { + m_joysticks[i]->AddAxes( axes ); + } +} + + +void IOKitJoystickManager::Update() +{ + for ( size_t i = 0, count = m_joysticks.Size(); i < count; ++i ) + { + m_joysticks[i]->Update(); + } +} + + +void IOKitJoystickManager::Rescan() +{ + ReleaseJoysticks(); + DisableCallbacks(); + + const int usageCount = 2; + + const UInt32 usagePages[ usageCount ] = + { + kHIDPage_GenericDesktop, + kHIDPage_GenericDesktop + }; + + const UInt32 usages[ usageCount ] = + { + kHIDUsage_GD_Joystick, + kHIDUsage_GD_GamePad + }; + + if ( HIDUpdateDeviceList( usagePages, usages, usageCount ) ) + { + IOHIDDeviceRef device = HIDGetFirstDevice(); + + while ( NULL != device ) + { + IOKitJoystick* joystick = new IOKitJoystick( device ); + m_joysticks.Push( joystick ); + + device = HIDGetNextDevice( device ); + } + } + else + { + Printf( "IOKitJoystickManager: Failed to build gamepad/joystick device list.\n" ); + } + + EnableCallbacks(); +} + + +void IOKitJoystickManager::OnDeviceChanged( void* context, IOReturn result, void* sender, IOHIDDeviceRef device ) +{ + event_t event; + + memset( &event, 0, sizeof( event ) ); + event.type = EV_DeviceChange; + + D_PostEvent( &event ); +} + + +void IOKitJoystickManager::ReleaseJoysticks() +{ + for ( size_t i = 0, count = m_joysticks.Size(); i < count; ++i ) + { + delete m_joysticks[i]; + } + + m_joysticks.Clear(); +} + + +void IOKitJoystickManager::EnableCallbacks() +{ + if ( NULL == gIOHIDManagerRef ) + { + return; + } + + IOHIDManagerRegisterDeviceMatchingCallback( gIOHIDManagerRef, OnDeviceChanged, this ); + IOHIDManagerRegisterDeviceRemovalCallback ( gIOHIDManagerRef, OnDeviceChanged, this ); + IOHIDManagerScheduleWithRunLoop( gIOHIDManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); +} + +void IOKitJoystickManager::DisableCallbacks() +{ + if ( NULL == gIOHIDManagerRef ) + { + return; + } + + IOHIDManagerUnscheduleFromRunLoop( gIOHIDManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); + IOHIDManagerRegisterDeviceMatchingCallback( gIOHIDManagerRef, NULL, NULL ); + IOHIDManagerRegisterDeviceRemovalCallback ( gIOHIDManagerRef, NULL, NULL ); +} + + +IOKitJoystickManager* s_joystickManager; + + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +void I_StartupJoysticks() +{ + s_joystickManager = new IOKitJoystickManager; +} + +void I_ShutdownJoysticks() +{ + delete s_joystickManager; +} + +void I_GetJoysticks( TArray< IJoystickConfig* >& sticks ) +{ + if ( NULL != s_joystickManager ) + { + s_joystickManager->GetJoysticks( sticks ); + } +} + +void I_GetAxes( float axes[ NUM_JOYAXIS ] ) +{ + for ( size_t i = 0; i < NUM_JOYAXIS; ++i ) + { + axes[i] = 0.0f; + } + + if ( use_joystick && NULL != s_joystickManager ) + { + s_joystickManager->AddAxes( axes ); + } +} + +IJoystickConfig* I_UpdateDeviceList() +{ + if ( use_joystick && NULL != s_joystickManager ) + { + s_joystickManager->Rescan(); + } + + return NULL; +} + + +// --------------------------------------------------------------------------- + + +void I_ProcessJoysticks() +{ + if ( use_joystick && NULL != s_joystickManager ) + { + s_joystickManager->Update(); + } +} diff --git a/src/cocoa/i_timer.cpp b/src/cocoa/i_timer.cpp new file mode 100644 index 000000000..29d3f8865 --- /dev/null +++ b/src/cocoa/i_timer.cpp @@ -0,0 +1,191 @@ + +#include +#include +#include + +#include + +#include "basictypes.h" +#include "basicinlines.h" +#include "doomdef.h" +#include "i_system.h" +#include "templates.h" + + +unsigned int I_MSTime() +{ + return SDL_GetTicks(); +} + +unsigned int I_FPSTime() +{ + return SDL_GetTicks(); +} + + +bool g_isTicFrozen; + + +namespace +{ + +timespec GetNextTickTime() +{ + static const long MILLISECONDS_IN_SECOND = 1000; + static const long MICROSECONDS_IN_SECOND = 1000 * MILLISECONDS_IN_SECOND; + static const long NANOSECONDS_IN_SECOND = 1000 * MICROSECONDS_IN_SECOND; + + static timespec ts = {}; + + if (__builtin_expect((0 == ts.tv_sec), 0)) + { + timeval tv; + gettimeofday(&tv, NULL); + + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = (tv.tv_usec + MICROSECONDS_IN_SECOND / TICRATE) * MILLISECONDS_IN_SECOND; + } + else + { + ts.tv_nsec += (MICROSECONDS_IN_SECOND / TICRATE) * MILLISECONDS_IN_SECOND; + } + + if (ts.tv_nsec >= NANOSECONDS_IN_SECOND) + { + ts.tv_sec++; + ts.tv_nsec -= NANOSECONDS_IN_SECOND; + } + + return ts; +} + + +pthread_cond_t s_timerEvent; +pthread_mutex_t s_timerMutex; +pthread_t s_timerThread; + +bool s_timerInitialized; +bool s_timerExitRequested; + +uint32_t s_ticStart; +uint32_t s_ticNext; + +uint32_t s_timerStart; +uint32_t s_timerNext; + +int s_tics; + + +void* TimerThreadFunc(void*) +{ + assert(s_timerInitialized); + assert(!s_timerExitRequested); + + while (true) + { + if (s_timerExitRequested) + { + break; + } + + const timespec timeToNextTick = GetNextTickTime(); + + pthread_mutex_lock(&s_timerMutex); + pthread_cond_timedwait(&s_timerEvent, &s_timerMutex, &timeToNextTick); + + if (!g_isTicFrozen) + { + __sync_add_and_fetch(&s_tics, 1); + } + + s_timerStart = SDL_GetTicks(); + s_timerNext = Scale(Scale(s_timerStart, TICRATE, 1000) + 1, 1000, TICRATE); + + pthread_cond_broadcast(&s_timerEvent); + pthread_mutex_unlock(&s_timerMutex); + } + + return NULL; +} + +int GetTimeThreaded(bool saveMS) +{ + if (saveMS) + { + s_ticStart = s_timerStart; + s_ticNext = s_timerNext; + } + + return s_tics; +} + +int WaitForTicThreaded(int prevTic) +{ + assert(!g_isTicFrozen); + + while (s_tics <= prevTic) + { + pthread_mutex_lock(&s_timerMutex); + pthread_cond_wait(&s_timerEvent, &s_timerMutex); + pthread_mutex_unlock(&s_timerMutex); + } + + return s_tics; +} + +void FreezeTimeThreaded(bool frozen) +{ + g_isTicFrozen = frozen; +} + +} // unnamed namespace + + +fixed_t I_GetTimeFrac(uint32* ms) +{ + const uint32_t now = SDL_GetTicks(); + + if (NULL != ms) + { + *ms = s_ticNext; + } + + const uint32_t step = s_ticNext - s_ticStart; + + return 0 == step + ? FRACUNIT + : clamp( (now - s_ticStart) * FRACUNIT / step, 0, FRACUNIT); +} + + +void I_InitTimer () +{ + assert(!s_timerInitialized); + s_timerInitialized = true; + + pthread_cond_init (&s_timerEvent, NULL); + pthread_mutex_init(&s_timerMutex, NULL); + + pthread_create(&s_timerThread, NULL, TimerThreadFunc, NULL); + + I_GetTime = GetTimeThreaded; + I_WaitForTic = WaitForTicThreaded; + I_FreezeTime = FreezeTimeThreaded; +} + +void I_ShutdownTimer () +{ + if (!s_timerInitialized) + { + // This might happen if Cancel button was pressed + // in the IWAD selector window + return; + } + + s_timerExitRequested = true; + + pthread_join(s_timerThread, NULL); + + pthread_mutex_destroy(&s_timerMutex); + pthread_cond_destroy (&s_timerEvent); +} diff --git a/src/cocoa/zdoom-info.plist b/src/cocoa/zdoom-info.plist new file mode 100644 index 000000000..8371b0555 --- /dev/null +++ b/src/cocoa/zdoom-info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + zdoom.icns + CFBundleIdentifier + org.zdoom.zdoom + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ZDoom + CFBundlePackageType + APPL + CFBundleShortVersionString + Version 2.8.0 + CFBundleSignature + ???? + LSApplicationCategoryType + public.app-category.action-games + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + CFBundleDocumentTypes + + + CFBundleTypeName + Doom Resource File + CFBundleTypeRole + Viewer + CFBundleTypeExtensions + + wad + pk3 + zip + pk7 + 7z + + + + NSPrincipalClass + NSApplication + + diff --git a/src/cocoa/zdoom.icns b/src/cocoa/zdoom.icns new file mode 100644 index 0000000000000000000000000000000000000000..decb4c5ab4054a1ec0a67990d88f8dd89bc982d9 GIT binary patch literal 196176 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 ^mYw-ZTq{;6VDK=D6 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

>gRkX`I67s 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%0A1(#TI3w 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 literal 0 HcmV?d00001 diff --git a/src/cocoa/zdoom.xib b/src/cocoa/zdoom.xib new file mode 100644 index 000000000..56099dc4b --- /dev/null +++ b/src/cocoa/zdoom.xib @@ -0,0 +1,874 @@ + + + + 1060 + 11C74 + 851 + 1138.23 + 567.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 851 + + + YES + + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + YES + + NSApplication + + + FirstResponder + + + NSApplication + + + NSFontManager + + + Main Menu + + YES + + + ZDoom + + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + ZDoom + + YES + + + About ZDoom + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Preferencesā€¦ + , + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Services + + 2147483647 + + + submenuAction: + + Services + + YES + + _NSServicesMenu + + + + + YES + YES + + + 2147483647 + + + + + + Hide ZDoom + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Quit ZDoom + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + Edit + + 2147483647 + + + submenuAction: + + Edit + + YES + + + Undo + z + 1048576 + 2147483647 + + + + + + Redo + Z + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Cut + x + 1048576 + 2147483647 + + + + + + Copy + c + 1048576 + 2147483647 + + + + + + Paste + v + 1048576 + 2147483647 + + + + + + Delete + + 2147483647 + + + + + + Select All + a + 1048576 + 2147483647 + + + + + + + + + Window + + 2147483647 + + + submenuAction: + + Window + + YES + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Bring All to Front + + 2147483647 + + + + + _NSWindowsMenu + + + + _NSMainMenu + + + + + YES + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + performZoom: + + + + 240 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + terminate: + + + + 369 + + + + unhideAllApplications: + + + + 370 + + + + cut: + + + + 738 + + + + paste: + + + + 739 + + + + redo: + + + + 742 + + + + undo: + + + + 746 + + + + copy: + + + + 752 + + + + delete: + + + + 753 + + + + selectAll: + + + + 755 + + + + + YES + + 0 + + YES + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + YES + + + + + + + + 19 + + + YES + + + + + + 56 + + + YES + + + + + + 57 + + + YES + + + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 144 + + + + + 129 + + + + + 143 + + + + + 236 + + + + + 131 + + + YES + + + + + + 149 + + + + + 145 + + + + + 130 + + + + + 24 + + + YES + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 371 + + + + + 681 + + + YES + + + + + + 682 + + + YES + + + + + + + + + + + + + 683 + + + + + 684 + + + + + 685 + + + + + 686 + + + + + 687 + + + + + 688 + + + + + 690 + + + + + 691 + + + + + + + YES + + YES + -3.IBPluginDependency + 129.IBPluginDependency + 129.ImportedFromIB2 + 130.IBPluginDependency + 130.ImportedFromIB2 + 130.editorWindowContentRectSynchronizationRect + 131.IBPluginDependency + 131.ImportedFromIB2 + 134.IBPluginDependency + 134.ImportedFromIB2 + 136.IBPluginDependency + 136.ImportedFromIB2 + 143.IBPluginDependency + 143.ImportedFromIB2 + 144.IBPluginDependency + 144.ImportedFromIB2 + 145.IBPluginDependency + 145.ImportedFromIB2 + 149.IBPluginDependency + 149.ImportedFromIB2 + 150.IBPluginDependency + 150.ImportedFromIB2 + 19.IBPluginDependency + 19.ImportedFromIB2 + 23.IBPluginDependency + 23.ImportedFromIB2 + 236.IBPluginDependency + 236.ImportedFromIB2 + 239.IBPluginDependency + 239.ImportedFromIB2 + 24.IBEditorWindowLastContentRect + 24.IBPluginDependency + 24.ImportedFromIB2 + 24.editorWindowContentRectSynchronizationRect + 29.IBEditorWindowLastContentRect + 29.IBPluginDependency + 29.ImportedFromIB2 + 29.WindowOrigin + 29.editorWindowContentRectSynchronizationRect + 5.IBPluginDependency + 5.ImportedFromIB2 + 56.IBPluginDependency + 56.ImportedFromIB2 + 57.IBEditorWindowLastContentRect + 57.IBPluginDependency + 57.ImportedFromIB2 + 57.editorWindowContentRectSynchronizationRect + 58.IBPluginDependency + 58.ImportedFromIB2 + 681.IBPluginDependency + 682.IBEditorWindowLastContentRect + 682.IBPluginDependency + 683.IBPluginDependency + 684.IBPluginDependency + 685.IBPluginDependency + 686.IBPluginDependency + 687.IBPluginDependency + 688.IBPluginDependency + 690.IBPluginDependency + 691.IBPluginDependency + 92.IBPluginDependency + 92.ImportedFromIB2 + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{436, 809}, {64, 6}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{651, 262}, {194, 73}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{525, 802}, {197, 73}} + {{514, 335}, {220, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + + {74, 862} + {{11, 977}, {478, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{487, 217}, {195, 183}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{23, 794}, {245, 183}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + {{607, 182}, {151, 153}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + + YES + + + YES + + + + + YES + + + YES + + + + 842 + + + + YES + + NSObject + + IBFrameworkSource + Print.framework/Headers/PDEPluginInterface.h + + + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.macosx + + + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + ZDoom.xcodeproj + 3 + + YES + + YES + NSMenuCheckmark + NSMenuMixedState + + + YES + {9, 8} + {7, 2} + + + + From 177112603dd4d146a8e33879ef2a742e53a9eaee Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Sun, 3 Aug 2014 12:38:56 +0300 Subject: [PATCH 09/75] Added support for Cocoa back-end in CMake build system --- src/CMakeLists.txt | 79 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 87c3f4dfa..a21da98b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,8 @@ endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) option( DYN_FLUIDSYNTH "Dynamically load fluidsynth" ON ) +option( OSX_COCOA_BACKEND "Use native Cocoa backend instead of SDL" OFF ) + if( CMAKE_SIZEOF_VOID_P MATCHES "8" ) set( X64 64 ) endif( CMAKE_SIZEOF_VOID_P MATCHES "8" ) @@ -211,7 +213,9 @@ else( WIN32 ) if( NOT SDL_FOUND ) message( SEND_ERROR "SDL is required for building." ) endif( NOT SDL_FOUND ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} "${SDL_LIBRARY}" ) + if( NOT APPLE OR NOT OSX_COCOA_BACKEND ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${SDL_LIBRARY}" ) + endif( NOT APPLE OR NOT OSX_COCOA_BACKEND ) include_directories( "${SDL_INCLUDE_DIR}" ) find_path( FPU_CONTROL_DIR fpu_control.h ) @@ -555,23 +559,62 @@ set( PLAT_WIN32_SOURCES win32/i_system.cpp win32/st_start.cpp win32/win32video.cpp ) -set( PLAT_SDL_SOURCES +set( PLAT_SDL_SYSTEM_SOURCES sdl/crashcatcher.c sdl/hardware.cpp sdl/i_cd.cpp - sdl/i_cursor.cpp - sdl/i_input.cpp - sdl/i_joystick.cpp sdl/i_main.cpp sdl/i_movie.cpp sdl/i_system.cpp - sdl/i_timer.cpp sdl/sdlvideo.cpp sdl/st_start.cpp ) +set( PLAT_SDL_INPUT_SOURCES + sdl/i_cursor.cpp + sdl/i_input.cpp + sdl/i_joystick.cpp + sdl/i_timer.cpp ) set( PLAT_MAC_SOURCES - sdl/SDLMain.m sdl/iwadpicker_cocoa.mm sdl/i_system_cocoa.mm ) +set( PLAT_COCOA_SOURCES + cocoa/HID_Config_Utilities.c + cocoa/HID_Error_Handler.c + cocoa/HID_Name_Lookup.c + cocoa/HID_Queue_Utilities.c + cocoa/HID_Utilities.c + cocoa/IOHIDDevice_.c + cocoa/IOHIDElement_.c + cocoa/ImmrHIDUtilAddOn.c + cocoa/i_backend_cocoa.mm + cocoa/i_joystick.cpp + cocoa/i_timer.cpp + cocoa/zdoom.icns ) + +if( APPLE ) + if( OSX_COCOA_BACKEND ) + find_program( IBTOOL ibtool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin" ) + if( ${IBTOOL} STREQUAL "IBTOOL-NOTFOUND" ) + message( SEND_ERROR "ibtool can not be found to compile xib files." ) + endif( ${IBTOOL} STREQUAL "IBTOOL-NOTFOUND" ) + + set( NIB_FILE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/zdoom.nib" ) + add_custom_command( OUTPUT "${NIB_FILE}" + COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text + --compile "${NIB_FILE}" "${CMAKE_CURRENT_SOURCE_DIR}/cocoa/zdoom.xib" + COMMENT "Compiling zdoom.xib" ) + + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_COCOA_SOURCES} "${NIB_FILE}" "${FMOD_LIBRARY}" ) + + set_source_files_properties( "${NIB_FILE}" PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) + else( OSX_COCOA_BACKEND ) + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_SDL_INPUT_SOURCES} "${FMOD_LIBRARY}" ) + set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} sdl/SDLMain.m ) + endif( OSX_COCOA_BACKEND ) + + set_source_files_properties( cocoa/zdoom.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) + set_source_files_properties( "${FMOD_LIBRARY}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks ) +endif( APPLE ) + if( WIN32 ) set( SYSTEM_SOURCES_DIR win32 ) set( SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ) @@ -773,7 +816,7 @@ set( NOT_COMPILED_SOURCE_FILES asm_x86_64/tmap3.s ) -add_executable( zdoom WIN32 +add_executable( zdoom WIN32 MACOSX_BUNDLE ${HEADER_FILES} ${NOT_COMPILED_SOURCE_FILES} __autostart.cpp @@ -1165,6 +1208,25 @@ if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) endif( SSE_MATTERS ) endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) +if( APPLE ) + set_target_properties(zdoom PROPERTIES + LINK_FLAGS "-framework Cocoa -framework IOKit -framework OpenGL" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cocoa/zdoom-info.plist" ) + + # Fix fmod link so that it can be found in the app bundle. + find_program( OTOOL otool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin" ) + find_program( INSTALL_NAME_TOOL install_name_tool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin" ) + execute_process( COMMAND "${OTOOL}" -L "${FMOD_LIBRARY}" + COMMAND grep "libfmodex.dylib (compat" + COMMAND head -n1 + COMMAND awk "{print $1}" + OUTPUT_VARIABLE FMOD_LINK + OUTPUT_STRIP_TRAILING_WHITESPACE ) + add_custom_command( TARGET zdoom POST_BUILD + COMMAND "${INSTALL_NAME_TOOL}" -change "${FMOD_LINK}" @executable_path/../Frameworks/libfmodex.dylib "$" + COMMENT "Relinking FMOD Ex" ) +endif( APPLE ) + source_group("Assembly Files\\ia32" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/asm_ia32/.+") source_group("Assembly Files\\x86_64" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/asm_x86_64/.+") source_group("Audio Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sound/.+") @@ -1172,6 +1234,7 @@ source_group("Audio Files\\OPL Synth" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURC source_group("Audio Files\\OPL Synth\\DOSBox" FILES oplsynth/dosbox/opl.cpp oplsynth/dosbox/opl.h) source_group("Audio Files\\Timidity\\Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/timidity/.+\\.h$") source_group("Audio Files\\Timidity\\Source" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/timidity/.+\\.cpp$") +source_group("Cocoa Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/cocoa/.+") source_group("Decorate++" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/thingdef/.+") source_group("FraggleScript" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/fragglescript/.+") source_group("Games\\Doom Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_doom/.+") From 4fb1e7517c8586cbd6db4ea935ac2bd191ebc520 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:39:42 +0300 Subject: [PATCH 10/75] Added support for VSync option in Cocoa back-end --- src/sdl/sdlvideo.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/sdl/sdlvideo.cpp b/src/sdl/sdlvideo.cpp index 92741d325..c869545ab 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/sdl/sdlvideo.cpp @@ -15,6 +15,10 @@ #include +#ifdef __APPLE__ +#include +#endif // __APPLE__ + // MACROS ------------------------------------------------------------------ // TYPES ------------------------------------------------------------------- @@ -43,6 +47,8 @@ public: friend class SDLVideo; + virtual void SetVSync (bool vsync); + private: PalEntry SourcePalette[256]; BYTE GammaTable[3][256]; @@ -82,6 +88,7 @@ extern bool GUICapture; EXTERN_CVAR (Float, Gamma) EXTERN_CVAR (Int, vid_maxfps) EXTERN_CVAR (Bool, cl_capfps) +EXTERN_CVAR (Bool, vid_vsync) // PUBLIC DATA DEFINITIONS ------------------------------------------------- @@ -326,6 +333,7 @@ SDLFB::SDLFB (int width, int height, bool fullscreen) } memcpy (SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256); UpdateColors (); + SetVSync (vid_vsync); } SDLFB::~SDLFB () @@ -535,6 +543,19 @@ bool SDLFB::IsFullscreen () return (Screen->flags & SDL_FULLSCREEN) != 0; } +void SDLFB::SetVSync (bool vsync) +{ +#ifdef __APPLE__ + if (CGLContextObj context = CGLGetCurrentContext()) + { + // Apply vsync for native backend only (where OpenGL context is set) + + const GLint value = vsync ? 1 : 0; + CGLSetParameter(context, kCGLCPSwapInterval, &value); + } +#endif // __APPLE__ +} + ADD_STAT (blit) { FString out; From 313173aa32fb869bb038ad73f48beabf8b9e6201 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 3 Aug 2014 12:40:14 +0300 Subject: [PATCH 11/75] Added support for clipboard operations on OS X --- src/sdl/i_system.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index ca5ffa2e2..d65dbc6c8 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -78,6 +78,10 @@ #undef GC #endif +#ifdef __APPLE__ +#include +#endif // __APPLE__ + EXTERN_CVAR (String, language) extern "C" @@ -609,6 +613,10 @@ int I_FindAttr (findstate_t *fileinfo) return 0; } +#ifdef __APPLE__ +static PasteboardRef s_clipboard; +#endif // __APPLE__ + // Clipboard support requires GTK+ // TODO: GTK+ uses UTF-8. We don't, so some conversions would be appropriate. void I_PutInClipboard (const char *str) @@ -629,6 +637,23 @@ void I_PutInClipboard (const char *str) } */ } +#elif defined __APPLE__ + if (NULL == s_clipboard) + { + PasteboardCreate(kPasteboardClipboard, &s_clipboard); + } + + PasteboardClear(s_clipboard); + PasteboardSynchronize(s_clipboard); + + const CFDataRef textData = CFDataCreate(kCFAllocatorDefault, + reinterpret_cast(str), strlen(str)); + + if (NULL != textData) + { + PasteboardPutItemFlavor(s_clipboard, PasteboardItemID(1), + CFSTR("public.utf8-plain-text"), textData, 0); + } #endif } @@ -650,6 +675,61 @@ FString I_GetFromClipboard (bool use_primary_selection) } } } +#elif defined __APPLE__ + FString result; + + if (NULL == s_clipboard) + { + PasteboardCreate(kPasteboardClipboard, &s_clipboard); + } + + PasteboardSynchronize(s_clipboard); + + ItemCount itemCount = 0; + PasteboardGetItemCount(s_clipboard, &itemCount); + + if (0 == itemCount) + { + return FString(); + } + + PasteboardItemID itemID; + + if (0 != PasteboardGetItemIdentifier(s_clipboard, 1, &itemID)) + { + return FString(); + } + + CFArrayRef flavorTypeArray; + + if (0 != PasteboardCopyItemFlavors(s_clipboard, itemID, &flavorTypeArray)) + { + return FString(); + } + + const CFIndex flavorCount = CFArrayGetCount(flavorTypeArray); + + for (CFIndex flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex) + { + const CFStringRef flavorType = static_cast( + CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex)); + + if (UTTypeConformsTo(flavorType, CFSTR("public.utf8-plain-text"))) + { + CFDataRef flavorData; + + if (0 == PasteboardCopyItemFlavorData(s_clipboard, itemID, flavorType, &flavorData)) + { + result += reinterpret_cast(CFDataGetBytePtr(flavorData)); + } + + CFRelease(flavorData); + } + } + + CFRelease(flavorTypeArray); + + return result; #endif return ""; } From 82e8c514e993910dcd351f4227a7c8d5c42aefa8 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 9 Aug 2014 13:20:52 +0300 Subject: [PATCH 12/75] Renamed SDL specific source file with GUI related code --- src/CMakeLists.txt | 2 +- src/sdl/{i_cursor.cpp => i_gui.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/sdl/{i_cursor.cpp => i_gui.cpp} (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a21da98b5..72a7c7f01 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -569,7 +569,7 @@ set( PLAT_SDL_SYSTEM_SOURCES sdl/sdlvideo.cpp sdl/st_start.cpp ) set( PLAT_SDL_INPUT_SOURCES - sdl/i_cursor.cpp + sdl/i_gui.cpp sdl/i_input.cpp sdl/i_joystick.cpp sdl/i_timer.cpp ) diff --git a/src/sdl/i_cursor.cpp b/src/sdl/i_gui.cpp similarity index 100% rename from src/sdl/i_cursor.cpp rename to src/sdl/i_gui.cpp From 18c9caf68d764759d35182dbf86b4bfbc873862f Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 9 Aug 2014 13:32:32 +0300 Subject: [PATCH 13/75] Enhanced Cocoa version of IWAD picker window Added ability to specify custom command line parameters Added ability to browse for user files Improved handling of restart console command Improved layout for window --- src/cocoa/i_backend_cocoa.mm | 17 ++- src/sdl/i_gui.cpp | 5 + src/sdl/iwadpicker_cocoa.mm | 235 ++++++++++++++++++++++++++++++++--- 3 files changed, 239 insertions(+), 18 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index d4c353edd..675fde182 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -144,6 +144,8 @@ char* s_argv[ARGC_MAX]; TArray s_argvStorage; +bool s_restartedFromWADPicker; + bool s_nativeMouse = true; @@ -966,7 +968,9 @@ static ApplicationDelegate* s_applicationDelegate; { ZD_UNUSED(theApplication); - if (0 == [filename length] || s_argc + 2 >= ARGC_MAX) + if (s_restartedFromWADPicker + || 0 == [filename length] + || s_argc + 2 >= ARGC_MAX) { return FALSE; } @@ -1724,8 +1728,15 @@ int main(int argc, char** argv) continue; } - s_argvStorage.Push(argument); - s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + if (0 == strcmp(argument, "-wad_picker_restart")) + { + s_restartedFromWADPicker = true; + } + else + { + s_argvStorage.Push(argument); + s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + } } NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; diff --git a/src/sdl/i_gui.cpp b/src/sdl/i_gui.cpp index c4202a6b3..2ecdcdb74 100644 --- a/src/sdl/i_gui.cpp +++ b/src/sdl/i_gui.cpp @@ -71,3 +71,8 @@ bool I_SetCursor(FTexture *cursorpic) } return true; } + +void I_SetMainWindowVisible(bool visible) +{ + +} diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index 3b414d5e8..7a6b38623 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -33,9 +33,17 @@ ** */ +#include "cmdlib.h" #include "d_main.h" #include "version.h" +#include "c_cvars.h" +#include "m_argv.h" +#include "m_misc.h" +#include "gameconfigfile.h" #include +#include + +CVAR(String, osx_additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG); enum { @@ -107,6 +115,45 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; @end +static NSDictionary* GetKnownFileTypes() +{ + return [NSDictionary dictionaryWithObjectsAndKeys: + @"-file" , @"wad", + @"-file" , @"pk3", + @"-file" , @"zip", + @"-file" , @"pk7", + @"-file" , @"7z", + @"-deh" , @"deh", + @"-bex" , @"bex", + @"-exec" , @"cfg", + @"-playdemo", @"lmp", + nil]; +} + +static NSArray* GetKnownExtensions() +{ + return [GetKnownFileTypes() allKeys]; +} + +@interface NSMutableString(AppendKnownFileType) +- (void)appendKnownFileType:(NSString *)filePath; +@end + +@implementation NSMutableString(AppendKnownFileType) +- (void)appendKnownFileType:(NSString *)filePath +{ + NSString* extension = [[filePath pathExtension] lowercaseString]; + NSString* parameter = [GetKnownFileTypes() objectForKey:extension]; + + if (nil == parameter) + { + return; + } + + [self appendFormat:@"%@ \"%@\" ", parameter, filePath]; +} +@end + // So we can listen for button actions and such we need to have an Obj-C class. @interface IWADPicker : NSObject { @@ -114,13 +161,18 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; NSWindow *window; NSButton *okButton; NSButton *cancelButton; + NSButton *browseButton; + NSTextField *parametersTextField; bool cancelled; } - (void)buttonPressed:(id) sender; +- (void)browseButtonPressed:(id) sender; - (void)doubleClicked:(id) sender; - (void)makeLabel:(NSTextField *)label withString:(const char*) str; - (int)pickIWad:(WadStuff *)wads num:(int) numwads showWindow:(bool) showwin defaultWad:(int) defaultiwad; +- (NSString*)commandLineParameters; +- (void)menuActionSent:(NSNotification*)notification; @end @implementation IWADPicker @@ -134,6 +186,52 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; [app stopModal]; } +- (void)browseButtonPressed:(id) sender +{ + NSOpenPanel* openPanel = [NSOpenPanel openPanel]; + [openPanel setAllowsMultipleSelection:YES]; + [openPanel setCanChooseFiles:YES]; + [openPanel setCanChooseDirectories:YES]; + [openPanel setResolvesAliases:YES]; + [openPanel setAllowedFileTypes:GetKnownExtensions()]; + + if (NSOKButton == [openPanel runModal]) + { + NSArray* files = [openPanel URLs]; + NSMutableString* parameters = [NSMutableString string]; + + for (NSUInteger i = 0, ei = [files count]; i < ei; ++i) + { + NSString* filePath = [[files objectAtIndex:i] path]; + BOOL isDirectory = false; + + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] && isDirectory) + { + [parameters appendFormat:@"-file \"%@\" ", filePath]; + } + else + { + [parameters appendKnownFileType:filePath]; + } + } + + if ([parameters length] > 0) + { + NSString* newParameters = [parametersTextField stringValue]; + + if ([newParameters length] > 0 + && NO == [newParameters hasSuffix:@" "]) + { + newParameters = [newParameters stringByAppendingString:@" "]; + } + + newParameters = [newParameters stringByAppendingString:parameters]; + + [parametersTextField setStringValue: newParameters]; + } + } +} + - (void)doubleClicked:(id) sender { if ([sender clickedRow] >= 0) @@ -159,20 +257,18 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; cancelled = false; app = [NSApplication sharedApplication]; - id windowTitle = [NSString stringWithFormat:@GAMESIG " %s: Select an IWAD to use", GetVersionString()]; + id windowTitle = [NSString stringWithFormat:@"%s %s", GAMENAME, GetVersionString()]; NSRect frame = NSMakeRect(0, 0, 440, 450); window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]; [window setTitle:windowTitle]; - NSTextField *description = [[NSTextField alloc] initWithFrame:NSMakeRect(22, 379, 412, 50)]; - [self makeLabel:description withString:"ZDoom found more than one IWAD\nSelect from the list below to determine which one to use:"]; + NSTextField *description = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 384, 402, 50)]; + [self makeLabel:description withString:GAMENAME " found more than one IWAD\nSelect from the list below to determine which one to use:"]; [[window contentView] addSubview:description]; [description release]; - // Commented out version would account for an additional parameters box. - //NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 103, 412, 288)]; - NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 50, 412, 341)]; + NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 135, 402, 256)]; NSTableView *iwadTable = [[NSTableView alloc] initWithFrame:[iwadScroller bounds]]; IWADTableData *tableData = [[IWADTableData alloc] init:wads num:numwads]; for(int i = 0;i < NUM_COLUMNS;i++) @@ -200,11 +296,12 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; [iwadTable release]; [iwadScroller release]; - /*NSTextField *additionalParametersLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(17, 78, 144, 17)]; - [self makeLabel:additionalParametersLabel:"Additional Parameters"]; + NSTextField *additionalParametersLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 108, 144, 17)]; + [self makeLabel:additionalParametersLabel withString:"Additional Parameters:"]; [[window contentView] addSubview:additionalParametersLabel]; - NSTextField *additionalParameters = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 48, 360, 22)]; - [[window contentView] addSubview:additionalParameters];*/ + parametersTextField = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 48, 402, 54)]; + [parametersTextField setStringValue:[NSString stringWithUTF8String:osx_additional_parameters]]; + [[window contentView] addSubview:parametersTextField]; // Doesn't look like the SDL version implements this so lets not show it. /*NSButton *dontAsk = [[NSButton alloc] initWithFrame:NSMakeRect(18, 18, 178, 18)]; @@ -213,39 +310,147 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; [dontAsk setState:(showwin ? NSOffState : NSOnState)]; [[window contentView] addSubview:dontAsk];*/ - okButton = [[NSButton alloc] initWithFrame:NSMakeRect(236, 12, 96, 32)]; - [okButton setTitle:[NSString stringWithUTF8String:"OK"]]; + okButton = [[NSButton alloc] initWithFrame:NSMakeRect(236, 8, 96, 32)]; + [okButton setTitle:@"OK"]; [okButton setBezelStyle:NSRoundedBezelStyle]; [okButton setAction:@selector(buttonPressed:)]; [okButton setTarget:self]; [okButton setKeyEquivalent:@"\r"]; [[window contentView] addSubview:okButton]; - cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(332, 12, 96, 32)]; - [cancelButton setTitle:[NSString stringWithUTF8String:"Cancel"]]; + cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(332, 8, 96, 32)]; + [cancelButton setTitle:@"Cancel"]; [cancelButton setBezelStyle:NSRoundedBezelStyle]; [cancelButton setAction:@selector(buttonPressed:)]; [cancelButton setTarget:self]; [cancelButton setKeyEquivalent:@"\033"]; [[window contentView] addSubview:cancelButton]; + + browseButton = [[NSButton alloc] initWithFrame:NSMakeRect(14, 8, 96, 32)]; + [browseButton setTitle:@"Browse..."]; + [browseButton setBezelStyle:NSRoundedBezelStyle]; + [browseButton setAction:@selector(browseButtonPressed:)]; + [browseButton setTarget:self]; + [[window contentView] addSubview:browseButton]; + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(menuActionSent:) name:NSMenuDidSendActionNotification object:nil]; [window center]; [app runModalForWindow:window]; + [center removeObserver:self name:NSMenuDidSendActionNotification object:nil]; + [window release]; [okButton release]; [cancelButton release]; + [browseButton release]; return cancelled ? -1 : [iwadTable selectedRow]; } +- (NSString*)commandLineParameters +{ + return [parametersTextField stringValue]; +} + +- (void)menuActionSent:(NSNotification*)notification +{ + NSDictionary* userInfo = [notification userInfo]; + NSMenuItem* menuItem = [userInfo valueForKey:@"MenuItem"]; + + if ( @selector(terminate:) == [menuItem action] ) + { + exit(0); + } +} + @end + +EXTERN_CVAR(String, defaultiwad) + +static void RestartWithParameters(const char* iwadPath, NSString* parameters) +{ + assert(nil != parameters); + + defaultiwad = ExtractFileBase(iwadPath); + + GameConfig->DoGameSetup("Doom"); + M_SaveDefaults(NULL); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + @try + { + const int commandLineParametersCount = Args->NumArgs(); + assert(commandLineParametersCount > 0); + + NSString* executablePath = [NSString stringWithUTF8String:Args->GetArg(0)]; + + NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:commandLineParametersCount + 3]; + [arguments addObject:@"-wad_picker_restart"]; + [arguments addObject:@"-iwad"]; + [arguments addObject:[NSString stringWithUTF8String:iwadPath]]; + + for (int i = 1; i < commandLineParametersCount; ++i) + { + NSString* currentParameter = [NSString stringWithUTF8String:Args->GetArg(i)]; + [arguments addObject:currentParameter]; + } + + wordexp_t expansion; + + if (0 == wordexp([parameters UTF8String], &expansion, 0)) + { + for (size_t i = 0; i < expansion.we_wordc; ++i) + { + NSString* argumentString = [NSString stringWithCString:expansion.we_wordv[i] + encoding:NSUTF8StringEncoding]; + [arguments addObject:argumentString]; + } + + wordfree(&expansion); + } + + [NSTask launchedTaskWithLaunchPath:executablePath arguments:arguments]; + + _exit(0); // to avoid atexit()'s functions + } + @catch (NSException* e) + { + NSLog(@"Cannot restart: %@", [e reason]); + } + + [pool release]; +} + +void I_SetMainWindowVisible(bool visible); + // Simple wrapper so we can call this from outside. int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + I_SetMainWindowVisible(false); + IWADPicker *picker = [IWADPicker alloc]; int ret = [picker pickIWad:wads num:numwads showWindow:showwin defaultWad:defaultiwad]; - [picker release]; + + I_SetMainWindowVisible(true); + + NSString* parametersToAppend = [picker commandLineParameters]; + osx_additional_parameters = [parametersToAppend UTF8String]; + + if (ret >= 0) + { + if (0 != [parametersToAppend length]) + { + RestartWithParameters(wads[ret].Path, parametersToAppend); + } + } + + [pool release]; + return ret; } From efdfeeec908357db63de431bacb264ccada5d507 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 9 Aug 2014 15:51:39 +0300 Subject: [PATCH 14/75] Distinguish SDL and Cocoa back-ends in startup log --- src/cocoa/i_backend_cocoa.mm | 9 +++++++++ src/sdl/i_gui.cpp | 5 +++++ src/sdl/i_main.cpp | 6 ++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 675fde182..d7e46dfe0 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1310,6 +1310,15 @@ bool I_SetCursor(FTexture* cursorpic) // --------------------------------------------------------------------------- +const char* I_GetBackEndName() +{ + return "Native Cocoa"; +} + + +// --------------------------------------------------------------------------- + + extern "C" { diff --git a/src/sdl/i_gui.cpp b/src/sdl/i_gui.cpp index 2ecdcdb74..37fc750cb 100644 --- a/src/sdl/i_gui.cpp +++ b/src/sdl/i_gui.cpp @@ -76,3 +76,8 @@ void I_SetMainWindowVisible(bool visible) { } + +const char* I_GetBackEndName() +{ + return "SDL"; +} diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 3e199a165..7a18fb05f 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -239,6 +239,8 @@ static void unprotect_rtext() void I_StartupJoysticks(); void I_ShutdownJoysticks(); +const char* I_GetBackEndName(); + int main (int argc, char **argv) { #if !defined (__APPLE__) @@ -248,8 +250,8 @@ int main (int argc, char **argv) } #endif // !__APPLE__ - printf(GAMENAME" %s - %s - SDL version\nCompiled on %s\n", - GetVersionString(), GetGitTime(), __DATE__); + printf(GAMENAME" %s - %s - %s version\nCompiled on %s\n", + GetVersionString(), GetGitTime(), I_GetBackEndName(), __DATE__); seteuid (getuid ()); std::set_new_handler (NewFailure); From 406ee9234aee08b65450dacc57ffc027fc15c562 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 9 Aug 2014 16:27:07 +0300 Subject: [PATCH 15/75] Added two SDL functions needed for GZDoom --- src/cocoa/i_backend_cocoa.mm | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index d7e46dfe0..8d6f53b4c 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1476,8 +1476,13 @@ SDL_Rect** SDL_ListModes(SDL_PixelFormat* format, Uint32 flags) return &resolutions[0]; } +int SDL_ShowCursor(int) +{ + // Does nothing + return 0; +} + -//static GLAuxilium::Texture2D* s_softwareTexture; static GLuint s_frameBufferTexture = 0; static const Uint16 BYTES_PER_PIXEL = 4; @@ -1597,6 +1602,19 @@ void SDL_GL_SwapBuffers() [[NSOpenGLContext currentContext] flushBuffer]; } +int SDL_GL_SetAttribute(SDL_GLattr attr, int value) +{ + if (SDL_GL_MULTISAMPLESAMPLES == attr) + { + [s_applicationDelegate setMultisample:value]; + } + + // Not interested in other attributes + + return 0; +} + + int SDL_LockSurface(SDL_Surface* surface) { ZD_UNUSED(surface); From f8dfdbd4a45ce6fd303f7ed519fc351685502120 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 10 Aug 2014 11:12:35 +0300 Subject: [PATCH 16/75] Extracted fullscreen and windowed modes handling to separate methods --- src/cocoa/i_backend_cocoa.mm | 107 +++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 8d6f53b4c..f219d48ae 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -878,7 +878,6 @@ void ProcessMouseWheelEvent(NSEvent* theEvent) - (int)multisample; - (void)setMultisample:(int)multisample; -- (void)initializeOpenGL; - (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height; - (void)processEvents:(NSTimer*)timer; @@ -1077,70 +1076,78 @@ static ApplicationDelegate* s_applicationDelegate; m_openGLInitialized = true; } +- (void)fullscreenWithWidth:(int)width height:(int)height +{ + NSScreen* screen = [m_window screen]; + + const NSRect screenFrame = [screen frame]; + const NSRect displayRect = IsHiDPISupported() + ? [screen convertRectToBacking:screenFrame] + : screenFrame; + + const float displayWidth = displayRect.size.width; + const float displayHeight = displayRect.size.height; + + const float pixelScaleFactorX = displayWidth / static_cast< float >(width ); + const float pixelScaleFactorY = displayHeight / static_cast< float >(height); + + s_frameBufferParameters.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); + + s_frameBufferParameters.width = width * s_frameBufferParameters.pixelScale; + s_frameBufferParameters.height = height * s_frameBufferParameters.pixelScale; + + s_frameBufferParameters.shiftX = (displayWidth - s_frameBufferParameters.width ) / 2.0f; + s_frameBufferParameters.shiftY = (displayHeight - s_frameBufferParameters.height) / 2.0f; + + [m_window setLevel:NSMainMenuWindowLevel + 1]; + [m_window setStyleMask:NSBorderlessWindowMask]; + [m_window setHidesOnDeactivate:YES]; + [m_window setFrame:displayRect display:YES]; + [m_window setFrameOrigin:NSMakePoint(0.0f, 0.0f)]; +} + +- (void)windowedWithWidth:(int)width height:(int)height +{ + s_frameBufferParameters.pixelScale = 1.0f; + + s_frameBufferParameters.width = static_cast< float >(width ); + s_frameBufferParameters.height = static_cast< float >(height); + + s_frameBufferParameters.shiftX = 0.0f; + s_frameBufferParameters.shiftY = 0.0f; + + const NSSize windowPixelSize = NSMakeSize(width, height); + const NSSize windowSize = IsHiDPISupported() + ? [[m_window contentView] convertSizeFromBacking:windowPixelSize] + : windowPixelSize; + + [m_window setLevel:NSNormalWindowLevel]; + [m_window setStyleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask]; + [m_window setHidesOnDeactivate:NO]; + [m_window setContentSize:windowSize]; + [m_window center]; +} + - (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height { [self initializeOpenGL]; - CGLContextObj context = CGLGetCurrentContext(); - NSView* view = [m_window contentView]; - if (fullscreen) { - NSScreen* screen = [m_window screen]; - const NSRect screenFrame = [screen frame]; - const NSRect displayRect = IsHiDPISupported() - ? [screen convertRectToBacking:screenFrame] - : screenFrame; - - const float displayWidth = displayRect.size.width; - const float displayHeight = displayRect.size.height; - - const float pixelScaleFactorX = displayWidth / static_cast< float >(width ); - const float pixelScaleFactorY = displayHeight / static_cast< float >(height); - - s_frameBufferParameters.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); - - s_frameBufferParameters.width = width * s_frameBufferParameters.pixelScale; - s_frameBufferParameters.height = height * s_frameBufferParameters.pixelScale; - - s_frameBufferParameters.shiftX = (displayWidth - s_frameBufferParameters.width ) / 2.0f; - s_frameBufferParameters.shiftY = (displayHeight - s_frameBufferParameters.height) / 2.0f; - - [m_window setLevel:NSMainMenuWindowLevel + 1]; - [m_window setStyleMask:NSBorderlessWindowMask]; - [m_window setHidesOnDeactivate:YES]; - [m_window setFrame:displayRect display:YES]; - [m_window setFrameOrigin:NSMakePoint(0.0f, 0.0f)]; + [self fullscreenWithWidth:width height:height]; } else { - s_frameBufferParameters.pixelScale = 1.0f; - - s_frameBufferParameters.width = static_cast< float >(width ); - s_frameBufferParameters.height = static_cast< float >(height); - - s_frameBufferParameters.shiftX = 0.0f; - s_frameBufferParameters.shiftY = 0.0f; - - const NSSize windowPixelSize = NSMakeSize(width, height); - const NSSize windowSize = IsHiDPISupported() - ? [view convertSizeFromBacking:windowPixelSize] - : windowPixelSize; - - [m_window setLevel:NSNormalWindowLevel]; - [m_window setStyleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask]; - [m_window setHidesOnDeactivate:NO]; - [m_window setContentSize:windowSize]; - [m_window center]; + [self windowedWithWidth:width height:height]; } - + const NSSize viewSize = GetRealContentViewSize(m_window); glViewport(0, 0, viewSize.width, viewSize.height); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); - - CGLFlushDrawable(context); + + CGLFlushDrawable(CGLGetCurrentContext()); static NSString* const TITLE_STRING = [NSString stringWithFormat:@"%s %s", GAMESIG, GetVersionString()]; From 2649b4c26afdbb587535f76193b03b1e6a34057a Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 10 Aug 2014 11:22:20 +0300 Subject: [PATCH 17/75] Moved render buffer options definition to own header file This is needed for GZDoom --- src/cocoa/i_backend_cocoa.mm | 43 +++++++++++-------------------- src/cocoa/i_rbopts.h | 50 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 src/cocoa/i_rbopts.h diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index f219d48ae..3bb926a61 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -63,6 +63,7 @@ #include "textures.h" #include "v_video.h" #include "version.h" +#include "i_rbopts.h" #undef Class @@ -92,6 +93,7 @@ // --------------------------------------------------------------------------- +RenderBufferOptions rbOpts; CVAR(Bool, use_mouse, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -123,20 +125,6 @@ void I_ShutdownJoysticks(); namespace { -struct FrameBufferParameters -{ - float pixelScale; - - float shiftX; - float shiftY; - - float width; - float height; -}; - -FrameBufferParameters s_frameBufferParameters; - - const int ARGC_MAX = 64; int s_argc; @@ -619,8 +607,8 @@ void NSEventToGameMousePosition(NSEvent* inEvent, event_t* outEvent) const CGFloat frameHeight = GetRealContentViewSize(window).height; - const CGFloat posX = ( viewPos.x - s_frameBufferParameters.shiftX) / s_frameBufferParameters.pixelScale; - const CGFloat posY = (frameHeight - viewPos.y - s_frameBufferParameters.shiftY) / s_frameBufferParameters.pixelScale; + const CGFloat posX = ( viewPos.x - rbOpts.shiftX) / rbOpts.pixelScale; + const CGFloat posY = (frameHeight - viewPos.y - rbOpts.shiftY) / rbOpts.pixelScale; outEvent->data1 = static_cast< int >(posX); outEvent->data2 = static_cast< int >(posY); @@ -1091,13 +1079,13 @@ static ApplicationDelegate* s_applicationDelegate; const float pixelScaleFactorX = displayWidth / static_cast< float >(width ); const float pixelScaleFactorY = displayHeight / static_cast< float >(height); - s_frameBufferParameters.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); + rbOpts.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); - s_frameBufferParameters.width = width * s_frameBufferParameters.pixelScale; - s_frameBufferParameters.height = height * s_frameBufferParameters.pixelScale; + rbOpts.width = width * rbOpts.pixelScale; + rbOpts.height = height * rbOpts.pixelScale; - s_frameBufferParameters.shiftX = (displayWidth - s_frameBufferParameters.width ) / 2.0f; - s_frameBufferParameters.shiftY = (displayHeight - s_frameBufferParameters.height) / 2.0f; + rbOpts.shiftX = (displayWidth - rbOpts.width ) / 2.0f; + rbOpts.shiftY = (displayHeight - rbOpts.height) / 2.0f; [m_window setLevel:NSMainMenuWindowLevel + 1]; [m_window setStyleMask:NSBorderlessWindowMask]; @@ -1108,13 +1096,13 @@ static ApplicationDelegate* s_applicationDelegate; - (void)windowedWithWidth:(int)width height:(int)height { - s_frameBufferParameters.pixelScale = 1.0f; + rbOpts.pixelScale = 1.0f; - s_frameBufferParameters.width = static_cast< float >(width ); - s_frameBufferParameters.height = static_cast< float >(height); + rbOpts.width = static_cast< float >(width ); + rbOpts.height = static_cast< float >(height); - s_frameBufferParameters.shiftX = 0.0f; - s_frameBufferParameters.shiftY = 0.0f; + rbOpts.shiftX = 0.0f; + rbOpts.shiftY = 0.0f; const NSSize windowPixelSize = NSMakeSize(width, height); const NSSize windowSize = IsHiDPISupported() @@ -1580,8 +1568,7 @@ static void ResetSoftwareViewport() glViewport(0, 0, viewport[0], viewport[1]); glClear(GL_COLOR_BUFFER_BIT); - glViewport(s_frameBufferParameters.shiftX, s_frameBufferParameters.shiftY, - s_frameBufferParameters.width, s_frameBufferParameters.height); + glViewport(rbOpts.shiftX, rbOpts.shiftY, rbOpts.width, rbOpts.height); } int SDL_WM_ToggleFullScreen(SDL_Surface* surface) diff --git a/src/cocoa/i_rbopts.h b/src/cocoa/i_rbopts.h new file mode 100644 index 000000000..c1a9d9d4d --- /dev/null +++ b/src/cocoa/i_rbopts.h @@ -0,0 +1,50 @@ +/* + ** i_rbopts.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#ifndef SRC_COCOA_I_RBOPTS_H_INCLUDED +#define SRC_COCOA_I_RBOPTS_H_INCLUDED + +struct RenderBufferOptions +{ + float pixelScale; + + float shiftX; + float shiftY; + + float width; + float height; +}; + +extern RenderBufferOptions rbOpts; + +#endif // SRC_COCOA_I_RBOPTS_H_INCLUDED From 96a3e0f7298b75d89b01aa7b4c1118db8817b13c Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 10 Aug 2014 12:30:19 +0300 Subject: [PATCH 18/75] Set more descriptive name for SDL source files list in CMake file --- src/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 72a7c7f01..4a1a26867 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -568,7 +568,7 @@ set( PLAT_SDL_SYSTEM_SOURCES sdl/i_system.cpp sdl/sdlvideo.cpp sdl/st_start.cpp ) -set( PLAT_SDL_INPUT_SOURCES +set( PLAT_SDL_SPECIAL_SOURCES sdl/i_gui.cpp sdl/i_input.cpp sdl/i_joystick.cpp @@ -607,7 +607,7 @@ if( APPLE ) set_source_files_properties( "${NIB_FILE}" PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) else( OSX_COCOA_BACKEND ) - set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_SDL_INPUT_SOURCES} "${FMOD_LIBRARY}" ) + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_SDL_SPECIAL_SOURCES} "${FMOD_LIBRARY}" ) set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} sdl/SDLMain.m ) endif( OSX_COCOA_BACKEND ) From 9479a89b667af93028e1ea7690431a15f0f48b25 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 16 Aug 2014 13:55:05 +0300 Subject: [PATCH 19/75] Fixed crash on Release targets --- src/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4a1a26867..2ed4d0f00 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -431,9 +431,10 @@ if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) endif( PROFILE ) set( REL_CXX_FLAGS "-fno-rtti" ) - if( NOT PROFILE ) + if( NOT PROFILE AND NOT APPLE ) + # On OS X frame pointers are required for exception handling, at least with Clang set( REL_CXX_FLAGS "${REL_CXX_FLAGS} -fomit-frame-pointer" ) - endif( NOT PROFILE ) + endif( NOT PROFILE AND NOT APPLE ) set( CMAKE_CXX_FLAGS_RELEASE "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE}" ) set( CMAKE_CXX_FLAGS_MINSIZEREL "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_MINSIZEREL}" ) set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" ) From 45a88780b0db0b2da5839bbd5078236db7fd498c Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 6 Sep 2014 10:18:33 +0300 Subject: [PATCH 20/75] Fixed build with GCC 4.x / Xcode 3.2.x --- src/sdl/iwadpicker_cocoa.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index 7a6b38623..92eb47e40 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -33,6 +33,9 @@ ** */ +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + #include "cmdlib.h" #include "d_main.h" #include "version.h" @@ -40,6 +43,9 @@ #include "m_argv.h" #include "m_misc.h" #include "gameconfigfile.h" + +#undef Class + #include #include From e60d181e874bc1d04ef4f5a0ba2d19027987b08f Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 6 Sep 2014 10:22:37 +0300 Subject: [PATCH 21/75] Removed redundant #include's that broke build of native back-end --- src/cocoa/i_backend_cocoa.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 3bb926a61..b8b2babeb 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -48,8 +48,6 @@ // Avoid collision between DObject class and Objective-C #define Class ObjectClass -#include "basictypes.h" -#include "basicinlines.h" #include "bitmap.h" #include "c_console.h" #include "c_dispatch.h" From 0fd84bc8536cbaeb3cd72559c5873e7aa7539491 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Fri, 12 Sep 2014 18:35:45 +0300 Subject: [PATCH 22/75] Added ability to turn off hi-res backing surface rendering on Retina displays High resolution backing surface is enabled by default and controlled via vid_hidpi CVAR When it is set to false one pixel per point scale is used --- src/cocoa/i_backend_cocoa.mm | 160 ++++++++++++++++++++++++----------- src/cocoa/i_rbopts.h | 2 + 2 files changed, 111 insertions(+), 51 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index b8b2babeb..dfb357740 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -86,13 +86,15 @@ - (NSRect)convertRectToBacking:(NSRect)aRect; @end -#endif // NSAppKitVersionNumber10_7 +#endif // !NSAppKitVersionNumber10_7 // --------------------------------------------------------------------------- RenderBufferOptions rbOpts; +EXTERN_CVAR(Bool, vid_hidpi) + CVAR(Bool, use_mouse, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Bool, m_filter, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -585,7 +587,7 @@ NSSize GetRealContentViewSize(const NSWindow* const window) // TODO: figure out why [NSView frame] returns different values in "fullscreen" and in window // In "fullscreen" the result is multiplied by [NSScreen backingScaleFactor], but not in window - return (IsHiDPISupported() && NSNormalWindowLevel == [window level]) + return (vid_hidpi && NSNormalWindowLevel == [window level]) ? [view convertSizeToBacking:frameSize] : frameSize; } @@ -840,10 +842,16 @@ void ProcessMouseWheelEvent(NSEvent* theEvent) #endif { @private - FullscreenWindow* m_window; - - bool m_openGLInitialized; + FullscreenWindow* m_window; + int m_multisample; + + int m_width; + int m_height; + bool m_fullscreen; + bool m_hiDPI; + + bool m_openGLInitialized; } - (id)init; @@ -864,7 +872,8 @@ void ProcessMouseWheelEvent(NSEvent* theEvent) - (int)multisample; - (void)setMultisample:(int)multisample; -- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height; +- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height useHiDPI:(bool)hiDPI; +- (void)useHiDPI:(bool)hiDPI; - (void)processEvents:(NSTimer*)timer; @@ -883,9 +892,15 @@ static ApplicationDelegate* s_applicationDelegate; - (id)init { self = [super init]; - + + m_multisample = 0; + + m_width = -1; + m_height = -1; + m_fullscreen = false; + m_hiDPI = false; + m_openGLInitialized = false; - m_multisample = 0; return self; } @@ -1052,35 +1067,30 @@ static ApplicationDelegate* s_applicationDelegate; pixelFormat:pixelFormat]; [[glView openGLContext] makeCurrentContext]; - if (IsHiDPISupported()) - { - [glView setWantsBestResolutionOpenGLSurface:YES]; - } - [m_window setContentView:glView]; m_openGLInitialized = true; } -- (void)fullscreenWithWidth:(int)width height:(int)height +- (void)switchToFullscreen { NSScreen* screen = [m_window screen]; const NSRect screenFrame = [screen frame]; - const NSRect displayRect = IsHiDPISupported() + const NSRect displayRect = vid_hidpi ? [screen convertRectToBacking:screenFrame] : screenFrame; const float displayWidth = displayRect.size.width; const float displayHeight = displayRect.size.height; - const float pixelScaleFactorX = displayWidth / static_cast< float >(width ); - const float pixelScaleFactorY = displayHeight / static_cast< float >(height); + const float pixelScaleFactorX = displayWidth / static_cast(m_width ); + const float pixelScaleFactorY = displayHeight / static_cast(m_height); rbOpts.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); - rbOpts.width = width * rbOpts.pixelScale; - rbOpts.height = height * rbOpts.pixelScale; + rbOpts.width = m_width * rbOpts.pixelScale; + rbOpts.height = m_height * rbOpts.pixelScale; rbOpts.shiftX = (displayWidth - rbOpts.width ) / 2.0f; rbOpts.shiftY = (displayHeight - rbOpts.height) / 2.0f; @@ -1092,18 +1102,18 @@ static ApplicationDelegate* s_applicationDelegate; [m_window setFrameOrigin:NSMakePoint(0.0f, 0.0f)]; } -- (void)windowedWithWidth:(int)width height:(int)height +- (void)switchToWindowed { rbOpts.pixelScale = 1.0f; - rbOpts.width = static_cast< float >(width ); - rbOpts.height = static_cast< float >(height); + rbOpts.width = static_cast(m_width ); + rbOpts.height = static_cast(m_height); rbOpts.shiftX = 0.0f; rbOpts.shiftY = 0.0f; - const NSSize windowPixelSize = NSMakeSize(width, height); - const NSSize windowSize = IsHiDPISupported() + const NSSize windowPixelSize = NSMakeSize(m_width, m_height); + const NSSize windowSize = vid_hidpi ? [[m_window contentView] convertSizeFromBacking:windowPixelSize] : windowPixelSize; @@ -1114,19 +1124,40 @@ static ApplicationDelegate* s_applicationDelegate; [m_window center]; } -- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height +- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height useHiDPI:(bool)hiDPI { - [self initializeOpenGL]; - - if (fullscreen) + if (fullscreen == m_fullscreen + && width == m_width + && height == m_height + && hiDPI == m_hiDPI) { - [self fullscreenWithWidth:width height:height]; + return; + } + + m_fullscreen = fullscreen; + m_width = width; + m_height = height; + m_hiDPI = hiDPI; + + [self initializeOpenGL]; + + if (IsHiDPISupported()) + { + NSOpenGLView* const glView = [m_window contentView]; + [glView setWantsBestResolutionOpenGLSurface:m_hiDPI]; + } + + if (m_fullscreen) + { + [self switchToFullscreen]; } else { - [self windowedWithWidth:width height:height]; + [self switchToWindowed]; } + rbOpts.dirty = true; + const NSSize viewSize = GetRealContentViewSize(m_window); glViewport(0, 0, viewSize.width, viewSize.height); @@ -1145,6 +1176,19 @@ static ApplicationDelegate* s_applicationDelegate; } } +- (void)useHiDPI:(bool)hiDPI +{ + if (!m_openGLInitialized) + { + return; + } + + [self changeVideoResolution:m_fullscreen + width:m_width + height:m_height + useHiDPI:hiDPI]; +} + - (void)processEvents:(NSTimer*)timer { @@ -1230,6 +1274,22 @@ static ApplicationDelegate* s_applicationDelegate; // --------------------------------------------------------------------------- +CUSTOM_CVAR(Bool, vid_hidpi, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (IsHiDPISupported()) + { + [s_applicationDelegate useHiDPI:self]; + } + else if (0 != self) + { + self = 0; + } +} + + +// --------------------------------------------------------------------------- + + void I_SetMainWindowVisible(bool visible) { [s_applicationDelegate setMainWindowVisible:visible]; @@ -1508,7 +1568,10 @@ static SDL_PixelFormat* GetPixelFormat() SDL_Surface* SDL_SetVideoMode(int width, int height, int, Uint32 flags) { - [s_applicationDelegate changeVideoResolution:(SDL_FULLSCREEN & flags) width:width height:height]; + [s_applicationDelegate changeVideoResolution:(SDL_FULLSCREEN & flags) + width:width + height:height + useHiDPI:vid_hidpi]; static SDL_Surface result; @@ -1554,21 +1617,6 @@ void SDL_WM_SetCaption(const char* title, const char* icon) // Window title is set in SDL_SetVideoMode() } -static void ResetSoftwareViewport() -{ - // For an unknown reason the following call to glClear() is needed - // to avoid drawing of garbage in fullscreen mode - // when game video resolution's aspect ratio is different from display one - - GLint viewport[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport); - - glViewport(0, 0, viewport[0], viewport[1]); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(rbOpts.shiftX, rbOpts.shiftY, rbOpts.width, rbOpts.height); -} - int SDL_WM_ToggleFullScreen(SDL_Surface* surface) { if (surface->flags & SDL_FULLSCREEN) @@ -1582,8 +1630,8 @@ int SDL_WM_ToggleFullScreen(SDL_Surface* surface) [s_applicationDelegate changeVideoResolution:(SDL_FULLSCREEN & surface->flags) width:surface->w - height:surface->h]; - ResetSoftwareViewport(); + height:surface->h + useHiDPI:vid_hidpi]; return 1; } @@ -1638,8 +1686,6 @@ static void SetupSoftwareRendering(SDL_Surface* screen) glLoadIdentity(); glOrtho(0.0, screen->w, screen->h, 0.0, -1.0, 1.0); - ResetSoftwareViewport(); - glEnable(GL_TEXTURE_2D); glGenTextures(1, &s_frameBufferTexture); @@ -1660,12 +1706,24 @@ int SDL_Flip(SDL_Surface* screen) { SetupSoftwareRendering(screen); } + + if (rbOpts.dirty) + { + glViewport(rbOpts.shiftX, rbOpts.shiftY, rbOpts.width, rbOpts.height); + + // TODO: Figure out why the following glClear() call is needed + // to avoid drawing of garbage in fullscreen mode when + // in-game's aspect ratio is different from display one + glClear(GL_COLOR_BUFFER_BIT); + + rbOpts.dirty = false; + } const int width = screen->w; const int height = screen->h; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, - width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, screen->pixels); + width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, screen->pixels); glBegin(GL_QUADS); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); diff --git a/src/cocoa/i_rbopts.h b/src/cocoa/i_rbopts.h index c1a9d9d4d..40a9ff17a 100644 --- a/src/cocoa/i_rbopts.h +++ b/src/cocoa/i_rbopts.h @@ -43,6 +43,8 @@ struct RenderBufferOptions float width; float height; + + bool dirty; }; extern RenderBufferOptions rbOpts; From b6f829979afeec70c6231317b99b7282f9811f0b Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 13 Sep 2014 11:16:23 +0300 Subject: [PATCH 23/75] Cleaned up software rendering internals in Cocoa back-end --- src/cocoa/i_backend_cocoa.mm | 103 +++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 46 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index dfb357740..cd7bea109 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -770,6 +770,9 @@ void ProcessMouseWheelEvent(NSEvent* theEvent) D_PostEvent(&event); } + +const Uint16 BYTES_PER_PIXEL = 4; + } // unnamed namespace @@ -844,6 +847,9 @@ void ProcessMouseWheelEvent(NSEvent* theEvent) @private FullscreenWindow* m_window; + uint8_t* m_softwareRenderingBuffer; + GLuint m_softwareRenderingTexture; + int m_multisample; int m_width; @@ -875,6 +881,9 @@ void ProcessMouseWheelEvent(NSEvent* theEvent) - (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height useHiDPI:(bool)hiDPI; - (void)useHiDPI:(bool)hiDPI; +- (void)setupSoftwareRenderingWithWidth:(int)width height:(int)height; +- (void*)softwareRenderingBuffer; + - (void)processEvents:(NSTimer*)timer; - (void)invalidateCursorRects; @@ -893,6 +902,11 @@ static ApplicationDelegate* s_applicationDelegate; { self = [super init]; + m_window = nil; + + m_softwareRenderingBuffer = NULL; + m_softwareRenderingTexture = 0; + m_multisample = 0; m_width = -1; @@ -907,6 +921,11 @@ static ApplicationDelegate* s_applicationDelegate; - (void)dealloc { + delete[] m_softwareRenderingBuffer; + + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &m_softwareRenderingTexture); + [m_window release]; [super dealloc]; @@ -1190,6 +1209,38 @@ static ApplicationDelegate* s_applicationDelegate; } +- (void)setupSoftwareRenderingWithWidth:(int)width height:(int)height +{ + if (0 == m_softwareRenderingTexture) + { + glEnable(GL_TEXTURE_2D); + + glGenTextures(1, &m_softwareRenderingTexture); + glBindTexture(GL_TEXTURE_2D, m_softwareRenderingTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + delete[] m_softwareRenderingBuffer; + m_softwareRenderingBuffer = new uint8_t[width * height * BYTES_PER_PIXEL]; + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, width, height, 0.0, -1.0, 1.0); +} + +- (void*)softwareRenderingBuffer +{ + return m_softwareRenderingBuffer; +} + + - (void)processEvents:(NSTimer*)timer { ZD_UNUSED(timer); @@ -1536,10 +1587,6 @@ int SDL_ShowCursor(int) } -static GLuint s_frameBufferTexture = 0; - -static const Uint16 BYTES_PER_PIXEL = 4; - static SDL_PixelFormat* GetPixelFormat() { static SDL_PixelFormat result; @@ -1574,30 +1621,19 @@ SDL_Surface* SDL_SetVideoMode(int width, int height, int, Uint32 flags) useHiDPI:vid_hidpi]; static SDL_Surface result; - - const bool isSoftwareRenderer = !(SDL_OPENGL & flags); - - if (isSoftwareRenderer) + + if (!(SDL_OPENGL & flags)) { - if (NULL != result.pixels) - { - free(result.pixels); - } - - if (0 != s_frameBufferTexture) - { - glBindTexture(GL_TEXTURE_2D, 0); - glDeleteTextures(1, &s_frameBufferTexture); - s_frameBufferTexture = 0; - } + [s_applicationDelegate setupSoftwareRenderingWithWidth:width + height:height]; } - + result.flags = flags; result.format = GetPixelFormat(); result.w = width; result.h = height; result.pitch = width * BYTES_PER_PIXEL; - result.pixels = isSoftwareRenderer ? malloc(width * height * BYTES_PER_PIXEL) : NULL; + result.pixels = [s_applicationDelegate softwareRenderingBuffer]; result.refcount = 1; result.clip_rect.x = 0; @@ -1678,35 +1714,10 @@ int SDL_BlitSurface(SDL_Surface* src, SDL_Rect* srcrect, SDL_Surface* dst, SDL_R } -static void SetupSoftwareRendering(SDL_Surface* screen) -{ - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0, screen->w, screen->h, 0.0, -1.0, 1.0); - - glEnable(GL_TEXTURE_2D); - - glGenTextures(1, &s_frameBufferTexture); - glBindTexture(GL_TEXTURE_2D, s_frameBufferTexture); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -} - int SDL_Flip(SDL_Surface* screen) { assert(NULL != screen); - if (0 == s_frameBufferTexture) - { - SetupSoftwareRendering(screen); - } - if (rbOpts.dirty) { glViewport(rbOpts.shiftX, rbOpts.shiftY, rbOpts.width, rbOpts.height); From 7286ee96263cacc0059b7c5607fb4c7439105e77 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 13 Sep 2014 11:23:38 +0300 Subject: [PATCH 24/75] Renamed application delegate to controller --- src/cocoa/i_backend_cocoa.mm | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index cd7bea109..32754a262 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -839,7 +839,7 @@ const Uint16 BYTES_PER_PIXEL = 4; // --------------------------------------------------------------------------- -@interface ApplicationDelegate : NSResponder +@interface ApplicationController : NSResponder #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 #endif @@ -893,10 +893,10 @@ const Uint16 BYTES_PER_PIXEL = 4; @end -static ApplicationDelegate* s_applicationDelegate; +static ApplicationController* appCtrl; -@implementation ApplicationDelegate +@implementation ApplicationController - (id)init { @@ -1329,7 +1329,7 @@ CUSTOM_CVAR(Bool, vid_hidpi, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) { if (IsHiDPISupported()) { - [s_applicationDelegate useHiDPI:self]; + [appCtrl useHiDPI:self]; } else if (0 != self) { @@ -1343,7 +1343,7 @@ CUSTOM_CVAR(Bool, vid_hidpi, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) void I_SetMainWindowVisible(bool visible) { - [s_applicationDelegate setMainWindowVisible:visible]; + [appCtrl setMainWindowVisible:visible]; SetNativeMouse(!visible); } @@ -1405,7 +1405,7 @@ bool I_SetCursor(FTexture* cursorpic) hotSpot:NSMakePoint(0.0f, 0.0f)]; } - [s_applicationDelegate invalidateCursorRects]; + [appCtrl invalidateCursorRects]; return true; } @@ -1492,13 +1492,13 @@ int SDL_Init(Uint32 flags) void SDL_Quit() { - if (NULL != s_applicationDelegate) + if (NULL != appCtrl) { [NSApp setDelegate:nil]; [NSApp deactivate]; - [s_applicationDelegate release]; - s_applicationDelegate = NULL; + [appCtrl release]; + appCtrl = NULL; } } @@ -1615,17 +1615,17 @@ static SDL_PixelFormat* GetPixelFormat() SDL_Surface* SDL_SetVideoMode(int width, int height, int, Uint32 flags) { - [s_applicationDelegate changeVideoResolution:(SDL_FULLSCREEN & flags) - width:width - height:height - useHiDPI:vid_hidpi]; - + [appCtrl changeVideoResolution:(SDL_FULLSCREEN & flags) + width:width + height:height + useHiDPI:vid_hidpi]; + static SDL_Surface result; if (!(SDL_OPENGL & flags)) { - [s_applicationDelegate setupSoftwareRenderingWithWidth:width - height:height]; + [appCtrl setupSoftwareRenderingWithWidth:width + height:height]; } result.flags = flags; @@ -1633,7 +1633,7 @@ SDL_Surface* SDL_SetVideoMode(int width, int height, int, Uint32 flags) result.w = width; result.h = height; result.pitch = width * BYTES_PER_PIXEL; - result.pixels = [s_applicationDelegate softwareRenderingBuffer]; + result.pixels = [appCtrl softwareRenderingBuffer]; result.refcount = 1; result.clip_rect.x = 0; @@ -1664,10 +1664,10 @@ int SDL_WM_ToggleFullScreen(SDL_Surface* surface) surface->flags |= SDL_FULLSCREEN; } - [s_applicationDelegate changeVideoResolution:(SDL_FULLSCREEN & surface->flags) - width:surface->w - height:surface->h - useHiDPI:vid_hidpi]; + [appCtrl changeVideoResolution:(SDL_FULLSCREEN & surface->flags) + width:surface->w + height:surface->h + useHiDPI:vid_hidpi]; return 1; } @@ -1682,7 +1682,7 @@ int SDL_GL_SetAttribute(SDL_GLattr attr, int value) { if (SDL_GL_MULTISAMPLESAMPLES == attr) { - [s_applicationDelegate setMultisample:value]; + [appCtrl setMultisample:value]; } // Not interested in other attributes @@ -1832,8 +1832,8 @@ int main(int argc, char** argv) [NSApplication sharedApplication]; [NSBundle loadNibNamed:@"zdoom" owner:NSApp]; - s_applicationDelegate = [ApplicationDelegate new]; - [NSApp setDelegate:s_applicationDelegate]; + appCtrl = [ApplicationController new]; + [NSApp setDelegate:appCtrl]; [NSApp run]; From 87472bf002128a1c3ee2fe1355535b9356c5188b Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 13 Sep 2014 11:44:24 +0300 Subject: [PATCH 25/75] Use the same binary architecture to restart from IWAD picker --- src/sdl/iwadpicker_cocoa.mm | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index 92eb47e40..51e0ea0c8 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -376,6 +376,19 @@ static NSArray* GetKnownExtensions() EXTERN_CVAR(String, defaultiwad) +static NSString* GetArchitectureString() +{ +#ifdef __i386__ + return @"i386"; +#elif defined __x86_64__ + return @"x86_64"; +#elif defined __ppc__ + return @"ppc"; +#elif defined __ppc64__ + return @"ppc64"; +#endif +} + static void RestartWithParameters(const char* iwadPath, NSString* parameters) { assert(nil != parameters); @@ -393,8 +406,12 @@ static void RestartWithParameters(const char* iwadPath, NSString* parameters) assert(commandLineParametersCount > 0); NSString* executablePath = [NSString stringWithUTF8String:Args->GetArg(0)]; + NSString* architecture = GetArchitectureString(); - NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:commandLineParametersCount + 3]; + NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:commandLineParametersCount + 6]; + [arguments addObject:@"-arch"]; + [arguments addObject:architecture]; + [arguments addObject:executablePath]; [arguments addObject:@"-wad_picker_restart"]; [arguments addObject:@"-iwad"]; [arguments addObject:[NSString stringWithUTF8String:iwadPath]]; @@ -419,7 +436,7 @@ static void RestartWithParameters(const char* iwadPath, NSString* parameters) wordfree(&expansion); } - [NSTask launchedTaskWithLaunchPath:executablePath arguments:arguments]; + [NSTask launchedTaskWithLaunchPath:@"/usr/bin/arch" arguments:arguments]; _exit(0); // to avoid atexit()'s functions } From 1e3e94f8ad6073c6c148d7719668905eb668fd58 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 13 Sep 2014 15:46:32 +0300 Subject: [PATCH 26/75] Fixed crash Apple's HID Utilities in x86_64 build Only a small part of HID Utilities version 5.3, 2014-02-28 was merged in because of various issues it has https://developer.apple.com/library/mac/samplecode/HID_Config_Save/ --- src/cocoa/IOHIDDevice_.c | 194 ++++++++++++++++++++++++++------------- src/cocoa/IOHIDDevice_.h | 44 ++++----- 2 files changed, 153 insertions(+), 85 deletions(-) diff --git a/src/cocoa/IOHIDDevice_.c b/src/cocoa/IOHIDDevice_.c index c1e2d12cf..ab35c21e0 100755 --- a/src/cocoa/IOHIDDevice_.c +++ b/src/cocoa/IOHIDDevice_.c @@ -1,6 +1,6 @@ // File: IOHIDDevice_.c // Abstract: convieance functions for IOHIDDeviceGetProperty -// Version: 2.0 +// Version: 2.0 + 5.3 // // Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple // Inc. ("Apple") in consideration of your agreement to the following @@ -59,8 +59,17 @@ #pragma mark - local (static) function prototypes //----------------------------------------------------- -static Boolean IOHIDDevice_GetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long *outValue); -static void IOHIDDevice_SetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long inValue); +static Boolean IOHIDDevice_GetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, + CFStringRef inKey, + uint32_t * outValue); +// static void IOHIDDevice_SetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, uint32_t inValue); + +static Boolean IOHIDDevice_GetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, + CFStringRef inKey, + void ** outValue); +static void IOHIDDevice_SetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, + CFStringRef inKey, + void * inValue); //***************************************************** #pragma mark - exported globals @@ -120,12 +129,12 @@ CFStringRef IOHIDDevice_GetTransport(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the vendor ID for this device +// Returns: uint32_t - the vendor ID for this device // -long IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey), &result); +uint32_t IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey), &result); return (result); } // IOHIDDevice_GetVendorID @@ -137,12 +146,12 @@ long IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the VendorIDSource for this device +// Returns: uint32_t - the VendorIDSource for this device // -long IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDSourceKey), &result); +uint32_t IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDSourceKey), &result); return (result); } // IOHIDDevice_GetVendorIDSource @@ -154,12 +163,12 @@ long IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the product ID for this device +// Returns: uint32_t - the product ID for this device // -long IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey), &result); +uint32_t IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey), &result); return (result); } // IOHIDDevice_GetProductID @@ -171,12 +180,12 @@ long IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: CFStringRef - the VersionNumber for this device +// Returns: uint32_t - the VersionNumber for this device // -long IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVersionNumberKey), &result); +uint32_t IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDVersionNumberKey), &result); return (result); } // IOHIDDevice_GetVersionNumber @@ -236,12 +245,12 @@ CFStringRef IOHIDDevice_GetSerialNumber(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: CFStringRef - the CountryCode for this device +// Returns: uint32_t - the CountryCode for this device // -long IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDCountryCodeKey), &result); +uint32_t IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDCountryCodeKey), &result); return (result); } // IOHIDDevice_GetCountryCode @@ -253,12 +262,12 @@ long IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the location ID for this device +// Returns: uint32_t - the location ID for this device // -long IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey), &result); +uint32_t IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey), &result); return (result); } // IOHIDDevice_GetLocationID @@ -275,7 +284,7 @@ long IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef) { uint32_t IOHIDDevice_GetUsage(IOHIDDeviceRef inIOHIDDeviceRef) { uint32_t result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsageKey), (long *) &result); + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsageKey), &result); return (result); } // IOHIDDevice_GetUsage @@ -291,8 +300,8 @@ uint32_t IOHIDDevice_GetUsage(IOHIDDeviceRef inIOHIDDeviceRef) { // uint32_t IOHIDDevice_GetUsagePage(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsagePageKey), &result); + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsagePageKey), &result); return (result); } // IOHIDDevice_GetUsagePage @@ -320,12 +329,12 @@ CFArrayRef IOHIDDevice_GetUsagePairs(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: CFStringRef - the PrimaryUsage for this device +// Returns: uint32_t - the PrimaryUsage for this device // uint32_t IOHIDDevice_GetPrimaryUsage(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey), &result); + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey), &result); return (result); } // IOHIDDevice_GetPrimaryUsage @@ -337,12 +346,12 @@ uint32_t IOHIDDevice_GetPrimaryUsage(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: CFStringRef - the PrimaryUsagePage for this device +// Returns: uint32_t - the PrimaryUsagePage for this device // uint32_t IOHIDDevice_GetPrimaryUsagePage(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey), &result); + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey), &result); return (result); } // IOHIDDevice_GetPrimaryUsagePage @@ -354,12 +363,12 @@ uint32_t IOHIDDevice_GetPrimaryUsagePage(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the MaxInputReportSize for this device +// Returns: uint32_t - the MaxInputReportSize for this device // -long IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDMaxInputReportSizeKey), &result); +uint32_t IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDMaxInputReportSizeKey), &result); return (result); } // IOHIDDevice_GetMaxInputReportSize @@ -371,12 +380,12 @@ long IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the MaxOutput for this device +// Returns: uint32_t - the MaxOutput for this device // -long IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDMaxOutputReportSizeKey), &result); +uint32_t IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDMaxOutputReportSizeKey), &result); return (result); } // IOHIDDevice_GetMaxOutputReportSize @@ -388,12 +397,12 @@ long IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the MaxFeatureReportSize for this device +// Returns: uint32_t - the MaxFeatureReportSize for this device // -long IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDMaxFeatureReportSizeKey), &result); +uint32_t IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDMaxFeatureReportSizeKey), &result); return (result); } // IOHIDDevice_GetMaxFeatureReportSize @@ -405,14 +414,14 @@ long IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // -// Returns: long - the ReportInterval for this device +// Returns: uint32_t - the ReportInterval for this device // #ifndef kIOHIDReportIntervalKey #define kIOHIDReportIntervalKey "ReportInterval" #endif -long IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef) { - long result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDReportIntervalKey), &result); +uint32_t IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDReportIntervalKey), &result); return (result); } // IOHIDDevice_GetReportInterval @@ -429,7 +438,7 @@ long IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef) { IOHIDQueueRef IOHIDDevice_GetQueue(IOHIDDeviceRef inIOHIDDeviceRef) { IOHIDQueueRef result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), (long *) &result); + (void) IOHIDDevice_GetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), (void *) &result); if ( result ) { assert( IOHIDQueueGetTypeID() == CFGetTypeID(result) ); } @@ -450,7 +459,7 @@ IOHIDQueueRef IOHIDDevice_GetQueue(IOHIDDeviceRef inIOHIDDeviceRef) { // void IOHIDDevice_SetQueue(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDQueueRef inQueueRef) { - IOHIDDevice_SetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), (long) inQueueRef); + IOHIDDevice_SetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), inQueueRef); } //************************************************************************* @@ -466,7 +475,7 @@ void IOHIDDevice_SetQueue(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDQueueRef inQueue IOHIDTransactionRef IOHIDDevice_GetTransaction(IOHIDDeviceRef inIOHIDDeviceRef) { IOHIDTransactionRef result = 0; - (void) IOHIDDevice_GetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), (long *) &result); + (void) IOHIDDevice_GetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), (void *) &result); return (result); } // IOHIDDevice_GetTransaction @@ -483,7 +492,7 @@ IOHIDTransactionRef IOHIDDevice_GetTransaction(IOHIDDeviceRef inIOHIDDeviceRef) // void IOHIDDevice_SetTransaction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDTransactionRef inTransactionRef) { - IOHIDDevice_SetLongProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), (long) inTransactionRef); + IOHIDDevice_SetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), inTransactionRef); } //***************************************************** @@ -492,9 +501,9 @@ void IOHIDDevice_SetTransaction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDTransactio //************************************************************************* // -// IOHIDDevice_GetLongProperty( inIOHIDDeviceRef, inKey, outValue ) +// IOHIDDevice_GetUInt32Property( inIOHIDDeviceRef, inKey, outValue ) // -// Purpose: convieance function to return a long property of a device +// Purpose: convieance function to return a uint32_t property of a device // // Inputs: inIOHIDDeviceRef - the device // inKey - CFString for the @@ -503,7 +512,7 @@ void IOHIDDevice_SetTransaction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDTransactio // outValue - the device // -static Boolean IOHIDDevice_GetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long *outValue) { +static Boolean IOHIDDevice_GetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, uint32_t *outValue) { Boolean result = FALSE; if ( inIOHIDDeviceRef ) { assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); @@ -519,11 +528,66 @@ static Boolean IOHIDDevice_GetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFSt } return (result); -} // IOHIDDevice_GetLongProperty +} // IOHIDDevice_GetUInt32Property //************************************************************************* // -// IOHIDDevice_SetLongProperty( inIOHIDDeviceRef, inKey, inValue ) +// IOHIDDevice_SetUInt32Property( inIOHIDDeviceRef, inKey, inValue ) +// +// Purpose: convieance function to set a long property of an Device +// +// Inputs: inIOHIDDeviceRef - the Device +// inKey - CFString for the key +// inValue - the value to set it to +// Returns: nothing +// +#if 0 // unused +static void IOHIDDevice_SetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, uint32_t inValue) { + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); + if ( tCFNumberRef ) { + IOHIDDeviceSetProperty(inIOHIDDeviceRef, inKey, tCFNumberRef); + CFRelease(tCFNumberRef); + } +} // IOHIDDevice_SetUInt32Property +#endif +//************************************************************************* +// +// IOHIDDevice_GetPtrProperty( inIOHIDDeviceRef, inKey, outValue ) +// +// Purpose: convieance function to return a pointer property of a device +// +// Inputs: inIOHIDDeviceRef - the device +// inKey - CFString for the +// outValue - address where to restore the element +// Returns: the action cookie +// outValue - the device +// + +static Boolean IOHIDDevice_GetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, void **outValue) { + Boolean result = FALSE; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, inKey); + if ( tCFTypeRef ) { + // if this is a number + if ( CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef) ) { + // get it's value +#ifdef __LP64__ + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt64Type, outValue ); +#else + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, outValue ); +#endif // ifdef __LP64__ + } + } + } + + return (result); +} // IOHIDDevice_GetPtrProperty + +//************************************************************************* +// +// IOHIDDevice_SetPtrProperty( inIOHIDDeviceRef, inKey, inValue ) // // Purpose: convieance function to set a long property of an Device // @@ -533,12 +597,16 @@ static Boolean IOHIDDevice_GetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFSt // Returns: nothing // -static void IOHIDDevice_SetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long inValue) { +static void IOHIDDevice_SetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, void *inValue) { +#ifdef __LP64__ + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &inValue); +#else CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); +#endif // ifdef __LP64__ if ( tCFNumberRef ) { IOHIDDeviceSetProperty(inIOHIDDeviceRef, inKey, tCFNumberRef); CFRelease(tCFNumberRef); } -} // IOHIDDevice_SetLongProperty +} // IOHIDDevice_SetPtrProperty //***************************************************** diff --git a/src/cocoa/IOHIDDevice_.h b/src/cocoa/IOHIDDevice_.h index 739352844..eebccd667 100755 --- a/src/cocoa/IOHIDDevice_.h +++ b/src/cocoa/IOHIDDevice_.h @@ -1,6 +1,6 @@ // File: IOHIDDevice_.h // Abstract: convieance functions for IOHIDDeviceGetProperty -// Version: 2.0 +// Version: 2.0 + 5.3 // // Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple // Inc. ("Apple") in consideration of your agreement to the following @@ -40,7 +40,7 @@ // STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // -// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// Copyright (C) 2014 Apple Inc. All Rights Reserved. // //***************************************************** #ifndef __IOHIDDevice__ @@ -120,10 +120,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the vendor ID for this device + // Returns: uint32_t - the vendor ID for this device // - extern long IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -133,10 +133,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the VendorIDSource for this device + // Returns: uint32_t - the VendorIDSource for this device // - extern long IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -146,10 +146,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the product ID for this device + // Returns: uint32_t - the product ID for this device // - extern long IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -159,10 +159,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the VersionNumber for this device + // Returns: uint32_t - the VersionNumber for this device // - extern long IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -211,10 +211,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the CountryCode for this device + // Returns: uint32_t - the CountryCode for this device // - extern long IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -224,10 +224,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the location ID for this device + // Returns: uint32_t - the location ID for this device // - extern long IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -302,10 +302,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the MaxInputReportSize for this device + // Returns: uint32_t - the MaxInputReportSize for this device // - extern long IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -315,10 +315,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the MaxOutputReportSize for this device + // Returns: uint32_t - the MaxOutputReportSize for this device // - extern long IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -328,10 +328,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the MaxFeatureReportSize for this device + // Returns: uint32_t - the MaxFeatureReportSize for this device // - extern long IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // @@ -341,10 +341,10 @@ extern "C" { // // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device // - // Returns: long - the ReportInterval for this device + // Returns: uint32_t - the ReportInterval for this device // - extern long IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef); + extern uint32_t IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef); //************************************************************************* // From 1529ff7730858118e1315885dc854013ce62e0e5 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 14 Sep 2014 09:51:55 +0300 Subject: [PATCH 27/75] Fixed compilation warning in HID name formatting --- src/cocoa/i_joystick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cocoa/i_joystick.cpp b/src/cocoa/i_joystick.cpp index add1b1bd7..5a1c92e8c 100644 --- a/src/cocoa/i_joystick.cpp +++ b/src/cocoa/i_joystick.cpp @@ -376,7 +376,7 @@ FString IOKitJoystick::GetIdentifier() { char identifier[ 32 ] = {0}; - snprintf( identifier, sizeof( identifier ), "VID_%04lx_PID_%04lx", + snprintf( identifier, sizeof( identifier ), "VID_%04x_PID_%04x", IOHIDDevice_GetVendorID( m_device ), IOHIDDevice_GetProductID( m_device ) ); return FString( identifier ); From e8ca4fa94e9c92306b4da2566ee460d948df3029 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 20 Sep 2014 11:55:49 +0300 Subject: [PATCH 28/75] Applied fixes for timer to its thread-based implementation --- src/cocoa/i_timer.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/cocoa/i_timer.cpp b/src/cocoa/i_timer.cpp index 29d3f8865..db6811212 100644 --- a/src/cocoa/i_timer.cpp +++ b/src/cocoa/i_timer.cpp @@ -68,10 +68,7 @@ bool s_timerInitialized; bool s_timerExitRequested; uint32_t s_ticStart; -uint32_t s_ticNext; - uint32_t s_timerStart; -uint32_t s_timerNext; int s_tics; @@ -99,7 +96,6 @@ void* TimerThreadFunc(void*) } s_timerStart = SDL_GetTicks(); - s_timerNext = Scale(Scale(s_timerStart, TICRATE, 1000) + 1, 1000, TICRATE); pthread_cond_broadcast(&s_timerEvent); pthread_mutex_unlock(&s_timerMutex); @@ -113,7 +109,6 @@ int GetTimeThreaded(bool saveMS) if (saveMS) { s_ticStart = s_timerStart; - s_ticNext = s_timerNext; } return s_tics; @@ -147,14 +142,12 @@ fixed_t I_GetTimeFrac(uint32* ms) if (NULL != ms) { - *ms = s_ticNext; + *ms = s_ticStart + 1000 / TICRATE; } - const uint32_t step = s_ticNext - s_ticStart; - - return 0 == step + return 0 == s_ticStart ? FRACUNIT - : clamp( (now - s_ticStart) * FRACUNIT / step, 0, FRACUNIT); + : clamp( (now - s_ticStart) * FRACUNIT * TICRATE / 1000, 0, FRACUNIT); } From 49dca4d9cba994150e49b8b07fdf617a354b1f79 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 20 Sep 2014 11:56:15 +0300 Subject: [PATCH 29/75] Native Cocoa back-end is enabled by default --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ed4d0f00..3cfb73c26 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,7 +26,7 @@ endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) option( DYN_FLUIDSYNTH "Dynamically load fluidsynth" ON ) -option( OSX_COCOA_BACKEND "Use native Cocoa backend instead of SDL" OFF ) +option( OSX_COCOA_BACKEND "Use native Cocoa backend instead of SDL" ON ) if( CMAKE_SIZEOF_VOID_P MATCHES "8" ) set( X64 64 ) From b2110db1f5ff6fdc93113ea48243a22ee53dac88 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 18 Oct 2014 14:55:27 +0300 Subject: [PATCH 30/75] Extended list of display resolutions --- src/cocoa/i_backend_cocoa.mm | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 32754a262..ffd95b5b3 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1541,36 +1541,52 @@ SDL_Rect** SDL_ListModes(SDL_PixelFormat* format, Uint32 flags) ZD_UNUSED(format); ZD_UNUSED(flags); - static std::vector< SDL_Rect* > resolutions; + static std::vector resolutions; if (resolutions.empty()) { -#define DEFINE_RESOLUTION(WIDTH, HEIGHT) \ +#define DEFINE_RESOLUTION(WIDTH, HEIGHT) \ static SDL_Rect resolution_##WIDTH##_##HEIGHT = { 0, 0, WIDTH, HEIGHT }; \ resolutions.push_back(&resolution_##WIDTH##_##HEIGHT); - DEFINE_RESOLUTION(640, 480); - DEFINE_RESOLUTION(720, 480); - DEFINE_RESOLUTION(800, 600); + DEFINE_RESOLUTION( 640, 480); + DEFINE_RESOLUTION( 720, 480); + DEFINE_RESOLUTION( 800, 480); + DEFINE_RESOLUTION( 800, 600); + DEFINE_RESOLUTION(1024, 600); DEFINE_RESOLUTION(1024, 640); DEFINE_RESOLUTION(1024, 768); DEFINE_RESOLUTION(1152, 720); + DEFINE_RESOLUTION(1152, 864); DEFINE_RESOLUTION(1280, 720); + DEFINE_RESOLUTION(1280, 768); DEFINE_RESOLUTION(1280, 800); + DEFINE_RESOLUTION(1280, 854); DEFINE_RESOLUTION(1280, 960); DEFINE_RESOLUTION(1280, 1024); DEFINE_RESOLUTION(1366, 768); DEFINE_RESOLUTION(1400, 1050); DEFINE_RESOLUTION(1440, 900); + DEFINE_RESOLUTION(1440, 960); + DEFINE_RESOLUTION(1440, 1080); DEFINE_RESOLUTION(1600, 900); DEFINE_RESOLUTION(1600, 1200); DEFINE_RESOLUTION(1680, 1050); DEFINE_RESOLUTION(1920, 1080); DEFINE_RESOLUTION(1920, 1200); + DEFINE_RESOLUTION(2048, 1080); DEFINE_RESOLUTION(2048, 1536); + DEFINE_RESOLUTION(2560, 1080); DEFINE_RESOLUTION(2560, 1440); DEFINE_RESOLUTION(2560, 1600); + DEFINE_RESOLUTION(2560, 2048); DEFINE_RESOLUTION(2880, 1800); + DEFINE_RESOLUTION(3200, 1800); + DEFINE_RESOLUTION(3440, 1440); + DEFINE_RESOLUTION(3840, 2160); + DEFINE_RESOLUTION(3840, 2400); + DEFINE_RESOLUTION(4096, 2160); + DEFINE_RESOLUTION(5120, 2880); #undef DEFINE_RESOLUTION From 0569acd5d6550788eca06e80e40432d13785c205 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 2 Nov 2014 10:49:50 +0200 Subject: [PATCH 31/75] Fixed compilation issues with GCC 4.0.1 PowerPC --- src/cocoa/i_backend_cocoa.mm | 1 + src/cocoa/i_timer.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index ffd95b5b3..d2c8b3093 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -41,6 +41,7 @@ #include #include +#include #include #include diff --git a/src/cocoa/i_timer.cpp b/src/cocoa/i_timer.cpp index db6811212..0699a9967 100644 --- a/src/cocoa/i_timer.cpp +++ b/src/cocoa/i_timer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -92,7 +93,12 @@ void* TimerThreadFunc(void*) if (!g_isTicFrozen) { - __sync_add_and_fetch(&s_tics, 1); + // The following GCC/Clang intrinsic can be used instead of OS X specific function: + // __sync_add_and_fetch(&s_tics, 1); + // Although it's not supported on all platform/compiler combination, + // e.g. GCC 4.0.1 with PowerPC target architecture + + OSAtomicIncrement32(&s_tics); } s_timerStart = SDL_GetTicks(); From d8be168474e47a8a9060a8a9bad920d0894c00cf Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 2 Nov 2014 11:32:09 +0200 Subject: [PATCH 32/75] Set opaque clear color --- src/cocoa/i_backend_cocoa.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index d2c8b3093..ff602cb9d 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1181,7 +1181,7 @@ static ApplicationController* appCtrl; const NSSize viewSize = GetRealContentViewSize(m_window); glViewport(0, 0, viewSize.width, viewSize.height); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); CGLFlushDrawable(CGLGetCurrentContext()); From 2f59a8792af1c08dfc0acac424e45ab9ab0feb89 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 2 Nov 2014 15:50:43 +0200 Subject: [PATCH 33/75] Use rectangle texture for render buffer Game is rendered correctly even if hardware doesn't support non-power of two textures The ARB_texture_rectangle extension is supported on almost all Macs running 10.4 Tiger --- src/cocoa/i_backend_cocoa.mm | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index ff602cb9d..873b34c03 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -43,6 +43,7 @@ #include #include #include +#include #include @@ -1214,16 +1215,16 @@ static ApplicationController* appCtrl; { if (0 == m_softwareRenderingTexture) { - glEnable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_RECTANGLE_ARB); glGenTextures(1, &m_softwareRenderingTexture); - glBindTexture(GL_TEXTURE_2D, m_softwareRenderingTexture); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_softwareRenderingTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } delete[] m_softwareRenderingBuffer; @@ -1750,18 +1751,18 @@ int SDL_Flip(SDL_Surface* screen) const int width = screen->w; const int height = screen->h; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, screen->pixels); glBegin(GL_QUADS); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f); - glTexCoord2f(1.0f, 0.0f); + glTexCoord2f(width, 0.0f); glVertex2f(width, 0.0f); - glTexCoord2f(1.0f, 1.0f); + glTexCoord2f(width, height); glVertex2f(width, height); - glTexCoord2f(0.0f, 1.0f); + glTexCoord2f(0.0f, height); glVertex2f(0.0f, height); glEnd(); From c0ec1d5114693c4053f091882af8b675a18606d8 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 2 Nov 2014 15:56:22 +0200 Subject: [PATCH 34/75] Fixed color channels displacement on Big Endian targets --- src/cocoa/i_backend_cocoa.mm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 873b34c03..594f7fca6 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1750,7 +1750,13 @@ int SDL_Flip(SDL_Surface* screen) const int width = screen->w; const int height = screen->h; - + +#ifdef __LITTLE_ENDIAN__ + static const GLenum format = GL_RGBA; +#else // __BIG_ENDIAN__ + static const GLenum format = GL_ABGR_EXT; +#endif // __LITTLE_ENDIAN__ + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, screen->pixels); From eb98a999b25457e1c1f54d0bcd3e49ae4590c561 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 2 Nov 2014 16:18:28 +0200 Subject: [PATCH 35/75] Fixed color channels displacement on Big Endian targets, take two :( --- src/cocoa/i_backend_cocoa.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 594f7fca6..9c5f1eea7 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1758,7 +1758,7 @@ int SDL_Flip(SDL_Surface* screen) #endif // __LITTLE_ENDIAN__ glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, - width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, screen->pixels); + width, height, 0, format, GL_UNSIGNED_BYTE, screen->pixels); glBegin(GL_QUADS); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); From d2d6d2a7f8c1d501953d0ea4ab97d457c9c99d68 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 8 Nov 2014 13:30:41 +0200 Subject: [PATCH 36/75] Lowered minimum OS requirement to 10.5 Leopard --- src/CMakeLists.txt | 2 +- src/cocoa/i_backend_cocoa.mm | 187 ++++++++++++++++++++++++----------- 2 files changed, 132 insertions(+), 57 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a1630bed..ef252fc3a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1212,7 +1212,7 @@ endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) if( APPLE ) set_target_properties(zdoom PROPERTIES - LINK_FLAGS "-framework Cocoa -framework IOKit -framework OpenGL" + LINK_FLAGS "-framework Carbon -framework Cocoa -framework IOKit -framework OpenGL" MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cocoa/zdoom-info.plist" ) # Fix fmod link so that it can be found in the app bundle. diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 9c5f1eea7..c9ef6b9da 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -781,6 +781,19 @@ const Uint16 BYTES_PER_PIXEL = 4; // --------------------------------------------------------------------------- +namespace +{ + const NSInteger LEVEL_FULLSCREEN = NSMainMenuWindowLevel + 1; + const NSInteger LEVEL_WINDOWED = NSNormalWindowLevel; + + const NSUInteger STYLE_MASK_FULLSCREEN = NSBorderlessWindowMask; + const NSUInteger STYLE_MASK_WINDOWED = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask; +} + + +// --------------------------------------------------------------------------- + + @interface FullscreenWindow : NSWindow { @@ -788,27 +801,8 @@ const Uint16 BYTES_PER_PIXEL = 4; - (bool)canBecomeKeyWindow; -- (void)close; - -@end - - -@implementation FullscreenWindow - -- (bool)canBecomeKeyWindow -{ - return true; -} - - -- (void)close -{ - [super close]; - - I_ShutdownJoysticks(); - - [NSApp terminate:self]; -} +- (void)setLevel:(NSInteger)level; +- (void)setStyleMask:(NSUInteger)styleMask; @end @@ -892,12 +886,51 @@ const Uint16 BYTES_PER_PIXEL = 4; - (void)setMainWindowVisible:(bool)visible; +- (void)setWindowStyleMask:(NSUInteger)styleMask; + @end static ApplicationController* appCtrl; +// --------------------------------------------------------------------------- + + +@implementation FullscreenWindow + +- (bool)canBecomeKeyWindow +{ + return true; +} + +- (void)setLevel:(NSInteger)level +{ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + [super setLevel:level]; +#else // 10.5 or earlier + // Old Carbon-based way to make fullscreen window above dock and menu + // It's supported on 64-bit, but on 10.6 and later the following is preferred: + // [NSWindow setLevel:NSMainMenuWindowLevel + 1] + + const SystemUIMode mode = LEVEL_FULLSCREEN == level + ? kUIModeAllHidden + : kUIModeNormal; + SetSystemUIMode(mode, 0); +#endif // 10.6 or higher +} + +- (void)setStyleMask:(NSUInteger)styleMask +{ + [appCtrl setWindowStyleMask:styleMask]; +} + +@end + + +// --------------------------------------------------------------------------- + + @implementation ApplicationController - (id)init @@ -1040,6 +1073,23 @@ static ApplicationController* appCtrl; } +- (FullscreenWindow*)createWindow:(NSUInteger)styleMask +{ + FullscreenWindow* window = [[FullscreenWindow alloc] initWithContentRect:NSMakeRect(0, 0, 640, 480) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + [window setOpaque:YES]; + [window makeFirstResponder:self]; + [window setAcceptsMouseMovedEvents:YES]; + + NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton]; + [closeButton setAction:@selector(closeWindow:)]; + [closeButton setTarget:self]; + + return window; +} + - (void)initializeOpenGL { if (m_openGLInitialized) @@ -1047,15 +1097,7 @@ static ApplicationController* appCtrl; return; } - // Create window - - m_window = [[FullscreenWindow alloc] initWithContentRect:NSMakeRect(0, 0, 640, 480) - styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask - backing:NSBackingStoreBuffered - defer:NO]; - [m_window setOpaque:YES]; - [m_window makeFirstResponder:self]; - [m_window setAcceptsMouseMovedEvents:YES]; + m_window = [self createWindow:STYLE_MASK_WINDOWED]; // Create OpenGL context and view @@ -1093,7 +1135,7 @@ static ApplicationController* appCtrl; m_openGLInitialized = true; } -- (void)switchToFullscreen +- (void)setFullscreenModeWidth:(int)width height:(int)height { NSScreen* screen = [m_window screen]; @@ -1105,42 +1147,50 @@ static ApplicationController* appCtrl; const float displayWidth = displayRect.size.width; const float displayHeight = displayRect.size.height; - const float pixelScaleFactorX = displayWidth / static_cast(m_width ); - const float pixelScaleFactorY = displayHeight / static_cast(m_height); + const float pixelScaleFactorX = displayWidth / static_cast(width ); + const float pixelScaleFactorY = displayHeight / static_cast(height); rbOpts.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); - rbOpts.width = m_width * rbOpts.pixelScale; - rbOpts.height = m_height * rbOpts.pixelScale; + rbOpts.width = width * rbOpts.pixelScale; + rbOpts.height = height * rbOpts.pixelScale; rbOpts.shiftX = (displayWidth - rbOpts.width ) / 2.0f; rbOpts.shiftY = (displayHeight - rbOpts.height) / 2.0f; - [m_window setLevel:NSMainMenuWindowLevel + 1]; - [m_window setStyleMask:NSBorderlessWindowMask]; - [m_window setHidesOnDeactivate:YES]; + if (!m_fullscreen) + { + [m_window setLevel:LEVEL_FULLSCREEN]; + [m_window setStyleMask:STYLE_MASK_FULLSCREEN]; + [m_window setHidesOnDeactivate:YES]; + } + [m_window setFrame:displayRect display:YES]; [m_window setFrameOrigin:NSMakePoint(0.0f, 0.0f)]; } -- (void)switchToWindowed +- (void)setWindowedModeWidth:(int)width height:(int)height { rbOpts.pixelScale = 1.0f; - rbOpts.width = static_cast(m_width ); - rbOpts.height = static_cast(m_height); + rbOpts.width = static_cast(width ); + rbOpts.height = static_cast(height); rbOpts.shiftX = 0.0f; rbOpts.shiftY = 0.0f; - const NSSize windowPixelSize = NSMakeSize(m_width, m_height); + const NSSize windowPixelSize = NSMakeSize(width, height); const NSSize windowSize = vid_hidpi ? [[m_window contentView] convertSizeFromBacking:windowPixelSize] : windowPixelSize; - [m_window setLevel:NSNormalWindowLevel]; - [m_window setStyleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask]; - [m_window setHidesOnDeactivate:NO]; + if (m_fullscreen) + { + [m_window setLevel:LEVEL_WINDOWED]; + [m_window setStyleMask:STYLE_MASK_WINDOWED]; + [m_window setHidesOnDeactivate:NO]; + } + [m_window setContentSize:windowSize]; [m_window center]; } @@ -1155,33 +1205,28 @@ static ApplicationController* appCtrl; return; } - m_fullscreen = fullscreen; - m_width = width; - m_height = height; - m_hiDPI = hiDPI; - [self initializeOpenGL]; if (IsHiDPISupported()) { NSOpenGLView* const glView = [m_window contentView]; - [glView setWantsBestResolutionOpenGLSurface:m_hiDPI]; + [glView setWantsBestResolutionOpenGLSurface:hiDPI]; } - if (m_fullscreen) + if (fullscreen) { - [self switchToFullscreen]; + [self setFullscreenModeWidth:width height:height]; } else { - [self switchToWindowed]; + [self setWindowedModeWidth:width height:height]; } rbOpts.dirty = true; const NSSize viewSize = GetRealContentViewSize(m_window); - glViewport(0, 0, viewSize.width, viewSize.height); + glViewport(0, 0, static_cast(viewSize.width), static_cast(viewSize.height)); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); @@ -1194,7 +1239,12 @@ static ApplicationController* appCtrl; if (![m_window isKeyWindow]) { [m_window makeKeyAndOrderFront:nil]; - } + } + + m_fullscreen = fullscreen; + m_width = width; + m_height = height; + m_hiDPI = hiDPI; } - (void)useHiDPI:(bool)hiDPI @@ -1321,6 +1371,31 @@ static ApplicationController* appCtrl; } } + +- (void)setWindowStyleMask:(NSUInteger)styleMask +{ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + [m_window setStyleMask:styleMask]; +#else // 10.5 or earlier + // Before 10.6 it's impossible to change window's style mask + // To workaround this new window should be created with required style mask + + FullscreenWindow* tempWindow = [self createWindow:styleMask]; + [tempWindow setContentView:[m_window contentView]]; + + [m_window close]; + m_window = tempWindow; +#endif // 10.6 or higher +} + + +- (void)closeWindow:(id)sender +{ + I_ShutdownJoysticks(); + + [NSApp terminate:sender]; +} + @end From d061adfbd666857bf861739e4232e18e44295621 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 11:18:20 +0200 Subject: [PATCH 37/75] Fixed all compilation issues with 10.4 SDK except for HID Utilities --- src/cocoa/i_backend_cocoa.mm | 99 ++++++++++++++++++++++++++++++++---- src/sdl/iwadpicker_cocoa.mm | 6 +++ src/sdl/sdlvideo.cpp | 7 +++ 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index c9ef6b9da..859fcc495 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -45,6 +45,80 @@ #include #include +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + +// Missing definitions for 10.4 and earlier + +typedef unsigned int NSUInteger; +typedef int NSInteger; + +typedef float CGFloat; + +// From HIToolbox/Events.h +enum +{ + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +@interface NSView(SupportOutdatedOSX) +- (NSPoint)convertPointFromBase:(NSPoint)aPoint; +@end + +@implementation NSView(SupportOutdatedOSX) +- (NSPoint)convertPointFromBase:(NSPoint)aPoint +{ + return [self convertPoint:aPoint fromView:nil]; +} +@end + +#endif // prior to 10.5 + #include // Avoid collision between DObject class and Objective-C @@ -272,7 +346,7 @@ void I_ProcessJoysticks(); void I_GetEvent() { - [[NSRunLoop mainRunLoop] limitDateForMode:NSDefaultRunLoopMode]; + [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; } void I_StartTic() @@ -634,6 +708,7 @@ void ProcessMouseButtonEvent(NSEvent* theEvent) case NSLeftMouseUp: event.subtype = EV_GUI_LButtonUp; break; case NSRightMouseUp: event.subtype = EV_GUI_RButtonUp; break; case NSOtherMouseUp: event.subtype = EV_GUI_MButtonUp; break; + default: break; } NSEventToGameMousePosition(theEvent, &event); @@ -655,6 +730,9 @@ void ProcessMouseButtonEvent(NSEvent* theEvent) case NSOtherMouseUp: event.type = EV_KeyUp; break; + + default: + break; } event.data1 = std::min(KEY_MOUSE1 + [theEvent buttonNumber], NSInteger(KEY_MOUSE8)); @@ -1011,8 +1089,8 @@ static ApplicationController* appCtrl; selector:@selector(processEvents:) userInfo:nil repeats:YES]; - [[NSRunLoop mainRunLoop] addTimer:timer - forMode:NSDefaultRunLoopMode]; + [[NSRunLoop currentRunLoop] addTimer:timer + forMode:NSDefaultRunLoopMode]; exit(SDL_main(s_argc, s_argv)); } @@ -1106,22 +1184,22 @@ static ApplicationController* appCtrl; attributes[i++] = NSOpenGLPFADoubleBuffer; attributes[i++] = NSOpenGLPFAColorSize; - attributes[i++] = 32; + attributes[i++] = NSOpenGLPixelFormatAttribute(32); attributes[i++] = NSOpenGLPFADepthSize; - attributes[i++] = 24; + attributes[i++] = NSOpenGLPixelFormatAttribute(24); attributes[i++] = NSOpenGLPFAStencilSize; - attributes[i++] = 8; + attributes[i++] = NSOpenGLPixelFormatAttribute(8); if (m_multisample) { attributes[i++] = NSOpenGLPFAMultisample; attributes[i++] = NSOpenGLPFASampleBuffers; - attributes[i++] = 1; + attributes[i++] = NSOpenGLPixelFormatAttribute(1); attributes[i++] = NSOpenGLPFASamples; - attributes[i++] = m_multisample; + attributes[i++] = NSOpenGLPixelFormatAttribute(m_multisample); } - attributes[i] = 0; + attributes[i] = NSOpenGLPixelFormatAttribute(0); NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; @@ -1344,6 +1422,9 @@ static ApplicationController* appCtrl; case NSFlagsChanged: ProcessKeyboardFlagsEvent(event); break; + + default: + break; } [NSApp sendEvent:event]; diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index 51e0ea0c8..57a4ce7f4 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -48,6 +48,12 @@ #include #include +#include + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +// Missing type definition for 10.4 and earlier +typedef unsigned int NSUInteger; +#endif // prior to 10.5 CVAR(String, osx_additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG); diff --git a/src/sdl/sdlvideo.cpp b/src/sdl/sdlvideo.cpp index c869545ab..6753169f0 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/sdl/sdlvideo.cpp @@ -550,6 +550,13 @@ void SDLFB::SetVSync (bool vsync) { // Apply vsync for native backend only (where OpenGL context is set) +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + // Inconsistency between 10.4 and 10.5 SDKs: + // third argument of CGLSetParameter() is const long* on 10.4 and const GLint* on 10.5 + // So, GLint typedef'ed to long instead of int to workaround this issue + typedef long GLint; +#endif // prior to 10.5 + const GLint value = vsync ? 1 : 0; CGLSetParameter(context, kCGLCPSwapInterval, &value); } From a5993c4e2d3d57d4c4b71ba38e983f12d67a2ca1 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 11:58:47 +0200 Subject: [PATCH 38/75] Disabled usage of IOKit's HID Manager API on OS X prior to 10.5 Source code only, CMake needs to be updated too --- src/cocoa/i_joystick.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/cocoa/i_joystick.cpp b/src/cocoa/i_joystick.cpp index 5a1c92e8c..889c38fed 100644 --- a/src/cocoa/i_joystick.cpp +++ b/src/cocoa/i_joystick.cpp @@ -33,6 +33,8 @@ #include "m_joy.h" +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + #include "HID_Utilities_External.h" #include "d_event.h" @@ -774,3 +776,37 @@ void I_ProcessJoysticks() s_joystickManager->Update(); } } + +#else // prior to 10.5 + +void I_StartupJoysticks() +{ +} + +void I_ShutdownJoysticks() +{ +} + +void I_GetJoysticks(TArray& sticks) +{ + sticks.Clear(); +} + +void I_GetAxes(float axes[NUM_JOYAXIS]) +{ + for (size_t i = 0; i < NUM_JOYAXIS; ++i) + { + axes[i] = 0.0f; + } +} + +IJoystickConfig *I_UpdateDeviceList() +{ + return NULL; +} + +void I_ProcessJoysticks() +{ +} + +#endif // 10.5 or higher From fa1d62ffbf509a4231a58488a53676c7c2a0558e Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 15:23:34 +0200 Subject: [PATCH 39/75] Fixed random junk that may appear in transparency (alpha) channel of mouse cursor image --- src/cocoa/i_backend_cocoa.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 859fcc495..e2bd70374 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1539,6 +1539,8 @@ bool I_SetCursor(FTexture* cursorpic) // Load bitmap data to representation BYTE* buffer = [bitmapImageRep bitmapData]; + memset(buffer, 0, imagePitch * imageHeight); + FBitmap bitmap(buffer, imagePitch, imageWidth, imageHeight); cursorpic->CopyTrueColorPixels(&bitmap, 0, 0); From a40eb3443b2363606dd6d12e45e7ee8d19faf979 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 15:34:04 +0200 Subject: [PATCH 40/75] Fixed infinite recursion when setting window style on OS X 10.6 or newer --- src/cocoa/i_backend_cocoa.mm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index e2bd70374..4a029515e 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1000,7 +1000,11 @@ static ApplicationController* appCtrl; - (void)setStyleMask:(NSUInteger)styleMask { - [appCtrl setWindowStyleMask:styleMask]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + [super setStyleMask:styleMask]; +#else // 10.5 or earlier + [appCtrl setWindowStyleMask:styleMask]; +#endif // 10.6 or higher } @end @@ -1455,18 +1459,15 @@ static ApplicationController* appCtrl; - (void)setWindowStyleMask:(NSUInteger)styleMask { -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - [m_window setStyleMask:styleMask]; -#else // 10.5 or earlier // Before 10.6 it's impossible to change window's style mask // To workaround this new window should be created with required style mask + // This method should not be called when building for Snow Leopard or newer FullscreenWindow* tempWindow = [self createWindow:styleMask]; [tempWindow setContentView:[m_window contentView]]; [m_window close]; m_window = tempWindow; -#endif // 10.6 or higher } From 70bb80e2bfda676cef9cdb868bb785ecfe4ceed5 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 16:49:26 +0200 Subject: [PATCH 41/75] Removed OS X version check It didn't work correctly and .plist should set minimum OS version to run on --- src/cocoa/i_backend_cocoa.mm | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 4a029515e..dd376efb8 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1955,39 +1955,8 @@ int SDL_SetPalette(SDL_Surface* surface, int flags, SDL_Color* colors, int first #undef main #endif // main -static void CheckOSVersion() -{ - static const char* const PARAMETER_NAME = "kern.osrelease"; - - size_t size = 0; - - if (-1 == sysctlbyname(PARAMETER_NAME, NULL, &size, NULL, 0)) - { - return; - } - - char* version = static_cast(alloca(size)); - - if (-1 == sysctlbyname(PARAMETER_NAME, version, &size, NULL, 0)) - { - return; - } - - if (strcmp(version, "10.0") < 0) - { - CFOptionFlags responseFlags; - CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, - CFSTR("Unsupported version of OS X"), CFSTR("You need OS X 10.6 or higher running on Intel platform in order to play."), - NULL, NULL, NULL, &responseFlags); - - exit(EXIT_FAILURE); - } -} - int main(int argc, char** argv) { - CheckOSVersion(); - gettimeofday(&s_startTicks, NULL); for (int i = 0; i <= argc; ++i) From 660ebf2c6cf2796bec39242ed0ea8dc13d65421e Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 16:53:25 +0200 Subject: [PATCH 42/75] Added auto-release pools to decrease memory fragmentation on older OS X like 10.5 or 10.6 --- src/cocoa/i_backend_cocoa.mm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index dd376efb8..089d07f0d 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1378,7 +1378,9 @@ static ApplicationController* appCtrl; - (void)processEvents:(NSTimer*)timer { ZD_UNUSED(timer); - + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + while (true) { NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask @@ -1435,6 +1437,8 @@ static ApplicationController* appCtrl; } [NSApp updateWindows]; + + [pool release]; } @@ -1513,6 +1517,8 @@ void I_SetMainWindowVisible(bool visible) bool I_SetCursor(FTexture* cursorpic) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + if (NULL == cursorpic || FTexture::TEX_Null == cursorpic->UseType) { s_cursor = [NSCursor arrowCursor]; @@ -1567,7 +1573,9 @@ bool I_SetCursor(FTexture* cursorpic) } [appCtrl invalidateCursorRects]; - + + [pool release]; + return true; } From c024b30e9d3c8913b0138774a9b5ea9b073e8969 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 17:13:03 +0200 Subject: [PATCH 43/75] Availability of game controller API is now determined on runtime --- src/cocoa/i_joystick.cpp | 50 +++++++++++----------------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/src/cocoa/i_joystick.cpp b/src/cocoa/i_joystick.cpp index 889c38fed..4d983176d 100644 --- a/src/cocoa/i_joystick.cpp +++ b/src/cocoa/i_joystick.cpp @@ -33,7 +33,7 @@ #include "m_joy.h" -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#include #include "HID_Utilities_External.h" @@ -726,7 +726,19 @@ IOKitJoystickManager* s_joystickManager; void I_StartupJoysticks() { - s_joystickManager = new IOKitJoystickManager; + SInt32 majorVersion = 0; + SInt32 minorVersion = 0; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + Gestalt(gestaltSystemVersionMajor, &majorVersion); + Gestalt(gestaltSystemVersionMinor, &minorVersion); +#pragma clang diagnostic pop + + if (majorVersion >= 10 && minorVersion >= 5) + { + s_joystickManager = new IOKitJoystickManager; + } } void I_ShutdownJoysticks() @@ -776,37 +788,3 @@ void I_ProcessJoysticks() s_joystickManager->Update(); } } - -#else // prior to 10.5 - -void I_StartupJoysticks() -{ -} - -void I_ShutdownJoysticks() -{ -} - -void I_GetJoysticks(TArray& sticks) -{ - sticks.Clear(); -} - -void I_GetAxes(float axes[NUM_JOYAXIS]) -{ - for (size_t i = 0; i < NUM_JOYAXIS; ++i) - { - axes[i] = 0.0f; - } -} - -IJoystickConfig *I_UpdateDeviceList() -{ - return NULL; -} - -void I_ProcessJoysticks() -{ -} - -#endif // 10.5 or higher From a37459af2ca711d48775c6737b9fd31cacf9bd5b Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 9 Nov 2014 18:00:36 +0200 Subject: [PATCH 44/75] Removed all code to support compilation using 10.4 SDK 10.5 is minimum version of OS X SDK to build. Deployment target can be 10.4 though This reverts significant part of d061adf --- src/cocoa/i_backend_cocoa.mm | 76 +----------------------------------- src/sdl/iwadpicker_cocoa.mm | 5 --- src/sdl/sdlvideo.cpp | 7 ---- 3 files changed, 1 insertion(+), 87 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 089d07f0d..5d4aac859 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -45,80 +45,6 @@ #include #include -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 - -// Missing definitions for 10.4 and earlier - -typedef unsigned int NSUInteger; -typedef int NSInteger; - -typedef float CGFloat; - -// From HIToolbox/Events.h -enum -{ - kVK_Return = 0x24, - kVK_Tab = 0x30, - kVK_Space = 0x31, - kVK_Delete = 0x33, - kVK_Escape = 0x35, - kVK_Command = 0x37, - kVK_Shift = 0x38, - kVK_CapsLock = 0x39, - kVK_Option = 0x3A, - kVK_Control = 0x3B, - kVK_RightShift = 0x3C, - kVK_RightOption = 0x3D, - kVK_RightControl = 0x3E, - kVK_Function = 0x3F, - kVK_F17 = 0x40, - kVK_VolumeUp = 0x48, - kVK_VolumeDown = 0x49, - kVK_Mute = 0x4A, - kVK_F18 = 0x4F, - kVK_F19 = 0x50, - kVK_F20 = 0x5A, - kVK_F5 = 0x60, - kVK_F6 = 0x61, - kVK_F7 = 0x62, - kVK_F3 = 0x63, - kVK_F8 = 0x64, - kVK_F9 = 0x65, - kVK_F11 = 0x67, - kVK_F13 = 0x69, - kVK_F16 = 0x6A, - kVK_F14 = 0x6B, - kVK_F10 = 0x6D, - kVK_F12 = 0x6F, - kVK_F15 = 0x71, - kVK_Help = 0x72, - kVK_Home = 0x73, - kVK_PageUp = 0x74, - kVK_ForwardDelete = 0x75, - kVK_F4 = 0x76, - kVK_End = 0x77, - kVK_F2 = 0x78, - kVK_PageDown = 0x79, - kVK_F1 = 0x7A, - kVK_LeftArrow = 0x7B, - kVK_RightArrow = 0x7C, - kVK_DownArrow = 0x7D, - kVK_UpArrow = 0x7E -}; - -@interface NSView(SupportOutdatedOSX) -- (NSPoint)convertPointFromBase:(NSPoint)aPoint; -@end - -@implementation NSView(SupportOutdatedOSX) -- (NSPoint)convertPointFromBase:(NSPoint)aPoint -{ - return [self convertPoint:aPoint fromView:nil]; -} -@end - -#endif // prior to 10.5 - #include // Avoid collision between DObject class and Objective-C @@ -679,7 +605,7 @@ void NSEventToGameMousePosition(NSEvent* inEvent, event_t* outEvent) const NSPoint viewPos = IsHiDPISupported() ? [view convertPointToBacking:windowPos] - : [view convertPointFromBase:windowPos]; + : [view convertPoint:windowPos fromView:nil]; const CGFloat frameHeight = GetRealContentViewSize(window).height; diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index 57a4ce7f4..29b0454de 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -50,11 +50,6 @@ #include #include -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 -// Missing type definition for 10.4 and earlier -typedef unsigned int NSUInteger; -#endif // prior to 10.5 - CVAR(String, osx_additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG); enum diff --git a/src/sdl/sdlvideo.cpp b/src/sdl/sdlvideo.cpp index 6753169f0..c869545ab 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/sdl/sdlvideo.cpp @@ -550,13 +550,6 @@ void SDLFB::SetVSync (bool vsync) { // Apply vsync for native backend only (where OpenGL context is set) -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 - // Inconsistency between 10.4 and 10.5 SDKs: - // third argument of CGLSetParameter() is const long* on 10.4 and const GLint* on 10.5 - // So, GLint typedef'ed to long instead of int to workaround this issue - typedef long GLint; -#endif // prior to 10.5 - const GLint value = vsync ? 1 : 0; CGLSetParameter(context, kCGLCPSwapInterval, &value); } From 936ee41e413ca408febf6111e4ead1026c8d3e9e Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Mon, 10 Nov 2014 22:37:53 +0200 Subject: [PATCH 45/75] Removed executable permission from HID Utilities source files --- src/cocoa/HID_Config_Utilities.c | 0 src/cocoa/HID_Error_Handler.c | 0 src/cocoa/HID_Name_Lookup.c | 0 src/cocoa/HID_Queue_Utilities.c | 0 src/cocoa/HID_Utilities.c | 0 src/cocoa/HID_Utilities_External.h | 0 src/cocoa/IOHIDDevice_.c | 0 src/cocoa/IOHIDDevice_.h | 0 src/cocoa/IOHIDElement_.c | 0 src/cocoa/IOHIDElement_.h | 0 src/cocoa/IOHIDLib_.h | 0 src/cocoa/ImmrHIDUtilAddOn.c | 0 src/cocoa/ImmrHIDUtilAddOn.h | 0 13 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/cocoa/HID_Config_Utilities.c mode change 100755 => 100644 src/cocoa/HID_Error_Handler.c mode change 100755 => 100644 src/cocoa/HID_Name_Lookup.c mode change 100755 => 100644 src/cocoa/HID_Queue_Utilities.c mode change 100755 => 100644 src/cocoa/HID_Utilities.c mode change 100755 => 100644 src/cocoa/HID_Utilities_External.h mode change 100755 => 100644 src/cocoa/IOHIDDevice_.c mode change 100755 => 100644 src/cocoa/IOHIDDevice_.h mode change 100755 => 100644 src/cocoa/IOHIDElement_.c mode change 100755 => 100644 src/cocoa/IOHIDElement_.h mode change 100755 => 100644 src/cocoa/IOHIDLib_.h mode change 100755 => 100644 src/cocoa/ImmrHIDUtilAddOn.c mode change 100755 => 100644 src/cocoa/ImmrHIDUtilAddOn.h diff --git a/src/cocoa/HID_Config_Utilities.c b/src/cocoa/HID_Config_Utilities.c old mode 100755 new mode 100644 diff --git a/src/cocoa/HID_Error_Handler.c b/src/cocoa/HID_Error_Handler.c old mode 100755 new mode 100644 diff --git a/src/cocoa/HID_Name_Lookup.c b/src/cocoa/HID_Name_Lookup.c old mode 100755 new mode 100644 diff --git a/src/cocoa/HID_Queue_Utilities.c b/src/cocoa/HID_Queue_Utilities.c old mode 100755 new mode 100644 diff --git a/src/cocoa/HID_Utilities.c b/src/cocoa/HID_Utilities.c old mode 100755 new mode 100644 diff --git a/src/cocoa/HID_Utilities_External.h b/src/cocoa/HID_Utilities_External.h old mode 100755 new mode 100644 diff --git a/src/cocoa/IOHIDDevice_.c b/src/cocoa/IOHIDDevice_.c old mode 100755 new mode 100644 diff --git a/src/cocoa/IOHIDDevice_.h b/src/cocoa/IOHIDDevice_.h old mode 100755 new mode 100644 diff --git a/src/cocoa/IOHIDElement_.c b/src/cocoa/IOHIDElement_.c old mode 100755 new mode 100644 diff --git a/src/cocoa/IOHIDElement_.h b/src/cocoa/IOHIDElement_.h old mode 100755 new mode 100644 diff --git a/src/cocoa/IOHIDLib_.h b/src/cocoa/IOHIDLib_.h old mode 100755 new mode 100644 diff --git a/src/cocoa/ImmrHIDUtilAddOn.c b/src/cocoa/ImmrHIDUtilAddOn.c old mode 100755 new mode 100644 diff --git a/src/cocoa/ImmrHIDUtilAddOn.h b/src/cocoa/ImmrHIDUtilAddOn.h old mode 100755 new mode 100644 From d53e860d2841eed1146ee07d6346147e47e5707a Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Mon, 10 Nov 2014 22:59:40 +0200 Subject: [PATCH 46/75] Moved supported by OS features checks to runtime No more compile time checks via preprocessor macro definitions This introduces dependency from Carbon framework, see SetSystemUIMode() function. It's available in 64-bit, and so, it's not deprecated --- src/cocoa/i_backend_cocoa.mm | 68 ++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 5d4aac859..3f9a727b3 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -74,6 +74,14 @@ // --------------------------------------------------------------------------- +#ifndef NSAppKitVersionNumber10_6 + +@interface NSWindow(SetStyleMask) +- (void)setStyleMask:(NSUInteger)styleMask; +@end + +#endif // !NSAppKitVersionNumber10_6 + #ifndef NSAppKitVersionNumber10_7 @interface NSView(HiDPIStubs) @@ -95,6 +103,7 @@ RenderBufferOptions rbOpts; +EXTERN_CVAR(Bool, fullscreen) EXTERN_CVAR(Bool, vid_hidpi) CVAR(Bool, use_mouse, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -266,7 +275,7 @@ void CheckNativeMouse() } // unnamed namespace -// from iokit_joystick.cpp +// see cocoa/i_joystick.cpp void I_ProcessJoysticks(); @@ -574,11 +583,9 @@ void ProcessKeyboardEvent(NSEvent* theEvent) bool IsHiDPISupported() { -#ifdef NSAppKitVersionNumber10_7 - return NSAppKitVersionNumber >= NSAppKitVersionNumber10_7; -#else // !NSAppKitVersionNumber10_7 - return false; -#endif // NSAppKitVersionNumber10_7 + // The following value shoud be equal to NSAppKitVersionNumber10_7 + // and it's hard-coded in order to build on earlier SDKs + return NSAppKitVersionNumber >= 1138; } NSSize GetRealContentViewSize(const NSWindow* const window) @@ -589,7 +596,7 @@ NSSize GetRealContentViewSize(const NSWindow* const window) // TODO: figure out why [NSView frame] returns different values in "fullscreen" and in window // In "fullscreen" the result is multiplied by [NSScreen backingScaleFactor], but not in window - return (vid_hidpi && NSNormalWindowLevel == [window level]) + return (vid_hidpi && !fullscreen) ? [view convertSizeToBacking:frameSize] : frameSize; } @@ -903,6 +910,15 @@ static ApplicationController* appCtrl; @implementation FullscreenWindow +static bool s_fullscreenNewAPI; + ++ (void)initialize +{ + // The following value shoud be equal to NSAppKitVersionNumber10_6 + // and it's hard-coded in order to build on earlier SDKs + s_fullscreenNewAPI = NSAppKitVersionNumber >= 1038; +} + - (bool)canBecomeKeyWindow { return true; @@ -910,27 +926,33 @@ static ApplicationController* appCtrl; - (void)setLevel:(NSInteger)level { -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - [super setLevel:level]; -#else // 10.5 or earlier - // Old Carbon-based way to make fullscreen window above dock and menu - // It's supported on 64-bit, but on 10.6 and later the following is preferred: - // [NSWindow setLevel:NSMainMenuWindowLevel + 1] + if (s_fullscreenNewAPI) + { + [super setLevel:level]; + } + else + { + // Old Carbon-based way to make fullscreen window above dock and menu + // It's supported on 64-bit, but on 10.6 and later the following is preferred: + // [NSWindow setLevel:NSMainMenuWindowLevel + 1] - const SystemUIMode mode = LEVEL_FULLSCREEN == level - ? kUIModeAllHidden - : kUIModeNormal; - SetSystemUIMode(mode, 0); -#endif // 10.6 or higher + const SystemUIMode mode = LEVEL_FULLSCREEN == level + ? kUIModeAllHidden + : kUIModeNormal; + SetSystemUIMode(mode, 0); + } } - (void)setStyleMask:(NSUInteger)styleMask { -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - [super setStyleMask:styleMask]; -#else // 10.5 or earlier - [appCtrl setWindowStyleMask:styleMask]; -#endif // 10.6 or higher + if (s_fullscreenNewAPI) + { + [super setStyleMask:styleMask]; + } + else + { + [appCtrl setWindowStyleMask:styleMask]; + } } @end From 9e4a262c87765018c706db5648b89bc093ba5e83 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Wed, 12 Nov 2014 23:03:59 +0200 Subject: [PATCH 47/75] Enabled building with SDK 10.4 Game controllers support will disabled in this case --- src/cocoa/HID_Config_Utilities.c | 7 +++ src/cocoa/HID_Error_Handler.c | 7 +++ src/cocoa/HID_Name_Lookup.c | 7 +++ src/cocoa/HID_Queue_Utilities.c | 7 +++ src/cocoa/HID_Utilities.c | 7 +++ src/cocoa/IOHIDDevice_.c | 7 +++ src/cocoa/IOHIDElement_.c | 7 +++ src/cocoa/ImmrHIDUtilAddOn.c | 7 +++ src/cocoa/i_backend_cocoa.mm | 74 ++++++++++++++++++++++++++++++++ src/cocoa/i_joystick.cpp | 38 ++++++++++++++++ src/sdl/iwadpicker_cocoa.mm | 5 +++ src/sdl/sdlvideo.cpp | 7 +++ 12 files changed, 180 insertions(+) diff --git a/src/cocoa/HID_Config_Utilities.c b/src/cocoa/HID_Config_Utilities.c index 6cd1ad56a..18d4672d3 100644 --- a/src/cocoa/HID_Config_Utilities.c +++ b/src/cocoa/HID_Config_Utilities.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #define LOG_SCORING 0 #include // malloc @@ -917,3 +922,5 @@ int HIDGetElementConfig(HID_info_ptr inHIDInfoPtr, IOHIDDeviceRef *outIOHIDDevic return (inHIDInfoPtr->actionCookie); } } // HIDGetElementConfig + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Error_Handler.c b/src/cocoa/HID_Error_Handler.c index 8aaca7f36..0802a2ca7 100644 --- a/src/cocoa/HID_Error_Handler.c +++ b/src/cocoa/HID_Error_Handler.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #ifdef DEBUG // not used in release #if !defined (kBuildingLibrary) #define kVerboseErrors @@ -99,3 +104,5 @@ void HIDReportError(const char *strError) { } #endif // kVerboseErrors } // HIDReportError + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Name_Lookup.c b/src/cocoa/HID_Name_Lookup.c index 4e4b023bd..c9a1ca264 100644 --- a/src/cocoa/HID_Name_Lookup.c +++ b/src/cocoa/HID_Name_Lookup.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #pragma mark - includes & imports //***************************************************** #include "HID_Utilities_External.h" @@ -1201,3 +1206,5 @@ static Boolean hu_AddDeviceElementToUsageXML(IOHIDDeviceRef inIOHIDDeviceRef, IO return (results); } // hu_AddDeviceElementToUsageXML #endif + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Queue_Utilities.c b/src/cocoa/HID_Queue_Utilities.c index 586609d7f..ab1d83667 100644 --- a/src/cocoa/HID_Queue_Utilities.c +++ b/src/cocoa/HID_Queue_Utilities.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #include "HID_Utilities_External.h" // ================================== @@ -352,3 +357,5 @@ unsigned char HIDGetEvent(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDValueRef *pIOHID return (false); // did not get event } /* HIDGetEvent */ + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Utilities.c b/src/cocoa/HID_Utilities.c index 2beb6ca02..3152cfdd9 100644 --- a/src/cocoa/HID_Utilities.c +++ b/src/cocoa/HID_Utilities.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //*************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #pragma mark - includes & imports //----------------------------------------------------- @@ -1059,3 +1064,5 @@ static CFMutableDictionaryRef hu_SetUpMatchingDictionary(UInt32 inUsagePage, UIn return (refHIDMatchDictionary); } // hu_SetUpMatchingDictionary + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/IOHIDDevice_.c b/src/cocoa/IOHIDDevice_.c index ab35c21e0..30c01dc7c 100644 --- a/src/cocoa/IOHIDDevice_.c +++ b/src/cocoa/IOHIDDevice_.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #pragma mark - includes & imports //----------------------------------------------------- @@ -610,3 +615,5 @@ static void IOHIDDevice_SetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFString } // IOHIDDevice_SetPtrProperty //***************************************************** + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/IOHIDElement_.c b/src/cocoa/IOHIDElement_.c index f4d8be9eb..906d2926a 100644 --- a/src/cocoa/IOHIDElement_.c +++ b/src/cocoa/IOHIDElement_.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #pragma mark - includes & imports //----------------------------------------------------- @@ -500,3 +505,5 @@ void IOHIDElement_SetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKe } // IOHIDElement_SetLongProperty //***************************************************** + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/ImmrHIDUtilAddOn.c b/src/cocoa/ImmrHIDUtilAddOn.c index 07e6a31b1..4937d3687 100644 --- a/src/cocoa/ImmrHIDUtilAddOn.c +++ b/src/cocoa/ImmrHIDUtilAddOn.c @@ -43,6 +43,11 @@ // Copyright (C) 2009 Apple Inc. All Rights Reserved. // //***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #include #include @@ -99,3 +104,5 @@ bool FreeHIDObject(io_service_t inHIDObject) { return (kIOReturnSuccess == kr); } // FreeHIDObject + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 3f9a727b3..39b6642e2 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -45,6 +45,80 @@ #include #include +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 + +// Missing definitions for 10.4 and earlier + +typedef unsigned int NSUInteger; +typedef int NSInteger; + +typedef float CGFloat; + +// From HIToolbox/Events.h +enum +{ + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +@interface NSView(SupportOutdatedOSX) +- (NSPoint)convertPointFromBase:(NSPoint)aPoint; +@end + +@implementation NSView(SupportOutdatedOSX) +- (NSPoint)convertPointFromBase:(NSPoint)aPoint +{ + return [self convertPoint:aPoint fromView:nil]; +} +@end + +#endif // prior to 10.5 + #include // Avoid collision between DObject class and Objective-C diff --git a/src/cocoa/i_joystick.cpp b/src/cocoa/i_joystick.cpp index 4d983176d..bd2c59348 100644 --- a/src/cocoa/i_joystick.cpp +++ b/src/cocoa/i_joystick.cpp @@ -33,6 +33,10 @@ #include "m_joy.h" +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + #include #include "HID_Utilities_External.h" @@ -788,3 +792,37 @@ void I_ProcessJoysticks() s_joystickManager->Update(); } } + +#else // prior to 10.5 + +void I_StartupJoysticks() +{ +} + +void I_ShutdownJoysticks() +{ +} + +void I_GetJoysticks(TArray& sticks) +{ + sticks.Clear(); +} + +void I_GetAxes(float axes[NUM_JOYAXIS]) +{ + for (size_t i = 0; i < NUM_JOYAXIS; ++i) + { + axes[i] = 0.0f; + } +} + +IJoystickConfig *I_UpdateDeviceList() +{ + return NULL; +} + +void I_ProcessJoysticks() +{ +} + +#endif // 10.5 or higher diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index 29b0454de..c3b167c09 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -50,6 +50,11 @@ #include #include +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 +// Missing type definition for 10.4 and earlier +typedef unsigned int NSUInteger; +#endif // prior to 10.5 + CVAR(String, osx_additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG); enum diff --git a/src/sdl/sdlvideo.cpp b/src/sdl/sdlvideo.cpp index c869545ab..59587fc0b 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/sdl/sdlvideo.cpp @@ -550,6 +550,13 @@ void SDLFB::SetVSync (bool vsync) { // Apply vsync for native backend only (where OpenGL context is set) +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 + // Inconsistency between 10.4 and 10.5 SDKs: + // third argument of CGLSetParameter() is const long* on 10.4 and const GLint* on 10.5 + // So, GLint typedef'ed to long instead of int to workaround this issue + typedef long GLint; +#endif // prior to 10.5 + const GLint value = vsync ? 1 : 0; CGLSetParameter(context, kCGLCPSwapInterval, &value); } From ee977f94d73567aa3538b3a9b940b2711cf38aa3 Mon Sep 17 00:00:00 2001 From: ChillyDoom Date: Fri, 14 Nov 2014 16:54:56 +0000 Subject: [PATCH 48/75] - Moved bot thinking logic into DBot. --- src/b_bot.cpp | 21 ++++++ src/b_bot.h | 19 +++-- src/b_func.cpp | 32 +++++++-- src/b_game.cpp | 144 +++++++++++++------------------------ src/g_game.cpp | 9 +-- src/g_level.cpp | 4 ++ src/g_shared/a_pickups.cpp | 9 ++- src/p_interaction.cpp | 13 ++-- src/p_mobj.cpp | 29 ++++---- src/p_setup.cpp | 1 - src/p_user.cpp | 7 +- src/version.h | 2 +- 12 files changed, 154 insertions(+), 136 deletions(-) diff --git a/src/b_bot.cpp b/src/b_bot.cpp index dc6f2cdc4..6a088ffcb 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -30,6 +30,7 @@ DBot::DBot () void DBot::Clear () { + player = NULL; angle = 0; dest = NULL; prev = NULL; @@ -64,6 +65,10 @@ void DBot::Serialize (FArchive &arc) arc << savedyaw << savedpitch; } + else if (SaveVersion >= 4516) + { + arc << player; + } arc << angle << dest @@ -88,6 +93,22 @@ void DBot::Serialize (FArchive &arc) << oldy; } +void DBot::Tick () +{ + Super::Tick (); + + if (player->mo == NULL || bglobal.freeze) + { + return; + } + + BotThinkCycles.Clock(); + bglobal.m_Thinking = true; + bglobal.Think (player->mo, &netcmds[player - players][((gametic + 1)/ticdup)%BACKUPTICS]); + bglobal.m_Thinking = false; + BotThinkCycles.Unclock(); +} + CVAR (Int, bot_next_color, 11, 0) CVAR (Bool, bot_observer, false, 0) diff --git a/src/b_bot.h b/src/b_bot.h index 1862faee5..7740e5e00 100644 --- a/src/b_bot.h +++ b/src/b_bot.h @@ -14,6 +14,7 @@ #include "d_ticcmd.h" #include "r_defs.h" #include "a_pickups.h" +#include "stats.h" #define FORWARDWALK 0x1900 #define FORWARDRUN 0x3200 @@ -89,20 +90,22 @@ public: void ClearPlayer (int playernum, bool keepTeam); //(B_Game.c) - void Main (int buf); + void Main (); void Init (); void End(); bool SpawnBot (const char *name, int color = NOCOLOR); - bool LoadBots (); - void ForgetBots (); void TryAddBot (BYTE **stream, int player); void RemoveAllBots (bool fromlist); - void DestroyAllBots (); + bool LoadBots (); + void ForgetBots (); //(B_Func.c) bool Check_LOS (AActor *mobj1, AActor *mobj2, angle_t vangle); + void StartTravel (); + void FinishTravel (); //(B_Think.c) + void Think (AActor *actor, ticcmd_t *cmd); void WhatToGet (AActor *actor, AActor *item); //(B_move.c) @@ -144,7 +147,6 @@ private: bool SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FCheckPosition &tm); //(B_Think.c) - void Think (AActor *actor, ticcmd_t *cmd); void ThinkForMove (AActor *actor, ticcmd_t *cmd); void Set_enemy (AActor *actor); @@ -155,16 +157,18 @@ protected: bool observer; //Consoleplayer is observer. }; -class DBot : public DObject +class DBot : public DThinker { - DECLARE_CLASS(DBot,DObject) + DECLARE_CLASS(DBot,DThinker) HAS_OBJECT_POINTERS public: DBot (); void Clear (); void Serialize (FArchive &arc); + void Tick (); + player_t *player; angle_t angle; // The wanted angle that the bot try to get every tic. // (used to get a smooth view movement) TObjPtr dest; // Move Destination. @@ -205,6 +209,7 @@ public: //Externs extern FCajunMaster bglobal; +extern cycle_t BotThinkCycles, BotSupportCycles; EXTERN_CVAR (Float, bot_flag_return_time) EXTERN_CVAR (Int, bot_next_color) diff --git a/src/b_func.cpp b/src/b_func.cpp index 0303de8c6..9ea03d45e 100644 --- a/src/b_func.cpp +++ b/src/b_func.cpp @@ -272,10 +272,12 @@ shootmissile: bool FCajunMaster::IsLeader (player_t *player) { - for (int count = 0; count < MAXPLAYERS; count++) + DBot *Bot; + TThinkerIterator it; + + while ((Bot = it.Next ()) != NULL) { - if (players[count].Bot != NULL - && players[count].Bot->mate == player->mo) + if (Bot->mate == player->mo) { return true; } @@ -402,7 +404,7 @@ AActor *FCajunMaster::Find_enemy (AActor *bot) && bot != client->mo) { if (Check_LOS (bot, client->mo, vangle)) //Here's a strange one, when bot is standing still, the P_CheckSight within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up. - //if(P_CheckSight( bot, players[count].mo)) + //if(P_CheckSight(bot, players[count].mo)) { temp = P_AproxDistance (client->mo->x - bot->x, client->mo->y - bot->y); @@ -552,3 +554,25 @@ bool FCajunMaster::SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FChec actor->flags = savedFlags; return res; } + +void FCajunMaster::StartTravel () +{ + DBot *Bot; + TThinkerIterator it; + + while ((Bot = it.Next ()) != NULL) + { + Bot->ChangeStatNum (STAT_TRAVELLING); + } +} + +void FCajunMaster::FinishTravel () +{ + DBot *Bot; + TThinkerIterator it(STAT_TRAVELLING); + + while ((Bot = it.Next ()) != NULL) + { + Bot->ChangeStatNum (STAT_DEFAULT); + } +} diff --git a/src/b_game.cpp b/src/b_game.cpp index 853ba7f23..a05106a70 100644 --- a/src/b_game.cpp +++ b/src/b_game.cpp @@ -94,71 +94,46 @@ FCajunMaster::~FCajunMaster() ForgetBots(); } -//This function is called every tick (from g_game.c), -//send bots into thinking (+more). -void FCajunMaster::Main (int buf) +//This function is called every tick (from g_game.c). +void FCajunMaster::Main () { - int i; - BotThinkCycles.Reset(); - if (demoplayback) + if (demoplayback || gamestate != GS_LEVEL || consoleplayer != Net_Arbitrator) return; - if (gamestate != GS_LEVEL) - return; - - m_Thinking = true; - - //Think for bots. - if (botnum) + //Add new bots? + if (wanted_botnum > botnum && !freeze) { - BotThinkCycles.Clock(); - for (i = 0; i < MAXPLAYERS; i++) + if (t_join == ((wanted_botnum - botnum) * SPAWN_DELAY)) { - if (playeringame[i] && players[i].mo && !freeze && players[i].Bot != NULL) - Think (players[i].mo, &netcmds[i][buf]); + if (!SpawnBot (getspawned[spawn_tries])) + wanted_botnum--; + spawn_tries++; } - BotThinkCycles.Unclock(); + + t_join--; } - if (consoleplayer == Net_Arbitrator) + //Check if player should go observer. Or un observe + if (bot_observer && !observer && !netgame) { - //Add new bots? - if (wanted_botnum > botnum && !freeze) - { - if (t_join == ((wanted_botnum - botnum) * SPAWN_DELAY)) - { - if (!SpawnBot (getspawned[spawn_tries])) - wanted_botnum--; - spawn_tries++; - } - - t_join--; - } - - //Check if player should go observer. Or un observe - if (bot_observer && !observer && !netgame) - { - Printf ("%s is now observer\n", players[consoleplayer].userinfo.GetName()); - observer = true; - players[consoleplayer].mo->UnlinkFromWorld (); - players[consoleplayer].mo->flags = MF_DROPOFF|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOTDMATCH|MF_NOGRAVITY|MF_FRIENDLY; - players[consoleplayer].mo->flags2 |= MF2_FLY; - players[consoleplayer].mo->LinkToWorld (); - } - else if (!bot_observer && observer && !netgame) //Go back - { - Printf ("%s returned to the fray\n", players[consoleplayer].userinfo.GetName()); - observer = false; - players[consoleplayer].mo->UnlinkFromWorld (); - players[consoleplayer].mo->flags = MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH|MF_FRIENDLY; - players[consoleplayer].mo->flags2 &= ~MF2_FLY; - players[consoleplayer].mo->LinkToWorld (); - } + Printf ("%s is now observer\n", players[consoleplayer].userinfo.GetName()); + observer = true; + players[consoleplayer].mo->UnlinkFromWorld (); + players[consoleplayer].mo->flags = MF_DROPOFF|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOTDMATCH|MF_NOGRAVITY|MF_FRIENDLY; + players[consoleplayer].mo->flags2 |= MF2_FLY; + players[consoleplayer].mo->LinkToWorld (); + } + else if (!bot_observer && observer && !netgame) //Go back + { + Printf ("%s returned to the fray\n", players[consoleplayer].userinfo.GetName()); + observer = false; + players[consoleplayer].mo->UnlinkFromWorld (); + players[consoleplayer].mo->flags = MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH|MF_FRIENDLY; + players[consoleplayer].mo->flags2 &= ~MF2_FLY; + players[consoleplayer].mo->LinkToWorld (); } - - m_Thinking = false; } void FCajunMaster::Init () @@ -195,22 +170,18 @@ void FCajunMaster::Init () //Called on each level exit (from g_game.c). void FCajunMaster::End () { - int i; - //Arrange wanted botnum and their names, so they can be spawned next level. getspawned.Clear(); - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].Bot != NULL) - { - if (deathmatch) - { - getspawned.Push(players[i].userinfo.GetName()); - } - } - } if (deathmatch) { + DBot *Bot; + TThinkerIterator it; + + while ((Bot = it.Next ()) != NULL) + { + getspawned.Push(Bot->player->userinfo.GetName()); + } + wanted_botnum = botnum; } } @@ -400,7 +371,7 @@ bool FCajunMaster::DoAddBot (BYTE *info, botskill_t skill) multiplayer = true; //Prevents cheating and so on; emulates real netgame (almost). players[bnum].Bot = new DBot; - GC::WriteBarrier (players[bnum].Bot); + players[bnum].Bot->player = &players[bnum]; players[bnum].Bot->skill = skill; playeringame[bnum] = true; players[bnum].mo = NULL; @@ -422,31 +393,30 @@ bool FCajunMaster::DoAddBot (BYTE *info, botskill_t skill) void FCajunMaster::RemoveAllBots (bool fromlist) { - int i, j; + DBot *Bot; + TThinkerIterator it; + int i; - for (i = 0; i < MAXPLAYERS; ++i) + while ((Bot = it.Next ()) != NULL) { - if (playeringame[i] && players[i].Bot != NULL) + // If a player is looking through this bot's eyes, make him + // look through his own eyes instead. + for (i = 0; i < MAXPLAYERS; ++i) { - // If a player is looking through this bot's eyes, make him - // look through his own eyes instead. - for (j = 0; j < MAXPLAYERS; ++j) + if (Bot->player != &players[i] && playeringame[i] && players[i].Bot == NULL) { - if (i != j && playeringame[j] && players[j].Bot == NULL) + if (players[i].camera == Bot->player->mo) { - if (players[j].camera == players[i].mo) + players[i].camera = players[i].mo; + if (i == consoleplayer) { - players[j].camera = players[j].mo; - if (j == consoleplayer) - { - StatusBar->AttachToPlayer (players + j); - } + StatusBar->AttachToPlayer (players + i); } } } - ClearPlayer (i, !fromlist); - FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, i); } + ClearPlayer (Bot->player - players, !fromlist); + FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, Bot->player - players); } if (fromlist) @@ -456,18 +426,6 @@ void FCajunMaster::RemoveAllBots (bool fromlist) botnum = 0; } -void FCajunMaster::DestroyAllBots () -{ - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (players[i].Bot != NULL) - { - players[i].Bot->Destroy (); - players[i].Bot = NULL; - } - } -} - //------------------ //Reads data for bot from diff --git a/src/g_game.cpp b/src/g_game.cpp index af4a5411e..d56c58910 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1127,11 +1127,8 @@ void G_Ticker () // check, not just the player's x position like BOOM. DWORD rngsum = FRandom::StaticSumSeeds (); - if ((gametic % ticdup) == 0) - { - //Added by MC: For some of that bot stuff. The main bot function. - bglobal.Main (buf); - } + //Added by MC: For some of that bot stuff. The main bot function. + bglobal.Main (); for (i = 0; i < MAXPLAYERS; i++) { @@ -1394,7 +1391,6 @@ void G_PlayerReborn (int player) if (gamestate != GS_TITLELEVEL) { - // [GRB] Give inventory specified in DECORATE actor->GiveDefaultInventory (); p->ReadyWeapon = p->PendingWeapon; @@ -1405,6 +1401,7 @@ void G_PlayerReborn (int player) { botskill_t skill = p->Bot->skill; p->Bot->Clear (); + p->Bot->player = p; p->Bot->skill = skill; } } diff --git a/src/g_level.cpp b/src/g_level.cpp index 2a2d6471a..761ea4f48 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1164,6 +1164,8 @@ void G_StartTravel () } } } + + bglobal.StartTravel (); } //========================================================================== @@ -1261,6 +1263,8 @@ void G_FinishTravel () } } } + + bglobal.FinishTravel (); } //========================================================================== diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index fc3e863a2..f5c262d8c 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -1021,11 +1021,14 @@ void AInventory::Touch (AActor *toucher) P_GiveSecret(toucher, true, true, -1); } + DBot *Bot; + TThinkerIterator it; + //Added by MC: Check if item taken was the roam destination of any bot - for (int i = 0; i < MAXPLAYERS; i++) + while ((Bot = it.Next ()) != NULL) { - if (playeringame[i] && players[i].Bot != NULL && this == players[i].Bot->dest) - players[i].Bot->dest = NULL; + if (Bot->dest == this) + Bot->dest = NULL; } } diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 88d95b25e..88cc89668 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -611,14 +611,17 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) if (player->Bot != NULL) player->Bot->t_respawn = (pr_botrespawn()%15)+((bglobal.botnum-1)*2)+TICRATE+1; + DBot *Bot; + TThinkerIterator it; + //Added by MC: Discard enemies. - for (int i = 0; i < MAXPLAYERS; i++) + while ((Bot = it.Next ()) != NULL) { - if (players[i].Bot != NULL && this == players[i].Bot->enemy) + if (this == Bot->enemy) { - if (players[i].Bot->dest == players[i].Bot->enemy) - players[i].Bot->dest = NULL; - players[i].Bot->enemy = NULL; + if (Bot->dest == Bot->enemy) + Bot->dest = NULL; + Bot->enemy = NULL; } } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index c7bfcea21..27bc50a83 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -84,7 +84,6 @@ static void PlayerLandedOnThing (AActor *mo, AActor *onmobj); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern cycle_t BotSupportCycles; extern int BotWTG; EXTERN_CVAR (Int, cl_rockettrails) @@ -3043,7 +3042,6 @@ void AActor::Tick () AActor *onmo; - int i; //assert (state != NULL); if (state == NULL) @@ -3233,35 +3231,36 @@ void AActor::Tick () { BotSupportCycles.Clock(); bglobal.m_Thinking = true; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].Bot == NULL) - continue; + DBot *Bot; + TThinkerIterator it; + + while ((Bot = it.Next ()) != NULL) + { if (flags3 & MF3_ISMONSTER) { if (health > 0 - && !players[i].Bot->enemy - && player ? !IsTeammate (players[i].mo) : true - && P_AproxDistance (players[i].mo->x-x, players[i].mo->y-y) < MAX_MONSTER_TARGET_DIST - && P_CheckSight (players[i].mo, this, SF_SEEPASTBLOCKEVERYTHING)) + && !Bot->enemy + && player ? !IsTeammate (Bot->player->mo) : true + && P_AproxDistance (Bot->player->mo->x-x, Bot->player->mo->y-y) < MAX_MONSTER_TARGET_DIST + && P_CheckSight (Bot->player->mo, this, SF_SEEPASTBLOCKEVERYTHING)) { //Probably a monster, so go kill it. - players[i].Bot->enemy = this; + Bot->enemy = this; } } else if (flags & MF_SPECIAL) { //Item pickup time //clock (BotWTG); - bglobal.WhatToGet (players[i].mo, this); + bglobal.WhatToGet (Bot->player->mo, this); //unclock (BotWTG); BotWTG++; } else if (flags & MF_MISSILE) { - if (!players[i].Bot->missile && (flags3 & MF3_WARNBOT)) + if (!Bot->missile && (flags3 & MF3_WARNBOT)) { //warn for incoming missiles. - if (target != players[i].mo && bglobal.Check_LOS (players[i].mo, this, ANGLE_90)) - players[i].Bot->missile = this; + if (target != Bot->player->mo && bglobal.Check_LOS (Bot->player->mo, this, ANGLE_90)) + Bot->missile = this; } } } diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 53b288798..6b5d9403f 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -4175,7 +4175,6 @@ static void P_Shutdown () P_FreeLevelData (); P_FreeExtraLevelData (); ST_Clear(); - bglobal.DestroyAllBots (); } #if 0 diff --git a/src/p_user.cpp b/src/p_user.cpp index 72b92b7d1..bf656d2b9 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -3066,7 +3066,6 @@ void player_t::Serialize (FArchive &arc) if (SaveVersion < 4514 && IsBot) { Bot = new DBot; - GC::WriteBarrier (Bot); arc << Bot->angle << Bot->dest @@ -3089,6 +3088,12 @@ void player_t::Serialize (FArchive &arc) << Bot->oldx << Bot->oldy; } + + if (SaveVersion < 4516 && Bot != NULL) + { + Bot->player = this; + } + if (arc.IsLoading ()) { // If the player reloaded because they pressed +use after dying, we diff --git a/src/version.h b/src/version.h index f091f416a..15d01d315 100644 --- a/src/version.h +++ b/src/version.h @@ -76,7 +76,7 @@ const char *GetVersionString(); // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4515 +#define SAVEVER 4516 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) From e38aee070ce3dcbc394ffc44cf52d6f0fa5fd6cb Mon Sep 17 00:00:00 2001 From: ChillyDoom Date: Sat, 15 Nov 2014 08:58:29 +0000 Subject: [PATCH 49/75] - Changed TThinkerIterator loops back to MAXPLAYERS loops. - Added STAT_BOT. --- src/b_bot.cpp | 1 + src/b_func.cpp | 28 +++++++++++++--------------- src/b_game.cpp | 38 +++++++++++++++++++------------------- src/g_shared/a_pickups.cpp | 9 +++------ src/p_interaction.cpp | 13 +++++-------- src/p_mobj.cpp | 28 ++++++++++++++-------------- src/statnums.h | 1 + 7 files changed, 56 insertions(+), 62 deletions(-) diff --git a/src/b_bot.cpp b/src/b_bot.cpp index 6a088ffcb..d2acdcab6 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -24,6 +24,7 @@ IMPLEMENT_POINTY_CLASS(DBot) END_POINTERS DBot::DBot () +: DThinker(STAT_BOT) { Clear (); } diff --git a/src/b_func.cpp b/src/b_func.cpp index 9ea03d45e..349155a81 100644 --- a/src/b_func.cpp +++ b/src/b_func.cpp @@ -272,12 +272,10 @@ shootmissile: bool FCajunMaster::IsLeader (player_t *player) { - DBot *Bot; - TThinkerIterator it; - - while ((Bot = it.Next ()) != NULL) + for (int count = 0; count < MAXPLAYERS; count++) { - if (Bot->mate == player->mo) + if (players[count].Bot != NULL + && players[count].Bot->mate == player->mo) { return true; } @@ -557,22 +555,22 @@ bool FCajunMaster::SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FChec void FCajunMaster::StartTravel () { - DBot *Bot; - TThinkerIterator it; - - while ((Bot = it.Next ()) != NULL) + for (int i = 0; i < MAXPLAYERS; ++i) { - Bot->ChangeStatNum (STAT_TRAVELLING); + if (players[i].Bot != NULL) + { + players[i].Bot->ChangeStatNum (STAT_TRAVELLING); + } } } void FCajunMaster::FinishTravel () { - DBot *Bot; - TThinkerIterator it(STAT_TRAVELLING); - - while ((Bot = it.Next ()) != NULL) + for (int i = 0; i < MAXPLAYERS; ++i) { - Bot->ChangeStatNum (STAT_DEFAULT); + if (players[i].Bot != NULL) + { + players[i].Bot->ChangeStatNum (STAT_BOT); + } } } diff --git a/src/b_game.cpp b/src/b_game.cpp index a05106a70..44aff03fa 100644 --- a/src/b_game.cpp +++ b/src/b_game.cpp @@ -170,16 +170,15 @@ void FCajunMaster::Init () //Called on each level exit (from g_game.c). void FCajunMaster::End () { + int i; + //Arrange wanted botnum and their names, so they can be spawned next level. getspawned.Clear(); if (deathmatch) { - DBot *Bot; - TThinkerIterator it; - - while ((Bot = it.Next ()) != NULL) + for (i = 0; i < MAXPLAYERS; i++) { - getspawned.Push(Bot->player->userinfo.GetName()); + getspawned.Push(players[i].userinfo.GetName()); } wanted_botnum = botnum; @@ -393,30 +392,31 @@ bool FCajunMaster::DoAddBot (BYTE *info, botskill_t skill) void FCajunMaster::RemoveAllBots (bool fromlist) { - DBot *Bot; - TThinkerIterator it; - int i; + int i, j; - while ((Bot = it.Next ()) != NULL) + for (i = 0; i < MAXPLAYERS; ++i) { - // If a player is looking through this bot's eyes, make him - // look through his own eyes instead. - for (i = 0; i < MAXPLAYERS; ++i) + if (players[i].Bot != NULL) { - if (Bot->player != &players[i] && playeringame[i] && players[i].Bot == NULL) + // If a player is looking through this bot's eyes, make him + // look through his own eyes instead. + for (j = 0; j < MAXPLAYERS; ++j) { - if (players[i].camera == Bot->player->mo) + if (i != j && playeringame[j] && players[j].Bot == NULL) { - players[i].camera = players[i].mo; - if (i == consoleplayer) + if (players[j].camera == players[i].mo) { - StatusBar->AttachToPlayer (players + i); + players[j].camera = players[j].mo; + if (j == consoleplayer) + { + StatusBar->AttachToPlayer (players + j); + } } } } + ClearPlayer (i, !fromlist); + FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, i); } - ClearPlayer (Bot->player - players, !fromlist); - FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, Bot->player - players); } if (fromlist) diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index f5c262d8c..1aab998a9 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -1021,14 +1021,11 @@ void AInventory::Touch (AActor *toucher) P_GiveSecret(toucher, true, true, -1); } - DBot *Bot; - TThinkerIterator it; - //Added by MC: Check if item taken was the roam destination of any bot - while ((Bot = it.Next ()) != NULL) + for (int i = 0; i < MAXPLAYERS; i++) { - if (Bot->dest == this) - Bot->dest = NULL; + if (players[i].Bot != NULL && this == players[i].Bot->dest) + players[i].Bot->dest = NULL; } } diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 88cc89668..88d95b25e 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -611,17 +611,14 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) if (player->Bot != NULL) player->Bot->t_respawn = (pr_botrespawn()%15)+((bglobal.botnum-1)*2)+TICRATE+1; - DBot *Bot; - TThinkerIterator it; - //Added by MC: Discard enemies. - while ((Bot = it.Next ()) != NULL) + for (int i = 0; i < MAXPLAYERS; i++) { - if (this == Bot->enemy) + if (players[i].Bot != NULL && this == players[i].Bot->enemy) { - if (Bot->dest == Bot->enemy) - Bot->dest = NULL; - Bot->enemy = NULL; + if (players[i].Bot->dest == players[i].Bot->enemy) + players[i].Bot->dest = NULL; + players[i].Bot->enemy = NULL; } } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 27bc50a83..4ef075720 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -3042,6 +3042,7 @@ void AActor::Tick () AActor *onmo; + int i; //assert (state != NULL); if (state == NULL) @@ -3231,36 +3232,35 @@ void AActor::Tick () { BotSupportCycles.Clock(); bglobal.m_Thinking = true; - - DBot *Bot; - TThinkerIterator it; - - while ((Bot = it.Next ()) != NULL) + for (i = 0; i < MAXPLAYERS; i++) { + if (!playeringame[i] || players[i].Bot == NULL) + continue; + if (flags3 & MF3_ISMONSTER) { if (health > 0 - && !Bot->enemy - && player ? !IsTeammate (Bot->player->mo) : true - && P_AproxDistance (Bot->player->mo->x-x, Bot->player->mo->y-y) < MAX_MONSTER_TARGET_DIST - && P_CheckSight (Bot->player->mo, this, SF_SEEPASTBLOCKEVERYTHING)) + && !players[i].Bot->enemy + && player ? !IsTeammate (players[i].mo) : true + && P_AproxDistance (players[i].mo->x-x, players[i].mo->y-y) < MAX_MONSTER_TARGET_DIST + && P_CheckSight (players[i].mo, this, SF_SEEPASTBLOCKEVERYTHING)) { //Probably a monster, so go kill it. - Bot->enemy = this; + players[i].Bot->enemy = this; } } else if (flags & MF_SPECIAL) { //Item pickup time //clock (BotWTG); - bglobal.WhatToGet (Bot->player->mo, this); + bglobal.WhatToGet (players[i].mo, this); //unclock (BotWTG); BotWTG++; } else if (flags & MF_MISSILE) { - if (!Bot->missile && (flags3 & MF3_WARNBOT)) + if (!players[i].Bot->missile && (flags3 & MF3_WARNBOT)) { //warn for incoming missiles. - if (target != Bot->player->mo && bglobal.Check_LOS (Bot->player->mo, this, ANGLE_90)) - Bot->missile = this; + if (target != players[i].mo && bglobal.Check_LOS (players[i].mo, this, ANGLE_90)) + players[i].Bot->missile = this; } } } diff --git a/src/statnums.h b/src/statnums.h index 344a328c8..25f644d1d 100644 --- a/src/statnums.h +++ b/src/statnums.h @@ -63,6 +63,7 @@ enum STAT_SECTOREFFECT, // All sector effects that cause floor and ceiling movement STAT_ACTORMOVER, // actor movers STAT_SCRIPTS, // The ACS thinker. This is to ensure that it can't tick before all actors called PostBeginPlay + STAT_BOT, // Bot thinker }; #endif \ No newline at end of file From 4b5d7361cd9389f24506e562b563fcc3f5298783 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 15 Nov 2014 11:47:05 +0200 Subject: [PATCH 50/75] Fixed exiting when window is closed by clicking close button This happened on 10.6 and later when fullscreen mode was used before windowed --- src/cocoa/i_backend_cocoa.mm | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 39b6642e2..3d24fe8b5 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1163,6 +1163,8 @@ static bool s_fullscreenNewAPI; // Hide window as nothing will be rendered at this point [m_window orderOut:nil]; + + I_ShutdownJoysticks(); } @@ -1187,10 +1189,6 @@ static bool s_fullscreenNewAPI; [window makeFirstResponder:self]; [window setAcceptsMouseMovedEvents:YES]; - NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton]; - [closeButton setAction:@selector(closeWindow:)]; - [closeButton setTarget:self]; - return window; } @@ -1297,6 +1295,10 @@ static bool s_fullscreenNewAPI; [m_window setContentSize:windowSize]; [m_window center]; + + NSButton* closeButton = [m_window standardWindowButton:NSWindowCloseButton]; + [closeButton setAction:@selector(terminate:)]; + [closeButton setTarget:NSApp]; } - (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height useHiDPI:(bool)hiDPI @@ -1496,14 +1498,6 @@ static bool s_fullscreenNewAPI; m_window = tempWindow; } - -- (void)closeWindow:(id)sender -{ - I_ShutdownJoysticks(); - - [NSApp terminate:sender]; -} - @end From b63bd56da8bb27ad32a36eb1ecc522f60b2677ac Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 15 Nov 2014 13:32:37 +0200 Subject: [PATCH 51/75] Removed .xib file and, therefore, dependency from ibtool Menu is now created in code --- src/CMakeLists.txt | 15 +- src/cocoa/i_backend_cocoa.mm | 108 ++++- src/cocoa/zdoom.xib | 874 ----------------------------------- 3 files changed, 108 insertions(+), 889 deletions(-) delete mode 100644 src/cocoa/zdoom.xib diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ef252fc3a..af323f5b3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -594,20 +594,7 @@ set( PLAT_COCOA_SOURCES if( APPLE ) if( OSX_COCOA_BACKEND ) - find_program( IBTOOL ibtool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin" ) - if( ${IBTOOL} STREQUAL "IBTOOL-NOTFOUND" ) - message( SEND_ERROR "ibtool can not be found to compile xib files." ) - endif( ${IBTOOL} STREQUAL "IBTOOL-NOTFOUND" ) - - set( NIB_FILE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/zdoom.nib" ) - add_custom_command( OUTPUT "${NIB_FILE}" - COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text - --compile "${NIB_FILE}" "${CMAKE_CURRENT_SOURCE_DIR}/cocoa/zdoom.xib" - COMMENT "Compiling zdoom.xib" ) - - set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_COCOA_SOURCES} "${NIB_FILE}" "${FMOD_LIBRARY}" ) - - set_source_files_properties( "${NIB_FILE}" PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_COCOA_SOURCES} "${FMOD_LIBRARY}" ) else( OSX_COCOA_BACKEND ) set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_SDL_SPECIAL_SOURCES} "${FMOD_LIBRARY}" ) set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} sdl/SDLMain.m ) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 3d24fe8b5..05ee3a06e 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1975,6 +1975,107 @@ int SDL_SetPalette(SDL_Surface* surface, int flags, SDL_Color* colors, int first } // extern "C" + +namespace +{ + +NSMenuItem* CreateApplicationMenu() +{ + NSMenu* menu = [NSMenu new]; + + [menu addItemWithTitle:[@"About " stringByAppendingString:@GAMENAME] + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:[@"Hide " stringByAppendingString:@GAMENAME] + action:@selector(hide:) + keyEquivalent:@"h"]; + [[menu addItemWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"] + setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask]; + [menu addItemWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:[@"Quit " stringByAppendingString:@GAMENAME] + action:@selector(terminate:) + keyEquivalent:@"q"]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + return menuItem; +} + +NSMenuItem* CreateEditMenu() +{ + NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Edit"]; + + [menu addItemWithTitle:@"Undo" + action:@selector(undo:) + keyEquivalent:@"z"]; + [menu addItemWithTitle:@"Redo" + action:@selector(redo:) + keyEquivalent:@"Z"]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Cut" + action:@selector(cut:) + keyEquivalent:@"x"]; + [menu addItemWithTitle:@"Copy" + action:@selector(copy:) + keyEquivalent:@"c"]; + [menu addItemWithTitle:@"Paste" + action:@selector(paste:) + keyEquivalent:@"v"]; + [menu addItemWithTitle:@"Delete" + action:@selector(delete:) + keyEquivalent:@""]; + [menu addItemWithTitle:@"Select All" + action:@selector(selectAll:) + keyEquivalent:@"a"]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + return menuItem; +} + +NSMenuItem* CreateWindowMenu() +{ + NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Window"]; + [NSApp setWindowsMenu:menu]; + + [menu addItemWithTitle:@"Minimize" + action:@selector(performMiniaturize:) + keyEquivalent:@"m"]; + [menu addItemWithTitle:@"Zoom" + action:@selector(performZoom:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Bring All to Front" + action:@selector(arrangeInFront:) + keyEquivalent:@""]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + return menuItem; +} + +void CreateMenu() +{ + NSMenu* menuBar = [NSMenu new]; + [menuBar addItem:CreateApplicationMenu()]; + [menuBar addItem:CreateEditMenu()]; + [menuBar addItem:CreateWindowMenu()]; + + [NSApp setMainMenu:menuBar]; +} + +} // unnamed namespace + + #ifdef main #undef main #endif // main @@ -2006,7 +2107,12 @@ int main(int argc, char** argv) NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; [NSApplication sharedApplication]; - [NSBundle loadNibNamed:@"zdoom" owner:NSApp]; + + // The following line isn't mandatory + // but it enables to run without application bundle + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + CreateMenu(); appCtrl = [ApplicationController new]; [NSApp setDelegate:appCtrl]; diff --git a/src/cocoa/zdoom.xib b/src/cocoa/zdoom.xib deleted file mode 100644 index 56099dc4b..000000000 --- a/src/cocoa/zdoom.xib +++ /dev/null @@ -1,874 +0,0 @@ - - - - 1060 - 11C74 - 851 - 1138.23 - 567.00 - - com.apple.InterfaceBuilder.CocoaPlugin - 851 - - - YES - - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - - - PluginDependencyRecalculationVersion - - - - YES - - NSApplication - - - FirstResponder - - - NSApplication - - - NSFontManager - - - Main Menu - - YES - - - ZDoom - - 2147483647 - - NSImage - NSMenuCheckmark - - - NSImage - NSMenuMixedState - - submenuAction: - - ZDoom - - YES - - - About ZDoom - - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Preferencesā€¦ - , - 1048576 - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Services - - 2147483647 - - - submenuAction: - - Services - - YES - - _NSServicesMenu - - - - - YES - YES - - - 2147483647 - - - - - - Hide ZDoom - h - 1048576 - 2147483647 - - - - - - Hide Others - h - 1572864 - 2147483647 - - - - - - Show All - - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Quit ZDoom - q - 1048576 - 2147483647 - - - - - _NSAppleMenu - - - - - Edit - - 2147483647 - - - submenuAction: - - Edit - - YES - - - Undo - z - 1048576 - 2147483647 - - - - - - Redo - Z - 1048576 - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Cut - x - 1048576 - 2147483647 - - - - - - Copy - c - 1048576 - 2147483647 - - - - - - Paste - v - 1048576 - 2147483647 - - - - - - Delete - - 2147483647 - - - - - - Select All - a - 1048576 - 2147483647 - - - - - - - - - Window - - 2147483647 - - - submenuAction: - - Window - - YES - - - Minimize - m - 1048576 - 2147483647 - - - - - - Zoom - - 2147483647 - - - - - - YES - YES - - - 2147483647 - - - - - - Bring All to Front - - 2147483647 - - - - - _NSWindowsMenu - - - - _NSMainMenu - - - - - YES - - - performMiniaturize: - - - - 37 - - - - arrangeInFront: - - - - 39 - - - - orderFrontStandardAboutPanel: - - - - 142 - - - - performZoom: - - - - 240 - - - - hide: - - - - 367 - - - - hideOtherApplications: - - - - 368 - - - - terminate: - - - - 369 - - - - unhideAllApplications: - - - - 370 - - - - cut: - - - - 738 - - - - paste: - - - - 739 - - - - redo: - - - - 742 - - - - undo: - - - - 746 - - - - copy: - - - - 752 - - - - delete: - - - - 753 - - - - selectAll: - - - - 755 - - - - - YES - - 0 - - YES - - - - - - -2 - - - File's Owner - - - -1 - - - First Responder - - - -3 - - - Application - - - 29 - - - YES - - - - - - - - 19 - - - YES - - - - - - 56 - - - YES - - - - - - 57 - - - YES - - - - - - - - - - - - - - - - 58 - - - - - 134 - - - - - 150 - - - - - 136 - - - - - 144 - - - - - 129 - - - - - 143 - - - - - 236 - - - - - 131 - - - YES - - - - - - 149 - - - - - 145 - - - - - 130 - - - - - 24 - - - YES - - - - - - - - - 92 - - - - - 5 - - - - - 239 - - - - - 23 - - - - - 371 - - - - - 681 - - - YES - - - - - - 682 - - - YES - - - - - - - - - - - - - 683 - - - - - 684 - - - - - 685 - - - - - 686 - - - - - 687 - - - - - 688 - - - - - 690 - - - - - 691 - - - - - - - YES - - YES - -3.IBPluginDependency - 129.IBPluginDependency - 129.ImportedFromIB2 - 130.IBPluginDependency - 130.ImportedFromIB2 - 130.editorWindowContentRectSynchronizationRect - 131.IBPluginDependency - 131.ImportedFromIB2 - 134.IBPluginDependency - 134.ImportedFromIB2 - 136.IBPluginDependency - 136.ImportedFromIB2 - 143.IBPluginDependency - 143.ImportedFromIB2 - 144.IBPluginDependency - 144.ImportedFromIB2 - 145.IBPluginDependency - 145.ImportedFromIB2 - 149.IBPluginDependency - 149.ImportedFromIB2 - 150.IBPluginDependency - 150.ImportedFromIB2 - 19.IBPluginDependency - 19.ImportedFromIB2 - 23.IBPluginDependency - 23.ImportedFromIB2 - 236.IBPluginDependency - 236.ImportedFromIB2 - 239.IBPluginDependency - 239.ImportedFromIB2 - 24.IBEditorWindowLastContentRect - 24.IBPluginDependency - 24.ImportedFromIB2 - 24.editorWindowContentRectSynchronizationRect - 29.IBEditorWindowLastContentRect - 29.IBPluginDependency - 29.ImportedFromIB2 - 29.WindowOrigin - 29.editorWindowContentRectSynchronizationRect - 5.IBPluginDependency - 5.ImportedFromIB2 - 56.IBPluginDependency - 56.ImportedFromIB2 - 57.IBEditorWindowLastContentRect - 57.IBPluginDependency - 57.ImportedFromIB2 - 57.editorWindowContentRectSynchronizationRect - 58.IBPluginDependency - 58.ImportedFromIB2 - 681.IBPluginDependency - 682.IBEditorWindowLastContentRect - 682.IBPluginDependency - 683.IBPluginDependency - 684.IBPluginDependency - 685.IBPluginDependency - 686.IBPluginDependency - 687.IBPluginDependency - 688.IBPluginDependency - 690.IBPluginDependency - 691.IBPluginDependency - 92.IBPluginDependency - 92.ImportedFromIB2 - - - YES - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - {{436, 809}, {64, 6}} - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - {{651, 262}, {194, 73}} - com.apple.InterfaceBuilder.CocoaPlugin - - {{525, 802}, {197, 73}} - {{514, 335}, {220, 20}} - com.apple.InterfaceBuilder.CocoaPlugin - - {74, 862} - {{11, 977}, {478, 20}} - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - - {{487, 217}, {195, 183}} - com.apple.InterfaceBuilder.CocoaPlugin - - {{23, 794}, {245, 183}} - com.apple.InterfaceBuilder.CocoaPlugin - - com.apple.InterfaceBuilder.CocoaPlugin - {{607, 182}, {151, 153}} - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - - - - - YES - - - YES - - - - - YES - - - YES - - - - 842 - - - - YES - - NSObject - - IBFrameworkSource - Print.framework/Headers/PDEPluginInterface.h - - - - - 0 - IBCocoaFramework - - com.apple.InterfaceBuilder.CocoaPlugin.macosx - - - - com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 - - - YES - ZDoom.xcodeproj - 3 - - YES - - YES - NSMenuCheckmark - NSMenuMixedState - - - YES - {9, 8} - {7, 2} - - - - From cc8d8928ae416c846a9f077b0c115518dcf9ee3e Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 15 Nov 2014 17:07:27 +0200 Subject: [PATCH 52/75] Removed explicit set of activation policy for application Bundle is used anyway but this method requires 10.6 or higher --- src/cocoa/i_backend_cocoa.mm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 05ee3a06e..b30ab1ff0 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -2108,10 +2108,6 @@ int main(int argc, char** argv) [NSApplication sharedApplication]; - // The following line isn't mandatory - // but it enables to run without application bundle - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - CreateMenu(); appCtrl = [ApplicationController new]; From 398b36064b44084a0dbab99d143e2a4ab38e546e Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 15 Nov 2014 17:27:23 +0200 Subject: [PATCH 53/75] Added usage of OpenGL client storage extension This improves performance significantly of 10.4 --- src/cocoa/i_backend_cocoa.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index b30ab1ff0..22ed23254 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -1375,6 +1375,7 @@ static bool s_fullscreenNewAPI; glGenTextures(1, &m_softwareRenderingTexture); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_softwareRenderingTexture); + glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE); glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); From 65ce06f274a2ee5ed19cf010aec5d7527666440a Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sat, 15 Nov 2014 19:11:01 +0200 Subject: [PATCH 54/75] Added ability to run without application bundle You just need executable, zdoom.pk3 and IWAD --- src/cocoa/i_backend_cocoa.mm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 22ed23254..05aacd6c8 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -119,6 +119,21 @@ enum #endif // prior to 10.5 +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 + +enum +{ + NSApplicationActivationPolicyRegular +}; + +typedef NSInteger NSApplicationActivationPolicy; + +@interface NSApplication(ActivationPolicy) +- (BOOL)setActivationPolicy:(NSApplicationActivationPolicy)activationPolicy; +@end + +#endif // prior to 10.6 + #include // Avoid collision between DObject class and Objective-C @@ -2109,6 +2124,13 @@ int main(int argc, char** argv) [NSApplication sharedApplication]; + // The following code isn't mandatory, + // but it enables to run the application without a bundle + if ([NSApp respondsToSelector:@selector(setActivationPolicy:)]) + { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + } + CreateMenu(); appCtrl = [ApplicationController new]; From bcdf22b910b69ca66a203a8f96fe54fc8df5cca0 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 16 Nov 2014 09:30:57 +0200 Subject: [PATCH 55/75] Fixed potential crash in word expansion API 10.5.8 PPC crashed 100%, some versions of 10.6 i386 were affected too --- src/sdl/iwadpicker_cocoa.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index c3b167c09..d1364fa81 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -428,7 +428,7 @@ static void RestartWithParameters(const char* iwadPath, NSString* parameters) [arguments addObject:currentParameter]; } - wordexp_t expansion; + wordexp_t expansion = {}; if (0 == wordexp([parameters UTF8String], &expansion, 0)) { From 93a732f0cab3d95d23ec99712b13ffbf8bf67ee9 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 16 Nov 2014 10:25:25 +0200 Subject: [PATCH 56/75] Fixed empty application menu on 10.5 and earlier --- src/cocoa/i_backend_cocoa.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 05aacd6c8..81c487cc7 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -2021,6 +2021,11 @@ NSMenuItem* CreateApplicationMenu() NSMenuItem* menuItem = [NSMenuItem new]; [menuItem setSubmenu:menu]; + if ([NSApp respondsToSelector:@selector(setAppleMenu:)]) + { + [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; + } + return menuItem; } From fd85e116e54a91553e243004d208cf2411031957 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 16 Nov 2014 10:29:03 +0200 Subject: [PATCH 57/75] Fixed another potential crash in word expansion API Calling wordfree() may lead to a crash when wordexp_t instance is not zero-initialized This happened usually on older OS X like 10.5 or 10.6 --- src/sound/music_midi_timidity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp index 4acda49b9..38ba8557e 100644 --- a/src/sound/music_midi_timidity.cpp +++ b/src/sound/music_midi_timidity.cpp @@ -433,7 +433,7 @@ bool TimidityPPMIDIDevice::LaunchTimidity () } int forkres; - wordexp_t words; + wordexp_t words = {}; switch (wordexp (CommandLine.GetChars(), &words, 0)) { From 4b2af7074e038ae442e8cb8a56858ecd90cfadd2 Mon Sep 17 00:00:00 2001 From: Edoardo Prezioso Date: Sun, 16 Nov 2014 11:29:16 +0100 Subject: [PATCH 58/75] - Improve the 64 bit incompatibility patch. - Reorder the operands. First the numerator, then the denominator :) . - Add a comment regarding the motivations for this change. --- src/p_map.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/p_map.cpp b/src/p_map.cpp index b406dff7b..c0e5c3baa 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -736,8 +736,18 @@ bool PIT_CheckLine(line_t *ld, const FBoundingBox &box, FCheckPosition &tm) else { // Find the point on the line closest to the actor's center, and use // that to calculate openings - SQWORD r_den = (SQWORD(ld->dx)*ld->dx + SQWORD(ld->dy)*ld->dy) / (1 << 24); + // [EP] Use 64 bit integers in order to keep the exact result of the + // multiplication, because in the worst case, which is by the map limit + // (32767 units, which is 2147418112 in fixed_t notation), the result + // would occupy 62 bits (if I consider also the addition with another + // and possible 62 bit value, it's 63 bits). + // This privilege could not be available if the starting data would be + // 64 bit long. + // With this, the division is exact as the 32 bit float counterpart, + // though I don't know why I had to discard the first 24 bits from the + // divisor. SQWORD r_num = ((SQWORD(tm.x - ld->v1->x)*ld->dx) + (SQWORD(tm.y - ld->v1->y)*ld->dy)); + SQWORD r_den = (SQWORD(ld->dx)*ld->dx + SQWORD(ld->dy)*ld->dy) / (1 << 24); fixed_t r = (fixed_t)(r_num / r_den); /* Printf ("%d:%d: %d (%d %d %d %d) (%d %d %d %d)\n", level.time, ld-lines, r, ld->frontsector->floorplane.a, From 1bda54f3cd8fc97c6a1624216dc2f9d8a21a31d4 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 16 Nov 2014 13:15:56 +0200 Subject: [PATCH 59/75] Fixed a few endian issues in ACS system KDiZD Intro, a.k.a. Title Map, is now played correctly; camera on Z1M1 is functional --- src/p_acs.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 03de8b3df..0a8eef879 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -1873,7 +1873,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) funcm->HasReturnValue = funcf->HasReturnValue; funcm->ImportNum = funcf->ImportNum; funcm->LocalCount = funcf->LocalCount; - funcm->Address = funcf->Address; + funcm->Address = LittleLong(funcf->Address); } } @@ -2058,7 +2058,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) const char *const parse = (char *)&chunk[2]; DWORD i; - for (i = 0; i < chunk[1]; ) + for (i = 0; i < LittleLong(chunk[1]); ) { if (parse[i]) { @@ -2351,7 +2351,7 @@ void FBehavior::LoadScriptsDirectory () scripts.b = FindChunk (MAKE_ID('S','F','L','G')); if (scripts.dw != NULL) { - max = scripts.dw[1] / 4; + max = LittleLong(scripts.dw[1]) / 4; scripts.dw += 2; for (i = max; i > 0; --i, scripts.w += 2) { @@ -2367,7 +2367,7 @@ void FBehavior::LoadScriptsDirectory () scripts.b = FindChunk (MAKE_ID('S','V','C','T')); if (scripts.dw != NULL) { - max = scripts.dw[1] / 4; + max = LittleLong(scripts.dw[1]) / 4; scripts.dw += 2; for (i = max; i > 0; --i, scripts.w += 2) { @@ -2681,7 +2681,7 @@ BYTE *FBehavior::FindChunk (DWORD id) const { return chunk; } - chunk += ((DWORD *)chunk)[1] + 8; + chunk += LittleLong(((DWORD *)chunk)[1]) + 8; } return NULL; } @@ -2689,14 +2689,14 @@ BYTE *FBehavior::FindChunk (DWORD id) const BYTE *FBehavior::NextChunk (BYTE *chunk) const { DWORD id = *(DWORD *)chunk; - chunk += ((DWORD *)chunk)[1] + 8; + chunk += LittleLong(((DWORD *)chunk)[1]) + 8; while (chunk != NULL && chunk < Data + DataSize) { if (((DWORD *)chunk)[0] == id) { return chunk; } - chunk += ((DWORD *)chunk)[1] + 8; + chunk += LittleLong(((DWORD *)chunk)[1]) + 8; } return NULL; } From 64e34d883b74db7a3868cd81ba9556dac4ce9138 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Mon, 17 Nov 2014 14:54:46 +0200 Subject: [PATCH 60/75] Fixed build on UNIX systems other than OS X --- src/CMakeLists.txt | 9 ++++++--- src/sdl/i_timer.cpp | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index af323f5b3..fb1eab35a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -593,15 +593,18 @@ set( PLAT_COCOA_SOURCES cocoa/zdoom.icns ) if( APPLE ) + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} "${FMOD_LIBRARY}" ) + if( OSX_COCOA_BACKEND ) - set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_COCOA_SOURCES} "${FMOD_LIBRARY}" ) + set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} ${PLAT_COCOA_SOURCES} ) else( OSX_COCOA_BACKEND ) - set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_SDL_SPECIAL_SOURCES} "${FMOD_LIBRARY}" ) - set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} sdl/SDLMain.m ) + set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} ${PLAT_SDL_SPECIAL_SOURCES} sdl/SDLMain.m ) endif( OSX_COCOA_BACKEND ) set_source_files_properties( cocoa/zdoom.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) set_source_files_properties( "${FMOD_LIBRARY}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks ) +else( APPLE ) + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_SDL_SPECIAL_SOURCES} ) endif( APPLE ) if( WIN32 ) diff --git a/src/sdl/i_timer.cpp b/src/sdl/i_timer.cpp index 1b73edd5b..e3f9906b6 100644 --- a/src/sdl/i_timer.cpp +++ b/src/sdl/i_timer.cpp @@ -2,6 +2,7 @@ // Moved from sdl/i_system.cpp #include +#include #include #include From 601852d22439bc753b1cedd5269c1b899aaa7c37 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Mon, 17 Nov 2014 21:55:24 -0500 Subject: [PATCH 61/75] - Fixed: fixrtext isn't needed with Win64 builds. --- src/CMakeLists.txt | 12 ++++++------ tools/CMakeLists.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e887775fe..2484b3ad9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -357,19 +357,19 @@ if( NOT NO_ASM ) set( ASM_FLAGS -f win32 -DWIN32 -i${CMAKE_CURRENT_SOURCE_DIR}/ ) endif( X64 ) endif( UNIX ) - if( WIN32 ) + if( WIN32 AND NOT X64 ) set( FIXRTEXT fixrtext ) - else( WIN32 ) + else( WIN32 AND NOT X64 ) set( FIXRTEXT "" ) - endif( WIN32 ) + endif( WIN32 AND NOT X64 ) message( STATUS "Selected assembler: ${ASSEMBLER}" ) MACRO( ADD_ASM_FILE indir infile ) set( ASM_OUTPUT_${infile} "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/${indir}/${infile}${ASM_OUTPUT_EXTENSION}" ) - if( WIN32 ) + if( WIN32 AND NOT X64 ) set( FIXRTEXT_${infile} COMMAND ${FIXRTEXT} "${ASM_OUTPUT_${infile}}" ) - else( WIN32 ) + else( WIN32 AND NOT X64 ) set( FIXRTEXT_${infile} COMMAND "" ) - endif( WIN32 ) + endif( WIN32 AND NOT X64 ) add_custom_command( OUTPUT ${ASM_OUTPUT_${infile}} COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/${indir} COMMAND ${ASSEMBLER} ${ASM_FLAGS} -o"${ASM_OUTPUT_${infile}}" "${CMAKE_CURRENT_SOURCE_DIR}/${indir}/${infile}${ASM_SOURCE_EXTENSION}" diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 08a1c4f4b..4f9e9ae22 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required( VERSION 2.4 ) add_subdirectory( lemon ) add_subdirectory( re2c ) -if( WIN32 ) +if( WIN32 AND NOT CMAKE_SIZEOF_VOID_P MATCHES "8" ) add_subdirectory( fixrtext ) -endif( WIN32 ) +endif( WIN32 AND NOT CMAKE_SIZEOF_VOID_P MATCHES "8" ) add_subdirectory( updaterevision ) add_subdirectory( zipdir ) From 99b2cfa14715f6b470ac8531f6b396f88d24d413 Mon Sep 17 00:00:00 2001 From: Edward Richardson Date: Thu, 20 Nov 2014 18:57:40 +1300 Subject: [PATCH 62/75] Added APROP_DamageMultiply - Used with Set/GetActorProperty, adds a generic multiplier for damage a source deals. --- src/actor.h | 1 + src/p_acs.cpp | 6 ++++++ src/p_interaction.cpp | 10 ++++++++-- src/p_mobj.cpp | 13 +++++++++++-- src/version.h | 2 +- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/actor.h b/src/actor.h index 52a50b04e..f46ee7148 100644 --- a/src/actor.h +++ b/src/actor.h @@ -971,6 +971,7 @@ public: FNameNoInit DamageType; FNameNoInit DamageTypeReceived; fixed_t DamageFactor; + fixed_t DamageMultiply; FNameNoInit PainType; FNameNoInit DeathType; diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 03de8b3df..cc2e0f847 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -3673,6 +3673,7 @@ enum APROP_AttackZOffset = 40, APROP_StencilColor = 41, APROP_Friction = 42, + APROP_DamageMultiplier=43, }; // These are needed for ACS's APROP_RenderStyle @@ -3862,6 +3863,10 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) actor->DamageFactor = value; break; + case APROP_DamageMultiplier: + actor->DamageMultiply = value; + break; + case APROP_MasterTID: AActor *other; other = SingleActorFromTID (value, NULL); @@ -3933,6 +3938,7 @@ int DLevelScript::GetActorProperty (int tid, int property, const SDWORD *stack, case APROP_Speed: return actor->Speed; case APROP_Damage: return actor->Damage; // Should this call GetMissileDamage() instead? case APROP_DamageFactor:return actor->DamageFactor; + case APROP_DamageMultiplier: return actor->DamageMultiply; case APROP_Alpha: return actor->alpha; case APROP_RenderStyle: for (int style = STYLE_None; style < STYLE_Count; ++style) { // Check for a legacy render style that matches. diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 4ee86d82c..efed105f8 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -1065,10 +1065,16 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, } } // Handle active damage modifiers (e.g. PowerDamage) - if (source != NULL && source->Inventory != NULL) + if (source != NULL) { int olddam = damage; - source->Inventory->ModifyDamage(olddam, mod, damage, false); + + damage = FixedMul(damage, source->DamageMultiply); + if (source->Inventory != NULL) + { + source->Inventory->ModifyDamage(olddam, mod, damage, false); + } + if (olddam != damage && damage <= 0) { // Still allow FORCEPAIN if (MustForcePain(target, inflictor)) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index c7bfcea21..39531c6ef 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -313,8 +313,16 @@ void AActor::Serialize (FArchive &arc) } arc << lastpush << lastbump << PainThreshold - << DamageFactor - << WeaveIndexXY << WeaveIndexZ + << DamageFactor; + if (SaveVersion >= 4516) + { + arc << DamageMultiply; + } + else + { + DamageMultiply = FRACUNIT; + } + arc << WeaveIndexXY << WeaveIndexZ << PoisonDamageReceived << PoisonDurationReceived << PoisonPeriodReceived << Poisoner << PoisonDamage << PoisonDuration << PoisonPeriod; if (SaveVersion >= 3235) @@ -3868,6 +3876,7 @@ AActor *AActor::StaticSpawn (const PClass *type, fixed_t ix, fixed_t iy, fixed_t actor->touching_sectorlist = NULL; // NULL head of sector list // phares 3/13/98 if (G_SkillProperty(SKILLP_FastMonsters)) actor->Speed = actor->GetClass()->Meta.GetMetaFixed(AMETA_FastSpeed, actor->Speed); + actor->DamageMultiply = FRACUNIT; // set subsector and/or block links diff --git a/src/version.h b/src/version.h index f091f416a..15d01d315 100644 --- a/src/version.h +++ b/src/version.h @@ -76,7 +76,7 @@ const char *GetVersionString(); // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4515 +#define SAVEVER 4516 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) From e303833e5fb2db4c0c84fc8499ad2ec1ecf44f0d Mon Sep 17 00:00:00 2001 From: Edward Richardson Date: Thu, 20 Nov 2014 22:12:16 +1300 Subject: [PATCH 63/75] Inventory damage needs multiplying first --- src/p_interaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index efed105f8..fb947f309 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -1069,11 +1069,11 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, { int olddam = damage; - damage = FixedMul(damage, source->DamageMultiply); if (source->Inventory != NULL) { source->Inventory->ModifyDamage(olddam, mod, damage, false); } + damage = FixedMul(damage, source->DamageMultiply); if (olddam != damage && damage <= 0) { // Still allow FORCEPAIN From 3437f4fcaba244a290a6abe4f8dcccc8c35a9862 Mon Sep 17 00:00:00 2001 From: Edward Richardson Date: Fri, 21 Nov 2014 17:49:57 +1300 Subject: [PATCH 64/75] Check ACS module size in saved games --- src/p_acs.cpp | 8 ++++++++ src/p_acs.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 03de8b3df..be4dea612 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -1581,20 +1581,28 @@ void FBehavior::StaticSerializeModuleStates (FArchive &arc) for (modnum = 0; modnum < StaticModules.Size(); ++modnum) { FBehavior *module = StaticModules[modnum]; + int ModSize = module->GetDataSize(); if (arc.IsStoring()) { arc.WriteString (module->ModuleName); + if (SaveVersion >= 4516) arc << ModSize; } else { char *modname = NULL; arc << modname; + if (SaveVersion >= 4516) arc << ModSize; if (stricmp (modname, module->ModuleName) != 0) { delete[] modname; I_Error ("Level was saved with a different set of ACS modules."); } + else if (ModSize != module->GetDataSize()) + { + delete[] modname; + I_Error("ACS module %s has changed from what was saved. (Have %d bytes, save has %d bytes)", module->ModuleName, module->GetDataSize(), ModSize); + } delete[] modname; } module->SerializeVars (arc); diff --git a/src/p_acs.h b/src/p_acs.h index 02544e367..88016f0db 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -308,6 +308,7 @@ public: int GetScriptIndex (const ScriptPtr *ptr) const { ptrdiff_t index = ptr - Scripts; return index >= NumScripts ? -1 : (int)index; } ScriptPtr *GetScriptPtr(int index) const { return index >= 0 && index < NumScripts ? &Scripts[index] : NULL; } int GetLumpNum() const { return LumpNum; } + int GetDataSize() const { return DataSize; } const char *GetModuleName() const { return ModuleName; } ACSProfileInfo *GetFunctionProfileData(int index) { return index >= 0 && index < NumFunctions ? &FunctionProfileData[index] : NULL; } ACSProfileInfo *GetFunctionProfileData(ScriptFunction *func) { return GetFunctionProfileData((int)(func - (ScriptFunction *)Functions)); } From c494063eb926cb73811b41457cea7fe5fcb917b9 Mon Sep 17 00:00:00 2001 From: Edward Richardson Date: Fri, 21 Nov 2014 17:53:33 +1300 Subject: [PATCH 65/75] Forgot version bump --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index f091f416a..15d01d315 100644 --- a/src/version.h +++ b/src/version.h @@ -76,7 +76,7 @@ const char *GetVersionString(); // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4515 +#define SAVEVER 4516 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) From e9075334a3cb515c88f1bb4822cac53affde46fd Mon Sep 17 00:00:00 2001 From: khokh2001 Date: Sun, 23 Nov 2014 00:36:22 +0900 Subject: [PATCH 66/75] new opl3 emulator --- src/CMakeLists.txt | 1 + src/oplsynth/mlopl_io.cpp | 4 +- src/oplsynth/nukedopl3.cpp | 1145 ++++++++++++++++++++++++++++++++++++ src/oplsynth/nukedopl3.h | 118 ++++ src/oplsynth/opl.h | 1 + wadsrc/static/menudef.txt | 1 + zdoom.vcproj | 8 + 7 files changed, 1276 insertions(+), 2 deletions(-) create mode 100644 src/oplsynth/nukedopl3.cpp create mode 100644 src/oplsynth/nukedopl3.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2484b3ad9..605e32f23 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -992,6 +992,7 @@ add_executable( zdoom WIN32 oplsynth/opl_mus_player.cpp oplsynth/dosbox/opl.cpp oplsynth/OPL3.cpp + oplsynth/nukedopl3.cpp resourcefiles/ancientzip.cpp resourcefiles/file_7z.cpp resourcefiles/file_grp.cpp diff --git a/src/oplsynth/mlopl_io.cpp b/src/oplsynth/mlopl_io.cpp index b9d06629f..691463470 100644 --- a/src/oplsynth/mlopl_io.cpp +++ b/src/oplsynth/mlopl_io.cpp @@ -323,7 +323,7 @@ int OPLio::OPLinit(uint numchips, bool stereo, bool initopl3) { assert(numchips >= 1 && numchips <= countof(chips)); uint i; - IsOPL3 = (opl_core == 1 || opl_core == 2); + IsOPL3 = (opl_core == 1 || opl_core == 2 || opl_core == 3); memset(chips, 0, sizeof(chips)); if (IsOPL3) @@ -332,7 +332,7 @@ int OPLio::OPLinit(uint numchips, bool stereo, bool initopl3) } for (i = 0; i < numchips; ++i) { - OPLEmul *chip = IsOPL3 ? (opl_core == 1 ? DBOPLCreate(stereo) : JavaOPLCreate(stereo)) : YM3812Create(stereo); + OPLEmul *chip = IsOPL3 ? (opl_core == 1 ? DBOPLCreate(stereo) : (opl_core == 2 ? JavaOPLCreate(stereo) : NukedOPL3Create(stereo))) : YM3812Create(stereo); if (chip == NULL) { break; diff --git a/src/oplsynth/nukedopl3.cpp b/src/oplsynth/nukedopl3.cpp new file mode 100644 index 000000000..95d03ae8d --- /dev/null +++ b/src/oplsynth/nukedopl3.cpp @@ -0,0 +1,1145 @@ +/* +* Copyright (C) 2013-2014 Nuke.YKT +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Nuked Yamaha YMF262(aka OPL3) emulator. + Thanks: + MAME Development Team: + Feedback and Rhythm part calculation information. + forums.submarine.org.uk(carbon14, opl3): + Tremolo and phase generator calculation information. +*/ + +//version 1.4.2 + +/* Changelog: + v1.1: + Vibrato's sign fix + v1.2: + Operator key fix + Corrected 4-operator mode + Corrected rhythm mode + Some small fixes + v1.2.1: + Small envelope generator fix + Removed EX_Get function(not used) + v1.3: + Complete rewrite + (Not released) + v1.4: + New envelope and waveform generator + Some small fixes. + (Not released) + v1.4.1: + Envelope generator rate calculation fix + (Not released) + v1.4.2: + Version for ZDoom. +*/ + + +/* Verified: + Noise generator. + Waveform generator. + Envelope generator increase table. + Tremolo. +*/ + +/* TODO: + Verify: + kslrom[15] value(is it 128?). + Sustain level = 15. + Vibrato, Phase generator. + Rhythm part. + Envelope generator state switching(decay->sustain when egt = 1 and decay->release). + Feedback. + Register write. + 4-operator. +*/ + +#include +#include +#include "nukedopl3.h" + +// Channel types + +enum { + ch_4op2, + ch_2op, + ch_4op, + ch_drum +}; + +// Envelope generator states + +enum { + eg_off, + eg_attack, + eg_decay, + eg_sustain, + eg_release +}; + +// Envelope key types + +enum { + egk_norm = 1, + egk_drum = 2 +}; + +// +// logsin table +// + +static const Bit16u logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +// +// exp table +// + +static const Bit16u exprom[256] = { + 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, + 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, + 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, + 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, + 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, + 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, + 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, + 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, + 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, + 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, + 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, + 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, + 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, + 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, + 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, + 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa +}; + +// +// freq mult table multiplied by 2 +// +// 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 +// + +static const Bit8u mt[16] = { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 }; + +// +// ksl table +// + +static const Bit8u kslrom[16] = { 0, 64, 80, 90, 96, 102, 106, 110, 112, 116, 118, 120, 122, 124, 126, 127 }; + +static const Bit8u kslshift[4] = { 8, 1, 2, 0 }; + +// +// LFO vibrato +// + +static const Bit8u vib_table[8] = { 3, 1, 0, 1, 3, 1, 0, 1 }; +static const Bit8s vibsgn_table[8] = { 1, 1, 1, 1, -1, -1, -1, -1 }; + +// +// envelope generator constants +// + +static const Bit8u eg_incstep[3][4][8] = { + { { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } }, + { { 0, 1, 0, 1, 0, 1, 0, 1 }, { 1, 1, 0, 1, 0, 1, 0, 1 }, { 1, 1, 0, 1, 1, 1, 0, 1 }, { 1, 1, 1, 1, 1, 1, 0, 1 } }, + { { 1, 1, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 2, 2, 1, 1 }, { 2, 2, 2, 2, 2, 2, 1, 1 } } +}; + +// +// address decoding +// + +static const Bit8s ad_slot[0x20] = { 0, 2, 4, 1, 3, 5, -1, -1, 6, 8, 10, 7, 9, 11, -1, -1, 12, 14, 16, 13, 15, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + +static const Bit8u op_offset[18] = { 0x00, 0x03, 0x01, 0x04, 0x02, 0x05, 0x08, 0x0b, 0x09, 0x0c, 0x0a, 0x0d, 0x10, 0x13, 0x11, 0x14, 0x12, 0x15 }; + + + +static const Bit8u eg_incdesc[16] = { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2 +}; + +static const Bit8s eg_incsh[16] = { + 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -1, -2 +}; + +typedef Bit16s(*envelope_sinfunc)(Bit16u phase, Bit16u envelope); +typedef void(*envelope_genfunc)(slot *slott); + +// +// Phase generator +// + +void PG_Generate(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + channel *chan = &opl->Channels[op / 2]; + Bit16u fnum = chan->f_number; + if (slt->vibrato) { + Bit8u fnum_high = chan->f_number >> (7 + vib_table[opl->vib_pos] + (!opl->dvb)); + fnum += fnum_high * vibsgn_table[opl->vib_pos]; + } + slt->PG_pos += (((fnum << chan->block) >> 1) * mt[slt->mult]) >> 1; +} + +// +// Envelope generator +// + +Bit16s envelope_calcexp(Bit32u level) { + return ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 1) >> (level >> 8); +} + +Bit16s envelope_calcsin0(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + Bit16u neg = 0; + if (phase & 0x200 && (phase & 0x1ff)) { + phase--; + neg = ~0; + } + if (phase & 0x100) { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)) ^ neg; +} + +Bit16s envelope_calcsin1(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x200) { + out = 0x1000; + } + else if (phase & 0x100) { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin2(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x100) { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin3(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x100) { + out = 0x1000; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin4(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + Bit16u neg = 0; + if ((phase & 0x300) == 0x100 && (phase & 0xff)) { + phase--; + neg = ~0; + } + if (phase & 0x200) { + out = 0x1000; + } + else if (phase & 0x80) { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else { + out = logsinrom[(phase << 1) & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)) ^ neg; +} + +Bit16s envelope_calcsin5(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x200) { + out = 0x1000; + } + else if (phase & 0x80) { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else { + out = logsinrom[(phase << 1) & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin6(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u neg = 0; + if (phase & 0x200 && (phase & 0x1ff)) { + phase--; + neg = ~0; + } + return envelope_calcexp(envelope << 3) ^ neg; +} + +Bit16s envelope_calcsin7(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + Bit16u neg = 0; + if (phase & 0x200 && (phase & 0x1ff)) { + phase--; + neg = ~0; + phase = (phase & 0x1ff) ^ 0x1ff; + } + out = phase << 3; + return envelope_calcexp(out + (envelope << 3)) ^ neg; +} + +envelope_sinfunc envelope_sin[8] = { + envelope_calcsin0, + envelope_calcsin1, + envelope_calcsin2, + envelope_calcsin3, + envelope_calcsin4, + envelope_calcsin5, + envelope_calcsin6, + envelope_calcsin7 +}; + +void envelope_gen_off(slot *slott); +void envelope_gen_change(slot *slott); +void envelope_gen_attack(slot *slott); +void envelope_gen_decay(slot *slott); +void envelope_gen_sustain(slot *slott); +void envelope_gen_release(slot *slott); + +envelope_genfunc envelope_gen[6] = { + envelope_gen_off, + envelope_gen_attack, + envelope_gen_decay, + envelope_gen_sustain, + envelope_gen_release, + envelope_gen_change +}; + +enum envelope_gen_num { + envelope_gen_num_off = 0, + envelope_gen_num_attack = 1, + envelope_gen_num_decay = 2, + envelope_gen_num_sustain = 3, + envelope_gen_num_release = 4, + envelope_gen_num_change = 5 +}; + +void envelope_gen_off(slot *slott) { + slott->EG_out = 0x1ff; +} + +void envelope_gen_change(slot *slott) { + slott->eg_gen = slott->eg_gennext; +} + +void envelope_gen_attack(slot *slott) { + slott->EG_out += ((~slott->EG_out) *slott->eg_inc) >> 3; + if (slott->EG_out < 0x00) { + slott->EG_out = 0x00; + } + if (slott->EG_out == 0x00) { + slott->eg_gen = envelope_gen_num_change; + slott->eg_gennext = envelope_gen_num_decay; + } +} + +void envelope_gen_decay(slot *slott) { + slott->EG_out += slott->eg_inc; + if (slott->EG_out >= slott->EG_sl << 4) { + slott->eg_gen = envelope_gen_num_change; + slott->eg_gennext = envelope_gen_num_sustain; + } +} + +void envelope_gen_sustain(slot *slott) { + if (!slott->EG_type) { + envelope_gen_release(slott); + } +} + +void envelope_gen_release(slot *slott) { + slott->EG_out += slott->eg_inc; + if (slott->EG_out >= 0x1ff) { + slott->eg_gen = envelope_gen_num_change; + slott->eg_gennext = envelope_gen_num_off; + } +} + +Bit8u EG_CalcRate(chip *opl, Bit8u op, Bit8u rate) { + slot *slt = &opl->OPs[op]; + channel *chan = &opl->Channels[op / 2]; + if (rate == 0x00) { + return 0x00; + } + Bit8u rof = slt->ksr ? chan->ksv : (chan->ksv >> 2); + Bit8u rat = (rate << 2) + rof; + if (rat > 0x3c) { + rat = 0x3c; + } + return rat; +} + +void envelope_calc(chip *opl, Bit8u op) { + slot *slott = &opl->OPs[op]; + Bit16u timer = opl->timer; + Bit8u rate_h, rate_l; + Bit8u rate; + Bit8u reg_rate = 0;; + switch (slott->eg_gen) { + case envelope_gen_num_attack: + reg_rate = slott->EG_ar; + break; + case envelope_gen_num_decay: + reg_rate = slott->EG_dr; + break; + case envelope_gen_num_sustain: + case envelope_gen_num_release: + reg_rate = slott->EG_rr; + break; + } + rate = EG_CalcRate(opl, op, reg_rate); + rate_h = rate >> 2; + rate_l = rate & 3; + Bit8u inc = 0; + if (slott->eg_gen == envelope_gen_num_attack && rate_h == 0x0f) { + inc = 8; + } + else if (eg_incsh[rate_h] > 0) { + if ((timer & ((1 << eg_incsh[rate_h]) - 1)) == 0) { + inc = eg_incstep[eg_incdesc[rate_h]][rate_l][((timer) >> eg_incsh[rate_h]) & 0x07]; + } + } + else { + inc = eg_incstep[eg_incdesc[rate_h]][rate_l][timer & 0x07] << (-eg_incsh[rate_h]); + } + slott->eg_inc = inc; + envelope_gen[slott->eg_gen](slott); +} + +void EG_UpdateKSL(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + channel *chan = &opl->Channels[op / 2]; + Bit8u fnum_high = (chan->f_number >> 6) & 0x0f; + Bit16s ksl = (kslrom[fnum_high] << 1) - ((chan->block ^ 0x07) << 5) - 0x20; + if (ksl < 0x00) { + ksl = 0x00; + } + slt->EG_ksl = ksl >> kslshift[slt->ksl]; +} + +void EG_Generate(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + envelope_calc(opl, op); + slt->EG_mout = slt->EG_out + slt->EG_ksl + (slt->EG_tl << 2); + if (slt->tremolo) { + slt->EG_mout += opl->trem_val; + } + if (slt->EG_mout > 0x1ff) { + slt->EG_mout = 0x1ff; + } +} + +void EG_KeyOn(chip *opl, Bit8u op, Bit8u type) { + slot *slt = &opl->OPs[op]; + if (!slt->key) { + slt->EG_state = eg_attack; + slt->eg_gen = envelope_gen_num_change; + slt->eg_gennext = envelope_gen_num_attack; + slt->PG_pos = 0; + } + slt->key |= type; +} + +void EG_KeyOff(chip *opl, Bit8u op, Bit8u type) { + slot *slt = &opl->OPs[op]; + if (slt->key) { + slt->key &= (~type); + if (slt->key == 0x00) { + slt->EG_state = eg_release; + slt->eg_gen = envelope_gen_num_change; + slt->eg_gennext = envelope_gen_num_release; + } + } +} + +// +// Noise Generator +// + +void N_Generate(chip *opl) { + if (opl->noise & 1) { + opl->noise ^= 0x800302; + } + opl->noise >>= 1; +} + +// +// Operator(Slot) +// + +void OP_Update20(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + slt->tremolo = (opl->opl_memory[0x20 + slt->offset] >> 7); + slt->vibrato = (opl->opl_memory[0x20 + slt->offset] >> 6) & 0x01; + slt->EG_type = (opl->opl_memory[0x20 + slt->offset] >> 5) & 0x01; + slt->ksr = (opl->opl_memory[0x20 + slt->offset] >> 4) & 0x01; + slt->mult = (opl->opl_memory[0x20 + slt->offset]) & 0x0f; +} + +void OP_Update40(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + slt->EG_tl = (opl->opl_memory[0x40 + slt->offset]) & 0x3f; + slt->ksl = (opl->opl_memory[0x40 + slt->offset] >> 6) & 0x03; + EG_UpdateKSL(opl, op); +} + +void OP_Update60(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + slt->EG_dr = (opl->opl_memory[0x60 + slt->offset]) & 0x0f; + slt->EG_ar = (opl->opl_memory[0x60 + slt->offset] >> 4) & 0x0f; +} + +void OP_Update80(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + slt->EG_rr = (opl->opl_memory[0x80 + slt->offset]) & 0x0f; + slt->EG_sl = (opl->opl_memory[0x80 + slt->offset] >> 4) & 0x0f; + if (slt->EG_sl == 0x0f) { + slt->EG_sl = 0x1f; + } +} + +void OP_UpdateE0(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + slt->waveform = opl->opl_memory[0xe0 + slt->offset] & 0x07; + if (!opl->newm) { + slt->waveform &= 0x03; + } +} + +void OP_GeneratePhase(chip *opl, Bit8u op, Bit16u phase) { + slot *slt = &opl->OPs[op]; + slt->out = envelope_sin[slt->waveform](phase, slt->EG_out); +} + +void OP_Generate(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + slt->out = envelope_sin[slt->waveform]((Bit16u)((slt->PG_pos >> 9) + (*slt->mod)), slt->EG_mout); +} + +void OP_GenerateZM(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + slt->out = envelope_sin[slt->waveform]((Bit16u)(slt->PG_pos >> 9), slt->EG_mout); +} + +void OP_CalcFB(chip *opl, Bit8u op) { + slot *slt = &opl->OPs[op]; + channel *chan = &opl->Channels[op / 2]; + slt->prevout[1] = slt->prevout[0]; + slt->prevout[0] = slt->out; + if (chan->feedback) { + slt->fbmod = (slt->prevout[0] + slt->prevout[1]) >> chan->feedback; + } else { + slt->fbmod = 0; + } +} + +// +// Channel +// + +void CH_UpdateRhythm(chip *opl) { + opl->rhythm = (opl->opl_memory[0xbd] & 0x3f); + if (opl->rhythm & 0x20) { + for (Bit8u i = 6; i < 9; i++) { + opl->Channels[i].chtype = ch_drum; + } + //HH + if (opl->rhythm & 0x01) { + EG_KeyOn(opl, 14, egk_drum); + } else { + EG_KeyOff(opl, 14, egk_drum); + } + //TC + if (opl->rhythm & 0x02) { + EG_KeyOn(opl, 17, egk_drum); + } else { + EG_KeyOff(opl, 17, egk_drum); + } + //TOM + if (opl->rhythm & 0x04) { + EG_KeyOn(opl, 16, egk_drum); + } else { + EG_KeyOff(opl, 16, egk_drum); + } + //SD + if (opl->rhythm & 0x08) { + EG_KeyOn(opl, 15, egk_drum); + } else { + EG_KeyOff(opl, 15, egk_drum); + } + //BD + if (opl->rhythm & 0x10) { + EG_KeyOn(opl, 12, egk_drum); + EG_KeyOn(opl, 13, egk_drum); + } else { + EG_KeyOff(opl, 12, egk_drum); + EG_KeyOff(opl, 13, egk_drum); + } + } else { + for (Bit8u i = 6; i < 9; i++) { + opl->Channels[i].chtype = ch_2op; + } + } +} + +void CH_UpdateAB0(chip *opl, Bit8u ch) { + channel *chan = &opl->Channels[ch]; + if (opl->newm && chan->chtype == ch_4op2) { + return; + } + Bit16u f_number = (opl->opl_memory[0xa0 + chan->offset]) | (((opl->opl_memory[0xb0 + chan->offset]) & 0x03) << 8); + Bit8u block = ((opl->opl_memory[0xb0 + chan->offset]) >> 2) & 0x07; + Bit8u ksv = block * 2 | ((f_number >> (9 - opl->nts)) & 0x01); + chan->f_number = f_number; + chan->block = block; + chan->ksv = ksv; + EG_UpdateKSL(opl, ch * 2); + EG_UpdateKSL(opl, ch * 2 + 1); + OP_Update60(opl, ch * 2); + OP_Update60(opl, ch * 2 + 1); + OP_Update80(opl, ch * 2); + OP_Update80(opl, ch * 2 + 1); + if (opl->newm && chan->chtype == ch_4op) { + chan = &opl->Channels[ch + 3]; + chan->f_number = f_number; + chan->block = block; + chan->ksv = ksv; + EG_UpdateKSL(opl, (ch + 3) * 2); + EG_UpdateKSL(opl, (ch + 3) * 2 + 1); + OP_Update60(opl, (ch + 3) * 2); + OP_Update60(opl, (ch + 3) * 2 + 1); + OP_Update80(opl, (ch + 3) * 2); + OP_Update80(opl, (ch + 3) * 2 + 1); + } +} + +void CH_SetupAlg(chip *opl, Bit8u ch) { + channel *chan = &opl->Channels[ch]; + if (chan->alg & 0x08) { + return; + } + if (chan->alg & 0x04) { + switch (chan->alg & 0x03) { + case 0: + opl->OPs[(ch - 3) * 2].mod = &opl->OPs[(ch - 3) * 2].fbmod; + opl->OPs[(ch - 3) * 2 + 1].mod = &opl->OPs[(ch - 3) * 2].out; + opl->OPs[ch * 2].mod = &opl->OPs[(ch - 3) * 2 + 1].out; + opl->OPs[ch * 2 + 1].mod = &opl->OPs[ch * 2].out; + break; + case 1: + opl->OPs[(ch - 3) * 2].mod = &opl->OPs[(ch - 3) * 2].fbmod; + opl->OPs[(ch - 3) * 2 + 1].mod = &opl->OPs[(ch - 3) * 2].out; + opl->OPs[ch * 2].mod = &opl->zm; + opl->OPs[ch * 2 + 1].mod = &opl->OPs[ch * 2].out; + break; + case 2: + opl->OPs[(ch - 3) * 2].mod = &opl->OPs[(ch - 3) * 2].fbmod; + opl->OPs[(ch - 3) * 2 + 1].mod = &opl->zm; + opl->OPs[ch * 2].mod = &opl->OPs[(ch - 3) * 2 + 1].out; + opl->OPs[ch * 2 + 1].mod = &opl->OPs[ch * 2].out; + break; + case 3: + opl->OPs[(ch - 3) * 2].mod = &opl->OPs[(ch - 3) * 2].fbmod; + opl->OPs[(ch - 3) * 2 + 1].mod = &opl->zm; + opl->OPs[ch * 2].mod = &opl->OPs[(ch - 3) * 2 + 1].out; + opl->OPs[ch * 2 + 1].mod = &opl->zm; + break; + } + } else { + switch (chan->alg & 0x01) { + case 0: + opl->OPs[ch * 2].mod = &opl->OPs[ch * 2].fbmod; + opl->OPs[ch * 2 + 1].mod = &opl->OPs[ch * 2].out; + break; + case 1: + opl->OPs[ch * 2].mod = &opl->OPs[ch * 2].fbmod; + opl->OPs[ch * 2 + 1].mod = &opl->zm; + break; + } + } +} + +void CH_UpdateC0(chip *opl, Bit8u ch) { + channel *chan = &opl->Channels[ch]; + Bit8u fb = (opl->opl_memory[0xc0 + chan->offset] & 0x0e) >> 1; + chan->feedback = fb ? (9 - fb) : 0; + chan->con = opl->opl_memory[0xc0 + chan->offset] & 0x01; + chan->alg = chan->con; + if (opl->newm) { + if (chan->chtype == ch_4op) { + channel *chan1 = &opl->Channels[ch + 3]; + chan1->alg = 0x04 | (chan->con << 1) | (chan1->con); + chan->alg = 0x08; + CH_SetupAlg(opl, ch + 3); + } else if (chan->chtype == ch_4op2) { + channel *chan1 = &opl->Channels[ch - 3]; + chan->alg = 0x04 | (chan1->con << 1) | (chan->con); + chan1->alg = 0x08; + CH_SetupAlg(opl, ch); + } else { + CH_SetupAlg(opl, ch); + } + } else { + CH_SetupAlg(opl, ch); + } + if (opl->newm) { + chan->cha = ((opl->opl_memory[0xc0 + chan->offset] >> 4) & 0x01) ? ~0 : 0; + chan->chb = ((opl->opl_memory[0xc0 + chan->offset] >> 5) & 0x01) ? ~0 : 0; + chan->chc = ((opl->opl_memory[0xc0 + chan->offset] >> 6) & 0x01) ? ~0 : 0; + chan->chd = ((opl->opl_memory[0xc0 + chan->offset] >> 7) & 0x01) ? ~0 : 0; + } else { + opl->Channels[ch].cha = opl->Channels[ch].chb = ~0; + opl->Channels[ch].chc = opl->Channels[ch].chd = 0; + } +} + +void CH_Set2OP(chip *opl) { + for (Bit8u i = 0; i < 18; i++) { + opl->Channels[i].chtype = ch_2op; + CH_UpdateC0(opl, i); + } +} + +void CH_Set4OP(chip *opl) { + for (Bit8u i = 0; i < 3; i++) { + if ((opl->opl_memory[0x104] >> i) & 0x01) { + opl->Channels[i].chtype = ch_4op; + opl->Channels[i + 3].chtype = ch_4op2; + CH_UpdateC0(opl, i); + CH_UpdateC0(opl, i + 3); + } + if ((opl->opl_memory[0x104] >> (i + 3)) & 0x01) { + opl->Channels[i + 9].chtype = ch_4op; + opl->Channels[i + 3 + 9].chtype = ch_4op2; + CH_UpdateC0(opl, i + 9); + CH_UpdateC0(opl, i + 3 + 9); + } + } +} + +void CH_GenerateRhythm(chip *opl) { + if (opl->rhythm & 0x20) { + channel *chan6 = &opl->Channels[6]; + channel *chan7 = &opl->Channels[7]; + channel *chan8 = &opl->Channels[8]; + slot *slt12 = &opl->OPs[12]; + slot *slt13 = &opl->OPs[13]; + slot *slt14 = &opl->OPs[14]; + slot *slt15 = &opl->OPs[15]; + slot *slt16 = &opl->OPs[16]; + slot *slt17 = &opl->OPs[17]; + //BD + OP_Generate(opl, 12); + OP_Generate(opl, 13); + chan6->out = slt13->out * 2; + Bit16u P14 = (slt14->PG_pos >> 9) & 0x3ff; + Bit16u P17 = (slt17->PG_pos >> 9) & 0x3ff; + Bit16u phase = 0; + // HH TC Phase bit + Bit16u PB = ((P14 & 0x08) | (((P14 >> 5) ^ P14) & 0x04) | (((P17 >> 2) ^ P17) & 0x08)) ? 0x01 : 0x00; + //HH + phase = (PB << 9) | (0x34 << ((PB ^ (opl->noise & 0x01) << 1))); + OP_GeneratePhase(opl, 14, phase); + //SD + phase = (0x100 << ((P14 >> 8) & 0x01)) ^ ((opl->noise & 0x01) << 8); + OP_GeneratePhase(opl, 15, phase); + //TT + OP_GenerateZM(opl, 16); + //TC + phase = 0x100 | (PB << 9); + OP_GeneratePhase(opl, 17, phase); + chan7->out = (slt14->out + slt15->out) * 2; + chan8->out = (slt16->out + slt17->out) * 2; + } +} + +void CH_Generate(chip *opl, Bit8u ch) { + channel *chan = &opl->Channels[ch]; + if (chan->chtype == ch_drum) { + return; + } + if (chan->alg & 0x08) { + chan->out = 0; + return; + } else if (chan->alg & 0x04) { + OP_Generate(opl, (ch - 3) * 2); + OP_Generate(opl, (ch - 3) * 2 + 1); + OP_Generate(opl, ch * 2); + OP_Generate(opl, ch * 2 + 1); + switch (chan->alg & 0x03) { + case 0: + chan->out = opl->OPs[ch * 2 + 1].out; + break; + case 1: + chan->out = opl->OPs[(ch - 3) * 2 + 1].out + opl->OPs[ch * 2 + 1].out; + break; + case 2: + chan->out = opl->OPs[(ch - 3) * 2].out + opl->OPs[ch * 2 + 1].out; + break; + case 3: + chan->out = opl->OPs[(ch - 3) * 2].out + opl->OPs[ch * 2].out + opl->OPs[ch * 2 + 1].out; + break; + } + } + else { + OP_Generate(opl, ch * 2); + OP_Generate(opl, ch * 2 + 1); + switch (chan->alg & 0x01) { + case 0: + chan->out = opl->OPs[ch * 2 + 1].out; + break; + case 1: + chan->out = opl->OPs[ch * 2].out + opl->OPs[ch * 2 + 1].out; + break; + } + } +} + +void CH_Enable(chip *opl, Bit8u ch) { + channel *chan = &opl->Channels[ch]; + if (opl->newm) { + if (chan->chtype == ch_4op) { + EG_KeyOn(opl, ch * 2, egk_norm); + EG_KeyOn(opl, ch * 2 + 1, egk_norm); + EG_KeyOn(opl, (ch + 3) * 2, egk_norm); + EG_KeyOn(opl, (ch + 3) * 2 + 1, egk_norm); + } + else if (chan->chtype == ch_2op || chan->chtype == ch_drum) { + EG_KeyOn(opl, ch * 2, egk_norm); + EG_KeyOn(opl, ch * 2 + 1, egk_norm); + } + } + else { + EG_KeyOn(opl, ch * 2, egk_norm); + EG_KeyOn(opl, ch * 2 + 1, egk_norm); + } +} + +void CH_Disable(chip *opl, Bit8u ch) { + channel *chan = &opl->Channels[ch]; + if (opl->newm) { + if (chan->chtype == ch_4op) { + EG_KeyOff(opl, ch * 2, egk_norm); + EG_KeyOff(opl, ch * 2 + 1, egk_norm); + EG_KeyOff(opl, (ch + 3) * 2, egk_norm); + EG_KeyOff(opl, (ch + 3) * 2 + 1, egk_norm); + } + else if (chan->chtype == ch_2op || chan->chtype == ch_drum) { + EG_KeyOff(opl, ch * 2, egk_norm); + EG_KeyOff(opl, ch * 2 + 1, egk_norm); + } + } + else { + EG_KeyOff(opl, ch * 2, egk_norm); + EG_KeyOff(opl, ch * 2 + 1, egk_norm); + } +} + +Bit16s limshort(Bit32s a) { + if (a > 32767) { + a = 32767; + } + else if (a < -32768) { + a = -32768; + } + return (Bit16s)a; +} + +void NukedOPL3::Reset() { + for (Bit8u i = 0; i < 36; i++) { + opl3.OPs[i].PG_pos = 0; + opl3.OPs[i].PG_inc = 0; + opl3.OPs[i].EG_out = 0x1ff; + opl3.OPs[i].EG_mout = 0x1ff; + opl3.OPs[i].eg_inc = 0; + opl3.OPs[i].eg_gen = 0; + opl3.OPs[i].eg_gennext = 0; + opl3.OPs[i].EG_ksl = 0; + opl3.OPs[i].EG_ar = 0; + opl3.OPs[i].EG_dr = 0; + opl3.OPs[i].EG_sl = 0; + opl3.OPs[i].EG_rr = 0; + opl3.OPs[i].EG_state = eg_off; + opl3.OPs[i].EG_type = 0; + opl3.OPs[i].out = 0; + opl3.OPs[i].prevout[0] = 0; + opl3.OPs[i].prevout[1] = 0; + opl3.OPs[i].fbmod = 0; + opl3.OPs[i].offset = op_offset[i % 18] + ((i > 17) << 8); + opl3.OPs[i].mult = 0; + opl3.OPs[i].vibrato = 0; + opl3.OPs[i].tremolo = 0; + opl3.OPs[i].ksr = 0; + opl3.OPs[i].EG_tl = 0; + opl3.OPs[i].ksl = 0; + opl3.OPs[i].key = 0; + opl3.OPs[i].waveform = 0; + } + for (Bit8u i = 0; i < 9; i++) { + opl3.Channels[i].con = 0; + opl3.Channels[i + 9].con = 0; + opl3.Channels[i].chtype = ch_2op; + opl3.Channels[i + 9].chtype = ch_2op; + opl3.Channels[i].alg = 0; + opl3.Channels[i + 9].alg = 0; + opl3.Channels[i].offset = i; + opl3.Channels[i + 9].offset = 0x100 + i; + opl3.Channels[i].feedback = 0; + opl3.Channels[i + 9].feedback = 0; + opl3.Channels[i].out = 0; + opl3.Channels[i + 9].out = 0; + opl3.Channels[i].cha = ~0; + opl3.Channels[i + 9].cha = ~0; + opl3.Channels[i].chb = ~0; + opl3.Channels[i + 9].chb = ~0; + opl3.Channels[i].chc = 0; + opl3.Channels[i + 9].chc = 0; + opl3.Channels[i].chd = 0; + opl3.Channels[i + 9].chd = 0; + opl3.Channels[i].out = 0; + opl3.Channels[i + 9].out = 0; + opl3.Channels[i].f_number = 0; + opl3.Channels[i + 9].f_number = 0; + opl3.Channels[i].block = 0; + opl3.Channels[i + 9].block = 0; + opl3.Channels[i].ksv = 0; + opl3.Channels[i + 9].ksv = 0; + opl3.Channels[i].panl = (float)CENTER_PANNING_POWER; + opl3.Channels[i + 9].panl = (float)CENTER_PANNING_POWER; + opl3.Channels[i].panr = (float)CENTER_PANNING_POWER; + opl3.Channels[i + 9].panr = (float)CENTER_PANNING_POWER; + } + memset(opl3.opl_memory, 0, 0x200); + opl3.newm = 0; + opl3.nts = 0; + opl3.rhythm = 0; + opl3.dvb = 0; + opl3.dam = 0; + opl3.noise = 0x306600; + opl3.vib_pos = 0; + opl3.timer = 0; + opl3.trem_inc = 0; + opl3.trem_tval = 0; + opl3.trem_dir = 0; + opl3.trem_val = 0; + opl3.zm = 0; + CH_Set2OP(&opl3); +} + +void NukedOPL3::WriteReg(int reg, int v) { + v &= 0xff; + reg &= 0x1ff; + Bit8u highbank = (reg >> 8) & 0x01; + Bit8u regm = reg & 0xff; + opl3.opl_memory[reg & 0x1ff] = v; + switch (regm & 0xf0) { + case 0x00: + if (highbank) { + switch (regm & 0x0f) { + case 0x04: + CH_Set2OP(&opl3); + CH_Set4OP(&opl3); + break; + case 0x05: + opl3.newm = v & 0x01; + break; + } + } + else { + switch (regm & 0x0f) { + case 0x08: + opl3.nts = (v >> 6) & 0x01; + break; + } + } + break; + case 0x20: + case 0x30: + if (ad_slot[regm & 0x1f] >= 0) { + OP_Update20(&opl3, 18 * highbank + ad_slot[regm & 0x1f]); + } + break; + case 0x40: + case 0x50: + if (ad_slot[regm & 0x1f] >= 0) { + OP_Update40(&opl3, 18 * highbank + ad_slot[regm & 0x1f]); + } + break; + case 0x60: + case 0x70: + if (ad_slot[regm & 0x1f] >= 0) { + OP_Update60(&opl3, 18 * highbank + ad_slot[regm & 0x1f]); + } + break; + case 0x80: + case 0x90: + if (ad_slot[regm & 0x1f] >= 0) { + OP_Update80(&opl3, 18 * highbank + ad_slot[regm & 0x1f]); + } + break; + case 0xe0: + case 0xf0: + if (ad_slot[regm & 0x1f] >= 0) { + OP_UpdateE0(&opl3, 18 * highbank + ad_slot[regm & 0x1f]); + } + break; + case 0xa0: + if ((regm & 0x0f) < 9) { + CH_UpdateAB0(&opl3, 9 * highbank + (regm & 0x0f)); + } + break; + case 0xb0: + if (regm == 0xbd && !highbank) { + opl3.dam = v >> 7; + opl3.dvb = (v >> 6) & 0x01; + CH_UpdateRhythm(&opl3); + } + else if ((regm & 0x0f) < 9) { + CH_UpdateAB0(&opl3, 9 * highbank + (regm & 0x0f)); + if (v & 0x20) { + CH_Enable(&opl3, 9 * highbank + (regm & 0x0f)); + } + else { + CH_Disable(&opl3, 9 * highbank + (regm & 0x0f)); + } + } + break; + case 0xc0: + if ((regm & 0x0f) < 9) { + CH_UpdateC0(&opl3, 9 * highbank + (regm & 0x0f)); + } + break; + } +} + +void NukedOPL3::Update(float* sndptr, int numsamples) { + Bit32s outa, outb; + Bit8u ii = 0; + for (Bit32u i = 0; i < (Bit32u)numsamples; i++) { + outa = 0; + outb = 0; + for (ii = 0; ii < 36; ii++) { + OP_CalcFB(&opl3, ii); + } + CH_GenerateRhythm(&opl3); + for (ii = 0; ii < 18; ii++) { + CH_Generate(&opl3, ii); + if (FullPan) { + outa += (Bit16s)(opl3.Channels[ii].out * opl3.Channels[ii].panl); + outb += (Bit16s)(opl3.Channels[ii].out * opl3.Channels[ii].panr); + } + else { + outa += (Bit16s)(opl3.Channels[ii].out & opl3.Channels[ii].cha); + outb += (Bit16s)(opl3.Channels[ii].out & opl3.Channels[ii].chb); + } + } + for (ii = 0; ii < 36; ii++) { + EG_Generate(&opl3, ii); + PG_Generate(&opl3, ii); + } + N_Generate(&opl3); + opl3.trem_inc++; + if (!(opl3.trem_inc & 0x3f)) { + if (!opl3.trem_dir) { + if (opl3.trem_tval == 105) { + opl3.trem_tval--; + opl3.trem_dir = 1; + } + else { + opl3.trem_tval++; + } + } + else { + if (opl3.trem_tval == 0) { + opl3.trem_tval++; + opl3.trem_dir = 0; + } + else { + opl3.trem_tval--; + } + } + opl3.trem_val = (opl3.trem_tval >> 2) >> ((!opl3.dam) << 1); + } + opl3.timer++; + opl3.vib_pos = (opl3.timer >> 10) & 0x07; + *sndptr++ += (float)(outa / 10240.0); + *sndptr++ += (float)(outb / 10240.0); + } +} + +void NukedOPL3::SetPanning(int c, float left, float right) { + if (FullPan) { + opl3.Channels[c].panl = left; + opl3.Channels[c].panr = right; + } +} + +NukedOPL3::NukedOPL3(bool stereo) { + FullPan = stereo; + Reset(); +} + +OPLEmul *NukedOPL3Create(bool stereo) { + return new NukedOPL3(stereo); +} \ No newline at end of file diff --git a/src/oplsynth/nukedopl3.h b/src/oplsynth/nukedopl3.h new file mode 100644 index 000000000..51b78935b --- /dev/null +++ b/src/oplsynth/nukedopl3.h @@ -0,0 +1,118 @@ +/* +* Copyright (C) 2013-2014 Nuke.YKT +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Nuked Yamaha YMF262(aka OPL3) emulator. + Thanks: + MAME Development Team: + Feedback and Rhythm part calculation information. + forums.submarine.org.uk(carbon14, opl3): + Tremolo and phase generator calculation information. +*/ + +//version 1.4.2 + +#include "opl.h" +#include "muslib.h" + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef DWORD Bit32u; +typedef SDWORD Bit32s; +typedef WORD Bit16u; +typedef SWORD Bit16s; +typedef BYTE Bit8u; +typedef SBYTE Bit8s; + +struct channel { + Bit8u con; + Bit8u chtype; + Bit8u alg; + Bit16u offset; + Bit8u feedback; + Bit16u cha, chb, chc, chd; + Bit16s out; + Bit16u f_number; + Bit8u block; + Bit8u ksv; + float panl; + float panr; +}; + +struct slot { + Bit32u PG_pos; + Bit32u PG_inc; + Bit16s EG_out; + Bit8u eg_inc; + Bit8u eg_gen; + Bit8u eg_gennext; + Bit16u EG_mout; + Bit8u EG_ksl; + Bit8u EG_ar; + Bit8u EG_dr; + Bit8u EG_sl; + Bit8u EG_rr; + Bit8u EG_state; + Bit8u EG_type; + Bit16s out; + Bit16s *mod; + Bit16s prevout[2]; + Bit16s fbmod; + Bit16u offset; + Bit8u mult; + Bit8u vibrato; + Bit8u tremolo; + Bit8u ksr; + Bit8u EG_tl; + Bit8u ksl; + Bit8u key; + Bit8u waveform; +}; + + +struct chip { + Bit8u opl_memory[0x200]; + Bit8u newm; + Bit8u nts; + Bit8u rhythm; + Bit8u dvb; + Bit8u dam; + Bit32u noise; + Bit16u vib_pos; + Bit16u timer; + Bit8u trem_inc; + Bit8u trem_tval; + Bit8u trem_dir; + Bit8u trem_val; + channel Channels[18]; + slot OPs[36]; + Bit16s zm; +}; + +class NukedOPL3 : public OPLEmul { +private: + chip opl3; + bool FullPan; +public: + void Reset(); + void Update(float* sndptr, int numsamples); + void WriteReg(int reg, int v); + void SetPanning(int c, float left, float right); + + NukedOPL3(bool stereo); +}; \ No newline at end of file diff --git a/src/oplsynth/opl.h b/src/oplsynth/opl.h index 661258a26..2cbb19c32 100644 --- a/src/oplsynth/opl.h +++ b/src/oplsynth/opl.h @@ -20,6 +20,7 @@ public: OPLEmul *YM3812Create(bool stereo); OPLEmul *DBOPLCreate(bool stereo); OPLEmul *JavaOPLCreate(bool stereo); +OPLEmul *NukedOPL3Create(bool stereo); #define OPL_SAMPLE_RATE 49716.0 #define CENTER_PANNING_POWER 0.70710678118 /* [RH] volume at center for EQP */ diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 408e534a8..abc654252 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1478,6 +1478,7 @@ OptionValue OplCores 0, "MAME OPL2" 1, "DOSBox OPL3" 2, "Java OPL3" + 3, "Nuked OPL3" } OptionMenu AdvSoundOptions diff --git a/zdoom.vcproj b/zdoom.vcproj index cabc6c84f..21aaccb8e 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2632,6 +2632,14 @@ RelativePath=".\src\oplsynth\opl_mus_player.h" > + + + + From 88f4305e7e985e8bfaa954c7d09bc85a15efc26e Mon Sep 17 00:00:00 2001 From: khokh2001 Date: Sun, 23 Nov 2014 00:39:26 +0900 Subject: [PATCH 67/75] new opl3 emulator --- src/oplsynth/nukedopl3.cpp | 2 +- src/oplsynth/nukedopl3.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oplsynth/nukedopl3.cpp b/src/oplsynth/nukedopl3.cpp index 95d03ae8d..ee95741f5 100644 --- a/src/oplsynth/nukedopl3.cpp +++ b/src/oplsynth/nukedopl3.cpp @@ -1142,4 +1142,4 @@ NukedOPL3::NukedOPL3(bool stereo) { OPLEmul *NukedOPL3Create(bool stereo) { return new NukedOPL3(stereo); -} \ No newline at end of file +} diff --git a/src/oplsynth/nukedopl3.h b/src/oplsynth/nukedopl3.h index 51b78935b..7a032cb5a 100644 --- a/src/oplsynth/nukedopl3.h +++ b/src/oplsynth/nukedopl3.h @@ -115,4 +115,4 @@ public: void SetPanning(int c, float left, float right); NukedOPL3(bool stereo); -}; \ No newline at end of file +}; From 5ed70d97a6ca00891cfa281734d1e370ea2aec8d Mon Sep 17 00:00:00 2001 From: khokh2001 Date: Sun, 23 Nov 2014 02:18:40 +0900 Subject: [PATCH 68/75] New OPL3 emulator. --- src/oplsynth/nukedopl3.cpp | 4 +++- src/oplsynth/nukedopl3.h | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/oplsynth/nukedopl3.cpp b/src/oplsynth/nukedopl3.cpp index ee95741f5..90673d855 100644 --- a/src/oplsynth/nukedopl3.cpp +++ b/src/oplsynth/nukedopl3.cpp @@ -19,10 +19,12 @@ /* Nuked Yamaha YMF262(aka OPL3) emulator. Thanks: - MAME Development Team: + MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): Feedback and Rhythm part calculation information. forums.submarine.org.uk(carbon14, opl3): Tremolo and phase generator calculation information. + OPLx decapsulated(Matthew Gambrell and Olli Niemitalo): + OPL2 ROMs. */ //version 1.4.2 diff --git a/src/oplsynth/nukedopl3.h b/src/oplsynth/nukedopl3.h index 7a032cb5a..15f264c1a 100644 --- a/src/oplsynth/nukedopl3.h +++ b/src/oplsynth/nukedopl3.h @@ -19,10 +19,12 @@ /* Nuked Yamaha YMF262(aka OPL3) emulator. Thanks: - MAME Development Team: + MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): Feedback and Rhythm part calculation information. forums.submarine.org.uk(carbon14, opl3): Tremolo and phase generator calculation information. + OPLx decapsulated(Matthew Gambrell and Olli Niemitalo): + OPL2 ROMs. */ //version 1.4.2 From e9b24a10a1f00bfc429876aa14b68a663d3b4d3a Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 23 Nov 2014 12:47:04 +0200 Subject: [PATCH 69/75] OS version detection without deprecated API --- src/cocoa/i_backend_cocoa.mm | 25 +++++++++++++++++++++ src/cocoa/i_osversion.h | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100755 src/cocoa/i_osversion.h diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 81c487cc7..70aeaeacb 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -153,6 +153,7 @@ typedef NSInteger NSApplicationActivationPolicy; #include "v_video.h" #include "version.h" #include "i_rbopts.h" +#include "i_osversion.h" #undef Class @@ -2094,9 +2095,33 @@ void CreateMenu() [NSApp setMainMenu:menuBar]; } +DarwinVersion GetDarwinVersion() +{ + DarwinVersion result = {}; + + int mib[2] = { CTL_KERN, KERN_OSRELEASE }; + size_t size = 0; + + if (0 == sysctl(mib, 2, NULL, &size, NULL, 0)) + { + char* version = static_cast(alloca(size)); + + if (0 == sysctl(mib, 2, version, &size, NULL, 0)) + { + sscanf(version, "%hu.%hu.%hu", + &result.major, &result.minor, &result.bugfix); + } + } + + return result; +} + } // unnamed namespace +const DarwinVersion darwinVersion = GetDarwinVersion(); + + #ifdef main #undef main #endif // main diff --git a/src/cocoa/i_osversion.h b/src/cocoa/i_osversion.h new file mode 100755 index 000000000..5e6ac6d20 --- /dev/null +++ b/src/cocoa/i_osversion.h @@ -0,0 +1,43 @@ +/* + ** i_osversion.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include + +struct DarwinVersion +{ + uint16_t major; + uint16_t minor; + uint16_t bugfix; +}; + +extern const DarwinVersion darwinVersion; From b4ff34dae51a45744432ce74955b8981fb463257 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 23 Nov 2014 12:48:05 +0200 Subject: [PATCH 70/75] Use recently added OS version detection to check for HID Manager API availability --- src/cocoa/i_joystick.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/cocoa/i_joystick.cpp b/src/cocoa/i_joystick.cpp index bd2c59348..56db8f815 100644 --- a/src/cocoa/i_joystick.cpp +++ b/src/cocoa/i_joystick.cpp @@ -37,13 +37,12 @@ #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 -#include - #include "HID_Utilities_External.h" #include "d_event.h" #include "doomdef.h" #include "templates.h" +#include "i_osversion.h" namespace @@ -730,16 +729,9 @@ IOKitJoystickManager* s_joystickManager; void I_StartupJoysticks() { - SInt32 majorVersion = 0; - SInt32 minorVersion = 0; + // HID Manager API is available on 10.5 (Darwin 9.x) or newer -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - Gestalt(gestaltSystemVersionMajor, &majorVersion); - Gestalt(gestaltSystemVersionMinor, &minorVersion); -#pragma clang diagnostic pop - - if (majorVersion >= 10 && minorVersion >= 5) + if (darwinVersion.major >= 9) { s_joystickManager = new IOKitJoystickManager; } From 877bfcd3280c3feceebd39838bde80d09cd6c43a Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 23 Nov 2014 22:18:41 +0200 Subject: [PATCH 71/75] Cleaned up support for older OS X SDKs --- src/cocoa/i_backend_cocoa.mm | 75 +++++++++++++++++------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm index 70aeaeacb..bd3dc3115 100644 --- a/src/cocoa/i_backend_cocoa.mm +++ b/src/cocoa/i_backend_cocoa.mm @@ -45,9 +45,39 @@ #include #include -#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 +#include -// Missing definitions for 10.4 and earlier +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + +#include "bitmap.h" +#include "c_console.h" +#include "c_dispatch.h" +#include "cmdlib.h" +#include "d_event.h" +#include "d_gui.h" +#include "dikeys.h" +#include "doomdef.h" +#include "doomstat.h" +#include "s_sound.h" +#include "textures.h" +#include "v_video.h" +#include "version.h" +#include "i_rbopts.h" +#include "i_osversion.h" + +#undef Class + + +#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) + + +// --------------------------------------------------------------------------- + + +// The following definitions are required to build with older OS X SDKs + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 typedef unsigned int NSUInteger; typedef int NSInteger; @@ -132,47 +162,13 @@ typedef NSInteger NSApplicationActivationPolicy; - (BOOL)setActivationPolicy:(NSApplicationActivationPolicy)activationPolicy; @end -#endif // prior to 10.6 - -#include - -// Avoid collision between DObject class and Objective-C -#define Class ObjectClass - -#include "bitmap.h" -#include "c_console.h" -#include "c_dispatch.h" -#include "cmdlib.h" -#include "d_event.h" -#include "d_gui.h" -#include "dikeys.h" -#include "doomdef.h" -#include "doomstat.h" -#include "s_sound.h" -#include "textures.h" -#include "v_video.h" -#include "version.h" -#include "i_rbopts.h" -#include "i_osversion.h" - -#undef Class - - -#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) - - -// --------------------------------------------------------------------------- - - -#ifndef NSAppKitVersionNumber10_6 - @interface NSWindow(SetStyleMask) - (void)setStyleMask:(NSUInteger)styleMask; @end -#endif // !NSAppKitVersionNumber10_6 +#endif // prior to 10.6 -#ifndef NSAppKitVersionNumber10_7 +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 @interface NSView(HiDPIStubs) - (NSPoint)convertPointToBacking:(NSPoint)aPoint; @@ -186,11 +182,12 @@ typedef NSInteger NSApplicationActivationPolicy; - (NSRect)convertRectToBacking:(NSRect)aRect; @end -#endif // !NSAppKitVersionNumber10_7 +#endif // prior to 10.7 // --------------------------------------------------------------------------- + RenderBufferOptions rbOpts; EXTERN_CVAR(Bool, fullscreen) From eacb9aa10570c505e938d1b547a7d860941b9b44 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Mon, 24 Nov 2014 19:18:07 -0500 Subject: [PATCH 72/75] - Fixed more non-OSX issues. --- src/sdl/i_gui.cpp | 52 ++++++++++++++++++++++++++++++++++++++-- src/sdl/i_system.cpp | 56 -------------------------------------------- 2 files changed, 50 insertions(+), 58 deletions(-) diff --git a/src/sdl/i_gui.cpp b/src/sdl/i_gui.cpp index 37fc750cb..bccd81972 100644 --- a/src/sdl/i_gui.cpp +++ b/src/sdl/i_gui.cpp @@ -9,10 +9,58 @@ #include "v_palette.h" #include "textures.h" +SDL_Surface *cursorSurface = NULL; +SDL_Rect cursorBlit = {0, 0, 32, 32}; -extern SDL_Surface *cursorSurface; -extern SDL_Rect cursorBlit; +#ifdef USE_XCURSOR +// Xlib has its own GC, so don't let it interfere. +#define GC XGC +#include +#undef GC +bool UseXCursor; +SDL_Cursor *X11Cursor; +SDL_Cursor *FirstCursor; + +// Hack! Hack! SDL does not provide a clean way to get the XDisplay. +// On the other hand, there are no more planned updates for SDL 1.2, +// so we should be fine making assumptions. +struct SDL_PrivateVideoData +{ + int local_X11; + Display *X11_Display; +}; + +struct SDL_VideoDevice +{ + const char *name; + int (*functions[9])(); + SDL_VideoInfo info; + SDL_PixelFormat *displayformatalphapixel; + int (*morefuncs[9])(); + Uint16 *gamma; + int (*somefuncs[9])(); + unsigned int texture; // Only here if SDL was compiled with OpenGL support. Ack! + int is_32bit; + int (*itsafuncs[13])(); + SDL_Surface *surfaces[3]; + SDL_Palette *physpal; + SDL_Color *gammacols; + char *wm_strings[2]; + int offsets[2]; + SDL_GrabMode input_grab; + int handles_any_size; + SDL_PrivateVideoData *hidden; // Why did they have to bury this so far in? +}; + +extern SDL_VideoDevice *current_video; +#define SDL_Display (current_video->hidden->X11_Display) + +SDL_Cursor *CreateColorCursor(FTexture *cursorpic) +{ + return NULL; +} +#endif bool I_SetCursor(FTexture *cursorpic) { diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index d65dbc6c8..41fdef323 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -71,13 +71,6 @@ #include "m_fixed.h" #include "g_level.h" -#ifdef USE_XCURSOR -// Xlib has its own GC, so don't let it interfere. -#define GC XGC -#include -#undef GC -#endif - #ifdef __APPLE__ #include #endif // __APPLE__ @@ -95,11 +88,6 @@ extern bool GtkAvailable; #elif defined(__APPLE__) int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad); #endif -#ifdef USE_XCURSOR -bool UseXCursor; -SDL_Cursor *X11Cursor; -SDL_Cursor *FirstCursor; -#endif DWORD LanguageIDs[4]; @@ -755,47 +743,3 @@ unsigned int I_MakeRNGSeed() } return seed; } - -#ifdef USE_XCURSOR -// Hack! Hack! SDL does not provide a clean way to get the XDisplay. -// On the other hand, there are no more planned updates for SDL 1.2, -// so we should be fine making assumptions. -struct SDL_PrivateVideoData -{ - int local_X11; - Display *X11_Display; -}; - -struct SDL_VideoDevice -{ - const char *name; - int (*functions[9])(); - SDL_VideoInfo info; - SDL_PixelFormat *displayformatalphapixel; - int (*morefuncs[9])(); - Uint16 *gamma; - int (*somefuncs[9])(); - unsigned int texture; // Only here if SDL was compiled with OpenGL support. Ack! - int is_32bit; - int (*itsafuncs[13])(); - SDL_Surface *surfaces[3]; - SDL_Palette *physpal; - SDL_Color *gammacols; - char *wm_strings[2]; - int offsets[2]; - SDL_GrabMode input_grab; - int handles_any_size; - SDL_PrivateVideoData *hidden; // Why did they have to bury this so far in? -}; - -extern SDL_VideoDevice *current_video; -#define SDL_Display (current_video->hidden->X11_Display) - -SDL_Cursor *CreateColorCursor(FTexture *cursorpic) -{ - return NULL; -} -#endif - -SDL_Surface *cursorSurface = NULL; -SDL_Rect cursorBlit = {0, 0, 32, 32}; From 7e579a0a2a962669dcfa2cb71607b18ea0d90a87 Mon Sep 17 00:00:00 2001 From: MajorCooke Date: Mon, 24 Nov 2014 18:30:17 -0600 Subject: [PATCH 73/75] - Fixed: Godmode didn't stop ALLOW/CAUSE/FORCEPAIN because the code execution always ended prematurely. - Optimized checks for fake pain and forced pain. Fake pain now calls a subfunction so the code is cleaner. --- src/p_interaction.cpp | 69 +++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 4ee86d82c..7d5a2a74d 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -925,6 +925,11 @@ static inline bool MustForcePain(AActor *target, AActor *inflictor) (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS)); } +static inline bool isFakePain(AActor *target, AActor *inflictor) +{ + return ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))); +} + // Returns the amount of damage actually inflicted upon the target, or -1 if // the damage was cancelled. @@ -940,6 +945,8 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, bool justhit = false; bool plrDontThrust = false; bool invulpain = false; + bool fakedPain = false; + bool forcedPain = false; int fakeDamage = 0; int holdDamage = 0; @@ -948,6 +955,10 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, return -1; } + //Rather than unnecessarily call the function over and over again, let's be a little more efficient. + fakedPain = (isFakePain(target, inflictor)); + forcedPain = (MustForcePain(target, inflictor)); + // Spectral targets only take damage from spectral projectiles. if (target->flags4 & MF4_SPECTRAL && damage < TELEFRAG_DAMAGE) { @@ -976,7 +987,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, { if (inflictor == NULL || (!(inflictor->flags3 & MF3_FOILINVUL) && !(flags & DMG_FOILINVUL))) { - if ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) + if (fakedPain) { invulpain = true; //This returns -1 later. fakeDamage = damage; @@ -991,7 +1002,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, // Players are optionally excluded from getting thrust by damage. if (static_cast(target)->PlayerFlags & PPF_NOTHRUSTWHENINVUL) { - if ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) + if (fakedPain) plrDontThrust = 1; else return -1; @@ -999,7 +1010,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, } } - if (((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) && (damage < TELEFRAG_DAMAGE)) + if ((fakedPain) && (damage < TELEFRAG_DAMAGE)) { //Intentionally do not jump to fakepain because the damage hasn't been dished out yet. //Once it's dished out, THEN we can disregard damage factors affecting pain chances. @@ -1057,9 +1068,9 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, if (damage > 0) damage = inflictor->DoSpecialDamage (target, damage, mod); - if (damage == -1) + if ((damage == -1) && (target->player == NULL)) //This isn't meant for the player. { - if ((target->flags7 & MF7_ALLOWPAIN) || (inflictor->flags7 & MF7_CAUSEPAIN)) //Hold off ending the function before we can deal the pain chances. + if (fakedPain) //Hold off ending the function before we can deal the pain chances. goto fakepain; return -1; } @@ -1069,12 +1080,13 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, { int olddam = damage; source->Inventory->ModifyDamage(olddam, mod, damage, false); - if (olddam != damage && damage <= 0) + if (((source->flags7 & MF7_CAUSEPAIN) && (fakeDamage <= 0)) || (olddam != damage && damage <= 0)) { // Still allow FORCEPAIN - if (MustForcePain(target, inflictor)) - { + if (forcedPain) goto dopain; - } + else if (fakedPain) + goto fakepain; + return -1; } } @@ -1083,13 +1095,11 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, { int olddam = damage; target->Inventory->ModifyDamage(olddam, mod, damage, true); - if (((target->flags7 & MF7_ALLOWPAIN) && (fakeDamage <= 0)) || (olddam != damage && damage <= 0)) + if ((olddam != damage && damage <= 0) && target->player == NULL) { // Still allow FORCEPAIN and make sure we're still passing along fake damage to hit enemies for their pain states. - if (MustForcePain(target, inflictor)) - { + if (forcedPain) goto dopain; - } - else if ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) + else if (fakedPain) goto fakepain; return -1; @@ -1103,13 +1113,11 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, { damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, mod, target->GetClass()->ActorInfo->DamageFactors); } - if (damage <= 0) + if (damage <= 0 && target->player == NULL) { // Still allow FORCEPAIN - if (MustForcePain(target, inflictor)) - { + if (forcedPain) goto dopain; - } - else if ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) + else if (fakedPain) goto fakepain; return -1; @@ -1118,9 +1126,9 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, if (damage > 0) damage = target->TakeSpecialDamage (inflictor, source, damage, mod); } - if (damage == -1) + if (damage == -1 && target->player == NULL) //Make sure it's not a player, the pain has yet to be processed with cheats. { - if ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) + if (fakedPain) goto fakepain; return -1; @@ -1247,17 +1255,18 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NODAMAGE)) //Absolutely no hurting if NODAMAGE is involved. Same for GODMODE2. { // player is invulnerable, so don't hurt him - - if (((!(player->cheats & CF_GODMODE)) && (!(player->cheats & CF_GODMODE2)) && (!(player->mo->flags5 & MF5_NOPAIN))) && - (((player->mo->flags7 & MF7_ALLOWPAIN) || (player->mo->flags5 & MF5_NODAMAGE)) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) //Make sure no godmodes and NOPAIN flags are found first. //Then, check to see if the player has NODAMAGE or ALLOWPAIN, or inflictor has CAUSEPAIN. - { + if ((player->cheats & CF_GODMODE) || (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NOPAIN)) + return -1; + else if ((((player->mo->flags7 & MF7_ALLOWPAIN) || (player->mo->flags5 & MF5_NODAMAGE)) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) + { invulpain = true; fakeDamage = damage; goto fakepain; } - return -1; + else + return -1; } if (!(flags & DMG_NO_ARMOR) && player->mo->Inventory != NULL) @@ -1306,6 +1315,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, // telefrag him right? ;) (Unfortunately the damage is "absorbed" by armor, // but telefragging should still do enough damage to kill the player) // Ignore players that are already dead. + // [MC]Buddha2 absorbs telefrag damage, and anything else thrown their way. if ((player->cheats & CF_BUDDHA2) || (((player->cheats & CF_BUDDHA) || (player->mo->flags7 & MF7_BUDDHA)) && (damage < TELEFRAG_DAMAGE)) && (player->playerstate != PST_DEAD)) { // If this is a voodoo doll we need to handle the real player as well. @@ -1339,7 +1349,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, damage = newdam; if (damage <= 0) { - if ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) + if (fakedPain) goto fakepain; else return damage; @@ -1370,6 +1380,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, if (target->health <= 0) { + //[MC]Buddha flag for monsters. if ((target->flags7 & MF7_BUDDHA) && (damage < TELEFRAG_DAMAGE) && ((inflictor == NULL || !(inflictor->flags3 & MF7_FOILBUDDHA)) && !(flags & DMG_FOILBUDDHA))) { //FOILBUDDHA or Telefrag damage must kill it. target->health = 1; @@ -1434,7 +1445,7 @@ fakepain: //Needed so we can skip the rest of the above, but still obey the orig //CAUSEPAIN can always attempt to trigger the chances of pain. //ALLOWPAIN can do the same, only if the (unfiltered aka fake) damage is greater than 0. if ((((target->flags7 & MF7_ALLOWPAIN) && (fakeDamage > 0)) - || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) && (fakeDamage != damage)) + || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) { holdDamage = damage; //Store the modified damage away after factors are taken into account. damage = fakeDamage; //Retrieve the original damage. @@ -1535,7 +1546,7 @@ dopain: { return -1; //NOW we return -1! } - else if ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))) + else if (fakedPain) { return holdDamage; //This is the calculated damage after all is said and done. } From 0123279a1f3dd844bd7fc6e34537c8b225a1b97a Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Mon, 24 Nov 2014 22:22:29 -0500 Subject: [PATCH 74/75] - Work around an i386 specific optimizer bug in Apple's GCC 4.2 (GCC 4.0 has worse bugs). - Fixed: zdoom-info.plist used some non-existant/wrong variables. --- src/cocoa/zdoom-info.plist | 4 ++-- src/g_shared/a_armor.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/cocoa/zdoom-info.plist b/src/cocoa/zdoom-info.plist index 8371b0555..2a1911cdf 100644 --- a/src/cocoa/zdoom-info.plist +++ b/src/cocoa/zdoom-info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion English CFBundleExecutable - ${EXECUTABLE_NAME} + ${MACOSX_BUNDLE_EXECUTABLE_NAME} CFBundleIconFile zdoom.icns CFBundleIdentifier @@ -23,7 +23,7 @@ LSApplicationCategoryType public.app-category.action-games LSMinimumSystemVersion - ${MACOSX_DEPLOYMENT_TARGET} + 10.4 CFBundleDocumentTypes diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index a745197c2..0f343e523 100644 --- a/src/g_shared/a_armor.cpp +++ b/src/g_shared/a_armor.cpp @@ -516,7 +516,14 @@ void AHexenArmor::AbsorbDamage (int damage, FName damageType, int &newdamage) // with the dragon skin bracers. if (damage < 10000) { +#if __APPLE__ && __GNUC__ == 4 && __GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ == 1 + // -O1 optimizer bug work around. Only needed for + // GCC 4.2.1 on OS X for 10.4/10.5 tools compatibility. + volatile fixed_t tmp = 300; + Slots[i] -= Scale (damage, SlotsIncrement[i], tmp); +#else Slots[i] -= Scale (damage, SlotsIncrement[i], 300); +#endif if (Slots[i] < 2*FRACUNIT) { Slots[i] = 0; From 46eb5ce2658a62be579cc81669fae01019b77561 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Mon, 24 Nov 2014 23:26:38 -0500 Subject: [PATCH 75/75] - Move cursor variables again due to shared code with Cocoa and SDL backends. --- src/sdl/i_gui.cpp | 4 ++-- src/sdl/sdlvideo.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sdl/i_gui.cpp b/src/sdl/i_gui.cpp index bccd81972..b40fb7517 100644 --- a/src/sdl/i_gui.cpp +++ b/src/sdl/i_gui.cpp @@ -9,8 +9,8 @@ #include "v_palette.h" #include "textures.h" -SDL_Surface *cursorSurface = NULL; -SDL_Rect cursorBlit = {0, 0, 32, 32}; +extern SDL_Surface *cursorSurface; +extern SDL_Rect cursorBlit; #ifdef USE_XCURSOR // Xlib has its own GC, so don't let it interfere. diff --git a/src/sdl/sdlvideo.cpp b/src/sdl/sdlvideo.cpp index 59587fc0b..e4222633d 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/sdl/sdlvideo.cpp @@ -81,10 +81,11 @@ struct MiniModeInfo // EXTERNAL DATA DECLARATIONS ---------------------------------------------- extern IVideo *Video; -extern SDL_Surface *cursorSurface; -extern SDL_Rect cursorBlit; extern bool GUICapture; +SDL_Surface *cursorSurface = NULL; +SDL_Rect cursorBlit = {0, 0, 32, 32}; + EXTERN_CVAR (Float, Gamma) EXTERN_CVAR (Int, vid_maxfps) EXTERN_CVAR (Bool, cl_capfps)