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:
Kovid Goyal 2024-07-25 12:22:58 +05:30
parent f66a58ebe2
commit 7e610eb645
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 71 additions and 29 deletions

9
glfw/linux_notify.c vendored
View file

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

View file

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

View file

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

View file

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

View file

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