Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions fusepb/controllers/DebuggerController.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ - (void)mainThreadDebuggerDisassemble:(NSNumber *)address;
/* Is the debugger window active (as opposed to the debugger itself)? */
static int debugger_active;

/* Semaphore that blocks the emulation thread inside ui_debugger_activate()
until the user dismisses the debugger (Continue / close). */
static dispatch_semaphore_t debugger_semaphore;

@implementation DebuggerController

static DebuggerController *singleton = nil;
Expand Down Expand Up @@ -102,6 +106,7 @@ - (id)init
[super init];
self = [super initWithWindowNibName:@"Debugger"];
singleton = self;
debugger_semaphore = dispatch_semaphore_create( 0 );

[self performSelectorOnMainThread:@selector(habdleCloseNotification) withObject:nil waitUntilDone:YES];
[self performSelectorOnMainThread:@selector(setWindowFrameAutosaveName:) withObject:@"DebuggerWindow" waitUntilDone:YES];
Expand Down Expand Up @@ -142,7 +147,13 @@ - (void)handleWillClose:(NSNotification *)note
[memoryMapContents release];
memoryMapContents = nil;

debugger_run();
[[self window] unpinFromParent];

/* Only resume execution when the user dismissed the window directly.
If we arrived via deactivate_debugger(), debugger_active is already
0 and the caller has set debugger_mode; calling debugger_run() now
would overwrite a HALTED set by debugger_step(). */
if( debugger_active ) debugger_run();
}

- (void)eventsDoubleAction:(id)sender
Expand Down Expand Up @@ -191,20 +202,36 @@ - (void)debugger_activate:(id)sender
{
[[DisplayOpenGLView instance] pause];

/* Breakpoints fire unprompted. Bring FuseX (and the main window) back
into view if the user has Cmd-Tabbed away, hidden the app, or
minimized the main window — otherwise the Debugger window, as a
child of main, would not be visible. After this point the panel
behaves like any other child window: it sinks with FuseX when the
user switches to another app, and rises with FuseX on return. */
[NSApp activateIgnoringOtherApps:YES];
NSWindow *mainWindow = [[DisplayOpenGLView instance] window];
if( [mainWindow isMiniaturized] ) {
[mainWindow deminiaturize:nil];
}

BOOL wasVisible = [[singleton window] isVisible];
[singleton showWindow:nil];
if( !wasVisible ) {
[[singleton window] makeFirstResponder:entry];
}

[[singleton window] pinAsChildOf:mainWindow];

[continueButton setEnabled:YES];
[breakButton setEnabled:NO];
if( !debugger_active ) activate_debugger();

[NSApp runModalForWindow:[self window]];
activate_debugger();
}

- (void)debugger_deactivate:(int)interruptable
{
if( debugger_active ) deactivate_debugger();

[continueButton setEnabled:!interruptable ? YES : NO];
[continueButton setEnabled:interruptable ? NO : YES];
[breakButton setEnabled:interruptable ? YES : NO];
}

Expand Down Expand Up @@ -651,7 +678,11 @@ - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(int)rowIndex
int
ui_debugger_activate( void )
{
[[Emulator instance] debuggerActivate];
[[Emulator instance] performSelectorOnMainThread:@selector(debuggerActivate)
withObject:nil
waitUntilDone:YES];

dispatch_semaphore_wait( debugger_semaphore, DISPATCH_TIME_FOREVER );

return 0;
}
Expand Down Expand Up @@ -721,10 +752,10 @@ - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(int)rowIndex

static int
deactivate_debugger( void )
{
[NSApp stopModal];
{
debugger_active = 0;
[[DisplayOpenGLView instance] unpause];
dispatch_semaphore_signal( debugger_semaphore );
return 0;
}

Expand Down
3 changes: 0 additions & 3 deletions fusepb/controllers/FuseController.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,6 @@
- savePanelAccessoryView;
@property (getter=saveFileType,readonly) NSPopUpButton* saveFileType;

- (void)releaseCmdKeys:(NSString *)character withCode:(int)keyCode;
- (void)releaseKey:(int)keyCode;

- (void)ui_menu_activate_media_cartridge:(NSNumber*)active;
- (void)ui_menu_activate_media_cartridge_dock:(NSNumber*)active;
- (void)ui_menu_activate_media_cartridge_dock_eject:(NSNumber*)active;
Expand Down
150 changes: 84 additions & 66 deletions fusepb/controllers/FuseController.m
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,56 @@ - (id)init

recentSnapFileNames = [NSMutableArray arrayWithCapacity:NUM_RECENT_ITEMS];
[recentSnapFileNames retain];

/* Resync the emulator's modifier ivars whenever the main window
regains key status. AppKit delivers NSFlagsChanged to the key
window's first responder, so a modifier release while a utility
window was key never reaches DisplayOpenGLView and
Emulator.commandDown stays stuck — dropping every subsequent
keystroke in Emulator.keyDown:. The user-visible form is the
Cmd+, → Preferences flow: open Settings, close, then the
Spectrum keyboard ignores everything until the next modifier
press triggers a real NSFlagsChanged on the main window. The
filter inside the handler skips notifications for other
windows. */
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(mainWindowDidBecomeKey:)
name:NSWindowDidBecomeKeyNotification
object:nil];
}

