Use startup notifications in single instance mode

This commit is contained in:
Kovid Goyal 2017-11-17 20:57:06 +05:30
parent fa1ae39480
commit b08f4ab593
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 127 additions and 45 deletions

View File

@ -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')

View File

@ -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;

101
kitty/desktop.c Normal file
View File

@ -0,0 +1,101 @@
/*
* desktop.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <dlfcn.h>
#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;
}

View File

@ -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

View File

@ -3,7 +3,6 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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()

View File

@ -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: