#include #include "shell.h" #include "wayland.h" #include "virtual-keyboard.h" #include "input.h" #include "files-monior.h" #define diya_shell_config_css_file(priv) (g_strconcat(g_get_user_config_dir(), "/diya/themes/", priv->theme?priv->theme:"default", "/", priv->name, ".css", NULL)) enum { NO_PROP, PROP_SHELL_WAYLAND, PROP_SHELL_APP, PROP_SHELL_NAME, PROP_SHELL_THEME, N_PROPERTIES }; static GParamSpec *shell_properties[N_PROPERTIES] = {0}; typedef struct _DiyaShellPrivate { DiyaObject parent; DiyaWayland *wayland; GtkApplication *app; gchar *name; GHashTable *vkbs; GtkCssProvider *css_provider; DiyaInput *input; DiyaFilesMonitor *files_watchdog; gchar *theme; } DiyaShellPrivate; G_DEFINE_TYPE_WITH_PRIVATE(DiyaShell, diya_shell, DIYA_TYPE_OBJECT); static void diya_shell_dispose(GObject *object) { g_debug("diya_shell_dispose: %s", diya_object_to_string(object)); DiyaShell *self = DIYA_SHELL(object); DiyaShellPrivate *priv = diya_shell_get_instance_private(self); if (priv->wayland) { g_object_unref(priv->wayland); } if (priv->name) { g_free(priv->name); priv->name = NULL; } if(priv->theme) { g_free(priv->theme); priv->name = NULL; } if (priv->css_provider) { g_object_unref(priv->css_provider); } if (priv->input) { g_object_unref(priv->input); } g_hash_table_destroy(priv->vkbs); if (priv->files_watchdog) { g_object_unref(priv->files_watchdog); priv->files_watchdog = NULL; } G_OBJECT_CLASS(diya_shell_parent_class)->dispose(object); if (priv->app) { g_application_quit(G_APPLICATION(priv->app)); } } static void diya_shell_reload_theme(DiyaShell *shell) { GError *err = NULL; GBytes *bytes = NULL; gchar *css_string = NULL; DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); gchar *css_file = diya_shell_config_css_file(priv); g_debug("diya_shell_reload: Looking for css file: %s", css_file); g_file_get_contents(css_file, &css_string, NULL, &err); g_free(css_file); if (err != NULL) { g_warning("diya_shell_reload: Unable to load CSS from file: %s", err->message); g_error_free(err); err = NULL; g_debug("diya_shell_reload: Fallback to default theme"); css_file = g_strconcat("/dev/iohub/diya/shell/", priv->name, ".css", NULL); bytes = g_resources_lookup_data(css_file, 0, &err); free(css_file); if (err != NULL) { g_critical("diya_shell_reload: Unable to load CSS from resource: %s", err->message); g_error_free(err); css_string = NULL; } else { css_string = (gchar *)g_bytes_get_data(bytes, NULL); } } if (css_string) { if (priv->css_provider) { gtk_style_context_remove_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(priv->css_provider)); g_object_unref(priv->css_provider); priv->css_provider = NULL; } g_debug("diya_shell_reload: Applying stylesheet:\n %s", css_string); priv->css_provider = gtk_css_provider_new(); gtk_css_provider_load_from_string(priv->css_provider, css_string); gtk_style_context_add_provider_for_display( gdk_display_get_default(), GTK_STYLE_PROVIDER(priv->css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER); if (!bytes) { free(css_string); } } if (bytes) { g_bytes_unref(bytes); } } void diya_shell_reload(DiyaShell* self) { diya_shell_reload_theme(self); } void on_diya_shell_theme_file_changed(GFileMonitor *monitor, GFile *file, GFile *other, GFileMonitorEvent evtype, gpointer user_data) { (void)monitor; DiyaShell *shell = user_data; char *fpath = g_file_get_path(file); char *opath = NULL; g_debug("%s event %x", fpath, evtype); if (other) { opath = g_file_get_path(other); } switch (evtype) { case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: case G_FILE_MONITOR_EVENT_DELETED: case G_FILE_MONITOR_EVENT_RENAMED: case G_FILE_MONITOR_EVENT_MOVED_IN: case G_FILE_MONITOR_EVENT_MOVED_OUT: //case G_FILE_MONITOR_EVENT_CHANGED: //case G_FILE_MONITOR_EVENT_CREATED: diya_shell_reload_theme(shell); break; default: break; } if (opath) { g_free(opath); } g_free(fpath); } static void on_gtk_app_startup(GtkApplication *app, void *data) { (void)app; DiyaShell *shell = data; DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); priv->wayland = diya_wayland_new(DIYA_SHELL(shell)); DiyaShellClass *class = DIYA_SHELL_GET_CLASS(shell); // read envar const char* default_theme = g_getenv(DIYA_ENV_THEME); if(default_theme) { priv->theme = g_strdup(default_theme); } gchar *css_file = diya_shell_config_css_file(priv); diya_shell_watch_file(shell, css_file, G_CALLBACK(on_diya_shell_theme_file_changed), (gpointer)shell); g_free(css_file); diya_shell_reload(shell); if (class->startup_handle) { class->startup_handle(shell); } if (class->monitor_changed_handle) { GListModel *monitor_list = gdk_display_get_monitors(gdk_display_get_default()); g_debug("listen to monitor changed"); g_signal_connect(monitor_list, "items-changed", G_CALLBACK(class->monitor_changed_handle), shell); } } static void on_gtk_app_active(GtkApplication *app, void *data) { (void)app; DiyaShell *shell = data; DiyaShellClass *class = DIYA_SHELL_GET_CLASS(shell); if (class->active_handle) { class->active_handle(shell); } } static gboolean diya_shell_sigint_handle(gpointer data) { g_object_unref(DIYA_SHELL(data)); return true; } static gboolean diya_shell_sighub_handle(DiyaShell *self) { // reload css file diya_shell_reload(self); DiyaShellClass *class = DIYA_SHELL_GET_CLASS(self); if (class->reload_handle) { class->reload_handle(self); } return true; } static void init_gtk_application(DiyaShell *self) { DiyaShellPrivate *priv = diya_shell_get_instance_private(self); if (priv->app) { return; } priv->app = gtk_application_new(priv->name, G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(priv->app, "startup", G_CALLBACK(on_gtk_app_startup), (void *)self); g_signal_connect(priv->app, "activate", G_CALLBACK(on_gtk_app_active), (void *)self); g_unix_signal_add(SIGINT, (GSourceFunc)diya_shell_sigint_handle, (void *)self); g_unix_signal_add(SIGHUP, (GSourceFunc)diya_shell_sighub_handle, (void *)self); } static void diya_shell_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { DiyaShell *self = DIYA_SHELL(object); DiyaShellPrivate *priv = diya_shell_get_instance_private(self); switch (property_id) { case PROP_SHELL_NAME: if (priv->name) { return; } priv->name = g_strdup(g_value_get_string(value)); init_gtk_application(self); break; case PROP_SHELL_THEME: { const gchar* theme = g_value_get_string(value); if(!priv->theme || !g_str_equal(priv->theme, theme)) { if(priv->theme) { g_free(priv->theme); } priv->theme = g_strdup(theme); diya_shell_reload_theme(self); } break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void diya_shell_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { DiyaShell *self = DIYA_SHELL(object); DiyaShellPrivate *priv = diya_shell_get_instance_private(self); switch (property_id) { case PROP_SHELL_WAYLAND: g_value_set_pointer(value, priv->wayland); break; case PROP_SHELL_APP: g_value_set_pointer(value, priv->app); break; case PROP_SHELL_NAME: g_value_set_string(value, priv->name); break; case PROP_SHELL_THEME: g_value_set_string(value, priv->theme); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void diya_shell_init(DiyaShell *self) { DiyaShellPrivate *priv = diya_shell_get_instance_private(self); priv->wayland = NULL; priv->app = NULL; priv->name = NULL; priv->vkbs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref); priv->css_provider = NULL; priv->input = NULL; priv->files_watchdog = NULL; priv->theme = NULL; } DiyaWayland *diya_shell_get_wayland(DiyaShell *shell) { DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); assert(DIYA_IS_WAYLAND(priv->wayland)); return priv->wayland; } GtkApplication *diya_shell_get_application(DiyaShell *shell) { DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); assert(GTK_IS_APPLICATION(priv->app)); return priv->app; } const char *diya_shell_get_name(DiyaShell *shell) { DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); return priv->name; } static const gchar *diya_shell_to_string(DiyaObject *object) { return diya_shell_get_name(DIYA_SHELL(object)); } static void diya_shell_class_init(DiyaShellClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS(class); gobject_class->dispose = diya_shell_dispose; gobject_class->set_property = diya_shell_set_property; gobject_class->get_property = diya_shell_get_property; DiyaObjectClass *base_class = DIYA_OBJECT_CLASS(class); base_class->to_string = diya_shell_to_string; class->foreign_register = NULL; class->virtual_keyboard_register = diya_virtual_keyboard_register; class->monitor_changed_handle = NULL; class->startup_handle = NULL; class->active_handle = NULL; class->reload_handle = NULL; shell_properties[PROP_SHELL_WAYLAND] = g_param_spec_pointer("wayland", NULL, "Shell wayland", G_PARAM_READABLE); // shell_properties[PROP_SHELL_NAME] = g_param_spec_string("name", NULL, "Shell name", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); shell_properties[PROP_SHELL_APP] = g_param_spec_pointer("application", NULL, "Shell application", G_PARAM_READABLE); shell_properties[PROP_SHELL_THEME] = g_param_spec_string("theme", NULL, "Shell theme", NULL, G_PARAM_READWRITE); g_object_class_install_properties(gobject_class, N_PROPERTIES, shell_properties); } int diya_shell_run(DiyaShell *shell, int argc, char **argv) { DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); assert(GTK_IS_APPLICATION(priv->app)); GtkApplication *app = priv->app; /** * create shell config dir if not exists */ // theme dir gchar* path = g_strconcat(g_get_user_config_dir(), "/diya/themes/", NULL); g_mkdir_with_parents((const gchar*)path,0755); g_free(path); // keymap dir path = g_strconcat(g_get_user_config_dir(), "/diya/xkb/", NULL); g_mkdir_with_parents((const gchar*)path,0755); g_free(path); int status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; } DiyaVirtualKeyboard *diya_shell_get_virtual_keyboard(DiyaShell *shell, const gchar *name) { DiyaVirtualKeyboard *vkb = NULL; DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); if (name) { if (g_hash_table_contains(priv->vkbs, name)) { g_debug("diya_shell_get_virtual_keyboard: Getting keyboard %s from cache", name); vkb = g_hash_table_lookup(priv->vkbs, name); } else { gchar *keymap_file = g_strconcat(g_get_user_config_dir(), "/diya/xkb/", name, ".keymap", NULL); g_debug("diya_shell_get_virtual_keyboard: Looking for keymap file: %s", keymap_file); if (g_file_test(keymap_file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)) { vkb = diya_virtual_keyboard_new(shell, keymap_file); if (vkb) { g_debug("diya_shell_get_virtual_keyboard: add new keyboard %s to cache", name); g_hash_table_insert(priv->vkbs, (gpointer)g_strdup(name), vkb); } } free(keymap_file); } } if (!vkb) { g_debug("Fallback to default virtual key board"); if (!g_hash_table_contains(priv->vkbs, "default")) { g_debug("Add new keyboard instance to cache"); g_hash_table_insert(priv->vkbs, (gpointer)g_strdup("default"), diya_virtual_keyboard_new(shell, NULL)); } vkb = g_hash_table_lookup(priv->vkbs, "default"); } return vkb; } void diya_shell_monitor_input(DiyaShell *self) { DiyaShellPrivate *priv = diya_shell_get_instance_private(self); if (priv->input) { return; } priv->input = diya_input_new(self); } gboolean diya_shell_watch_file(DiyaShell *self, const gchar *file, GCallback callback, gpointer userdata) { DiyaShellPrivate *priv = diya_shell_get_instance_private(self); if (!priv->files_watchdog) { priv->files_watchdog = diya_files_monitor_new(); } return diya_files_monitor_watch(priv->files_watchdog, file, callback, userdata); } void diya_shell_unwatch_file(DiyaShell *self, const gchar *file) { DiyaShellPrivate *priv = diya_shell_get_instance_private(self); if (priv->files_watchdog) { diya_files_monitor_unwatch(priv->files_watchdog, file); } }