return singleton;
}

- (void)mainWindowDidBecomeKey:(NSNotification *)note
{
/* The observer is registered with object:nil because the main window
does not yet exist when the FuseController singleton is constructed
during MainMenu.xib load. Filter to the main emulator window inside
the handler — and bail out early if the main window does not yet
exist, otherwise a non-nil note object would falsely pass the !=
comparison against nil during startup. */
NSWindow *mainWindow = [[DisplayOpenGLView instance] window];
if( !mainWindow ) return;
if( [note object] != mainWindow ) return;

Emulator *emu = [Emulator instance];
if( !emu ) return;

NSEvent *sync = [NSEvent keyEventWithType:NSFlagsChanged
location:NSZeroPoint
modifierFlags:[NSEvent modifierFlags]
timestamp:0
windowNumber:0
context:nil
characters:@""
charactersIgnoringModifiers:@""
isARepeat:NO
keyCode:0];
[emu flagsChanged:sync];
}

- (IBAction)disk_eject_a:(id)sender
{
cocoaui_disk_eject( 0 );
Expand Down Expand Up @@ -652,7 +697,6 @@ - (IBAction)open:(id)sender

[[DisplayOpenGLView instance] unpause];
}
[self releaseCmdKeys:@"o" withCode:QZ_o];
}

- (IBAction)reset:(id)sender
Expand Down Expand Up @@ -697,13 +741,11 @@ - (IBAction)rzx_play:(id)sender
- (IBAction)rzx_insert_snap:(id)sender
{
[[DisplayOpenGLView instance] rzxInsertSnap];
[self releaseCmdKeys:@"b" withCode:QZ_b];
}

- (IBAction)rzx_rollback:(id)sender
{
[[DisplayOpenGLView instance] rzxRollback];
[self releaseCmdKeys:@"z" withCode:QZ_z];
}

- (IBAction)rzx_start:(id)sender
Expand Down Expand Up @@ -863,7 +905,6 @@ - (IBAction)save_as:(id)sender
save_as_exit:
[[DisplayOpenGLView instance] unpause];
}
[self releaseCmdKeys:@"s" withCode:QZ_s];
}

- (IBAction)open_screen:(id)sender
Expand Down Expand Up @@ -991,13 +1032,11 @@ - (IBAction)hard_reset:(id)sender
- (IBAction)joystick_keyboard:(id)sender
{
[[DisplayOpenGLView instance] joystickToggleKeyboard];
[self releaseCmdKeys:@"j" withCode:QZ_j];
}

- (IBAction)keyboard_recreated:(id)sender
{
[[DisplayOpenGLView instance] keyboardToggleRecreatedZXSpectrum];
[self releaseCmdKeys:@"r" withCode:QZ_r];
}

- (IBAction)keyboard_arrows_shifted:(id)sender
Expand Down Expand Up @@ -1032,7 +1071,6 @@ - (IBAction)tape_open:(id)sender
- (IBAction)tape_play:(id)sender
{
[[DisplayOpenGLView instance] tapeTogglePlay];
[self releaseCmdKeys:@"p" withCode:QZ_p];
}

- (IBAction)tape_rewind:(id)sender
Expand Down Expand Up @@ -1124,35 +1162,49 @@ - (IBAction)quit:(id)sender
if( !settings_current.full_screen ) {
[[NSApp keyWindow] performClose:self];
}
[self releaseCmdKeys:@"q" withCode:QZ_q];
}

- (IBAction)hide:(id)sender
{
[NSApp hide:self];
[self releaseCmdKeys:@"h" withCode:QZ_h];
}

- (IBAction)help:(id)sender
{
if( !settings_current.full_screen ) {
[NSApp showHelp:self];
}
[self releaseCmdKeys:@"?" withCode:QZ_SLASH];
}

- (IBAction)cocoa_break:(id)sender
{
if ( gdbserver_debugging_enabled ) {
return;
}

/* Re-entry: if the Debugger is already showing, bring it forward and
leave state alone. Calling debugger_activate: a second time would
double-pause and unbalance the counter. */
DebuggerController *dbg = [DebuggerController loadedSingleton];
if ( dbg && [[dbg window] isVisible] ) {
[[dbg window] makeKeyAndOrderFront:nil];
return;
}

if ( paused ) {
debugger_mode = DEBUGGER_MODE_HALTED;
/* User-pause: release the user-pause contribution so the timer can
run, then set HALTED. The emulator detects HALTED on the next
instruction and ui_debugger_activate parks the thread on the
semaphore via the proper path. The user-pause is not restored on
dismissal — the user explicitly opened the Debugger from a paused
state, and Continue means "resume." */
paused = 0;
[[DebuggerController singleton] debugger_activate:nil];
} else {
[[DisplayOpenGLView instance] cocoaBreak];
[[DisplayOpenGLView instance] unpause];
}
/* The utility-window case (fuse_emulation_paused > 0 && !paused) is
unreachable: validateMenuItem: disables the Debugger menu item in
that state. */
[[DisplayOpenGLView instance] cocoaBreak];
[self setPauseState];
}

