Protocol 11: sit out replaces kicking; nudge

Anyone can sit anyone out (host included); only you bring yourself back.
Out members stay in chat and the party, never block or count, and load no
media; a card with the live position and Join back (Space) replaces the
video. The mini player's x sits you out; optionally idle does too. Nudge:
the server forwards it to the target only (3/s, 20/min per sender); the
target hears the nudge sound, gets a toast and a taskbar attention
request. Kick, its messages, errors and dialog are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-09 21:05:35 +03:00
1 parent dbfd351ee4
commit 8b81da4069
46 files changed
+855 -599

No files matched your search

@@ -10,6 +10,8 @@
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
// "starsync/window" (lib/src/platform/attention.dart).
FlMethodChannel* window_channel;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
@@ -19,6 +21,29 @@ static void first_frame_cb(MyApplication* self, FlView* view) {
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}
// "starsync/window": `attention` sets the urgency hint while the window is
// not focused; focusing it clears the hint.
static void window_method_cb(FlMethodChannel* channel, FlMethodCall* call,
gpointer user_data) {
GtkWindow* window = GTK_WINDOW(user_data);
g_autoptr(FlMethodResponse) response = nullptr;
if (g_strcmp0(fl_method_call_get_name(call), "attention") == 0) {
if (!gtk_window_is_active(window)) {
gtk_window_set_urgency_hint(window, TRUE);
}
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
fl_method_call_respond(call, response, nullptr);
}
static gboolean window_focus_in_cb(GtkWidget* widget, GdkEvent* event,
gpointer user_data) {
gtk_window_set_urgency_hint(GTK_WINDOW(widget), FALSE);
return FALSE;
}
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
@@ -88,6 +113,15 @@ static void my_application_activate(GApplication* application) {
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->window_channel = fl_method_channel_new(
fl_engine_get_binary_messenger(fl_view_get_engine(view)),
"starsync/window", FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(self->window_channel,
window_method_cb, window, nullptr);
g_signal_connect(window, "focus-in-event", G_CALLBACK(window_focus_in_cb),
nullptr);
gtk_widget_grab_focus(GTK_WIDGET(view));
}
@@ -134,6 +168,7 @@ static void my_application_shutdown(GApplication* application) {
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
g_clear_object(&self->window_channel);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}