244 lines
7.9 KiB
Objective-C
244 lines
7.9 KiB
Objective-C
/*
|
|
* cocoa_window.m
|
|
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
|
*
|
|
* Distributed under terms of the GPL3 license.
|
|
*/
|
|
|
|
|
|
#include "state.h"
|
|
#include <Cocoa/Cocoa.h>
|
|
|
|
#include <AvailabilityMacros.h>
|
|
// Needed for _NSGetProgname
|
|
#include <crt_externs.h>
|
|
typedef void* rusage_info_t; // needed for libproc.h
|
|
#include <libproc.h>
|
|
|
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
|
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
|
#define NSEventModifierFlagOption NSAlternateKeyMask
|
|
#define NSEventModifierFlagCommand NSCommandKeyMask
|
|
#define NSEventModifierFlagControl NSControlKeyMask
|
|
#endif
|
|
|
|
static NSMenuItem* title_menu = NULL;
|
|
static bool change_titlebar_color = false;
|
|
static color_type titlebar_color = 0;
|
|
|
|
|
|
static NSString*
|
|
find_app_name(void) {
|
|
size_t i;
|
|
NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
|
|
|
|
// Keys to search for as potential application names
|
|
NSString* name_keys[] =
|
|
{
|
|
@"CFBundleDisplayName",
|
|
@"CFBundleName",
|
|
@"CFBundleExecutable",
|
|
};
|
|
|
|
for (i = 0; i < sizeof(name_keys) / sizeof(name_keys[0]); i++)
|
|
{
|
|
id name = [infoDictionary objectForKey:name_keys[i]];
|
|
if (name &&
|
|
[name isKindOfClass:[NSString class]] &&
|
|
![name isEqualToString:@""])
|
|
{
|
|
return name;
|
|
}
|
|
}
|
|
|
|
char** progname = _NSGetProgname();
|
|
if (progname && *progname)
|
|
return [NSString stringWithUTF8String:*progname];
|
|
|
|
// Really shouldn't get here
|
|
return @"kitty";
|
|
}
|
|
|
|
|
|
void
|
|
cocoa_create_global_menu(void) {
|
|
NSString* app_name = find_app_name();
|
|
NSMenu* bar = [[NSMenu alloc] init];
|
|
[NSApp setMainMenu:bar];
|
|
|
|
NSMenuItem* appMenuItem =
|
|
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
|
|
NSMenu* appMenu = [[NSMenu alloc] init];
|
|
[appMenuItem setSubmenu:appMenu];
|
|
|
|
[appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", app_name]
|
|
action:@selector(orderFrontStandardAboutPanel:)
|
|
keyEquivalent:@""];
|
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
|
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
|
|
action:@selector(hide:)
|
|
keyEquivalent:@"h"];
|
|
[[appMenu addItemWithTitle:@"Hide Others"
|
|
action:@selector(hideOtherApplications:)
|
|
keyEquivalent:@"h"]
|
|
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
|
|
[appMenu addItemWithTitle:@"Show All"
|
|
action:@selector(unhideAllApplications:)
|
|
keyEquivalent:@""];
|
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
NSMenu* servicesMenu = [[NSMenu alloc] init];
|
|
[NSApp setServicesMenu:servicesMenu];
|
|
[[appMenu addItemWithTitle:@"Services"
|
|
action:NULL
|
|
keyEquivalent:@""] setSubmenu:servicesMenu];
|
|
[servicesMenu release];
|
|
|
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
|
|
action:@selector(terminate:)
|
|
keyEquivalent:@"q"];
|
|
[appMenu release];
|
|
|
|
NSMenuItem* windowMenuItem =
|
|
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
|
|
NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
|
|
[windowMenuItem setSubmenu:windowMenu];
|
|
|
|
[windowMenu addItemWithTitle:@"Minimize"
|
|
action:@selector(performMiniaturize:)
|
|
keyEquivalent:@"m"];
|
|
[windowMenu addItemWithTitle:@"Zoom"
|
|
action:@selector(performZoom:)
|
|
keyEquivalent:@""];
|
|
[windowMenu addItem:[NSMenuItem separatorItem]];
|
|
[windowMenu addItemWithTitle:@"Bring All to Front"
|
|
action:@selector(arrangeInFront:)
|
|
keyEquivalent:@""];
|
|
|
|
[windowMenu addItem:[NSMenuItem separatorItem]];
|
|
[[windowMenu addItemWithTitle:@"Enter Full Screen"
|
|
action:@selector(toggleFullScreen:)
|
|
keyEquivalent:@"f"]
|
|
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
|
|
[NSApp setWindowsMenu:windowMenu];
|
|
[windowMenu release];
|
|
|
|
|
|
[bar release];
|
|
}
|
|
|
|
void
|
|
cocoa_update_title(PyObject *pytitle) {
|
|
NSString *title = [[NSString alloc] initWithUTF8String:PyUnicode_AsUTF8(pytitle)];
|
|
NSMenu *bar = [NSApp mainMenu];
|
|
if (title_menu != NULL) {
|
|
[bar removeItem:title_menu];
|
|
}
|
|
title_menu = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
|
|
NSMenu *m = [[NSMenu alloc] initWithTitle:[NSString stringWithFormat:@" :: %@", title]];
|
|
[title_menu setSubmenu:m];
|
|
[m release];
|
|
[title release];
|
|
}
|
|
|
|
bool
|
|
cocoa_make_window_resizable(void *w) {
|
|
NSWindow *window = (NSWindow*)w;
|
|
|
|
@try {
|
|
[window setStyleMask:
|
|
[window styleMask] | NSWindowStyleMaskResizable];
|
|
} @catch (NSException *e) {
|
|
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static PyObject*
|
|
cocoa_get_lang(PyObject UNUSED *self) {
|
|
NSString* locale = nil;
|
|
NSString* lang_code = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode];
|
|
NSString* country_code = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
|
|
if (lang_code && country_code) {
|
|
locale = [NSString stringWithFormat:@"%@_%@", lang_code, country_code];
|
|
} else {
|
|
locale = [[NSLocale currentLocale] localeIdentifier];
|
|
}
|
|
if (!locale) { Py_RETURN_NONE; }
|
|
return Py_BuildValue("s", [locale UTF8String]);
|
|
}
|
|
|
|
static PyObject*
|
|
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
|
long pid = PyLong_AsLong(pid_);
|
|
struct proc_vnodepathinfo vpi;
|
|
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
|
|
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
|
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
|
}
|
|
|
|
static inline color_type
|
|
color_as_int(PyObject *color) {
|
|
#define I(n, s) ((PyLong_AsUnsignedLong(PyTuple_GET_ITEM(color, n)) & 0xff) << s)
|
|
return (I(0, 16) | I(1, 8) | I(2, 0)) & 0xffffff;
|
|
#undef I
|
|
}
|
|
|
|
static PyObject*
|
|
macos_change_titlebar_color(PyObject *self UNUSED, PyObject *val) {
|
|
if (val == Py_None) change_titlebar_color = false;
|
|
else if (val == Py_True) {
|
|
change_titlebar_color = true;
|
|
titlebar_color = OPT(background);
|
|
} else {
|
|
if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_TypeError, "Not a color tuple"); return NULL; }
|
|
change_titlebar_color = true;
|
|
titlebar_color = color_as_int(val);
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
void
|
|
cocoa_set_titlebar_color(void *w)
|
|
{
|
|
if (!change_titlebar_color) return;
|
|
|
|
NSWindow *window = (NSWindow *)w;
|
|
|
|
double red = ((titlebar_color >> 16) & 0xFF) / 255.0;
|
|
double green = ((titlebar_color >> 8) & 0xFF) / 255.0;
|
|
double blue = (titlebar_color & 0xFF) / 255.0;
|
|
|
|
NSColor *background =
|
|
[NSColor colorWithSRGBRed:red
|
|
green:green
|
|
blue:blue
|
|
alpha:1.0];
|
|
[window setTitlebarAppearsTransparent:YES];
|
|
[window setBackgroundColor:background];
|
|
|
|
double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
|
|
|
|
if (luma < 0.5) {
|
|
[window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
|
|
} else {
|
|
[window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
|
|
}
|
|
}
|
|
|
|
static PyMethodDef module_methods[] = {
|
|
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
|
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
|
|
{"macos_change_titlebar_color", (PyCFunction)macos_change_titlebar_color, METH_O, ""},
|
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
|
};
|
|
|
|
bool
|
|
init_cocoa(PyObject *module) {
|
|
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
|
return true;
|
|
}
|