Expand Down Expand Up @@ -1186,7 +1238,6 @@ - (IBAction)showKeyboardPane:(id)sender
[keyboardController showCloseWindow:self];
}
}
[self releaseCmdKeys:@"k" withCode:QZ_k];
}

- (IBAction)showLoadBinaryPane:(id)sender
Expand Down Expand Up @@ -1248,7 +1299,6 @@ - (IBAction)showPreferencesPane:(id)sender;
}
[preferencesController showWindow:self];
}
[self releaseCmdKeys:@"." withCode:QZ_PERIOD];
}

- (IBAction)saveFileTypeClicked:(id)sender;
Expand Down Expand Up @@ -1280,6 +1330,8 @@ - (IBAction)resetUserDefaults:(id)sender

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];

[tapeBrowserController release];
[keyboardController release];
[saveBinaryController release];
Expand All @@ -1291,55 +1343,6 @@ - (void)dealloc
[super dealloc];
}

/*------------------------------------------------------------------------------
* releaseCmdKeys - This method fixes an issue when modal windows are used with
* the Mac OSX version of the SDL library.
* As the SDL normally captures all keystrokes, but we need to type in some
* Mac windows, all of the control menu windows run in modal mode. However,
* when this happens, the release of the command key and the shortcut key
* are not sent to SDL. We have to manually cause these events to happen
* to keep the SDL library in a sane state, otherwise only every other
* shortcut keypress will work.
*-----------------------------------------------------------------------------*/
- (void) releaseCmdKeys:(NSString *)character withCode:(int)keyCode
{
NSEvent *event1, *event2;
NSPoint point = { 0, 0 };

event1 = [NSEvent keyEventWithType:NSKeyUp location:point modifierFlags:0
timestamp:0 windowNumber:0 context:nil characters:character
charactersIgnoringModifiers:character isARepeat:NO
keyCode:keyCode];
[NSApp postEvent:event1 atStart:NO];

event2 = [NSEvent keyEventWithType:NSFlagsChanged location:point
modifierFlags:0 timestamp:0 windowNumber:0 context:nil
characters:nil charactersIgnoringModifiers:nil isARepeat:NO
keyCode:0];
[NSApp postEvent:event2 atStart:NO];
}

/*------------------------------------------------------------------------------
* releaseKey - This method fixes an issue when modal windows are used with
* the Mac OSX version of the SDL library.
* As the SDL normally captures all keystrokes, but we need to type in some
* Mac windows, all of the control menu windows run in modal mode. However,
* when this happens, the release of function key which started the process
* is not sent to SDL. We have to manually cause these events to happen
* to keep the SDL library in a sane state, otherwise only everyother shortcut
* keypress will work.
*-----------------------------------------------------------------------------*/
- (void) releaseKey:(int)keyCode
{
NSEvent *event1;
NSPoint point = { 0, 0 };

event1 = [NSEvent keyEventWithType:NSKeyUp location:point modifierFlags:0
timestamp:0 windowNumber:0 context:nil characters:@" "
charactersIgnoringModifiers:@" " isARepeat:NO keyCode:keyCode];
[NSApp postEvent:event1 atStart:NO];
}

- (void)ui_menu_activate_media_cartridge:(NSNumber*)active
{
[cartridge setEnabled:[active boolValue]];
Expand Down Expand Up @@ -2112,7 +2115,22 @@ - (BOOL)validateMenuItem:(NSMenuItem*)menuItem
return multiface == 0 ? NO : YES;
break;
case 176:
return debuggerEnabled == 0 ? NO : YES;
if( !debuggerEnabled ) return NO;
/* Disable Machine→Debugger when a utility window is holding the
emulator paused and the Debugger itself is not already up. The
menu cannot do anything useful in that state: cocoa_break: sets
HALTED, but z80 is not executing because the utility window's
pause stopped the timer, so no trap fires and the Debugger
window never appears until the user closes the utility. The
comparison must subtract the user-pause contribution so the
guard fires regardless of whether Machine→Pause is also set —
the utility-window holds are the part that breaks the trap
path. */
if( fuse_emulation_paused > (paused ? 1 : 0) ) {
DebuggerController *dbg = [DebuggerController loadedSingleton];
if( !dbg || ![[dbg window] isVisible] ) return NO;
}
return YES;
break;
default:
return YES;
Expand Down
Loading
Loading