SDL2/src/video/cocoa/SDL_cocoakeyboard.m
Sylvain c5c94a6be6 Change return type from void to int, for functions that set an error
(SDL_SetError(), SDL_OutOfMemory(), SDL_Unsupported(), SDL_InvalidParam())

Update prototype to forward errors to generic layer, for the functions:
MoveCursor, WarpMouse, GL_DeleteContext, GetDisplayModes.

Check invalid parameter in SDL_SetTextInputRect() generic layer.
2023-02-07 13:51:45 -08:00

448 lines
13 KiB
Objective-C

/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if SDL_VIDEO_DRIVER_COCOA
#include "SDL_cocoavideo.h"
#include "../../events/SDL_events_c.h"
#include "../../events/SDL_keyboard_c.h"
#include "../../events/scancodes_darwin.h"
#include <Carbon/Carbon.h>
/*#define DEBUG_IME NSLog */
#define DEBUG_IME(...)
@interface SDLTranslatorResponder : NSView <NSTextInputClient>
{
NSString *_markedText;
NSRange _markedRange;
NSRange _selectedRange;
SDL_Rect _inputRect;
}
- (void)doCommandBySelector:(SEL)myselector;
- (void)setInputRect:(const SDL_Rect *)rect;
@end
@implementation SDLTranslatorResponder
- (void)setInputRect:(const SDL_Rect *)rect
{
_inputRect = *rect;
}
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
{
/* TODO: Make use of replacementRange? */
const char *str;
DEBUG_IME(@"insertText: %@", aString);
/* Could be NSString or NSAttributedString, so we have
* to test and convert it before return as SDL event */
if ([aString isKindOfClass:[NSAttributedString class]]) {
str = [[aString string] UTF8String];
} else {
str = [aString UTF8String];
}
/* We're likely sending the composed text, so we reset the IME status. */
if ([self hasMarkedText]) {
[self unmarkText];
}
SDL_SendKeyboardText(str);
}
- (void)doCommandBySelector:(SEL)myselector
{
/* No need to do anything since we are not using Cocoa
selectors to handle special keys, instead we use SDL
key events to do the same job.
*/
}
- (BOOL)hasMarkedText
{
return _markedText != nil;
}
- (NSRange)markedRange
{
return _markedRange;
}
- (NSRange)selectedRange
{
return _selectedRange;
}
- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
{
if ([aString isKindOfClass:[NSAttributedString class]]) {
aString = [aString string];
}
if ([aString length] == 0) {
[self unmarkText];
return;
}
if (_markedText != aString) {
_markedText = aString;
}
_selectedRange = selectedRange;
_markedRange = NSMakeRange(0, [aString length]);
SDL_SendEditingText([aString UTF8String],
(int)selectedRange.location, (int)selectedRange.length);
DEBUG_IME(@"setMarkedText: %@, (%d, %d)", _markedText,
selectedRange.location, selectedRange.length);
}
- (void)unmarkText
{
_markedText = nil;
SDL_SendEditingText("", 0, 0);
}
- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
{
NSWindow *window = [self window];
NSRect contentRect = [window contentRectForFrameRect:[window frame]];
float windowHeight = contentRect.size.height;
NSRect rect = NSMakeRect(_inputRect.x, windowHeight - _inputRect.y - _inputRect.h,
_inputRect.w, _inputRect.h);
if (actualRange) {
*actualRange = aRange;
}
DEBUG_IME(@"firstRectForCharacterRange: (%d, %d): windowHeight = %g, rect = %@",
aRange.location, aRange.length, windowHeight,
NSStringFromRect(rect));
rect = [window convertRectToScreen:rect];
return rect;
}
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
{
DEBUG_IME(@"attributedSubstringFromRange: (%d, %d)", aRange.location, aRange.length);
return nil;
}
- (NSInteger)conversationIdentifier
{
return (NSInteger)self;
}
/* This method returns the index for character that is
* nearest to thePoint. thPoint is in screen coordinate system.
*/
- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
{
DEBUG_IME(@"characterIndexForPoint: (%g, %g)", thePoint.x, thePoint.y);
return 0;
}
/* This method is the key to attribute extension.
* We could add new attributes through this method.
* NSInputServer examines the return value of this
* method & constructs appropriate attributed string.
*/
- (NSArray *)validAttributesForMarkedText
{
return [NSArray array];
}
@end
static void HandleModifiers(_THIS, unsigned short scancode, unsigned int modifierFlags)
{
SDL_Scancode code = darwin_scancode_table[scancode];
const SDL_Scancode codes[] = {
SDL_SCANCODE_LSHIFT,
SDL_SCANCODE_LCTRL,
SDL_SCANCODE_LALT,
SDL_SCANCODE_LGUI,
SDL_SCANCODE_RSHIFT,
SDL_SCANCODE_RCTRL,
SDL_SCANCODE_RALT,
SDL_SCANCODE_RGUI,
SDL_SCANCODE_LSHIFT,
SDL_SCANCODE_LCTRL,
SDL_SCANCODE_LALT,
SDL_SCANCODE_LGUI,
};
const unsigned int modifiers[] = {
NX_DEVICELSHIFTKEYMASK,
NX_DEVICELCTLKEYMASK,
NX_DEVICELALTKEYMASK,
NX_DEVICELCMDKEYMASK,
NX_DEVICERSHIFTKEYMASK,
NX_DEVICERCTLKEYMASK,
NX_DEVICERALTKEYMASK,
NX_DEVICERCMDKEYMASK,
NX_SHIFTMASK,
NX_CONTROLMASK,
NX_ALTERNATEMASK,
NX_COMMANDMASK
};
for (int i = 0; i < 12; i++) {
if (code == codes[i]) {
if (modifierFlags & modifiers[i]) {
SDL_SendKeyboardKey(0, SDL_PRESSED, code);
} else {
SDL_SendKeyboardKey(0, SDL_RELEASED, code);
}
}
}
}
static void UpdateKeymap(SDL_VideoData *data, SDL_bool send_event)
{
TISInputSourceRef key_layout;
const void *chr_data;
int i;
SDL_Scancode scancode;
SDL_Keycode keymap[SDL_NUM_SCANCODES];
CFDataRef uchrDataRef;
/* See if the keymap needs to be updated */
key_layout = TISCopyCurrentKeyboardLayoutInputSource();
if (key_layout == data.key_layout) {
return;
}
data.key_layout = key_layout;
SDL_GetDefaultKeymap(keymap);
/* Try Unicode data first */
uchrDataRef = TISGetInputSourceProperty(key_layout, kTISPropertyUnicodeKeyLayoutData);
if (uchrDataRef) {
chr_data = CFDataGetBytePtr(uchrDataRef);
} else {
goto cleanup;
}
if (chr_data) {
UInt32 keyboard_type = LMGetKbdType();
OSStatus err;
for (i = 0; i < SDL_arraysize(darwin_scancode_table); i++) {
UniChar s[8];
UniCharCount len;
UInt32 dead_key_state;
/* Make sure this scancode is a valid character scancode */
scancode = darwin_scancode_table[i];
if (scancode == SDL_SCANCODE_UNKNOWN ||
(keymap[scancode] & SDLK_SCANCODE_MASK)) {
continue;
}
dead_key_state = 0;
err = UCKeyTranslate((UCKeyboardLayout *)chr_data,
i, kUCKeyActionDown,
0, keyboard_type,
kUCKeyTranslateNoDeadKeysMask,
&dead_key_state, 8, &len, s);
if (err != noErr) {
continue;
}
if (len > 0 && s[0] != 0x10) {
keymap[scancode] = s[0];
}
}
SDL_SetKeymap(0, keymap, SDL_NUM_SCANCODES, send_event);
return;
}
cleanup:
CFRelease(key_layout);
}
void Cocoa_InitKeyboard(_THIS)
{
SDL_VideoData *data = _this->driverdata;
UpdateKeymap(data, SDL_FALSE);
/* Set our own names for the platform-dependent but layout-independent keys */
/* This key is NumLock on the MacBook keyboard. :) */
/*SDL_SetScancodeName(SDL_SCANCODE_NUMLOCKCLEAR, "Clear");*/
SDL_SetScancodeName(SDL_SCANCODE_LALT, "Left Option");
SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Command");
SDL_SetScancodeName(SDL_SCANCODE_RALT, "Right Option");
SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Command");
data.modifierFlags = (unsigned int)[NSEvent modifierFlags];
SDL_ToggleModState(SDL_KMOD_CAPS, (data.modifierFlags & NSEventModifierFlagCapsLock) ? SDL_TRUE : SDL_FALSE);
}
void Cocoa_StartTextInput(_THIS)
{
@autoreleasepool {
NSView *parentView;
SDL_VideoData *data = _this->driverdata;
SDL_Window *window = SDL_GetKeyboardFocus();
NSWindow *nswindow = nil;
if (window) {
nswindow = window->driverdata.nswindow;
}
parentView = [nswindow contentView];
/* We only keep one field editor per process, since only the front most
* window can receive text input events, so it make no sense to keep more
* than one copy. When we switched to another window and requesting for
* text input, simply remove the field editor from its superview then add
* it to the front most window's content view */
if (!data.fieldEdit) {
data.fieldEdit =
[[SDLTranslatorResponder alloc] initWithFrame:NSMakeRect(0.0, 0.0, 0.0, 0.0)];
}
if (![[data.fieldEdit superview] isEqual:parentView]) {
/* DEBUG_IME(@"add fieldEdit to window contentView"); */
[data.fieldEdit removeFromSuperview];
[parentView addSubview:data.fieldEdit];
[nswindow makeFirstResponder:data.fieldEdit];
}
}
}
void Cocoa_StopTextInput(_THIS)
{
@autoreleasepool {
SDL_VideoData *data = _this->driverdata;
if (data && data.fieldEdit) {
[data.fieldEdit removeFromSuperview];
data.fieldEdit = nil;
}
}
}
int Cocoa_SetTextInputRect(_THIS, const SDL_Rect *rect)
{
SDL_VideoData *data = _this->driverdata;
[data.fieldEdit setInputRect:rect];
return 0;
}
void Cocoa_HandleKeyEvent(_THIS, NSEvent *event)
{
unsigned short scancode;
SDL_Scancode code;
SDL_VideoData *data = _this ? _this->driverdata : nil;
if (!data) {
return; /* can happen when returning from fullscreen Space on shutdown */
}
scancode = [event keyCode];
#if 0
const char *text;
#endif
if ((scancode == 10 || scancode == 50) && KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) {
/* see comments in SDL_cocoakeys.h */
scancode = 60 - scancode;
}
if (scancode < SDL_arraysize(darwin_scancode_table)) {
code = darwin_scancode_table[scancode];
} else {
/* Hmm, does this ever happen? If so, need to extend the keymap... */
code = SDL_SCANCODE_UNKNOWN;
}
switch ([event type]) {
case NSEventTypeKeyDown:
if (![event isARepeat]) {
/* See if we need to rebuild the keyboard layout */
UpdateKeymap(data, SDL_TRUE);
}
SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), SDL_PRESSED, code);
#ifdef DEBUG_SCANCODES
if (code == SDL_SCANCODE_UNKNOWN) {
SDL_Log("The key you just pressed is not recognized by SDL. To help get this fixed, report this to the SDL forums/mailing list <https://discourse.libsdl.org/> or to Christian Walther <cwalther@gmx.ch>. Mac virtual key code is %d.\n", scancode);
}
#endif
if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) {
/* FIXME CW 2007-08-16: only send those events to the field editor for which we actually want text events, not e.g. esc or function keys. Arrow keys in particular seem to produce crashes sometimes. */
[data.fieldEdit interpretKeyEvents:[NSArray arrayWithObject:event]];
#if 0
text = [[event characters] UTF8String];
if(text && *text) {
SDL_SendKeyboardText(text);
[data->fieldEdit setString:@""];
}
#endif
}
break;
case NSEventTypeKeyUp:
SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), SDL_RELEASED, code);
break;
case NSEventTypeFlagsChanged:
HandleModifiers(_this, scancode, (unsigned int)[event modifierFlags]);
break;
default: /* just to avoid compiler warnings */
break;
}
}
void Cocoa_QuitKeyboard(_THIS)
{
}
typedef int CGSConnection;
typedef enum
{
CGSGlobalHotKeyEnable = 0,
CGSGlobalHotKeyDisable = 1,
} CGSGlobalHotKeyOperatingMode;
extern CGSConnection _CGSDefaultConnection(void);
extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode);
void Cocoa_SetWindowKeyboardGrab(_THIS, SDL_Window *window, SDL_bool grabbed)
{
#if SDL_MAC_NO_SANDBOX
CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), grabbed ? CGSGlobalHotKeyDisable : CGSGlobalHotKeyEnable);
#endif
}
#endif /* SDL_VIDEO_DRIVER_COCOA */