3232#include " ../../configuration.h"
3333#include " ../../frontend/frontend.h"
3434#include " ../../input/drivers/cocoa_input.h"
35+ #include " ../../input/input_driver.h"
3536#include " ../../input/drivers_keyboard/keyboard_event_apple.h"
3637#include " ../../retroarch.h"
3738#include " ../../tasks/task_content.h"
@@ -513,6 +514,14 @@ @interface RetroArch_iOS () <MXMetricManagerSubscriber, UIPointerInteractionDele
513514@end
514515#endif
515516
517+ @interface RetroArch_iOS () <UITextFieldDelegate>
518+ @property (nonatomic , strong ) UITextField *keyboardTextField;
519+ @property (nonatomic , copy ) void (^keyboardCompletionCallback)(const char *);
520+ @property (nonatomic , assign ) char **keyboardBufferPtr;
521+ @property (nonatomic , assign ) size_t *keyboardSizePtr;
522+ @property (nonatomic , assign ) char *keyboardAllocatedBuffer;
523+ @end
524+
516525@implementation RetroArch_iOS
517526
518527#pragma mark - ApplePlatform
@@ -1060,6 +1069,21 @@ - (void)showGameView
10601069
10611070 [self .window setRootViewController: [CocoaView get ]];
10621071
1072+ /* Initialize hidden keyboard text field for iOS native keyboard support */
1073+ if (!self.keyboardTextField )
1074+ {
1075+ self.keyboardTextField = [[UITextField alloc ] initWithFrame: CGRectMake (0 , -100 , 1 , 1 )];
1076+ self.keyboardTextField .delegate = self;
1077+ self.keyboardTextField .autocapitalizationType = UITextAutocapitalizationTypeNone;
1078+ self.keyboardTextField .autocorrectionType = UITextAutocorrectionTypeNo;
1079+ self.keyboardTextField .spellCheckingType = UITextSpellCheckingTypeNo;
1080+ self.keyboardTextField .smartQuotesType = UITextSmartQuotesTypeNo;
1081+ self.keyboardTextField .smartDashesType = UITextSmartDashesTypeNo;
1082+ self.keyboardTextField .smartInsertDeleteType = UITextSmartInsertDeleteTypeNo;
1083+ self.keyboardTextField .returnKeyType = UIReturnKeyDone;
1084+ [[CocoaView get ].view addSubview: self .keyboardTextField];
1085+ }
1086+
10631087 dispatch_after (dispatch_time (DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), dispatch_get_main_queue (), ^{
10641088 command_event (CMD_EVENT_AUDIO_START, NULL );
10651089 });
@@ -1112,6 +1136,93 @@ - (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction
11121136}
11131137#endif
11141138
1139+ #pragma mark - UITextFieldDelegate (iOS/tvOS Native Keyboard Support)
1140+
1141+ - (BOOL )textField : (UITextField *)textField shouldChangeCharactersInRange : (NSRange )range replacementString : (NSString *)string
1142+ {
1143+ if (textField != self.keyboardTextField || !self.keyboardAllocatedBuffer || !self.keyboardSizePtr )
1144+ return YES ;
1145+
1146+ /* Calculate new text */
1147+ NSString *newText = [textField.text stringByReplacingCharactersInRange: range withString: string];
1148+
1149+ /* Update the RetroArch buffer in real-time so the menu can display it */
1150+ const char *utf8Text = [newText UTF8String ];
1151+ if (utf8Text)
1152+ {
1153+ strlcpy (self.keyboardAllocatedBuffer , utf8Text, 512 );
1154+ *self.keyboardSizePtr = strlen (self.keyboardAllocatedBuffer );
1155+ }
1156+
1157+ return YES ;
1158+ }
1159+
1160+ - (BOOL )textFieldShouldReturn : (UITextField *)textField
1161+ {
1162+ if (textField == self.keyboardTextField )
1163+ {
1164+ /* Update buffer with final text before calling callback */
1165+ if (self.keyboardAllocatedBuffer && self.keyboardSizePtr )
1166+ {
1167+ const char *finalText = [textField.text UTF8String ];
1168+ if (finalText)
1169+ {
1170+ strlcpy (self.keyboardAllocatedBuffer , finalText, 512 );
1171+ *self.keyboardSizePtr = strlen (self.keyboardAllocatedBuffer );
1172+ }
1173+ }
1174+
1175+ /* Store callback and buffer before clearing callback reference */
1176+ void (^callback)(const char *) = self.keyboardCompletionCallback ;
1177+ char *buffer = self.keyboardAllocatedBuffer ;
1178+
1179+ /* Clear callback to prevent double-invoke, but keep buffer references
1180+ * since the callback will free the buffer via input_keyboard_line_free() */
1181+ self.keyboardCompletionCallback = nil ;
1182+
1183+ /* DON'T dismiss keyboard here - let menu_input_dialog_end() -> ios_keyboard_end() do it
1184+ * This ensures ios_keyboard_active() returns true when the callback checks it */
1185+
1186+ /* Call completion callback with buffer pointer
1187+ * The callback will call menu_input_dialog_end() which will call ios_keyboard_end() */
1188+ if (callback && buffer)
1189+ callback (buffer);
1190+ /* Clear our references after callback completes */
1191+ self.keyboardBufferPtr = NULL ;
1192+ self.keyboardSizePtr = NULL ;
1193+ self.keyboardAllocatedBuffer = NULL ;
1194+
1195+ return NO ; /* Return NO to prevent UIKit from processing the return key event further */
1196+ }
1197+ return YES ;
1198+ }
1199+
1200+ - (void )textFieldDidEndEditing : (UITextField *)textField
1201+ {
1202+ if (textField == self.keyboardTextField )
1203+ {
1204+ /* Only call callback if it wasn't already called (by textFieldShouldReturn) */
1205+ if (self.keyboardCompletionCallback )
1206+ {
1207+ /* User dismissed keyboard without hitting return - treat as cancel */
1208+ void (^callback)(const char *) = self.keyboardCompletionCallback ;
1209+
1210+ /* Clear callback to prevent double-invoke, but keep buffer references
1211+ * since the callback will free the buffer via input_keyboard_line_free() */
1212+ self.keyboardCompletionCallback = nil ;
1213+
1214+ /* Call callback with NULL to indicate cancel
1215+ * The callback will handle cleanup via input_keyboard_line_free() */
1216+ callback (NULL );
1217+
1218+ /* Clear our references after callback completes */
1219+ self.keyboardBufferPtr = NULL ;
1220+ self.keyboardSizePtr = NULL ;
1221+ self.keyboardAllocatedBuffer = NULL ;
1222+ }
1223+ }
1224+ }
1225+
11151226@end
11161227
11171228ui_companion_driver_t ui_companion_cocoatouch = {
@@ -1135,6 +1246,79 @@ - (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction
11351246 " cocoatouch" ,
11361247};
11371248
1249+ /* C interface for iOS/tvOS native keyboard support */
1250+ bool ios_keyboard_start (char **buffer_ptr, size_t *size_ptr, const char *label,
1251+ input_keyboard_line_complete_t callback, void *userdata)
1252+ {
1253+ RetroArch_iOS *app = [RetroArch_iOS get ];
1254+ if (!app || !app.keyboardTextField || !buffer_ptr || !size_ptr)
1255+ return false ;
1256+
1257+ /* Allocate a fixed-size buffer for keyboard input */
1258+ char *allocated_buffer = (char *)malloc (512 );
1259+ if (!allocated_buffer)
1260+ return false ;
1261+
1262+ /* Initialize buffer with existing content if any */
1263+ if (*buffer_ptr && **buffer_ptr)
1264+ strlcpy (allocated_buffer, *buffer_ptr, 512 );
1265+ else
1266+ allocated_buffer[0 ] = ' \0 ' ;
1267+
1268+ /* Update the keyboard_line buffer pointer to point to our allocated buffer */
1269+ *buffer_ptr = allocated_buffer;
1270+ *size_ptr = strlen (allocated_buffer);
1271+
1272+ /* Store pointers so we can update them as user types */
1273+ app.keyboardBufferPtr = buffer_ptr;
1274+ app.keyboardSizePtr = size_ptr;
1275+ app.keyboardAllocatedBuffer = allocated_buffer;
1276+
1277+ /* Set up the text field with initial text from the buffer */
1278+ app.keyboardTextField .text = (allocated_buffer[0 ] != ' \0 ' ) ?
1279+ [NSString stringWithUTF8String: allocated_buffer] : @" " ;
1280+
1281+ /* Optionally set placeholder from label */
1282+ if (label)
1283+ app.keyboardTextField .placeholder = [NSString stringWithUTF8String: label];
1284+
1285+ /* Store the completion callback */
1286+ app.keyboardCompletionCallback = ^(const char *text) {
1287+ input_driver_state_t *input_st = input_state_get_ptr ();
1288+
1289+ if (callback)
1290+ callback (userdata, text);
1291+
1292+ /* Clean up RetroArch's keyboard state, mirroring what the built-in keyboard does */
1293+ if (input_st)
1294+ {
1295+ RARCH_LOG (" [iOS KB] cleaning up input state\n " );
1296+ input_keyboard_line_free (input_st);
1297+ input_st->flags &= ~INP_FLAG_KB_MAPPING_BLOCKED;
1298+ }
1299+ };
1300+
1301+ /* Show the keyboard */
1302+ [app.keyboardTextField becomeFirstResponder ];
1303+ return true ;
1304+ }
1305+
1306+ bool ios_keyboard_active (void )
1307+ {
1308+ RetroArch_iOS *app = [RetroArch_iOS get ];
1309+ return app && app.keyboardTextField && [app.keyboardTextField isFirstResponder ];
1310+ }
1311+
1312+ void ios_keyboard_end (void )
1313+ {
1314+ RetroArch_iOS *app = [RetroArch_iOS get ];
1315+ if (app && app.keyboardTextField )
1316+ {
1317+ [app.keyboardTextField resignFirstResponder ];
1318+ app.keyboardCompletionCallback = nil ;
1319+ }
1320+ }
1321+
11381322int main (int argc, char *argv[])
11391323{
11401324#if TARGET_OS_IOS
0 commit comments