From 472384ede6543674547de26a982110a722dda0ce Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Mon, 13 Jul 2026 11:09:44 +0800 Subject: [PATCH 3/6] Cocoa: split screens, macOS-style menus and SSH connection dialog --- uitoolkit/quartz/cocoa.h | 6 + uitoolkit/quartz/cocoa.m | 717 ++++++++++++++++--- uitoolkit/quartz/ui_connect_dialog.c | 20 +- uitoolkit/quartz/ui_event_source-cocoa.m | 19 + uitoolkit/quartz/ui_im_status_screen-cocoa.m | 10 +- 5 files changed, 652 insertions(+), 120 deletions(-) diff --git uitoolkit/quartz/cocoa.h uitoolkit/quartz/cocoa.h index bea7b6ba..5b27c70f 100644 --- uitoolkit/quartz/cocoa.h +++ uitoolkit/quartz/cocoa.h @@ -109,8 +109,14 @@ Pixmap cocoa_load_image(const char *path, u_int *width, u_int *height); int cocoa_dialog_connection(char **uri, char **pass, const char *msg); +int cocoa_dialog_ssh_full(char **uri, char **pass, char **exec_cmd, char **privkey, int *x11_fwd, + const char *def_server); + int cocoa_dialog_okcancel(const char *msg); int cocoa_dialog_alert(const char *msg); +/* Termination flag to prevent race conditions during cleanup */ +extern int cocoa_is_terminating; + #endif diff --git uitoolkit/quartz/cocoa.m uitoolkit/quartz/cocoa.m index 9caca702..143e43a6 100644 --- uitoolkit/quartz/cocoa.m +++ uitoolkit/quartz/cocoa.m @@ -13,6 +13,10 @@ #include "../ui_event_source.h" #include "../ui_selection_encoding.h" +#include /* strchr/strncmp */ +#include /* snprintf */ +#include /* malloc/free */ + /* XXX USE_CGLAYER is defined if 10.14 Mojave or later for now. */ #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 #define USE_CGLAYER @@ -85,6 +89,14 @@ static ui_window_t *uiwindow_for_mlterm_view; static NSMenuItem *configMenuItem; static NSMenuItem *pasteMenuItem; +static NSMenuItem *copyMenuItem; +static NSMenuItem *newWindowMenuItem; +static NSMenuItem *splitHMenuItem; +static NSMenuItem *splitVMenuItem; +static NSMenuItem *closeSplitMenuItem; +static NSMenuItem *bgColorMenuItem; +static NSMenuItem *sshConnMenuItem; +static NSMenuItem *quitMenuItem; char *global_args; @@ -103,12 +115,15 @@ char *global_args; void main_loop_final(void); #endif +int cocoa_is_terminating = 0; + static void exit_program(void) { /* This function is called twice from willClose() and monitor_pty() */ static int exited; if (!exited) { exited = 1; + cocoa_is_terminating = 1; #ifdef DEBUG main_loop_final(); @@ -286,6 +301,10 @@ static NSAlert *create_dialog(const char *msg, int has_cancel) { /* --- class --- */ int cocoa_dialog_alert(const char *msg); +#ifdef USE_LIBSSH2 +int cocoa_dialog_ssh_full(char **uri, char **pass, char **exec_cmd, char **privkey, int *x11_fwd, + const char *def_server); +#endif @implementation MLTermView @@ -334,23 +353,23 @@ int cocoa_dialog_alert(const char *msg); monitor_pty(); /* - * addLocalMonitorForEventsMatchingMask: takes an ObjC block, which older - * GCC (e.g. targeting Mac OS X 10.6) doesn't support. Skip the Eisu/Kana - * key monitor there rather than fail to build. + * addLocalMonitorForEventsMatchingMask: takes an ObjC block, which the + * GCC toolchain used for PowerPC doesn't support. Skip the Eisu/Kana key + * monitor there rather than fail to build. */ -#ifdef __clang__ +#ifdef __BLOCKS__ [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) { - switch (event.keyCode) { + switch ([event keyCode]) { case 0x66: /* Eisu */ case 0x68: /* Kana */ ignoreKeyDown = TRUE; + [[[event window] firstResponder] + keyDown:event]; + return (NSEvent *)nil; } - [event.window.firstResponder - keyDown:event]; - - return (NSEvent *)nil; + return event; }]; #endif @@ -358,14 +377,79 @@ int cocoa_dialog_alert(const char *msg); setenv("PANGO_LIBDIR", [[[NSBundle mainBundle] bundlePath] UTF8String], 1); NSMenu *appmenu = [[NSMenu alloc] initWithTitle:@""]; - configMenuItem = [appmenu addItemWithTitle:@"Config" - action:@selector(configMenu:) - keyEquivalent:@"C"]; + + newWindowMenuItem = [appmenu addItemWithTitle:@"New Window" + action:@selector(newWindowMenu:) + keyEquivalent:@"n"]; + [newWindowMenuItem setTarget:self]; + + NSMenuItem *closeWindowMenuItem = [appmenu addItemWithTitle:@"Close Window" + action:@selector(performClose:) + keyEquivalent:@"w"]; + [closeWindowMenuItem setTarget:nil]; + + [appmenu addItem:[NSMenuItem separatorItem]]; + + copyMenuItem = [appmenu addItemWithTitle:@"Copy" + action:@selector(copyMenu:) + keyEquivalent:@"c"]; + [copyMenuItem setTarget:self]; + pasteMenuItem = [appmenu addItemWithTitle:@"Paste" action:@selector(pasteMenu:) - keyEquivalent:@"P"]; - [configMenuItem setTarget:self]; + keyEquivalent:@"v"]; [pasteMenuItem setTarget:self]; + + [appmenu addItem:[NSMenuItem separatorItem]]; + + splitHMenuItem = [appmenu addItemWithTitle:@"Split Horizontal" + action:@selector(splitHorizontalMenu:) + keyEquivalent:@"h"]; + [splitHMenuItem setTarget:self]; + [splitHMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask | NSShiftKeyMask]; + + splitVMenuItem = [appmenu addItemWithTitle:@"Split Vertical" + action:@selector(splitVerticalMenu:) + keyEquivalent:@"d"]; + [splitVMenuItem setTarget:self]; + [splitVMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask | NSShiftKeyMask]; + + closeSplitMenuItem = [appmenu addItemWithTitle:@"Close Split" + action:@selector(closeSplitMenu:) + keyEquivalent:@"W"]; + [closeSplitMenuItem setTarget:self]; + [closeSplitMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask | NSShiftKeyMask]; + + [appmenu addItem:[NSMenuItem separatorItem]]; + +#ifdef USE_LIBSSH2 + sshConnMenuItem = [appmenu addItemWithTitle:@"New SSH Connection..." + action:@selector(sshConnMenu:) + keyEquivalent:@""]; + [sshConnMenuItem setTarget:self]; + + [appmenu addItem:[NSMenuItem separatorItem]]; +#endif + + bgColorMenuItem = [appmenu addItemWithTitle:@"Toggle Light/Dark" + action:@selector(bgColorMenu:) + keyEquivalent:@""]; + [bgColorMenuItem setTarget:self]; + + [appmenu addItem:[NSMenuItem separatorItem]]; + + configMenuItem = [appmenu addItemWithTitle:@"Config" + action:@selector(configMenu:) + keyEquivalent:@""]; + [configMenuItem setTarget:self]; + + [appmenu addItem:[NSMenuItem separatorItem]]; + + quitMenuItem = [appmenu addItemWithTitle:@"Quit mlterm" + action:@selector(terminate:) + keyEquivalent:@"q"]; + [quitMenuItem setTarget:[NSApplication sharedApplication]]; + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; [[menu addItemWithTitle:@"" action:nil keyEquivalent:@""] setSubmenu:appmenu]; @@ -389,19 +473,136 @@ int cocoa_dialog_alert(const char *msg); } - (void)configMenu:(id)sender { - /* if by any change */ - if (((ui_screen_t *)uiwindow)->term) { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { ui_screen_exec_cmd((ui_screen_t *)uiwindow, "mlconfig"); } } +- (void)newWindowMenu:(id)sender { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; + NSString *executablePath = [NSString stringWithFormat:@"%@/Contents/MacOS/mlterm", bundlePath]; + + NSTask *task = [[NSTask alloc] init]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + [task setExecutableURL:[NSURL fileURLWithPath:executablePath]]; + [task launchAndReturnError:nil]; +#else + [task setLaunchPath:executablePath]; + [task launch]; +#endif + [task release]; + } +} + +#ifdef USE_LIBSSH2 +- (void)sshConnMenu:(id)sender { + char *uri = NULL; + char *pass = NULL; + char *exec_cmd = NULL; + char *privkey = NULL; + int x11_fwd = 0; + + NSWindow *mainWin = [self window]; + + cocoa_dialog_ssh_full(&uri, &pass, &exec_cmd, &privkey, &x11_fwd, ""); + + /* ALWAYS restore window and first responder after modal dialog, regardless of how it was closed */ + if (mainWin) { + [mainWin makeKeyAndOrderFront:nil]; + [mainWin makeFirstResponder:self]; + } + + /* ALWAYS reset menu targets after modal dialog closes (OK, Cancel, or (x) button) */ + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + [newWindowMenuItem setTarget:uiwindow->my_window]; +#ifdef USE_LIBSSH2 + [sshConnMenuItem setTarget:uiwindow->my_window]; +#endif + [copyMenuItem setTarget:uiwindow->my_window]; + [pasteMenuItem setTarget:uiwindow->my_window]; + [splitHMenuItem setTarget:uiwindow->my_window]; + [splitVMenuItem setTarget:uiwindow->my_window]; + [closeSplitMenuItem setTarget:uiwindow->my_window]; + [bgColorMenuItem setTarget:uiwindow->my_window]; + [configMenuItem setTarget:uiwindow->my_window]; + } + + if (uri) { + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "mlclient %s%s%s%s%s%s", + uri, + (privkey && *privkey) ? " --privkey " : "", + (privkey && *privkey) ? privkey : "", + (exec_cmd && *exec_cmd) ? " -e " : "", + (exec_cmd && *exec_cmd) ? exec_cmd : "", + x11_fwd ? " --x11fwd" : ""); + + if (pass && *pass) { + setenv("MLTERM_PASSWORD", pass, 1); + } + + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + ui_screen_exec_cmd((ui_screen_t *)uiwindow, cmd); + } + + if (pass && *pass) { + unsetenv("MLTERM_PASSWORD"); + } + + free(uri); + if (pass) free(pass); + if (exec_cmd) free(exec_cmd); + if (privkey) free(privkey); + } +} +#endif + +- (void)copyMenu:(id)sender { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + ui_window_set_selection_owner(uiwindow, CurrentTime, SEL_CLIPBOARD); + } +} + - (void)pasteMenu:(id)sender { - /* if by any change */ - if (((ui_screen_t *)uiwindow)->term) { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { ui_screen_exec_cmd((ui_screen_t *)uiwindow, "paste"); } } +- (void)splitHorizontalMenu:(id)sender { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + ui_screen_exec_cmd((ui_screen_t *)uiwindow, "vsplit_screen"); + } +} + +- (void)splitVerticalMenu:(id)sender { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + ui_screen_exec_cmd((ui_screen_t *)uiwindow, "hsplit_screen"); + } +} + +- (void)closeSplitMenu:(id)sender { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + ui_screen_exec_cmd((ui_screen_t *)uiwindow, "close_screen"); + } +} + +- (void)bgColorMenu:(id)sender { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { + ui_color_t *bg = &uiwindow->bg_color; + int is_dark = (bg->pixel & 0xffffff) < 0x808080; + + if (is_dark) { + ui_screen_set_config((ui_screen_t *)uiwindow, NULL, "fg_color", "black"); + ui_screen_set_config((ui_screen_t *)uiwindow, NULL, "bg_color", "white"); + } else { + ui_screen_set_config((ui_screen_t *)uiwindow, NULL, "fg_color", "white"); + ui_screen_set_config((ui_screen_t *)uiwindow, NULL, "bg_color", "black"); + } + } +} + #ifdef USE_CGLAYER - (void)setFrameSize:(NSSize)s { if (layer) { @@ -430,16 +631,16 @@ static void reset_position(ui_window_t *uiwindow) { } - (void)resized:(NSNotification *)note { - if (!uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { + if (!uiwindow || !uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { /* It has been already removed from ui_layout or term has been detached. */ return; } uiwindow->parent->width = - ((NSView *)[self window].contentView).frame.size.width - + [((NSView *)[[self window] contentView]) frame].size.width - uiwindow->parent->hmargin * 2; uiwindow->parent->height = - ((NSView *)[self window].contentView).frame.size.height - + [((NSView *)[[self window] contentView]) frame].size.height - uiwindow->parent->vmargin * 2; (*uiwindow->parent->window_resized)(uiwindow->parent); @@ -458,7 +659,7 @@ static void reset_position(ui_window_t *uiwindow) { [[self window] orderOut:self]; if (uiwindow->event_mask & PointerMotionMask) { - [self window].acceptsMouseMovedEvents = YES; + [[self window] setAcceptsMouseMovedEvents:YES]; } } @@ -466,7 +667,7 @@ static void reset_position(ui_window_t *uiwindow) { addObserver:self selector:@selector(resized:) name:NSViewFrameDidChangeNotification - object:[self window].contentView]; + object:[[self window] contentView]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(focused:) @@ -483,15 +684,14 @@ static void reset_position(ui_window_t *uiwindow) { name:NSWindowWillCloseNotification object:[self window]]; - int diff_x = [self window].frame.size.width - self.frame.size.width; - int diff_y = [self window].frame.size.height - self.frame.size.height; + int diff_x = [[self window] frame].size.width - [self frame].size.width; + int diff_y = [[self window] frame].size.height - [self frame].size.height; u_int sb_width = [NSScroller scrollerWidth]; int y = ACTUAL_HEIGHT(uiwindow->parent) - ACTUAL_HEIGHT(uiwindow) - uiwindow->y; /* Change view size */ - self.frame = - CGRectMake(uiwindow->x, y, ACTUAL_WIDTH(uiwindow), ACTUAL_HEIGHT(uiwindow)); + [self setFrame:NSMakeRect(uiwindow->x, y, ACTUAL_WIDTH(uiwindow), ACTUAL_HEIGHT(uiwindow))]; /* Creating scrollbar */ @@ -502,15 +702,15 @@ static void reset_position(ui_window_t *uiwindow) { [scroller setTarget:self]; [scroller setAction:@selector(scrollerAction:)]; #if 1 - [scroller setFloatValue:0.0 knobProportion:1.0]; /* Deprecated since 10.6 */ + [scroller setFloatValue:0.0 knobProportion:1.0]; #else - scroller.knobProportion = 1.0; - scroller.doubleValue = 0.0; + [scroller setKnobProportion:1.0]; + [scroller setDoubleValue:0.0]; #endif #if 0 [scroller setArrowsPosition:NSScrollerArrowsMaxEnd]; #endif - [[self window].contentView addSubview:scroller]; + [[[self window] contentView] addSubview:scroller]; term->scrollbar.window.my_window = (NSView *)scroller; @@ -532,7 +732,7 @@ static void reset_position(ui_window_t *uiwindow) { uiwindow->parent->my_window = [self window]; - r = [self window].frame; + r = [[self window] frame]; r.size.width = ACTUAL_WIDTH(uiwindow->parent) + diff_x; float new_height = ACTUAL_HEIGHT(uiwindow->parent) + diff_y; r.origin.y += (r.size.height - new_height); @@ -553,7 +753,7 @@ static void reset_position(ui_window_t *uiwindow) { * window position. */ if (uiwindow->parent->x > 0 || uiwindow->parent->y > 0) { - r = [self window].frame; + r = [[self window] frame]; r.origin.x += uiwindow->parent->x; r.origin.y -= uiwindow->parent->y; @@ -570,6 +770,10 @@ static void reset_position(ui_window_t *uiwindow) { } - (void)scrollerAction:(id)sender { + if (!uiwindow || !((ui_screen_t *)uiwindow)->term) { + return; + } + struct terminal *term = ((ui_screen_t *)uiwindow)->screen_scroll_listener->self; ui_scrollbar_t *sb = &term->scrollbar; float pos = [sender floatValue]; @@ -601,7 +805,7 @@ static void reset_position(ui_window_t *uiwindow) { } - (void)drawRect:(NSRect)rect { - if (!uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { + if (!uiwindow || !uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { /* It has been already removed from ui_layout or term has been detached. */ return; } @@ -622,7 +826,7 @@ static void reset_position(ui_window_t *uiwindow) { #ifdef USE_CGLAYER if (!layer) { - layer = CGLayerCreateWithContext(screen_ctx, self.bounds.size, NULL); + layer = CGLayerCreateWithContext(screen_ctx, [self bounds].size, NULL); ctx = CGLayerGetContext(layer); if (uiwindow->update_window_flag == 0) { @@ -699,7 +903,7 @@ static ui_window_t *get_current_window(ui_window_t *win) { } - (void)focused:(NSNotification *)note { - if (!uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { + if (!uiwindow || !uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { /* It has been already removed from ui_layout or term has been detached. */ return; } @@ -713,13 +917,22 @@ static ui_window_t *get_current_window(ui_window_t *win) { ui_window_t *focused; if ((focused = get_current_window(ui_get_root_window(uiwindow)))) { - [configMenuItem setTarget:focused->my_window]; + [newWindowMenuItem setTarget:focused->my_window]; +#ifdef USE_LIBSSH2 + [sshConnMenuItem setTarget:focused->my_window]; +#endif + [copyMenuItem setTarget:focused->my_window]; [pasteMenuItem setTarget:focused->my_window]; + [splitHMenuItem setTarget:focused->my_window]; + [splitVMenuItem setTarget:focused->my_window]; + [closeSplitMenuItem setTarget:focused->my_window]; + [bgColorMenuItem setTarget:focused->my_window]; + [configMenuItem setTarget:focused->my_window]; } } - (void)unfocused:(NSNotification *)note { - if (!uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { + if (!uiwindow || !uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { /* It has been already removed from ui_layout or term has been detached. */ return; } @@ -735,24 +948,52 @@ static ui_window_t *get_current_window(ui_window_t *win) { return YES; } +- (BOOL)acceptsFirstMouse:(NSEvent *)event { + return YES; +} + - (BOOL)becomeFirstResponder { if ([super becomeFirstResponder] != YES) { return NO; } + if (!uiwindow || !uiwindow->parent || !((ui_screen_t *)uiwindow)->term) { + return YES; + } + XEvent ev; ev.type = UI_KEY_FOCUS_IN; ui_window_receive_event(uiwindow, &ev); - [configMenuItem setTarget:uiwindow->my_window]; + /* Set menu targets to this view so actions route correctly */ + [newWindowMenuItem setTarget:uiwindow->my_window]; +#ifdef USE_LIBSSH2 + [sshConnMenuItem setTarget:uiwindow->my_window]; +#endif + [copyMenuItem setTarget:uiwindow->my_window]; [pasteMenuItem setTarget:uiwindow->my_window]; + [splitHMenuItem setTarget:uiwindow->my_window]; + [splitVMenuItem setTarget:uiwindow->my_window]; + [closeSplitMenuItem setTarget:uiwindow->my_window]; + [bgColorMenuItem setTarget:uiwindow->my_window]; + [configMenuItem setTarget:uiwindow->my_window]; return YES; } +- (BOOL)resignFirstResponder { + /* Just call super - becomeFirstResponder on the new view will handle focus change */ + return [super resignFirstResponder]; +} + - (void)willClose:(NSNotification *)note { + /* If terminating the app, skip cleanup to avoid crashes during multi-window close */ + if (cocoa_is_terminating) { + return; + } + if (ui_get_all_screens(NULL) == 0) { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -771,6 +1012,13 @@ static ui_window_t *get_current_window(ui_window_t *win) { remove_all_observers(root); + /* If this is the last screen/window, we're quitting - skip cleanup to avoid resize crashes */ + if (ui_get_all_screens(NULL) <= 1) { + cocoa_is_terminating = 1; + exit_program(); + return; + } + XEvent ev; ev.type = UI_CLOSE_WINDOW; @@ -783,22 +1031,24 @@ static ui_window_t *get_current_window(ui_window_t *win) { } - (void)mouseDown:(NSEvent *)event { + [[self window] makeFirstResponder:self]; + NSPoint loc = [event locationInWindow]; XButtonEvent bev; bev.type = UI_BUTTON_PRESS; - bev.time = event.timestamp * 1000; - bev.x = loc.x - self.frame.origin.x; + bev.time = [event timestamp] * 1000; + bev.x = loc.x - [self frame].origin.x; bev.y = - ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y - */ 1; - bev.state = event.modifierFlags & + ACTUAL_HEIGHT(uiwindow) - (loc.y - [self frame].origin.y) - 1; + bev.state = [event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask); - if (event.type == NSLeftMouseDown) { + if ([event type] == NSLeftMouseDown) { bev.button = 1; } else { bev.button = 3; } - bev.click_count = event.clickCount; + bev.click_count = [event clickCount]; ui_window_receive_event(uiwindow, (XEvent *)&bev); } @@ -808,13 +1058,13 @@ static ui_window_t *get_current_window(ui_window_t *win) { XButtonEvent bev; bev.type = UI_BUTTON_RELEASE; - bev.time = event.timestamp * 1000; - bev.x = loc.x - self.frame.origin.x; + bev.time = [event timestamp] * 1000; + bev.x = loc.x - [self frame].origin.x; bev.y = - ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y - */ 1; - bev.state = event.modifierFlags & + ACTUAL_HEIGHT(uiwindow) - (loc.y - [self frame].origin.y) - 1; + bev.state = [event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask); - if (event.type == NSLeftMouseUp) { + if ([event type] == NSLeftMouseUp) { bev.button = 1; } else { bev.button = 3; @@ -828,11 +1078,11 @@ static ui_window_t *get_current_window(ui_window_t *win) { XButtonEvent bev; bev.type = UI_BUTTON_RELEASE; - bev.time = event.timestamp * 1000; - bev.x = loc.x - self.frame.origin.x; + bev.time = [event timestamp] * 1000; + bev.x = loc.x - [self frame].origin.x; bev.y = - ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y - */ 1; - bev.state = event.modifierFlags & + ACTUAL_HEIGHT(uiwindow) - (loc.y - [self frame].origin.y) - 1; + bev.state = [event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask); bev.button = 3; @@ -840,18 +1090,20 @@ static ui_window_t *get_current_window(ui_window_t *win) { } - (NSMenu *)menuForEvent:(NSEvent *)event { + [[self window] makeFirstResponder:self]; + NSPoint loc = [event locationInWindow]; XButtonEvent bev; bev.type = UI_BUTTON_PRESS; - bev.time = event.timestamp * 1000; - bev.x = loc.x - self.frame.origin.x; + bev.time = [event timestamp] * 1000; + bev.x = loc.x - [self frame].origin.x; bev.y = - ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y - */ 1; - bev.state = event.modifierFlags & + ACTUAL_HEIGHT(uiwindow) - (loc.y - [self frame].origin.y) - 1; + bev.state = [event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask); bev.button = 3; - bev.click_count = event.clickCount; + bev.click_count = [event clickCount]; ui_window_receive_event(uiwindow, (XEvent *)&bev); @@ -863,10 +1115,10 @@ static ui_window_t *get_current_window(ui_window_t *win) { XMotionEvent mev; mev.type = UI_BUTTON_MOTION; - mev.time = event.timestamp * 1000; - mev.x = loc.x - self.frame.origin.x; - mev.y = ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y */ -1; - if (event.type == NSLeftMouseDragged) { + mev.time = [event timestamp] * 1000; + mev.x = loc.x - [self frame].origin.x; + mev.y = ACTUAL_HEIGHT(uiwindow) - (loc.y - [self frame].origin.y) - 1; + if ([event type] == NSLeftMouseDragged) { mev.state = Button1Mask; } else { mev.state = Button3Mask; @@ -880,9 +1132,9 @@ static ui_window_t *get_current_window(ui_window_t *win) { XMotionEvent mev; mev.type = UI_BUTTON_MOTION; - mev.time = event.timestamp * 1000; - mev.x = loc.x - self.frame.origin.x; - mev.y = ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y */ -1; + mev.time = [event timestamp] * 1000; + mev.x = loc.x - [self frame].origin.x; + mev.y = ACTUAL_HEIGHT(uiwindow) - (loc.y - [self frame].origin.y) - 1; mev.state = Button3Mask; ui_window_receive_event(uiwindow, (XEvent *)&mev); @@ -893,9 +1145,9 @@ static ui_window_t *get_current_window(ui_window_t *win) { XMotionEvent mev; mev.type = UI_POINTER_MOTION; - mev.time = event.timestamp * 1000; - mev.x = loc.x - self.frame.origin.x; - mev.y = ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y */ -1; + mev.time = [event timestamp] * 1000; + mev.x = loc.x - [self frame].origin.x; + mev.y = ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* [self frame].origin.y */ -1; mev.state = 0; ui_window_receive_event(uiwindow, (XEvent *)&mev); @@ -909,35 +1161,35 @@ static ui_window_t *get_current_window(ui_window_t *win) { bevPress.type = UI_BUTTON_PRESS; bevRelease.type = UI_BUTTON_RELEASE; - bevPress.time = event.timestamp * 1000; - bevRelease.time = (event.timestamp * 1000) + 1; + bevPress.time = [event timestamp] * 1000; + bevRelease.time = ([event timestamp] * 1000) + 1; - bevPress.x = loc.x - self.frame.origin.x; - bevRelease.x = loc.x - self.frame.origin.x; + bevPress.x = loc.x - [self frame].origin.x; + bevRelease.x = loc.x - [self frame].origin.x; bevPress.y = - ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y - */ 1; + ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* [self frame].origin.y - */ 1; bevRelease.y = - ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* self.frame.origin.y - */ 1; + ACTUAL_HEIGHT(uiwindow->parent) - loc.y - /* [self frame].origin.y - */ 1; - bevPress.state = event.modifierFlags & + bevPress.state = [event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask); - bevRelease.state = event.modifierFlags & + bevRelease.state = [event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask); bevPress.click_count = 1; - if (event.deltaY > 1) { + if ([event deltaY] > 1) { bevPress.button = 4; bevRelease.button = 4; } - if (event.deltaY < -1) { + if ([event deltaY] < -1) { bevPress.button = 5; bevRelease.button = 5; } - if (event.deltaY < -1 || event.deltaY > 1) { + if ([event deltaY] < -1 || [event deltaY] > 1) { ui_window_receive_event(uiwindow, (XEvent *)&bevPress); ui_window_receive_event(uiwindow, (XEvent *)&bevRelease); } @@ -948,7 +1200,7 @@ static ui_window_t *get_current_window(ui_window_t *win) { return; } - u_int flags = event.modifierFlags; + u_int flags = [event modifierFlags]; /* ShiftMask is not set without this. (see insertText()) */ currentShiftMask = flags & NSShiftKeyMask; @@ -972,13 +1224,13 @@ static ui_window_t *get_current_window(ui_window_t *win) { if (kev.state & NSControlKeyMask) { kev.keysym = [[event charactersIgnoringModifiers] characterAtIndex:0]; - kev.utf8 = [event characters].UTF8String; + kev.utf8 = [[event characters] UTF8String]; } else if (kev.state & NSAlternateKeyMask) { kev.keysym = [[event charactersIgnoringModifiers] characterAtIndex:0]; kev.utf8 = NULL; } else { kev.keysym = [[event characters] characterAtIndex:0]; - kev.utf8 = [event characters].UTF8String; + kev.utf8 = [[event characters] UTF8String]; } if ((kev.state & NSShiftKeyMask) && 'A' <= kev.keysym && @@ -1001,7 +1253,7 @@ static ui_window_t *get_current_window(ui_window_t *win) { * If this view exits with owning pasteboard, this method is called * after ui_screen_t::term is deleted. */ - if (((ui_screen_t *)uiwindow)->term) { + if (uiwindow && ((ui_screen_t *)uiwindow)->term) { XSelectionRequestEvent ev; ev.type = UI_SELECTION_REQUESTED; @@ -1090,7 +1342,11 @@ static ui_window_t *get_current_window(ui_window_t *win) { NSRect r = NSMakeRect(x, y, ui_col_width((ui_screen_t *)uiwindow), ui_line_height((ui_screen_t *)uiwindow)); +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + r = [[self window] convertRectToScreen:r]; +#else r.origin = [[self window] convertBaseToScreen:r.origin]; +#endif return r; } @@ -1502,7 +1758,7 @@ void view_alloc(ui_window_t *uiwindow) { MLTermView *view = [[MLTermView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]; - [((NSWindow *)uiwindow->parent->my_window).contentView addSubview:view]; + [[(NSWindow *)uiwindow->parent->my_window contentView] addSubview:view]; [view release]; } @@ -1584,8 +1840,8 @@ void window_alloc(ui_window_t *root) { void window_dealloc(NSWindow *window) { [window close]; } void window_resize(NSWindow *window, int width, int height) { - NSRect wr = window.frame; - NSSize vs = ((NSView *)window.contentView).frame.size; + NSRect wr = [window frame]; + NSSize vs = [(NSView *)[window contentView] frame].size; int diff_x = wr.size.width - vs.width; int diff_y = wr.size.height - vs.height; @@ -1596,8 +1852,8 @@ void window_resize(NSWindow *window, int width, int height) { } void window_move_resize(NSWindow *window, int x, int y, int width, int height) { - NSRect wr = window.frame; - NSSize vs = ((NSView *)window.contentView).frame.size; + NSRect wr = [window frame]; + NSSize vs = [(NSView *)[window contentView] frame].size; int diff_x = wr.size.width - vs.width; int diff_y = wr.size.height - vs.height; @@ -1609,7 +1865,7 @@ void window_move_resize(NSWindow *window, int x, int y, int width, int height) { } void window_accepts_mouse_moved_events(NSWindow *window, int accept) { - window.acceptsMouseMovedEvents = (accept ? YES : NO); + [window setAcceptsMouseMovedEvents:(accept ? YES : NO)]; } void window_set_normal_hints(NSWindow *window, u_int width_inc, @@ -1618,9 +1874,9 @@ void window_set_normal_hints(NSWindow *window, u_int width_inc, } void window_get_position(NSWindow *window, int *x, int *y) { - *x = window.frame.origin.x; - *y = [[window screen] visibleFrame].size.height - window.frame.origin.y - - [window.contentView frame].size.height; + *x = [window frame].origin.x; + *y = [[window screen] visibleFrame].size.height - [window frame].origin.y - + [[window contentView] frame].size.height; } static int is_valid_utf8(const char *str, size_t len) { @@ -1659,10 +1915,10 @@ void app_urgent_bell(int on) { void scroller_update(NSScroller *scroller, float pos, float knob) { #if 1 - [scroller setFloatValue:pos knobProportion:knob]; /* Deprecated since 10.6 */ + [scroller setFloatValue:pos knobProportion:knob]; #else - scroller.knobProportion = knob; - scroller.doubleValue = pos; + [scroller setKnobProportion:knob]; + [scroller setDoubleValue:pos]; #endif } @@ -1792,6 +2048,275 @@ int cocoa_dialog_connection(char **uri, char **pass, const char *msg) { } } +/* Helper class to handle modal panel close button */ +@interface ModalPanelDelegate : NSObject +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 + +#endif +@end + +@implementation ModalPanelDelegate +- (BOOL)windowShouldClose:(id)sender { + /* Stop modal session when (x) button is clicked */ + [NSApp stopModalWithCode:NSCancelButton]; + return YES; +} +/* + * stopModalWithCode: takes the return code directly, so it can't be wired + * as a button action (the sender would be used as the code). Read the + * button's tag and pass it as the modal result. + */ +- (void)buttonPressed:(id)sender { + [NSApp stopModalWithCode:[sender tag]]; +} +@end + +int cocoa_dialog_ssh_full(char **uri, char **pass, char **exec_cmd, char **privkey, int *x11_fwd, + const char *def_server) { + if (![NSThread isMainThread]) { + return 0; + } + + NSPanel *panel = [[NSPanel alloc] initWithContentRect:NSMakeRect(0, 0, 400, 320) + styleMask:NSTitledWindowMask | NSClosableWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + [panel autorelease]; + [panel setTitle:@"SSH Connection"]; + [panel center]; + + /* Set delegate to handle (x) button */ + ModalPanelDelegate *delegate = [[ModalPanelDelegate alloc] init]; + [delegate autorelease]; + [panel setDelegate:delegate]; + + NSView *content = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 400, 320)]; + [content autorelease]; + [panel setContentView:content]; + + int ypos = 280; + int labelWidth = 100; + int fieldWidth = 270; + int rowHeight = 24; + int spacing = 30; + + NSTextField *hostLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, ypos, labelWidth, 20)]; + [hostLabel autorelease]; + [hostLabel setStringValue:@"Host:"]; + [hostLabel setEditable:NO]; + [hostLabel setBordered:NO]; + [hostLabel setDrawsBackground:NO]; + [content addSubview:hostLabel]; + + NSTextField *hostField = [[NSTextField alloc] initWithFrame:NSMakeRect(120, ypos, fieldWidth, rowHeight)]; + [hostField autorelease]; + [content addSubview:hostField]; + ypos -= spacing; + + NSTextField *portLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, ypos, labelWidth, 20)]; + [portLabel autorelease]; + [portLabel setStringValue:@"Port:"]; + [portLabel setEditable:NO]; + [portLabel setBordered:NO]; + [portLabel setDrawsBackground:NO]; + [content addSubview:portLabel]; + + NSTextField *portField = [[NSTextField alloc] initWithFrame:NSMakeRect(120, ypos, fieldWidth, rowHeight)]; + [portField autorelease]; + [portField setStringValue:@"22"]; + [content addSubview:portField]; + ypos -= spacing; + + NSTextField *userLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, ypos, labelWidth, 20)]; + [userLabel autorelease]; + [userLabel setStringValue:@"User:"]; + [userLabel setEditable:NO]; + [userLabel setBordered:NO]; + [userLabel setDrawsBackground:NO]; + [content addSubview:userLabel]; + + NSTextField *userField = [[NSTextField alloc] initWithFrame:NSMakeRect(120, ypos, fieldWidth, rowHeight)]; + [userField autorelease]; + [content addSubview:userField]; + ypos -= spacing; + + NSTextField *passLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, ypos, labelWidth, 20)]; + [passLabel autorelease]; + [passLabel setStringValue:@"Password:"]; + [passLabel setEditable:NO]; + [passLabel setBordered:NO]; + [passLabel setDrawsBackground:NO]; + [content addSubview:passLabel]; + + NSTextField *passField = [[MLSecureTextField alloc] initWithFrame:NSMakeRect(120, ypos, fieldWidth, rowHeight)]; + [passField autorelease]; + [content addSubview:passField]; + ypos -= spacing; + + NSTextField *keyLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, ypos, labelWidth, 20)]; + [keyLabel autorelease]; + [keyLabel setStringValue:@"Private Key:"]; + [keyLabel setEditable:NO]; + [keyLabel setBordered:NO]; + [keyLabel setDrawsBackground:NO]; + [content addSubview:keyLabel]; + + NSTextField *keyField = [[NSTextField alloc] initWithFrame:NSMakeRect(120, ypos, fieldWidth, rowHeight)]; + [keyField autorelease]; + if (*privkey) { + [keyField setStringValue:[NSString stringWithCString:*privkey encoding:NSUTF8StringEncoding]]; + } + [content addSubview:keyField]; + ypos -= spacing; + + NSTextField *encLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, ypos, labelWidth, 20)]; + [encLabel autorelease]; + [encLabel setStringValue:@"Encoding:"]; + [encLabel setEditable:NO]; + [encLabel setBordered:NO]; + [encLabel setDrawsBackground:NO]; + [content addSubview:encLabel]; + + NSTextField *encField = [[NSTextField alloc] initWithFrame:NSMakeRect(120, ypos, fieldWidth, rowHeight)]; + [encField autorelease]; + [encField setStringValue:@"UTF-8"]; + [content addSubview:encField]; + ypos -= spacing; + + NSTextField *cmdLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, ypos, labelWidth, 20)]; + [cmdLabel autorelease]; + [cmdLabel setStringValue:@"Exec Cmd:"]; + [cmdLabel setEditable:NO]; + [cmdLabel setBordered:NO]; + [cmdLabel setDrawsBackground:NO]; + [content addSubview:cmdLabel]; + + NSTextField *cmdField = [[NSTextField alloc] initWithFrame:NSMakeRect(120, ypos, fieldWidth, rowHeight)]; + [cmdField autorelease]; + [content addSubview:cmdField]; + ypos -= spacing; + + NSButton *x11Check = [[NSButton alloc] initWithFrame:NSMakeRect(120, ypos, 200, 20)]; + [x11Check autorelease]; + [x11Check setButtonType:NSSwitchButton]; + [x11Check setTitle:@"X11 Forwarding"]; + [x11Check setState:(*x11_fwd ? NSOnState : NSOffState)]; + [content addSubview:x11Check]; + ypos -= 40; + + NSButton *okButton = [[NSButton alloc] initWithFrame:NSMakeRect(210, 15, 80, 30)]; + [okButton autorelease]; + [okButton setTitle:@"OK"]; + [okButton setBezelStyle:NSRoundedBezelStyle]; + [okButton setKeyEquivalent:@"\r"]; + [okButton setTarget:delegate]; + [okButton setAction:@selector(buttonPressed:)]; + [okButton setTag:NSOKButton]; + [content addSubview:okButton]; + + NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(300, 15, 80, 30)]; + [cancelButton autorelease]; + [cancelButton setTitle:@"Cancel"]; + [cancelButton setBezelStyle:NSRoundedBezelStyle]; + [cancelButton setKeyEquivalent:@"\e"]; + [cancelButton setTarget:delegate]; + [cancelButton setAction:@selector(buttonPressed:)]; + [cancelButton setTag:NSCancelButton]; + [content addSubview:cancelButton]; + + if (def_server && *def_server) { + char *server_copy = strdup(def_server); + char *user = NULL; + char *host = server_copy; + char *port_str = NULL; + char *encoding = NULL; + + if (strncmp(host, "ssh://", 6) == 0) { + host += 6; + } + + char *at = strchr(host, '@'); + if (at) { + *at = '\0'; + user = host; + host = at + 1; + } + + char *colon = strchr(host, ':'); + if (colon) { + *colon = '\0'; + port_str = colon + 1; + char *colon2 = strchr(port_str, ':'); + if (colon2) { + *colon2 = '\0'; + encoding = colon2 + 1; + } + } + + if (user && *user) { + [userField setStringValue:[NSString stringWithCString:user encoding:NSUTF8StringEncoding]]; + } + if (host && *host) { + [hostField setStringValue:[NSString stringWithCString:host encoding:NSUTF8StringEncoding]]; + } + if (port_str && *port_str) { + [portField setStringValue:[NSString stringWithCString:port_str encoding:NSUTF8StringEncoding]]; + } + if (encoding && *encoding) { + [encField setStringValue:[NSString stringWithCString:encoding encoding:NSUTF8StringEncoding]]; + } + + free(server_copy); + } + + [panel makeFirstResponder:hostField]; + + NSInteger result = [NSApp runModalForWindow:panel]; + [panel orderOut:nil]; + + if (result == NSOKButton) { + const char *host_str = [[hostField stringValue] UTF8String]; + const char *port_str = [[portField stringValue] UTF8String]; + const char *user_str = [[userField stringValue] UTF8String]; + const char *enc_str = [[encField stringValue] UTF8String]; + + if (!host_str || !*host_str) { + return 0; + } + + size_t uri_len = strlen("ssh://") + (user_str ? strlen(user_str) + 1 : 0) + strlen(host_str) + + 1 + strlen(port_str) + (enc_str ? strlen(enc_str) + 1 : 0) + 10; + *uri = malloc(uri_len); + if (*uri) { + if (user_str && *user_str) { + snprintf(*uri, uri_len, "ssh://%s@%s:%s%s%s", user_str, host_str, port_str, + (enc_str && *enc_str) ? ":" : "", (enc_str && *enc_str) ? enc_str : ""); + } else { + snprintf(*uri, uri_len, "ssh://%s:%s%s%s", host_str, port_str, + (enc_str && *enc_str) ? ":" : "", (enc_str && *enc_str) ? enc_str : ""); + } + } + + const char *pass_str = [[passField stringValue] UTF8String]; + *pass = (pass_str && *pass_str) ? strdup(pass_str) : strdup(""); + + const char *cmd_str = [[cmdField stringValue] UTF8String]; + *exec_cmd = (cmd_str && *cmd_str) ? strdup(cmd_str) : NULL; + + const char *key_str = [[keyField stringValue] UTF8String]; + if (*privkey) { + free(*privkey); + } + *privkey = (key_str && *key_str) ? strdup(key_str) : NULL; + + *x11_fwd = ([x11Check state] == NSOnState) ? 1 : 0; + + return 1; + } + + return 0; +} + int cocoa_dialog_okcancel(const char *msg) { NSAlert *alert = create_dialog(msg, 1); if (alert == nil) { diff --git uitoolkit/quartz/ui_connect_dialog.c uitoolkit/quartz/ui_connect_dialog.c index 3fac2ffc..7726e6a3 100644 --- uitoolkit/quartz/ui_connect_dialog.c +++ uitoolkit/quartz/ui_connect_dialog.c @@ -19,25 +19,7 @@ int ui_connect_dialog(char **uri, /* Should be free'ed by those who call th int *x11_fwd, /* in/out */ char *display_name, Window parent_window, const char *def_server /* (@)(:)(:). */) { - char *msg; - - if (!(*uri = strdup(def_server))) { - return 0; - } - - if ((msg = alloca(19 + strlen(*uri) + 1))) { - sprintf(msg, "Enter password for %s", *uri); - - if (cocoa_dialog_connection(uri, pass, msg)) { - *exec_cmd = NULL; - return 1; - } - } - - free(*uri); - *uri = NULL; - - return 0; + return cocoa_dialog_ssh_full(uri, pass, exec_cmd, privkey, x11_fwd, def_server); } #endif diff --git uitoolkit/quartz/ui_event_source-cocoa.m uitoolkit/quartz/ui_event_source-cocoa.m index db9e7a13..6d504670 100644 --- uitoolkit/quartz/ui_event_source-cocoa.m +++ uitoolkit/quartz/ui_event_source-cocoa.m @@ -15,6 +15,8 @@ #include #include #include "../ui_display.h" +#include "../ui_screen_manager.h" +#include "cocoa.h" /* --- static variables --- */ @@ -45,6 +47,10 @@ static void ui_event_source_process_main(void *ctx_void) { vt_close_dead_terms(); +#ifndef COCOA_TOUCH + ui_close_dead_screens(); +#endif + vt_term_t **terms; u_int num_terms = vt_get_all_terms(&terms); @@ -120,6 +126,13 @@ int ui_event_source_process(void) { }; for (;;) { +#ifndef COCOA_TOUCH + /* Avoid a race with main-thread cleanup while the app is quitting. */ + if (cocoa_is_terminating) { + return 0; + } +#endif + tval.tv_usec = 100000; /* 0.1 sec */ tval.tv_sec = 0; @@ -129,6 +142,12 @@ int ui_event_source_process(void) { ui_event_source_process_main(&ctx); #endif +#ifndef COCOA_TOUCH + if (ui_get_all_screens(NULL) == 0) { + return 0; + } +#endif + if (maxfd == -1 || ((ret = select(maxfd + 1, &read_fds, NULL, NULL, &tval)) < 0 && errno != EAGAIN && errno != EINTR && diff --git uitoolkit/quartz/ui_im_status_screen-cocoa.m uitoolkit/quartz/ui_im_status_screen-cocoa.m index 7d35d119..8e3c75a1 100644 --- uitoolkit/quartz/ui_im_status_screen-cocoa.m +++ uitoolkit/quartz/ui_im_status_screen-cocoa.m @@ -77,12 +77,12 @@ static int set(ui_im_status_screen_t* stat_screen, ef_parser_t* parser, utf8[len] = '\0'; NSWindow* window = stat_screen->window.my_window; - NSTextField* label = window.contentView; + NSTextField* label = [window contentView]; - label.stringValue = - [NSString stringWithCString:utf8 encoding:NSUTF8StringEncoding]; + [label setStringValue: + [NSString stringWithCString:utf8 encoding:NSUTF8StringEncoding]]; [label sizeToFit]; - [window setContentSize:label.frame.size]; + [window setContentSize:[label frame].size]; /* XXX (see ui_im_status_screen_new()) */ if (stat_screen->filled_len == 1) { @@ -131,7 +131,7 @@ ui_im_status_screen_t *ui_im_status_screen_new(ui_display_t* disp, NSTextField* label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 10, 10)]; - label.drawsBackground = YES; + [label setDrawsBackground:YES]; [label setEditable:NO]; [label setSelectable:NO]; -- 2.54.0