diff --git a/kitty/boss.py b/kitty/boss.py index 5eb9fef36..3a782c7e7 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -19,8 +19,8 @@ from .keys import get_key_map, get_shortcut from .session import create_session from .tabs import SpecialWindow, TabManager from .utils import ( - get_primary_selection, open_url, safe_print, set_primary_selection, - single_instance + end_startup_notification, get_primary_selection, init_startup_notification, + open_url, safe_print, set_primary_selection, single_instance ) @@ -84,11 +84,12 @@ class Boss: os_window_id = create_os_window(w, h, self.args.cls) tm = TabManager(os_window_id, self.opts, self.args, startup_session) self.os_window_map[os_window_id] = tm + return os_window_id def new_os_window(self, *args): sw = self.args_to_special_window(args) if args else None startup_session = create_session(self.opts, special_window=sw) - self.add_os_window(startup_session) + return self.add_os_window(startup_session) def add_child(self, window): self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) @@ -98,10 +99,14 @@ class Boss: import json msg = json.loads(msg.decode('utf-8')) if isinstance(msg, dict) and msg.get('cmd') == 'new_instance': + startup_id = msg.get('startup_id') args = option_parser().parse_args(msg['args'][1:]) opts = create_opts(args) session = create_session(opts, args) - self.add_os_window(session) + os_window_id = self.add_os_window(session) + if startup_id: + ctx = init_startup_notification(os_window_id, startup_id) + end_startup_notification(ctx) else: safe_print('Unknown message received from peer, ignoring') diff --git a/kitty/data-types.c b/kitty/data-types.c index 4c94d02be..099c2b9e5 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -174,6 +174,7 @@ extern int init_ColorProfile(PyObject *); extern int init_Screen(PyObject *); extern bool init_freetype_library(PyObject*); extern bool init_fontconfig_library(PyObject*); +extern bool init_desktop(PyObject*); extern bool init_fonts(PyObject*); extern bool init_glfw(PyObject *m); extern bool init_state(PyObject *module); @@ -215,6 +216,7 @@ PyInit_fast_data_types(void) { if (!init_cocoa(m)) return NULL; #else if (!init_fontconfig_library(m)) return NULL; + if (!init_desktop(m)) return NULL; #endif if (!init_fonts(m)) return NULL; diff --git a/kitty/desktop.c b/kitty/desktop.c new file mode 100644 index 000000000..f242f9e82 --- /dev/null +++ b/kitty/desktop.c @@ -0,0 +1,101 @@ +/* + * desktop.c + * Copyright (C) 2017 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "data-types.h" +#include + +#define FUNC(name, restype, ...) typedef restype (*name##_func)(__VA_ARGS__); static name##_func name = NULL +#define LOAD_FUNC(handle, name) {\ + *(void **) (&name) = dlsym(handle, #name); \ + const char* error = dlerror(); \ + if (error != NULL) { \ + PyErr_Format(PyExc_OSError, "Failed to load the function %s with error: %s", #name, error); dlclose(handle); handle = NULL; return NULL; \ + } \ +} + +FUNC(sn_display_new, void*, void*, void*, void*); +FUNC(sn_launchee_context_new_from_environment, void*, void*, int); +FUNC(sn_launchee_context_new, void*, void*, int, const char*); +FUNC(sn_display_unref, void, void*); +FUNC(sn_launchee_context_setup_window, void, void*, int32_t); +FUNC(sn_launchee_context_complete, void, void*); +FUNC(sn_launchee_context_unref, void, void*); + +static void* libsn_handle = NULL; + +static PyObject* +init_x11_startup_notification(PyObject UNUSED *self, PyObject *args) { + static bool done = false; + static const char* libname = "libstartup-notification-1.so"; + if (!done) { + done = true; + + libsn_handle = dlopen(libname, RTLD_LAZY); + if (libsn_handle == NULL) { + PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libname, dlerror()); + return NULL; + } + dlerror(); /* Clear any existing error */ +#define F(name) LOAD_FUNC(libsn_handle, name) + F(sn_display_new); + F(sn_launchee_context_new_from_environment); + F(sn_launchee_context_new); + F(sn_display_unref); + F(sn_launchee_context_setup_window); + F(sn_launchee_context_complete); + F(sn_launchee_context_unref); +#undef F + } + + int window_id; + PyObject *dp; + char *startup_id = NULL; + if (!PyArg_ParseTuple(args, "O!i|z", &PyLong_Type, &dp, &window_id, &startup_id)) return NULL; + void* display = PyLong_AsVoidPtr(dp); + void* sn_display = sn_display_new(display, NULL, NULL); + if (!sn_display) { PyErr_SetString(PyExc_OSError, "Failed to create SnDisplay"); return NULL; } + printf("%s", startup_id); + void *ctx = startup_id ? sn_launchee_context_new(sn_display, 0, startup_id) : sn_launchee_context_new_from_environment(sn_display, 0); + sn_display_unref(sn_display); + if (!ctx) { PyErr_SetString(PyExc_OSError, "Failed to create startup-notification context"); return NULL; } + sn_launchee_context_setup_window(ctx, window_id); + return PyLong_FromVoidPtr(ctx); +} + +static PyObject* +end_x11_startup_notification(PyObject UNUSED *self, PyObject *args) { + if (!libsn_handle) Py_RETURN_NONE; + PyObject *dp; + if (!PyArg_ParseTuple(args, "O!", &PyLong_Type, &dp)) return NULL; + void *ctx = PyLong_AsVoidPtr(dp); + sn_launchee_context_complete(ctx); + sn_launchee_context_unref(ctx); + + Py_RETURN_NONE; +} + +static PyMethodDef module_methods[] = { + METHODB(init_x11_startup_notification, METH_VARARGS), + METHODB(end_x11_startup_notification, METH_VARARGS), + + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +static void +finalize(void) { + if (libsn_handle) dlclose(libsn_handle); +} + +bool +init_desktop(PyObject *m) { + if (PyModule_AddFunctions(m, module_methods) != 0) return false; + if (Py_AtExit(finalize) != 0) { + PyErr_SetString(PyExc_RuntimeError, "Failed to register the desktop.c at exit handler"); + return false; + } + return true; +} diff --git a/kitty/main.py b/kitty/main.py index 8064f4ecb..ebfc78d08 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -131,7 +131,7 @@ def main(): is_first = single_instance(args.instance_group) if not is_first: import json - data = {'cmd': 'new_instance', 'args': tuple(sys.argv)} + data = {'cmd': 'new_instance', 'args': tuple(sys.argv), 'startup_id': os.environ.get('DESKTOP_STARTUP_ID')} data = json.dumps(data, ensure_ascii=False).encode('utf-8') single_instance.socket.sendall(data) return diff --git a/kitty/utils.py b/kitty/utils.py index 0c6091f58..1d7a31f78 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -3,7 +3,6 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal import atexit -import ctypes import errno import fcntl import math @@ -13,10 +12,8 @@ import shlex import socket import string import subprocess -import sys import tempfile from contextlib import contextmanager -from ctypes.util import find_library from functools import lru_cache from time import monotonic @@ -240,54 +237,29 @@ def adjust_line_height(cell_height, val): return int(cell_height * val) -def init_startup_notification_x11(window_id): +def init_startup_notification_x11(window_id, startup_id=None): # https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt + from kitty.fast_data_types import init_x11_startup_notification + sid = startup_id or os.environ.pop('DESKTOP_STARTUP_ID', None) # ensure child processes dont get this env var + if not sid: + return display = x11_display() if not display: return - lib = find_library('startup-notification-1') - if not lib: - safe_print('libstartup-notification-1.so not found, disabling startup notification', file=sys.stderr) - return - lib = init_startup_notification_x11.lib = ctypes.CDLL(lib) - f = lib.sn_display_new - f.restype = ctypes.c_void_p - f.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - display = f(x11_display(), None, None) - f = lib.sn_launchee_context_new_from_environment - f.restype = ctypes.c_void_p - f.argtypes = [ctypes.c_void_p, ctypes.c_int] - ctx = f(display, 0) - f = lib.sn_display_unref - f.argtypes = [ctypes.c_void_p] - f.restype = None - f(display) - os.environ.pop('DESKTOP_STARTUP_ID', None) # ensure child processes dont get this env var - if ctx: - f = lib.sn_launchee_context_setup_window - f.argtypes = [ctypes.c_void_p, ctypes.c_int32] - f(ctx, x11_window_id(window_id)) - return ctx + window_id = x11_window_id(window_id) + return init_x11_startup_notification(display, window_id, sid) def end_startup_notification_x11(ctx): - lib = init_startup_notification_x11.lib - del init_startup_notification_x11.lib - f = lib.sn_launchee_context_complete - f.restype = None - f.argtypes = [ctypes.c_void_p] - f(ctx) - f = lib.sn_launchee_context_unref - f.restype = None - f.argtypes = [ctypes.c_void_p] - f(ctx) + from kitty.fast_data_types import end_x11_startup_notification + end_x11_startup_notification(ctx) -def init_startup_notification(window): +def init_startup_notification(window, startup_id=None): if isosx or iswayland: return try: - return init_startup_notification_x11(window) + return init_startup_notification_x11(window, startup_id) except Exception: import traceback traceback.print_exc() diff --git a/setup.py b/setup.py index 05c3a4db4..ab9b0200a 100755 --- a/setup.py +++ b/setup.py @@ -197,6 +197,8 @@ def init_env( ] if not isosx: ldpaths += ['-lrt'] + if '-ldl' not in ldpaths: + ldpaths.append('-ldl') if '-lz' not in ldpaths: ldpaths.append('-lz') @@ -338,7 +340,7 @@ def option_parser(): def find_c_files(): ans, headers = [], [] d = os.path.join(base, 'kitty') - exclude = {'fontconfig.c'} if isosx else {'core_text.m', 'cocoa_window.m'} + exclude = {'fontconfig.c', 'desktop.c'} if isosx else {'core_text.m', 'cocoa_window.m'} for x in os.listdir(d): ext = os.path.splitext(x)[1] if ext in ('.c', '.m') and os.path.basename(x) not in exclude: