Split implementation of native OS X backend into several files

This commit is contained in:
alexey.lysiuk 2014-12-28 16:35:00 +02:00
parent 40d4dc502e
commit a67dc4148b
6 changed files with 2718 additions and 2582 deletions

View file

@ -575,9 +575,11 @@ set( PLAT_COCOA_SOURCES
posix/cocoa/hid/IOHIDElement_.c
posix/cocoa/hid/ImmrHIDUtilAddOn.c
posix/cocoa/critsec.cpp
posix/cocoa/i_backend_cocoa.mm
posix/cocoa/i_input.mm
posix/cocoa/i_joystick.cpp
posix/cocoa/i_timer.cpp )
posix/cocoa/i_main.mm
posix/cocoa/i_timer.cpp
posix/cocoa/i_video.mm )
if( WIN32 )
set( SYSTEM_SOURCES_DIR win32 )

File diff suppressed because it is too large Load diff

170
src/posix/cocoa/i_common.h Normal file
View file

@ -0,0 +1,170 @@
/*
** i_common.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 <AppKit/NSApplication.h>
inline bool I_IsHiDPISupported()
{
// The following value shoud be equal to NSAppKitVersionNumber10_7
// and it's hard-coded in order to build on earlier SDKs
return NSAppKitVersionNumber >= 1138;
}
void I_ProcessKeyboardEvent(NSEvent* event);
void I_ProcessKeyboardFlagsEvent(NSEvent* event);
void I_ProcessMouseMoveEvent(NSEvent* event);
void I_ProcessMouseButtonEvent(NSEvent* event);
void I_ProcessMouseWheelEvent(NSEvent* event);
void I_StartupJoysticks();
void I_ShutdownJoysticks();
void I_ProcessJoysticks();
NSSize I_GetContentViewSize(const NSWindow* window);
void I_SetMainWindowVisible(bool visible);
void I_SetNativeMouse(bool wantNative);
// 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;
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
#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060
enum
{
NSApplicationActivationPolicyRegular
};
typedef NSInteger NSApplicationActivationPolicy;
@interface NSApplication(ActivationPolicy)
- (BOOL)setActivationPolicy:(NSApplicationActivationPolicy)activationPolicy;
@end
@interface NSWindow(SetStyleMask)
- (void)setStyleMask:(NSUInteger)styleMask;
@end
#endif // prior to 10.6
#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070
@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 // prior to 10.7

692
src/posix/cocoa/i_input.mm Normal file
View file

@ -0,0 +1,692 @@
/*
** i_input.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 <AppKit/AppKit.h>
#include <Carbon/Carbon.h>
#include "c_console.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "d_event.h"
#include "d_gui.h"
#include "dikeys.h"
#include "doomdef.h"
#include "doomstat.h"
#include "v_video.h"
#include "i_common.h"
#include "i_rbopts.h"
EXTERN_CVAR(Int, m_use_mouse)
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;
}
}
extern int paused, chatmodeon;
extern constate_e ConsoleState;
bool GUICapture;
namespace
{
// TODO: remove this magic!
size_t s_skipMouseMoves;
// ---------------------------------------------------------------------------
void CheckGUICapture()
{
const bool wantCapture = (MENU_Off == menuactive)
? (c_down == ConsoleState || c_falling == ConsoleState || chatmodeon)
: (MENU_On == menuactive || MENU_OnNoPause == menuactive);
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 CheckNativeMouse()
{
const 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);
}
I_SetNativeMouse(wantNative);
}
} // unnamed namespace
void I_GetEvent()
{
[[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];
}
void I_StartTic()
{
CheckGUICapture();
CheckNativeMouse();
I_ProcessJoysticks();
I_GetEvent();
}
void I_StartFrame()
{
}
void I_SetMouseCapture()
{
}
void I_ReleaseMouseCapture()
{
}
void I_SetNativeMouse(bool wantNative)
{
static bool nativeMouse = true;
if (wantNative != nativeMouse)
{
nativeMouse = wantNative;
if (!wantNative)
{
CenterCursor();
}
CGAssociateMouseAndMouseCursorPosition(wantNative);
if (wantNative)
{
[NSCursor unhide];
}
else
{
[NSCursor hide];
}
}
}
// ---------------------------------------------------------------------------
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);
}
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<const unsigned char*>([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 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 = I_IsHiDPISupported()
? [view convertPointToBacking:windowPos]
: [view convertPoint:windowPos fromView:nil];
const CGFloat frameHeight = I_GetContentViewSize(window).height;
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);
}
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);
}
}
} // unnamed namespace
void I_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);
}
}
}
void I_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);
}
void I_ProcessMouseMoveEvent(NSEvent* theEvent)
{
if (GUICapture)
{
ProcessMouseMoveInMenu(theEvent);
}
else
{
ProcessMouseMoveInGame(theEvent);
}
}
void I_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;
default: 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;
default:
break;
}
event.data1 = MIN(KEY_MOUSE1 + [theEvent buttonNumber], NSInteger(KEY_MOUSE8));
D_PostEvent(&event);
}
}
void I_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);
}

626
src/posix/cocoa/i_main.mm Normal file
View file

@ -0,0 +1,626 @@
/*
** i_main.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 <sys/sysctl.h>
#include <AppKit/NSMenu.h>
#include <AppKit/NSScreen.h>
#include "c_console.h"
#include "c_cvars.h"
#include "cmdlib.h"
#include "d_main.h"
#include "doomerrors.h"
#include "i_system.h"
#include "m_argv.h"
#include "s_sound.h"
#include "version.h"
#include "i_common.h"
#include "i_osversion.h"
#include "i_rbopts.h"
#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE))
// ---------------------------------------------------------------------------
EXTERN_CVAR(Int, vid_defwidth )
EXTERN_CVAR(Int, vid_defheight)
EXTERN_CVAR(Bool, vid_vsync )
EXTERN_CVAR(Bool, fullscreen )
// ---------------------------------------------------------------------------
DArgs* Args; // command line arguments
namespace
{
// The maximum number of functions that can be registered with atterm.
static const size_t MAX_TERMS = 64;
static void (*TermFuncs[MAX_TERMS])();
static const char *TermNames[MAX_TERMS];
static size_t NumTerms;
void call_terms()
{
while (NumTerms > 0)
{
TermFuncs[--NumTerms]();
}
}
} // unnamed namespace
void addterm(void (*func)(), const char *name)
{
// Make sure this function wasn't already registered.
for (size_t i = 0; i < NumTerms; ++i)
{
if (TermFuncs[i] == func)
{
return;
}
}
if (NumTerms == MAX_TERMS)
{
func();
I_FatalError("Too many exit functions registered.");
}
TermNames[NumTerms] = name;
TermFuncs[NumTerms] = func;
++NumTerms;
}
void popterm()
{
if (NumTerms)
{
--NumTerms;
}
}
void Mac_I_FatalError(const char* const message)
{
I_SetMainWindowVisible(false);
const CFStringRef errorString = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
message, kCFStringEncodingASCII, kCFAllocatorNull);
if (NULL != errorString)
{
CFOptionFlags dummy;
CFUserNotificationDisplayAlert( 0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL,
CFSTR("Fatal Error"), errorString, CFSTR("Exit"), NULL, NULL, &dummy);
CFRelease(errorString);
}
}
namespace
{
const int ARGC_MAX = 64;
int s_argc;
char* s_argv[ARGC_MAX];
TArray<FString> s_argvStorage;
bool s_restartedFromWADPicker;
void NewFailure()
{
I_FatalError("Failed to allocate memory from system heap");
}
int OriginalMain(int argc, char** argv)
{
printf(GAMENAME" %s - %s - Cocoa version\nCompiled on %s\n\n",
GetVersionString(), GetGitTime(), __DATE__);
seteuid(getuid());
std::set_new_handler(NewFailure);
// Set LC_NUMERIC environment variable in case some library decides to
// clear the setlocale call at least this will be correct.
// Note that the LANG environment variable is overridden by LC_*
setenv("LC_NUMERIC", "C", 1);
setlocale(LC_ALL, "C");
// Set reasonable default values for video settings
const NSSize screenSize = [[NSScreen mainScreen] frame].size;
vid_defwidth = static_cast<int>(screenSize.width);
vid_defheight = static_cast<int>(screenSize.height);
vid_vsync = true;
fullscreen = true;
try
{
Args = new DArgs(argc, argv);
/*
killough 1/98:
This fixes some problems with exit handling
during abnormal situations.
The old code called I_Quit() to end program,
while now I_Quit() is installed as an exit
handler and exit() is called to exit, either
normally or abnormally. Seg faults are caught
and the error handler is used, to prevent
being left in graphics mode or having very
loud SFX noise because the sound card is
left in an unstable state.
*/
atexit (call_terms);
atterm (I_Quit);
// Should we even be doing anything with progdir on Unix systems?
char program[PATH_MAX];
if (realpath (argv[0], program) == NULL)
strcpy (program, argv[0]);
char *slash = strrchr (program, '/');
if (slash != NULL)
{
*(slash + 1) = '\0';
progdir = program;
}
else
{
progdir = "./";
}
I_StartupJoysticks();
atterm(I_ShutdownJoysticks);
C_InitConsole(80 * 8, 25 * 8, false);
D_DoomMain();
}
catch(const CDoomError& error)
{
const char* const message = error.GetMessage();
if (NULL != message)
{
fprintf(stderr, "%s\n", message);
Mac_I_FatalError(message);
}
exit(-1);
}
catch(...)
{
call_terms();
throw;
}
return 0;
}
} // unnamed namespace
// ---------------------------------------------------------------------------
@interface ApplicationController : NSResponder
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
<NSFileManagerDelegate>
#endif
{
}
- (void)keyDown:(NSEvent*)theEvent;
- (void)keyUp:(NSEvent*)theEvent;
- (void)applicationDidBecomeActive:(NSNotification*)aNotification;
- (void)applicationWillResignActive:(NSNotification*)aNotification;
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification;
- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename;
- (void)processEvents:(NSTimer*)timer;
@end
ApplicationController* appCtrl;
@implementation ApplicationController
- (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 currentRunLoop] addTimer:timer
forMode:NSDefaultRunLoopMode];
exit(OriginalMain(s_argc, s_argv));
}
- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename
{
ZD_UNUSED(theApplication);
if (s_restartedFromWADPicker
|| 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)processEvents:(NSTimer*)timer
{
ZD_UNUSED(timer);
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
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:
I_ProcessMouseMoveEvent(event);
break;
case NSLeftMouseDown:
case NSLeftMouseUp:
case NSRightMouseDown:
case NSRightMouseUp:
case NSOtherMouseDown:
case NSOtherMouseUp:
I_ProcessMouseButtonEvent(event);
break;
case NSLeftMouseDragged:
case NSRightMouseDragged:
case NSOtherMouseDragged:
I_ProcessMouseButtonEvent(event);
I_ProcessMouseMoveEvent(event);
break;
case NSScrollWheel:
I_ProcessMouseWheelEvent(event);
break;
case NSKeyDown:
case NSKeyUp:
I_ProcessKeyboardEvent(event);
break;
case NSFlagsChanged:
I_ProcessKeyboardFlagsEvent(event);
break;
default:
break;
}
[NSApp sendEvent:event];
}
[NSApp updateWindows];
[pool release];
}
@end
// ---------------------------------------------------------------------------
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];
if ([NSApp respondsToSelector:@selector(setAppleMenu:)])
{
[NSApp performSelector:@selector(setAppleMenu:) withObject: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];
}
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<char*>(alloca(size));
if (0 == sysctl(mib, 2, version, &size, NULL, 0))
{
sscanf(version, "%hu.%hu.%hu",
&result.major, &result.minor, &result.bugfix);
}
}
return result;
}
void ReleaseApplicationController()
{
if (NULL != appCtrl)
{
[NSApp setDelegate:nil];
[NSApp deactivate];
[appCtrl release];
appCtrl = NULL;
}
}
} // unnamed namespace
const DarwinVersion darwinVersion = GetDarwinVersion();
int main(int argc, char** argv)
{
for (int i = 0; i <= argc; ++i)
{
const char* const argument = argv[i];
if (NULL == argument || '\0' == argument[0])
{
continue;
}
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];
[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();
atterm(ReleaseApplicationController);
appCtrl = [ApplicationController new];
[NSApp setDelegate:appCtrl];
[NSApp run];
[pool release];
return EXIT_SUCCESS;
}

1226
src/posix/cocoa/i_video.mm Normal file

File diff suppressed because it is too large Load diff