From 224252807ace4a16d5a5dd5ed3f73082a384d3d9 Mon Sep 17 00:00:00 2001 From: DanyLE Date: Sat, 14 Jun 2025 23:08:11 +0200 Subject: [PATCH] feat: add files monitor FilesMonitor allows shell to monitor changes on a set of files. This allows the shell to react in realtime any changes related to its configurations such as: themes, keyboard, setting, etc. --- meson.build | 1 + src/files-monior.h | 14 ++++++++ src/files-monitor.c | 83 ++++++++++++++++++++++++++++++++++++++++++ src/input.h | 2 +- src/shell.c | 88 ++++++++++++++++++++++++++++++++++++++++++--- src/shell.h | 2 ++ 6 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 src/files-monior.h create mode 100644 src/files-monitor.c diff --git a/meson.build b/meson.build index 8745dad..dd1016b 100644 --- a/meson.build +++ b/meson.build @@ -63,6 +63,7 @@ base = [ 'src/input.c', 'src/virtual-keyboard.c', 'src/widgets/virtual-keyboard-widgets.c', + 'src/files-monitor.c', wayland_targets ] diff --git a/src/files-monior.h b/src/files-monior.h new file mode 100644 index 0000000..f797640 --- /dev/null +++ b/src/files-monior.h @@ -0,0 +1,14 @@ +#ifndef DIYA_FILES_MONITOR_H +#define DIYA_FILES_MONITOR_H + +#include "base.h" + + +#define DIYA_TYPE_FILES_MONITOR (diya_files_monitor_get_type ()) +G_DECLARE_FINAL_TYPE (DiyaFilesMonitor, diya_files_monitor, DIYA, FILES_MONITOR, DiyaObject) + +DiyaFilesMonitor* diya_files_monitor_new(); +gboolean diya_files_monitor_watch(DiyaFilesMonitor* monitor, const gchar* file, GCallback callback, gpointer userdata); +void diya_files_monitor_unwatch(DiyaFilesMonitor* monitor, const gchar* file); + +#endif \ No newline at end of file diff --git a/src/files-monitor.c b/src/files-monitor.c new file mode 100644 index 0000000..c15ab3a --- /dev/null +++ b/src/files-monitor.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include "files-monior.h" + + +struct _DiyaFilesMonitor +{ + DiyaObject parent; + GHashTable *watch_table; +}; + + +G_DEFINE_FINAL_TYPE(DiyaFilesMonitor, diya_files_monitor, DIYA_TYPE_OBJECT) + +static void diya_files_monitor_dispose(GObject *object) +{ + DiyaFilesMonitor *self = DIYA_FILES_MONITOR(object); + g_debug("diya_files_monitor_dispose: %s", diya_object_to_string(object)); + g_hash_table_destroy(self->watch_table); + G_OBJECT_CLASS(diya_files_monitor_parent_class)->dispose(object); +} + +static void diya_files_monitor_init(DiyaFilesMonitor *self) +{ + self->watch_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref); +} + +static const gchar *diya_files_monitor_to_string(DiyaObject *object) +{ + (void) object; + return "Diya Files Monitor"; +} + +static void diya_files_monitor_class_init(DiyaFilesMonitorClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + DiyaObjectClass *base_class = DIYA_OBJECT_CLASS(class); + + gobject_class->dispose = diya_files_monitor_dispose; + base_class->to_string = diya_files_monitor_to_string; +} + +DiyaFilesMonitor* diya_files_monitor_new() +{ + DiyaFilesMonitor* self = DIYA_FILES_MONITOR(g_object_new(DIYA_TYPE_FILES_MONITOR, NULL)); + return self; +} + +gboolean diya_files_monitor_watch(DiyaFilesMonitor* self, const gchar* path, GCallback callback, gpointer userdata) +{ + GFileMonitor* fm = NULL; + if(!g_hash_table_contains(self->watch_table, path)) + { + GError *err = NULL; + GFile* file = g_file_new_for_path(path); + fm = g_file_monitor(file, G_FILE_MONITOR_WATCH_MOVES, NULL, &err); + g_object_unref(file); + if (err) + { + g_error("Unable to watch file %s: %s", path, err->message); + g_error_free(err); + return false; + } + g_info("DiyaFilesMonitor: watch new file: %s", path); + g_hash_table_insert(self->watch_table, (gpointer)g_strdup(path), fm); + } + else + { + g_info("File %s is already watched, register callback handle to the existing watcher", path); + fm = g_hash_table_lookup(self->watch_table, path); + } + g_signal_connect(G_OBJECT(fm), "changed", callback, userdata); + return true; +} + +void diya_files_monitor_unwatch(DiyaFilesMonitor* self, const gchar* path) +{ + if(!g_hash_table_contains(self->watch_table, path)) + { + g_hash_table_remove(self->watch_table, (gconstpointer)path); + } +} \ No newline at end of file diff --git a/src/input.h b/src/input.h index 8cb8949..ee0d7d1 100644 --- a/src/input.h +++ b/src/input.h @@ -1,6 +1,6 @@ #ifndef DIYA_INPUT_H #define DIYA_INPUT_H -#include "base.h" + #include "shell.h" #define DIYA_TYPE_INPUT (diya_input_get_type ()) diff --git a/src/shell.c b/src/shell.c index 7633dfb..152a784 100644 --- a/src/shell.c +++ b/src/shell.c @@ -3,6 +3,10 @@ #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->name, ".css", NULL)) + enum { NO_PROP, @@ -23,6 +27,7 @@ typedef struct _DiyaShellPrivate GHashTable *vkbs; GtkCssProvider *css_provider; DiyaInput *input; + DiyaFilesMonitor *files_watchdog; } DiyaShellPrivate; G_DEFINE_TYPE_WITH_PRIVATE(DiyaShell, diya_shell, DIYA_TYPE_OBJECT); @@ -50,6 +55,11 @@ static void diya_shell_dispose(GObject *object) 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) { @@ -63,10 +73,10 @@ static void diya_shell_reload(DiyaShell *shell) GBytes *bytes = NULL; gchar *css_string = NULL; DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); - gchar *css_file = g_strconcat(g_get_user_config_dir(), "/diya/themes/", priv->name, ".css", NULL); + 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); - free(css_file); + g_free(css_file); if (err != NULL) { g_warning("diya_shell_reload: Unable to load CSS from file: %s", err->message); @@ -114,6 +124,38 @@ static void diya_shell_reload(DiyaShell *shell) } } +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; + 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(shell); + break; + default: + g_debug("%s event %x", fpath, evtype); + break; + } + if (opath) + { + g_free(opath); + } + g_free(fpath); +} + static void on_gtk_app_startup(GtkApplication *app, void *data) { (void)app; @@ -121,6 +163,9 @@ static void on_gtk_app_startup(GtkApplication *app, void *data) DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); priv->wayland = diya_wayland_new(DIYA_SHELL(shell)); DiyaShellClass *class = DIYA_SHELL_GET_CLASS(shell); + 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) { @@ -225,9 +270,10 @@ static void diya_shell_init(DiyaShell *self) priv->wayland = NULL; priv->app = NULL; priv->name = NULL; - priv->vkbs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); + 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; } DiyaWayland *diya_shell_get_wayland(DiyaShell *shell) @@ -284,6 +330,19 @@ 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; @@ -310,7 +369,7 @@ DiyaVirtualKeyboard *diya_shell_get_virtual_keyboard(DiyaShell *shell, const gch if (vkb) { g_debug("diya_shell_get_virtual_keyboard: add new keyboard %s to cache", name); - g_hash_table_insert(priv->vkbs, (gpointer)name, vkb); + g_hash_table_insert(priv->vkbs, (gpointer)g_strdup(name), vkb); } } free(keymap_file); @@ -323,7 +382,7 @@ DiyaVirtualKeyboard *diya_shell_get_virtual_keyboard(DiyaShell *shell, const gch if (!g_hash_table_contains(priv->vkbs, "default")) { g_debug("Add new keyboard instance to cache"); - g_hash_table_insert(priv->vkbs, "default", diya_virtual_keyboard_new(shell, NULL)); + g_hash_table_insert(priv->vkbs, (gpointer)g_strdup("default"), diya_virtual_keyboard_new(shell, NULL)); } vkb = g_hash_table_lookup(priv->vkbs, "default"); } @@ -338,4 +397,23 @@ void diya_shell_monitor_input(DiyaShell *self) 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); + } } \ No newline at end of file diff --git a/src/shell.h b/src/shell.h index 72f8f43..a9d4b45 100644 --- a/src/shell.h +++ b/src/shell.h @@ -34,6 +34,8 @@ GtkApplication* diya_shell_get_application(DiyaShell* shell); const char* diya_shell_get_name(DiyaShell* shell); DiyaVirtualKeyboard* diya_shell_get_virtual_keyboard(DiyaShell* shell, const gchar* name); void diya_shell_monitor_input(DiyaShell* shell); +gboolean diya_shell_watch_file(DiyaShell* shell, const gchar* file, GCallback callback, gpointer userdata); +void diya_shell_unwatch_file(DiyaShell* shell, const gchar* file); int diya_shell_run(DiyaShell* shell, int argc, char **argv); #endif \ No newline at end of file