pulsar/linux/runner/my_application.cc
2026-04-30 04:39:20 +01:00

217 lines
7.4 KiB
C++

#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#include <X11/extensions/XTest.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
static void first_frame_cb(MyApplication* self, FlView* view) {
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}
#ifdef GDK_WINDOWING_X11
static void handle_inject_input(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
const gchar* method = fl_method_call_get_name(method_call);
if (g_strcmp0(method, "injectInput") != 0) {
fl_method_call_respond_not_implemented(method_call, nullptr);
return;
}
FlValue* args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
fl_method_call_respond_error(method_call, "INVALID", "expected map", nullptr, nullptr);
return;
}
Display* display = XOpenDisplay(nullptr);
if (!display) {
fl_method_call_respond_error(method_call, "DISPLAY", "cannot open display", nullptr, nullptr);
return;
}
int screen = DefaultScreen(display);
int screen_w = DisplayWidth(display, screen);
int screen_h = DisplayHeight(display, screen);
FlValue* type_val = fl_value_lookup_string(args, "type");
if (!type_val) {
XCloseDisplay(display);
fl_method_call_respond_success(method_call, nullptr, nullptr);
return;
}
const gchar* type = fl_value_get_string(type_val);
if (g_strcmp0(type, "move") == 0) {
FlValue* xv = fl_value_lookup_string(args, "x");
FlValue* yv = fl_value_lookup_string(args, "y");
if (xv && yv) {
double nx = fl_value_get_float(xv);
double ny = fl_value_get_float(yv);
int px = (int)(nx * screen_w);
int py = (int)(ny * screen_h);
XTestFakeMotionEvent(display, screen, px, py, CurrentTime);
XFlush(display);
}
} else if (g_strcmp0(type, "click") == 0) {
FlValue* xv = fl_value_lookup_string(args, "x");
FlValue* yv = fl_value_lookup_string(args, "y");
FlValue* bv = fl_value_lookup_string(args, "button");
if (xv && yv) {
double nx = fl_value_get_float(xv);
double ny = fl_value_get_float(yv);
int px = (int)(nx * screen_w);
int py = (int)(ny * screen_h);
int btn = bv ? (int)fl_value_get_int(bv) : 0;
unsigned int xbtn = (btn == 1) ? 3 : 1; // X11: 1=left, 3=right
XTestFakeMotionEvent(display, screen, px, py, CurrentTime);
XTestFakeButtonEvent(display, xbtn, True, CurrentTime);
XTestFakeButtonEvent(display, xbtn, False, CurrentTime);
XFlush(display);
}
} else if (g_strcmp0(type, "keydown") == 0 || g_strcmp0(type, "keyup") == 0) {
FlValue* kv = fl_value_lookup_string(args, "keyCode");
if (kv) {
// rough mapping: use low byte of Flutter logical key as X11 keysym
int logical = (int)fl_value_get_int(kv);
KeySym sym = (KeySym)(logical & 0xFFFF);
KeyCode kc = XKeysymToKeycode(display, sym);
if (kc) {
Bool press = g_strcmp0(type, "keydown") == 0 ? True : False;
XTestFakeKeyEvent(display, kc, press, CurrentTime);
XFlush(display);
}
}
}
XCloseDisplay(display);
fl_method_call_respond_success(method_call, nullptr, nullptr);
}
#endif // GDK_WINDOWING_X11
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "pulsar");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "pulsar");
}
gtk_window_set_default_size(window, 1280, 720);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(
project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
GdkRGBA background_color;
gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self);
gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
#ifdef GDK_WINDOWING_X11
// register input injection channel (X11 only — Wayland not supported)
FlEngine* engine = fl_view_get_engine(view);
FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(engine);
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
FlMethodChannel* input_channel = fl_method_channel_new(
messenger, "com.pulsar/input", FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(
input_channel, handle_inject_input, nullptr, nullptr);
#endif
gtk_widget_grab_focus(GTK_WIDGET(view));
}
static gboolean my_application_local_command_line(GApplication* application,
gchar*** arguments,
int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
static void my_application_startup(GApplication* application) {
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
static void my_application_shutdown(GApplication* application) {
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}