liberty-xui: add AppKit support
Alpine 3.23 Success
OpenBSD 7.8 Success

IME is not working, otherwise feature parity with X11.
This commit is contained in:
p
2026-05-25 20:50:02 +02:00
parent c0eda1c23f
commit d7c70bd43a
2 changed files with 869 additions and 9 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2014 - 2025, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2014 - 2026, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
+868 -8
View File
@@ -1,7 +1,7 @@
/*
* liberty-xui.c: the ultimate C unlibrary: hybrid terminal/X11 UI
*
* Copyright (c) 2016 - 2024, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2016 - 2026, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -73,6 +73,11 @@ enum { XUI_KEYMOD_DOUBLE_CLICK = 1 << 15 };
#include "liberty-xdg.c"
#endif // LIBERTY_XUI_WANT_X11
#ifdef LIBERTY_XUI_WANT_APPKIT
#import <AppKit/AppKit.h>
#import <CoreFoundation/CoreFoundation.h>
#endif // LIBERTY_XUI_WANT_APPKIT
// The application needs to implement these.
static void app_quit (void);
static void app_layout (void);
@@ -592,7 +597,7 @@ struct widget
chtype attrs; ///< Rendition, in Curses terms
unsigned extended_attrs; ///< XUI-specific attributes
int id; ///< Post-layouting identification
int widget_id; ///< Post-layouting identification
int userdata; ///< Action ID/Tab index/...
char text[]; ///< Any text label
};
@@ -704,6 +709,23 @@ struct xui
XRenderColor *x_fg; ///< Foreground per attribute
XRenderColor *x_bg; ///< Background per attribute
#endif // LIBERTY_XUI_WANT_X11
// AppKit:
#ifdef LIBERTY_XUI_WANT_APPKIT
NSWindow *appkit_window; ///< Application window
NSView *appkit_view; ///< Application view
id appkit_delegate; ///< Application delegate
struct poller_timer appkit_event; ///< Poll for AppKit events
NSFont *appkit_font_regular; ///< Regular proportional font
NSFont *appkit_font_monospace; ///< Regular monospace font
NSColor *appkit_colors[256]; ///< Colour cube
NSColor **appkit_fg; ///< Foreground per attribute (weak ref)
NSColor **appkit_bg; ///< Background per attribute (weak ref)
#endif // LIBERTY_XUI_WANT_APPKIT
}
g_xui;
@@ -1523,11 +1545,11 @@ on_x11_keypress (XEvent *e)
}
bool result = true;
key.type = TERMO_TYPE_KEY;
key.modifiers &= ~TERMO_KEYMOD_SHIFT;
if (len)
{
key.type = TERMO_TYPE_KEY;
key.modifiers &= ~TERMO_KEYMOD_SHIFT;
int32_t cp = 0;
struct utf8_iter iter = { .s = buf, .len = len };
size_t cp_len = 0;
@@ -2019,6 +2041,820 @@ x11_init (struct poller *poller, struct attrs *app_attrs, size_t app_attrs_len)
#endif // LIBERTY_XUI_WANT_X11
// --- AppKit ------------------------------------------------------------------
#ifdef LIBERTY_XUI_WANT_APPKIT
static const int appkit_default_fg = 16;
static const int appkit_default_bg = 231;
static NSString *
appkit_string (const char *text)
{
NSString *string = [NSString stringWithUTF8String:text];
if (!string)
string = @"?";
return string;
}
static int
appkit_font_hadvance (NSFont *font, const char *text)
{
NSDictionary *attrs
= [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
return ceil ([appkit_string (text) sizeWithAttributes:attrs].width);
}
static void
appkit_font_draw (NSFont *font, NSColor *color, int x, int y,
const char *text, int max_width)
{
NSMutableParagraphStyle *style = [NSMutableParagraphStyle new];
[style setLineBreakMode:NSLineBreakByTruncatingTail];
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
color, NSForegroundColorAttributeName,
style, NSParagraphStyleAttributeName,
nil];
[style release];
NSRect rect = NSMakeRect (x, y, max_width, g_xui.vunit);
[appkit_string (text) drawInRect:rect withAttributes:attrs];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static NSFont *
appkit_widget_font (struct widget *self)
{
NSFont *base = (self->extended_attrs & XUI_ATTR_MONOSPACE)
? g_xui.appkit_font_monospace
: g_xui.appkit_font_regular;
NSFontTraitMask traits = 0;
if (self->attrs & A_BOLD)
traits |= NSBoldFontMask;
#ifdef A_ITALIC
if (self->attrs & A_ITALIC)
traits |= NSItalicFontMask;
#endif // A_ITALIC
NSFont *font = [[NSFontManager sharedFontManager]
convertFont:base toHaveTrait:traits];
return font ? font : base;
}
static NSColor *
appkit_fg_attrs (chtype attrs)
{
int pair = PAIR_NUMBER (attrs);
if (!pair--)
return g_xui.appkit_colors[appkit_default_fg];
return ((attrs & A_REVERSE) ? g_xui.appkit_bg : g_xui.appkit_fg)[pair];
}
static NSColor *
appkit_fg (struct widget *self)
{
return appkit_fg_attrs (self->attrs);
}
static NSColor *
appkit_bg_attrs (chtype attrs)
{
int pair = PAIR_NUMBER (attrs);
if (!pair--)
return g_xui.appkit_colors[appkit_default_bg];
return ((attrs & A_REVERSE) ? g_xui.appkit_fg : g_xui.appkit_bg)[pair];
}
static NSColor *
appkit_bg (struct widget *self)
{
return appkit_bg_attrs (self->attrs);
}
static void
appkit_render_padding (struct widget *self)
{
if (PAIR_NUMBER (self->attrs))
{
[appkit_bg (self) setFill];
NSRectFill (NSMakeRect (self->x, self->y, self->width, self->height));
}
if (self->attrs & A_UNDERLINE)
{
[appkit_fg (self) setFill];
NSRectFill (NSMakeRect
(self->x, self->y + self->height - 1, self->width, 1));
}
}
static struct widget *
appkit_make_padding (chtype attrs, float width, float height)
{
struct widget *w = xcalloc (1, sizeof *w + 2);
w->text[0] = ' ';
w->on_render = appkit_render_padding;
w->attrs = attrs;
w->width = g_xui.vunit * width;
w->height = g_xui.vunit * height;
return w;
}
static void
appkit_render_label (struct widget *self)
{
appkit_render_padding (self);
int space = MIN (self->width, g_xui.width - self->x);
if (space <= 0)
return;
NSFont *font = appkit_widget_font (self);
int y = self->y + (self->height - g_xui.vunit) / 2;
appkit_font_draw (font, appkit_fg (self), self->x, y, self->text, space);
}
static struct widget *
appkit_make_label (chtype attrs, unsigned extended, const char *label)
{
size_t label_len = strlen (label) + 1;
struct widget *w = xcalloc (1, sizeof *w + label_len);
w->on_render = appkit_render_label;
w->attrs = attrs;
w->extended_attrs = extended;
memcpy (w->text, label, label_len);
NSFont *font = appkit_widget_font (w);
w->width = appkit_font_hadvance (font, w->text);
w->height = g_xui.vunit;
return w;
}
static void
appkit_render_widget (struct widget *w, NSRect clip)
{
if (w->width < 0 || w->height < 0)
return;
NSRect rect = NSMakeRect (w->x, w->y, w->width, w->height);
NSRect subclip = NSIntersectionRect (rect, clip);
if (NSIsEmptyRect (subclip))
return;
[NSGraphicsContext saveGraphicsState];
NSRectClip (subclip);
if (w->on_render)
w->on_render (w);
[NSGraphicsContext restoreGraphicsState];
LIST_FOR_EACH (struct widget, child, w->children)
appkit_render_widget (child, subclip);
}
static void
appkit_render (void)
{
@autoreleasepool
{
[g_xui.appkit_view setNeedsDisplay:YES];
[g_xui.appkit_view displayIfNeeded];
[NSApp updateWindows];
}
}
static void
appkit_flip (void)
{
// AppKit does its own double-buffering, flipping is done in render(),
// or more specifically in the drawRect: method.
}
static void
appkit_destroy (void)
{
[g_xui.appkit_window close];
g_xui.appkit_event.dispatcher (NULL);
poller_timer_reset (&g_xui.appkit_event);
[g_xui.appkit_window setDelegate:nil];
[g_xui.appkit_delegate release];
g_xui.appkit_delegate = nil;
[g_xui.appkit_view release];
g_xui.appkit_view = nil;
[g_xui.appkit_window release];
g_xui.appkit_window = nil;
[g_xui.appkit_font_regular release];
g_xui.appkit_font_regular = nil;
[g_xui.appkit_font_monospace release];
g_xui.appkit_font_monospace = nil;
for (size_t i = 0; i < N_ELEMENTS (g_xui.appkit_colors); i++)
[g_xui.appkit_colors[i] release];
memset (g_xui.appkit_colors, 0, sizeof g_xui.appkit_colors);
free (g_xui.appkit_fg);
free (g_xui.appkit_bg);
g_xui.appkit_fg = NULL;
g_xui.appkit_bg = NULL;
}
static struct ui appkit_ui =
{
.padding = appkit_make_padding,
.label = appkit_make_label,
.render = appkit_render,
.flip = appkit_flip,
.destroy = appkit_destroy,
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static termo_sym_t
appkit_convert_special (unichar c)
{
// Leaving out TERMO_TYPE_FUNCTION, TERMO_SYM_DEL (N/A),
// and TERMO_SYM_SPACE (governed by TERMO_FLAG_SPACESYMBOL, not in use).
switch (c)
{
case NSBackspaceCharacter: return TERMO_SYM_BACKSPACE;
case NSDeleteCharacter: return TERMO_SYM_BACKSPACE;
case NSTabCharacter: return TERMO_SYM_TAB;
case NSBackTabCharacter: return TERMO_SYM_TAB;
case NSCarriageReturnCharacter: return TERMO_SYM_ENTER;
case NSEnterCharacter: return TERMO_SYM_ENTER;
case 0x1b: return TERMO_SYM_ESCAPE;
case NSUpArrowFunctionKey: return TERMO_SYM_UP;
case NSDownArrowFunctionKey: return TERMO_SYM_DOWN;
case NSLeftArrowFunctionKey: return TERMO_SYM_LEFT;
case NSRightArrowFunctionKey: return TERMO_SYM_RIGHT;
case NSBeginFunctionKey: return TERMO_SYM_BEGIN;
case NSFindFunctionKey: return TERMO_SYM_FIND;
case NSInsertFunctionKey: return TERMO_SYM_INSERT;
case NSDeleteFunctionKey: return TERMO_SYM_DELETE;
case NSSelectFunctionKey: return TERMO_SYM_SELECT;
case NSPageUpFunctionKey: return TERMO_SYM_PAGEUP;
case NSPageDownFunctionKey: return TERMO_SYM_PAGEDOWN;
case NSHomeFunctionKey: return TERMO_SYM_HOME;
case NSEndFunctionKey: return TERMO_SYM_END;
// TERMO_SYM_CANCEL
// TERMO_SYM_CLEAR
// TERMO_SYM_CLOSE
// TERMO_SYM_COMMAND
// TERMO_SYM_COPY
// TERMO_SYM_EXIT
case NSHelpFunctionKey: return TERMO_SYM_HELP;
// TERMO_SYM_MARK
// TERMO_SYM_MESSAGE
// TERMO_SYM_MOVE
// TERMO_SYM_OPEN
// TERMO_SYM_OPTIONS
case NSPrintFunctionKey: return TERMO_SYM_PRINT;
case NSRedoFunctionKey: return TERMO_SYM_REDO;
// TERMO_SYM_REFERENCE
// TERMO_SYM_REFRESH
// TERMO_SYM_REPLACE
// TERMO_SYM_RESTART
// TERMO_SYM_RESUME
// TERMO_SYM_SAVE
// TERMO_SYM_SUSPEND
case NSUndoFunctionKey: return TERMO_SYM_UNDO;
// TERMO_SYM_KP*
}
return TERMO_SYM_UNKNOWN;
}
static int
appkit_modifiers (NSEventModifierFlags flags)
{
int modifiers = 0;
if (flags & NSEventModifierFlagShift) modifiers |= TERMO_KEYMOD_SHIFT;
if (flags & NSEventModifierFlagControl) modifiers |= TERMO_KEYMOD_CTRL;
if (flags & NSEventModifierFlagOption) modifiers |= TERMO_KEYMOD_ALT;
return modifiers;
}
static bool
on_appkit_key_down (NSEvent *event)
{
if ([event modifierFlags] & NSEventModifierFlagCommand)
return true;
termo_key_t key = {};
key.modifiers = appkit_modifiers ([event modifierFlags]);
NSString *ignoring_modifiers = [event charactersIgnoringModifiers];
if ([ignoring_modifiers length] == 1)
{
unichar c = [ignoring_modifiers characterAtIndex:0];
if (c >= NSF1FunctionKey && c <= NSF35FunctionKey)
{
key.type = TERMO_TYPE_FUNCTION;
key.code.number = 1 + c - NSF1FunctionKey;
return xui_process_termo_event (&key);
}
if ((key.code.sym = appkit_convert_special (c)) != TERMO_SYM_UNKNOWN)
{
key.type = TERMO_TYPE_KEYSYM;
return xui_process_termo_event (&key);
}
}
// Let's make the right Option key act as AltGr, if possible.
CGEventRef e = [event CGEvent];
if (e && (CGEventGetFlags (e) & NX_DEVICERALTKEYMASK))
key.modifiers &= ~TERMO_KEYMOD_ALT;
bool result = true;
key.type = TERMO_TYPE_KEY;
key.modifiers &= ~TERMO_KEYMOD_SHIFT;
NSData *utf8 = [[event characters] dataUsingEncoding:NSUTF8StringEncoding];
if (utf8 && [utf8 length] > 0)
{
int32_t cp = 0;
struct utf8_iter iter = { .s = [utf8 bytes], .len = [utf8 length] };
const char *p = iter.s;
size_t cp_len = 0;
while ((cp = utf8_iter_next (&iter, &cp_len)) >= 0)
{
termo_key_t k = key;
memcpy (k.multibyte, p, MIN (cp_len, sizeof k.multibyte - 1));
p += cp_len;
// This is all unfortunate, but probably in the right place.
if (!cp)
{
k.code.codepoint = ' ';
if ([event modifierFlags] & NSEventModifierFlagShift)
k.modifiers |= TERMO_KEYMOD_SHIFT;
}
else if (cp >= 32)
k.code.codepoint = cp;
else if ([event modifierFlags] & NSEventModifierFlagShift)
k.code.codepoint = cp + 64;
else
k.code.codepoint = cp + 96;
if (!xui_process_termo_event (&k))
result = false;
}
}
return result;
}
static char *
appkit_find_text (struct widget *list, int x, int y)
{
struct widget *target = NULL;
LIST_FOR_EACH (struct widget, w, list)
if (x >= w->x && x < w->x + w->width
&& y >= w->y && y < w->y + w->height)
target = w;
if (!target)
return NULL;
char *result = appkit_find_text (target->children, x, y);
if (result)
return result;
return xstrdup (target->text);
}
static void
appkit_update_dimensions (void)
{
NSRect bounds = [g_xui.appkit_view bounds];
g_xui.width = bounds.size.width;
g_xui.height = bounds.size.height;
}
static bool
appkit_event_position (NSEvent *event, int *x, int *y)
{
NSPoint p = [g_xui.appkit_view
convertPoint:[event locationInWindow] fromView:nil];
*x = p.x;
*y = p.y;
return true;
}
static int
appkit_mouse_button (NSEvent *event)
{
NSUInteger button = [event buttonNumber];
switch (button)
{
case 0:
return 1;
case 1:
return 3;
case 2:
return 2;
default:
// Skip all scroll wheel directions, so that we match X11.
return button + 1 + 4;
}
}
static bool
appkit_process_press (int x, int y, int button, int modifiers)
{
if (button != 3)
goto out;
char *text = appkit_find_text (g_xui.widgets, x, y);
if (!text || !*(cstr_strip_in_place (text, " \t")))
{
free (text);
goto out;
}
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
[pasteboard setString:appkit_string (text) forType:NSPasteboardTypeString];
app_on_clipboard_copy (text);
free (text);
return true;
out:
return app_process_mouse (TERMO_MOUSE_PRESS, x, y, button, modifiers);
}
static bool
on_appkit_mouse_event (NSEvent *event, termo_mouse_event_t type, int button)
{
int x = 0, y = 0;
if (!appkit_event_position (event, &x, &y))
return false;
int modifiers = appkit_modifiers ([event modifierFlags]);
if (type == TERMO_MOUSE_PRESS && [event clickCount] > 1)
modifiers |= XUI_KEYMOD_DOUBLE_CLICK;
if (type == TERMO_MOUSE_PRESS)
return appkit_process_press (x, y, button, modifiers);
return app_process_mouse (type, x, y, button, modifiers);
}
static bool
on_appkit_scroll_event (NSEvent *event)
{
int x = 0, y = 0;
if (!appkit_event_position (event, &x, &y))
return false;
double delta_y = [event scrollingDeltaY];
if (!delta_y)
return true;
int modifiers = appkit_modifiers ([event modifierFlags]);
int button = delta_y > 0 ? 4 : 5;
int repeats = MAX (1, (int) round (fabs (delta_y)));
bool result = true;
while (result && repeats-- > 0)
{
result &= app_process_mouse (TERMO_MOUSE_PRESS,
x, y, button, modifiers);
// XXX: Terminals do not send it, but X11 does. We should either
// suppress the releases, or synthesize them for terminals as well.
result &= app_process_mouse (TERMO_MOUSE_RELEASE,
x, y, button, modifiers);
}
return result;
}
static void
appkit_send_focus_event (bool focused)
{
termo_key_t key = { .type = TERMO_TYPE_FOCUS };
key.code.focused = focused;
xui_process_termo_event (&key);
}
@interface XUIAppKitView : NSView
@end
@implementation XUIAppKitView
- (BOOL) isFlipped { return YES; }
- (BOOL) acceptsFirstResponder { return YES; }
- (void) drawRect:(NSRect)dirty_rect
{
[g_xui.appkit_colors[appkit_default_bg] setFill];
NSRectFill (dirty_rect);
LIST_FOR_EACH (struct widget, w, g_xui.widgets)
appkit_render_widget (w, dirty_rect);
}
- (void) keyDown:(NSEvent *)event
{
if (!on_appkit_key_down (event))
NSBeep ();
}
- (void) mouseDown:(NSEvent *)event
{
if (!on_appkit_mouse_event (event, TERMO_MOUSE_PRESS, 1))
NSBeep ();
}
- (void) mouseUp:(NSEvent *)event
{
if (!on_appkit_mouse_event (event, TERMO_MOUSE_RELEASE, 1))
NSBeep ();
}
- (void) rightMouseDown:(NSEvent *)event
{
if (!on_appkit_mouse_event (event, TERMO_MOUSE_PRESS, 3))
NSBeep ();
}
- (void) rightMouseUp:(NSEvent *)event
{
if (!on_appkit_mouse_event (event, TERMO_MOUSE_RELEASE, 3))
NSBeep ();
}
- (void) otherMouseDown:(NSEvent *)event
{
if (!on_appkit_mouse_event (event,
TERMO_MOUSE_PRESS, appkit_mouse_button (event)))
NSBeep ();
}
- (void) otherMouseUp:(NSEvent *)event
{
if (!on_appkit_mouse_event (event,
TERMO_MOUSE_RELEASE, appkit_mouse_button (event)))
NSBeep ();
}
- (void) mouseDragged:(NSEvent *)event
{
if (!on_appkit_mouse_event (event, TERMO_MOUSE_DRAG, 1))
NSBeep ();
}
- (void) rightMouseDragged:(NSEvent *)event
{
if (!on_appkit_mouse_event (event, TERMO_MOUSE_DRAG, 3))
NSBeep ();
}
- (void) otherMouseDragged:(NSEvent *)event
{
if (!on_appkit_mouse_event (event,
TERMO_MOUSE_DRAG, appkit_mouse_button (event)))
NSBeep ();
}
- (void) scrollWheel:(NSEvent *)event
{
if (!on_appkit_scroll_event (event))
NSBeep ();
}
@end
@interface XUIAppKitDelegate : NSObject <NSWindowDelegate>
@end
@implementation XUIAppKitDelegate
- (void) windowWillClose:(NSNotification *)notification
{
(void) notification;
app_quit ();
}
- (void) windowDidResize:(NSNotification *)notification
{
(void) notification;
appkit_update_dimensions ();
// AppKit seems to run an event subloop, so relayout immediately.
g_xui.refresh_event.dispatcher (NULL);
poller_idle_reset (&g_xui.refresh_event);
}
- (void) windowDidBecomeKey:(NSNotification *)notification
{
(void) notification;
appkit_send_focus_event (true);
}
- (void) windowDidResignKey:(NSNotification *)notification
{
(void) notification;
appkit_send_focus_event (false);
}
@end
static NSEvent *
appkit_next_event (NSDate *limit)
{
// Probably not needing NSEventTrackingRunLoopMode
// or NSModalPanelRunLoopMode.
return [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:limit inMode:NSDefaultRunLoopMode dequeue:true];
}
static void
on_appkit_ready (void *user_data)
{
(void) user_data;
// AppKit likes to own the event loop; we give up and poll.
poller_timer_set (&g_xui.appkit_event, 1000 / 60 /* FPS */);
@autoreleasepool
{
NSDate *limit = [NSDate distantPast];
NSEvent *event = nil;
while ((event = appkit_next_event (limit)))
{
[NSApp sendEvent:event];
[NSApp updateWindows];
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static NSColor *
appkit_convert_color (int color)
{
hard_assert (color >= 0 && color <= 255);
static const uint16_t base[16] =
{
0x000, 0x800, 0x080, 0x880, 0x008, 0x808, 0x088, 0xccc,
0x888, 0xf00, 0x0f0, 0xff0, 0x00f, 0xf0f, 0x0ff, 0xfff,
};
CGFloat red = 1, green = 1, blue = 1, alpha = 1;
if (color < 16)
{
red = (base[color] >> 8) / 15.0;
green = (0xf & (base[color] >> 4)) / 15.0;
blue = (0xf & (base[color])) / 15.0;
}
else if (color >= 232)
red = green = blue = (8 + (color - 232) * 10) / 255.0;
else
{
color -= 16;
int r = color / 36;
int g = (color / 6) % 6;
int b = (color % 6);
red = !!r * (55 + 40 * r) / 255.0;
green = !!g * (55 + 40 * g) / 255.0;
blue = !!b * (55 + 40 * b) / 255.0;
}
return [NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha];
}
static void
appkit_init_attributes (struct attrs *attrs, size_t attrs_len)
{
for (size_t i = 0; i < N_ELEMENTS (g_xui.appkit_colors); i++)
g_xui.appkit_colors[i] = [appkit_convert_color (i) retain];
g_xui.appkit_fg = xcalloc (attrs_len, sizeof *g_xui.appkit_fg);
g_xui.appkit_bg = xcalloc (attrs_len, sizeof *g_xui.appkit_bg);
for (size_t a = 0; a < attrs_len; a++)
{
g_xui.appkit_fg[a] = g_xui.appkit_colors[appkit_default_fg];
g_xui.appkit_bg[a] = g_xui.appkit_colors[appkit_default_bg];
if (attrs[a].fg >= 256 || attrs[a].fg < -1
|| attrs[a].bg >= 256 || attrs[a].bg < -1)
continue;
if (attrs[a].fg != -1)
g_xui.appkit_fg[a] = g_xui.appkit_colors[attrs[a].fg];
if (attrs[a].bg != -1)
g_xui.appkit_bg[a] = g_xui.appkit_colors[attrs[a].bg];
attrs[a].attrs |= COLOR_PAIR (a + 1);
}
}
static void
appkit_init_menu (void)
{
NSMenu *app_menu = [[NSMenu new] autorelease];
NSMenuItem *services_item = [app_menu
addItemWithTitle:@"Services"
action:nil
keyEquivalent:@""];
NSMenu *services_menu = [[NSMenu new] autorelease];
[services_item setSubmenu:services_menu];
[NSApp setServicesMenu:services_menu];
[app_menu addItem:[NSMenuItem separatorItem]];
[app_menu addItemWithTitle:@"Hide " PROGRAM_NAME
action:@selector (hide:)
keyEquivalent:@"h"];
[app_menu addItem:[NSMenuItem separatorItem]];
[app_menu addItemWithTitle:@"Quit " PROGRAM_NAME
action:@selector (terminate:)
keyEquivalent:@"q"];
NSMenuItem *app_menu_item = [[NSMenuItem new] autorelease];
[app_menu_item setSubmenu:app_menu];
NSMenu *menubar = [[NSMenu new] autorelease];
[menubar addItem:app_menu_item];
[NSApp setMainMenu:menubar];
}
static void
appkit_init (struct poller *poller, struct attrs *attrs, size_t attrs_len)
{
(void) [NSApplication sharedApplication];
// Apparently this won't succeed when run from within a bundle.
(void) [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
appkit_init_menu ();
[NSApp finishLaunching];
NSFont *system =
[NSFont systemFontOfSize:[NSFont systemFontSize]];
NSFont *mono =
[NSFont userFixedPitchFontOfSize:[NSFont systemFontSize]];
if (!mono)
mono = system;
g_xui.appkit_font_regular = [system retain];
g_xui.appkit_font_monospace = [mono retain];
if (!g_xui.appkit_font_regular || !g_xui.appkit_font_monospace)
[NSException raise:@"failed" format:@"failed to load fonts"];
g_xui.vunit = ceil ([system ascender] - [system descender]
+ [system leading]);
g_xui.vunit = MAX (1, g_xui.vunit);
g_xui.hunit = MAX (1, g_xui.vunit / 2);
g_xui.height = 24 * g_xui.vunit;
g_xui.width = g_xui.height * 4 / 3;
NSRect frame = NSMakeRect (0, 0, g_xui.width, g_xui.height);
NSWindowStyleMask style = NSWindowStyleMaskTitled
| NSWindowStyleMaskClosable
| NSWindowStyleMaskMiniaturizable
| NSWindowStyleMaskResizable;
g_xui.appkit_window = [[NSWindow alloc]
initWithContentRect:frame
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
if (!g_xui.appkit_window)
[NSException raise:@"failed" format:@"failed to create window"];
g_xui.appkit_window.releasedWhenClosed = false;
[g_xui.appkit_window center];
g_xui.appkit_view = [[XUIAppKitView alloc] initWithFrame:frame];
if (!g_xui.appkit_view)
[NSException raise:@"failed" format:@"failed to create view"];
[g_xui.appkit_window setContentView:g_xui.appkit_view];
g_xui.appkit_delegate = [XUIAppKitDelegate new];
if (!g_xui.appkit_delegate)
[NSException raise:@"failed" format:@"failed to create delegate"];
[g_xui.appkit_window setDelegate:g_xui.appkit_delegate];
[g_xui.appkit_window setAcceptsMouseMovedEvents:YES];
[g_xui.appkit_window setTitle:appkit_string (PROGRAM_NAME)];
[g_xui.appkit_window makeFirstResponder:g_xui.appkit_view];
[g_xui.appkit_window makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
appkit_update_dimensions ();
appkit_init_attributes (attrs, attrs_len);
g_xui.appkit_event = poller_timer_make (poller);
g_xui.appkit_event.dispatcher = on_appkit_ready;
poller_timer_set (&g_xui.appkit_event, 0);
g_xui.ui = &appkit_ui;
}
#endif // LIBERTY_XUI_WANT_APPKIT
// --- Containers --------------------------------------------------------------
static void
@@ -2207,7 +3043,9 @@ static void
xui_start (struct poller *poller,
bool force_x11, struct attrs *attrs, size_t attrs_len)
{
#if !defined LIBERTY_XUI_WANT_X11 && !defined LIBERTY_XUI_WANT_APPKIT
(void) force_x11;
#endif // !LIBERTY_XUI_WANT_X11 && !LIBERTY_XUI_WANT_APPKIT
g_xui.refresh_event = poller_idle_make (poller);
g_xui.refresh_event.dispatcher = xui_on_refresh;
@@ -2221,11 +3059,33 @@ xui_start (struct poller *poller,
g_xui.tk_timer.dispatcher = tui_on_key_timer;
#ifdef LIBERTY_XUI_WANT_X11
if (force_x11 || (!isatty (STDIN_FILENO) && getenv ("DISPLAY")))
if (getenv ("DISPLAY")
&& (force_x11 || !isatty (STDIN_FILENO)))
{
x11_init (poller, attrs, attrs_len);
else
return;
}
#endif // LIBERTY_XUI_WANT_X11
tui_init (poller, attrs, attrs_len);
#ifdef LIBERTY_XUI_WANT_APPKIT
if (force_x11 || !isatty (STDIN_FILENO))
{
@autoreleasepool
{
@try
{
appkit_init (poller, attrs, attrs_len);
}
@catch (NSException *e)
{
exit_fatal ("AppKit setup failed: %s", [[e reason] UTF8String]);
}
}
return;
}
#endif // LIBERTY_XUI_WANT_APPKIT
tui_init (poller, attrs, attrs_len);
}
static void