ui/cocoa: capture all keys and combos when mouse is grabbed
Applications such as Gnome may use Alt-Tab and Super-Tab for different purposes, some use Ctrl-arrows so we want to allow qemu to handle everything when it captures the mouse/keyboard. However, Mac OS handles some combos like Command-Tab and Ctrl-arrows at an earlier part of the event handling chain, not letting qemu see it. We add a global Event Tap that allows qemu to see all events when the mouse is grabbed. Note that this requires additional permissions. See: https://developer.apple.com/documentation/coregraphics/1454426-cgeventtapcreate?language=objc#discussion https://support.apple.com/en-in/guide/mac-help/mh32356/mac Acked-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Gustavo Noronha Silva <gustavo@noronha.dev.br> Message-Id: <20210713213200.2547-2-gustavo@noronha.dev.br> Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> Message-Id: <20220306121119.45631-2-akihiko.odaki@gmail.com> Reviewed-by: Will Cohen <wwcohen@gmail.com> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
This commit is contained in:
		
				
					committed by
					
						 Philippe Mathieu-Daudé
						Philippe Mathieu-Daudé
					
				
			
			
				
	
			
			
			
						parent
						
							69221df8cd
						
					
				
				
					commit
					f844cdb997
				
			| @@ -1270,11 +1270,17 @@ | ||||
| #                    host without sending this key to the guest when | ||||
| #                    "off". Defaults to "on" | ||||
| # | ||||
| # @full-grab: Capture all key presses, including system combos. This | ||||
| #             requires accessibility permissions, since it performs | ||||
| #             a global grab on key events. (default: off) | ||||
| #             See https://support.apple.com/en-in/guide/mac-help/mh32356/mac | ||||
| # | ||||
| # Since: 7.0 | ||||
| ## | ||||
| { 'struct': 'DisplayCocoa', | ||||
|   'data': { | ||||
|       '*left-command-key': 'bool' | ||||
|       '*left-command-key': 'bool', | ||||
|       '*full-grab': 'bool' | ||||
|   } } | ||||
|  | ||||
| ## | ||||
|   | ||||
| @@ -1916,6 +1916,9 @@ DEF("display", HAS_ARG, QEMU_OPTION_display, | ||||
| #if defined(CONFIG_CURSES) | ||||
|     "-display curses[,charset=<encoding>]\n" | ||||
| #endif | ||||
| #if defined(CONFIG_COCOA) | ||||
|     "-display cocoa[,full_grab=on|off]\n" | ||||
| #endif | ||||
| #if defined(CONFIG_OPENGL) | ||||
|     "-display egl-headless[,rendernode=<file>]\n" | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										65
									
								
								ui/cocoa.m
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								ui/cocoa.m
									
									
									
									
									
								
							| @@ -309,11 +309,13 @@ static void handleAnyDeviceErrors(Error * err) | ||||
|     BOOL isMouseGrabbed; | ||||
|     BOOL isFullscreen; | ||||
|     BOOL isAbsoluteEnabled; | ||||
|     CFMachPortRef eventsTap; | ||||
| } | ||||
| - (void) switchSurface:(pixman_image_t *)image; | ||||
| - (void) grabMouse; | ||||
| - (void) ungrabMouse; | ||||
| - (void) toggleFullScreen:(id)sender; | ||||
| - (void) setFullGrab:(id)sender; | ||||
| - (void) handleMonitorInput:(NSEvent *)event; | ||||
| - (bool) handleEvent:(NSEvent *)event; | ||||
| - (bool) handleEventLocked:(NSEvent *)event; | ||||
| @@ -336,6 +338,19 @@ static void handleAnyDeviceErrors(Error * err) | ||||
|  | ||||
| QemuCocoaView *cocoaView; | ||||
|  | ||||
| static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo) | ||||
| { | ||||
|     QemuCocoaView *cocoaView = userInfo; | ||||
|     NSEvent *event = [NSEvent eventWithCGEvent:cgEvent]; | ||||
|     if ([cocoaView isMouseGrabbed] && [cocoaView handleEvent:event]) { | ||||
|         COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n"); | ||||
|         return NULL; | ||||
|     } | ||||
|     COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n"); | ||||
|  | ||||
|     return cgEvent; | ||||
| } | ||||
|  | ||||
| @implementation QemuCocoaView | ||||
| - (id)initWithFrame:(NSRect)frameRect | ||||
| { | ||||
| @@ -361,6 +376,11 @@ QemuCocoaView *cocoaView; | ||||
|     } | ||||
|  | ||||
|     qkbd_state_free(kbd); | ||||
|  | ||||
|     if (eventsTap) { | ||||
|         CFRelease(eventsTap); | ||||
|     } | ||||
|  | ||||
|     [super dealloc]; | ||||
| } | ||||
|  | ||||
| @@ -655,6 +675,36 @@ QemuCocoaView *cocoaView; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void) setFullGrab:(id)sender | ||||
| { | ||||
|     COCOA_DEBUG("QemuCocoaView: setFullGrab\n"); | ||||
|  | ||||
|     CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged); | ||||
|     eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, | ||||
|                                  mask, handleTapEvent, self); | ||||
|     if (!eventsTap) { | ||||
|         warn_report("Could not create event tap, system key combos will not be captured.\n"); | ||||
|         return; | ||||
|     } else { | ||||
|         COCOA_DEBUG("Global events tap created! Will capture system key combos.\n"); | ||||
|     } | ||||
|  | ||||
|     CFRunLoopRef runLoop = CFRunLoopGetCurrent(); | ||||
|     if (!runLoop) { | ||||
|         warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0); | ||||
|     if (!tapEventsSrc ) { | ||||
|         warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode); | ||||
|     CFRelease(tapEventsSrc); | ||||
| } | ||||
|  | ||||
| - (void) toggleKey: (int)keycode { | ||||
|     qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode)); | ||||
| } | ||||
| @@ -1281,6 +1331,13 @@ QemuCocoaView *cocoaView; | ||||
|     [cocoaView toggleFullScreen:sender]; | ||||
| } | ||||
|  | ||||
| - (void) setFullGrab:(id)sender | ||||
| { | ||||
|     COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n"); | ||||
|  | ||||
|     [cocoaView setFullGrab:sender]; | ||||
| } | ||||
|  | ||||
| /* Tries to find then open the specified filename */ | ||||
| - (void) openDocumentation: (NSString *) filename | ||||
| { | ||||
| @@ -1994,11 +2051,17 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) | ||||
|     qemu_sem_wait(&app_started_sem); | ||||
|     COCOA_DEBUG("cocoa_display_init: app start completed\n"); | ||||
|  | ||||
|     QemuCocoaAppController *controller = (QemuCocoaAppController *)[[NSApplication sharedApplication] delegate]; | ||||
|     /* if fullscreen mode is to be used */ | ||||
|     if (opts->has_full_screen && opts->full_screen) { | ||||
|         dispatch_async(dispatch_get_main_queue(), ^{ | ||||
|             [NSApp activateIgnoringOtherApps: YES]; | ||||
|             [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; | ||||
|             [controller toggleFullScreen: nil]; | ||||
|         }); | ||||
|     } | ||||
|     if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) { | ||||
|         dispatch_async(dispatch_get_main_queue(), ^{ | ||||
|             [controller setFullGrab: nil]; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user