Get closing of notifications working on Linux
Also implement support for the ActivationToken signal so that OS Windows are focused on Wayland when clicking on a notification.
This commit is contained in:
parent
f66a58ebe2
commit
7e610eb645
5 changed files with 71 additions and 29 deletions
9
glfw/linux_notify.c
vendored
9
glfw/linux_notify.c
vendored
|
|
@ -16,8 +16,6 @@
|
|||
static inline void cleanup_free(void *p) { free(*(void**)p); }
|
||||
#define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer
|
||||
|
||||
static notification_id_type notification_id = 0;
|
||||
|
||||
typedef struct {
|
||||
notification_id_type next_id;
|
||||
GLFWDBusnotificationcreatedfun callback;
|
||||
|
|
@ -41,8 +39,10 @@ notification_created(DBusMessage *msg, const char* errmsg, void *data) {
|
|||
uint32_t id;
|
||||
if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return;
|
||||
NotificationCreatedData *ncd = (NotificationCreatedData*)data;
|
||||
if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
|
||||
if (data) free(data);
|
||||
if (ncd) {
|
||||
if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
|
||||
free(ncd);
|
||||
}
|
||||
}
|
||||
|
||||
static DBusHandlerResult
|
||||
|
|
@ -107,6 +107,7 @@ glfw_dbus_send_user_notification(const char *app_name, const char* icon, const c
|
|||
}
|
||||
RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData)));
|
||||
if (!data) return 0;
|
||||
static notification_id_type notification_id = 0;
|
||||
data->next_id = ++notification_id;
|
||||
data->callback = callback; data->data = user_data;
|
||||
if (!data->next_id) data->next_id = ++notification_id;
|
||||
|
|
|
|||
|
|
@ -2750,9 +2750,6 @@ def on_monitored_pid_death(self, pid: int, exit_status: int) -> None:
|
|||
except Exception as e:
|
||||
log_error(f'Failed to process update check data {raw!r}, with error: {e}')
|
||||
|
||||
def dbus_notification_callback(self, event_type: str, a: int, b: Union[int, str]) -> None:
|
||||
self.notification_manager.desktop_integration.dispatch_event_from_desktop(event_type, a, b)
|
||||
|
||||
def show_bad_config_lines(self, bad_lines: Iterable[BadLine], misc_errors: Iterable[str] = ()) -> None:
|
||||
|
||||
def format_bad_line(bad_line: BadLine) -> str:
|
||||
|
|
|
|||
|
|
@ -543,6 +543,8 @@ def os_window_has_background_image(os_window_id: int) -> bool:
|
|||
pass
|
||||
|
||||
|
||||
def dbus_set_notification_callback(c: Callable[[str, int, Union[str, int]], None]) -> None: ...
|
||||
|
||||
def dbus_send_notification(
|
||||
app_name: str,
|
||||
icon: str,
|
||||
|
|
|
|||
24
kitty/glfw.c
24
kitty/glfw.c
|
|
@ -1419,6 +1419,24 @@ error_callback(int error, const char* description) {
|
|||
|
||||
|
||||
#ifndef __APPLE__
|
||||
static PyObject *dbus_notification_callback = NULL;
|
||||
|
||||
static PyObject*
|
||||
dbus_set_notification_callback(PyObject *self UNUSED, PyObject *callback) {
|
||||
Py_CLEAR(dbus_notification_callback); dbus_notification_callback = callback; Py_INCREF(callback);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#define send_dbus_notification_event_to_python(event_type, a, b) { \
|
||||
if (dbus_notification_callback) { \
|
||||
const char call_args_fmt[] = {'s', \
|
||||
_Generic((a), unsigned long : 'k', unsigned long long : 'K'), _Generic((b), unsigned long : 'k', const char* : 's') }; \
|
||||
RAII_PyObject(ret, PyObject_CallFunction(dbus_notification_callback, call_args_fmt, event_type, a, b)); \
|
||||
if (!ret) PyErr_Print(); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dbus_user_notification_activated(uint32_t notification_id, int type, const char* action) {
|
||||
unsigned long nid = notification_id;
|
||||
|
|
@ -1427,7 +1445,7 @@ dbus_user_notification_activated(uint32_t notification_id, int type, const char*
|
|||
case 0: stype = "closed"; break;
|
||||
case 1: stype = "activation_token"; break;
|
||||
}
|
||||
call_boss(dbus_notification_callback, "sks", stype, nid, action);
|
||||
send_dbus_notification_event_to_python(stype, nid, action);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -2069,7 +2087,7 @@ request_frame_render(OSWindow *w) {
|
|||
void
|
||||
dbus_notification_created_callback(unsigned long long notification_id, uint32_t new_notification_id, void* data UNUSED) {
|
||||
unsigned long new_id = new_notification_id;
|
||||
call_boss(dbus_notification_callback, "sKk", "created", notification_id, new_id);
|
||||
send_dbus_notification_event_to_python("created", notification_id, new_id);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
|
|
@ -2263,6 +2281,7 @@ static PyMethodDef module_methods[] = {
|
|||
#ifndef __APPLE__
|
||||
METHODB(dbus_send_notification, METH_VARARGS),
|
||||
METHODB(dbus_close_notification, METH_VARARGS),
|
||||
METHODB(dbus_set_notification_callback, METH_O),
|
||||
#else
|
||||
{"cocoa_recreate_global_menu", (PyCFunction)py_recreate_global_menu, METH_NOARGS, ""},
|
||||
{"cocoa_clear_global_shortcuts", (PyCFunction)py_clear_global_shortcuts, METH_NOARGS, ""},
|
||||
|
|
@ -2286,6 +2305,7 @@ void cleanup_glfw(void) {
|
|||
logo.pixels = NULL;
|
||||
Py_CLEAR(edge_spacing_func);
|
||||
#ifndef __APPLE__
|
||||
Py_CLEAR(dbus_notification_callback);
|
||||
release_freetype_render_context(csd_title_render_ctx);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,9 +256,6 @@ def __init__(self, notification_manager: 'NotificationManager'):
|
|||
def initialize(self) -> None:
|
||||
pass
|
||||
|
||||
def dispatch_event_from_desktop(self, *a: Any) -> None:
|
||||
raise NotImplementedError('Implement me in subclass')
|
||||
|
||||
def close_notification(self, desktop_notification_id: int) -> bool:
|
||||
raise NotImplementedError('Implement me in subclass')
|
||||
|
||||
|
|
@ -310,27 +307,53 @@ def notification_activated(self, ident: str) -> None:
|
|||
|
||||
class FreeDesktopIntegration(DesktopIntegration):
|
||||
|
||||
def initialize(self) -> None:
|
||||
from .fast_data_types import dbus_set_notification_callback
|
||||
dbus_set_notification_callback(self.dispatch_event_from_desktop)
|
||||
# map the id returned by the notification daemon to the
|
||||
# desktop_notification_id we use for the notification
|
||||
self.creation_id_map: 'OrderedDict[int, int]' = OrderedDict()
|
||||
|
||||
def close_notification(self, desktop_notification_id: int) -> bool:
|
||||
from .fast_data_types import dbus_close_notification
|
||||
close_succeeded = dbus_close_notification(desktop_notification_id)
|
||||
if debug_desktop_integration:
|
||||
log_error(f'Close request for {desktop_notification_id=} {"succeeded" if close_succeeded else "failed"}')
|
||||
close_succeeded = False
|
||||
if dbus_id := self.get_dbus_notification_id(desktop_notification_id, 'close_request'):
|
||||
close_succeeded = dbus_close_notification(dbus_id)
|
||||
if debug_desktop_integration:
|
||||
log_error(f'Close request for {desktop_notification_id=} {"succeeded" if close_succeeded else "failed"}')
|
||||
return close_succeeded
|
||||
|
||||
def dispatch_event_from_desktop(self, *args: Any) -> None:
|
||||
event_type: str = args[0]
|
||||
dbus_notification_id: int = args[1]
|
||||
def get_desktop_notification_id(self, dbus_notification_id: int, event: str) -> Optional[int]:
|
||||
q = self.creation_id_map.get(dbus_notification_id)
|
||||
if q is None:
|
||||
if debug_desktop_integration:
|
||||
log_error(f'Could not find desktop_notification_id for {dbus_notification_id=} for event {event}')
|
||||
return q
|
||||
|
||||
def get_dbus_notification_id(self, desktop_notification_id: int, event: str) ->Optional[int]:
|
||||
for dbus_id, q in self.creation_id_map.items():
|
||||
if q == desktop_notification_id:
|
||||
return dbus_id
|
||||
if debug_desktop_integration:
|
||||
log_error(f'Got notification event from desktop: {args=}')
|
||||
log_error(f'Could not find dbus_notification_id for {desktop_notification_id=} for event {event}')
|
||||
return None
|
||||
|
||||
def dispatch_event_from_desktop(self, event_type: str, dbus_notification_id: int, extra: Union[int, str]) -> None:
|
||||
if debug_desktop_integration:
|
||||
log_error(f'Got notification event from desktop: {event_type=} {dbus_notification_id=} {extra=}')
|
||||
if event_type == 'created':
|
||||
self.creation_id_map[int(extra)] = dbus_notification_id
|
||||
if len(self.creation_id_map) > 128:
|
||||
self.creation_id_map.popitem(False)
|
||||
self.notification_manager.notification_created(dbus_notification_id)
|
||||
elif event_type == 'activation_token':
|
||||
token: str = args[2]
|
||||
self.notification_manager.notification_activation_token_received(dbus_notification_id, token)
|
||||
elif event_type == 'activated':
|
||||
self.notification_manager.notification_activated(dbus_notification_id)
|
||||
elif event_type == 'closed':
|
||||
self.notification_manager.notification_closed(dbus_notification_id)
|
||||
return
|
||||
if desktop_notification_id := self.get_desktop_notification_id(dbus_notification_id, event_type):
|
||||
if event_type == 'activation_token':
|
||||
self.notification_manager.notification_activation_token_received(desktop_notification_id, str(extra))
|
||||
elif event_type == 'activated':
|
||||
self.notification_manager.notification_activated(desktop_notification_id)
|
||||
elif event_type == 'closed':
|
||||
self.notification_manager.notification_closed(desktop_notification_id)
|
||||
|
||||
def notify(self,
|
||||
title: str,
|
||||
|
|
@ -381,6 +404,8 @@ def send(self, channel_id: int, osc_escape_code: str) -> bool:
|
|||
return False
|
||||
|
||||
def focus(self, channel_id: int, activation_token: str) -> None:
|
||||
if debug_desktop_integration:
|
||||
log_error(f'Focusing window: {channel_id} with activation_token: {activation_token}')
|
||||
boss = get_boss()
|
||||
if w := self.window_for_id(channel_id):
|
||||
boss.set_active_window(w, switch_os_window_if_needed=True, activation_token=activation_token)
|
||||
|
|
@ -423,9 +448,6 @@ def reset(self) -> None:
|
|||
self.in_progress_notification_commands_by_client_id: Dict[str, NotificationCommand] = {}
|
||||
self.pending_commands: Dict[int, NotificationCommand] = {}
|
||||
|
||||
def dispatch_event_from_desktop(self, *args: Any) -> None:
|
||||
self.desktop_integration.dispatch_event_from_desktop(*args)
|
||||
|
||||
def notification_created(self, desktop_notification_id: int) -> None:
|
||||
if n := self.in_progress_notification_commands.get(desktop_notification_id):
|
||||
n.created_by_desktop = True
|
||||
|
|
|
|||
Loading…
Reference in a new issue