From 377eaf7d8480213c30bd982d7370dc3e51dc4c77 Mon Sep 17 00:00:00 2001 From: Dany LE Date: Mon, 13 Oct 2025 18:08:50 +0200 Subject: [PATCH] feat: Add hook mechanism + use gsettings: - Allow to install hooks that execute lua scripts on changes of watched files - Use gsetting to store shell parameters such as: theme, idle-timeout, etc. --- hooks/check_compiled_schemas.lua | 9 + hooks/compile_schemas.lua | 21 + hooks/hooks.lua | 21 + meson.build | 24 +- resources/css/session-shell.css | 3 +- resources/gresource-login.xml | 2 +- resources/gresource-session.xml | 5 +- .../dev.iohub.diya.session.gschema.xml | 15 + resources/ui/taskbar.ui | 8 + src/files-monior.h | 2 +- src/files-monitor.c | 36 +- src/hook.c | 419 ++++++++++++++++++ src/hook.h | 18 + src/idle.c | 1 + src/login-shell.c | 6 + src/login.c | 2 +- src/session-shell.c | 67 +++ src/session-shell.h | 2 + src/session.c | 2 +- src/shell.c | 103 +++-- src/shell.h | 3 + src/widgets/taskbar-widget.c | 15 + 22 files changed, 721 insertions(+), 63 deletions(-) create mode 100644 hooks/check_compiled_schemas.lua create mode 100644 hooks/compile_schemas.lua create mode 100644 hooks/hooks.lua create mode 100644 resources/schemas/dev.iohub.diya.session.gschema.xml create mode 100644 src/hook.c create mode 100644 src/hook.h diff --git a/hooks/check_compiled_schemas.lua b/hooks/check_compiled_schemas.lua new file mode 100644 index 0000000..627561c --- /dev/null +++ b/hooks/check_compiled_schemas.lua @@ -0,0 +1,9 @@ +local compiled_schemas = diya.system.user_config_dir() .. "/diya/schemas/gschemas.compiled" + +if not diya.system.file_exists(compiled_schemas) then + diya.system.log_info("Regenerating "..compiled_schemas) + local script_path = debug.getinfo(1, "S").source:sub(2) + local script_dir = script_path:match("(.*/)") + local script_compile_schemas = script_dir .. "/compile_schemas.lua" + dofile(script_compile_schemas) +end \ No newline at end of file diff --git a/hooks/compile_schemas.lua b/hooks/compile_schemas.lua new file mode 100644 index 0000000..d31d739 --- /dev/null +++ b/hooks/compile_schemas.lua @@ -0,0 +1,21 @@ +local schemas_dir = diya.system.user_data_dir() .. "/glib-2.0/schemas" + +local target_dir = diya.system.user_config_dir() .. "/diya/schemas/" + +local session_schemas = schemas_dir.."/dev.iohub.diya.session.gschema.xml" +if not diya.system.file_exists(session_schemas) then + -- copy session schemas if not exists + diya.system.save_resource("/dev/iohub/diya/shell/schemas/session.gschema.xml", session_schemas) + return +end + +-- create the compiled schemas dir if not exists +diya.system.mkdir(target_dir) + +local cmd = "/usr/bin/glib-compile-schemas --targetdir="..target_dir.." "..schemas_dir -- .." &" +diya.system.log_info("Executing: "..cmd) +local handle = io.popen(cmd) +local result = handle:read("*a") -- Reads all output +handle:close() + +diya.system.log_info(result) \ No newline at end of file diff --git a/hooks/hooks.lua b/hooks/hooks.lua new file mode 100644 index 0000000..e407d60 --- /dev/null +++ b/hooks/hooks.lua @@ -0,0 +1,21 @@ + +local script_path = debug.getinfo(1, "S").source:sub(2) +local script_dir = script_path:match("(.*/)") +local schemas_dir = diya.system.user_data_dir() .. "/glib-2.0/schemas" + +-- create the schemas dir if not exists +diya.system.mkdir(schemas_dir) + +local script_compile_schemas = script_dir .. "/compile_schemas.lua" +-- install hook on schemas dir +diya.hook.changed(schemas_dir, script_compile_schemas, false) + +local session_schemas = schemas_dir.."/dev.iohub.diya.session.gschema.xml" +if not diya.system.file_exists(session_schemas) then + -- copy session schemas if not exists + diya.system.save_resource("/dev/iohub/diya/shell/schemas/session.gschema.xml", session_schemas) +end + +local compiled_schemas = diya.system.user_config_dir() .. "/diya/schemas/gschemas.compiled" +local script_check_schema = script_dir .. "/check_compiled_schemas.lua" +diya.hook.changed(compiled_schemas, script_check_schema, true) \ No newline at end of file diff --git a/meson.build b/meson.build index efdb87b..2125b55 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ wayland_protocols = dependency('wayland-protocols', version: '>=1.16') wl_protocol_dir = wayland_protocols.get_variable('pkgdatadir') +lua = dependency('lua') # pkg_config = import('pkgconfig') # gnome = import('gnome') @@ -68,6 +69,7 @@ base = [ 'src/widgets/virtual-keyboard-widgets.c', 'src/files-monitor.c', 'src/idle.c', + 'src/hook.c', wayland_targets ] @@ -84,15 +86,29 @@ dm_src = [ 'src/widgets/dashboard-widget.c', ] -buil_dep = [gtk, gtk_layer_shell, wayland_client, xkbcommon] +build_dep = [gtk, gtk_layer_shell, wayland_client, xkbcommon, lua] session_resource = gnome.compile_resources('session-resources','resources/gresource-session.xml') - +session_schemas_files = [ + 'resources/schemas/dev.iohub.diya.session.gschema.xml', +] +session_schema = custom_target('gschemas.compiled', + output: 'gschemas.compiled', + command: [ + find_program('glib-compile-schemas'), + '--targetdir='+meson.current_build_dir(), + meson.current_source_dir() / 'resources/schemas' + ], + depend_files: session_schemas_files, + build_by_default: true, + install: false, +) executable( 'diya-shell', dm_src, session_resource, - dependencies: buil_dep, + session_schema, + dependencies: build_dep, include_directories : incdir) login_src = [ @@ -107,5 +123,5 @@ executable( 'diya-login-shell', login_src, login_resource, - dependencies: buil_dep, + dependencies: build_dep, include_directories : incdir) \ No newline at end of file diff --git a/resources/css/session-shell.css b/resources/css/session-shell.css index 82e8a71..ff6b016 100644 --- a/resources/css/session-shell.css +++ b/resources/css/session-shell.css @@ -12,7 +12,8 @@ diya-dashboard #diya_shell_background { - background-image:url("file:///etc/xdg/labwc/wpp.jpg"); + /*background-image:url("file:///etc/xdg/labwc/wpp.jpg");*/ + background-color: violet; background-size: cover; } diff --git a/resources/gresource-login.xml b/resources/gresource-login.xml index 478bfb0..d01439a 100644 --- a/resources/gresource-login.xml +++ b/resources/gresource-login.xml @@ -1,7 +1,7 @@ - resources/css/login-shell.css + resources/css/login-shell.css resources/css/virtual-keyboard.css diff --git a/resources/gresource-session.xml b/resources/gresource-session.xml index 9b50c8a..5e03a82 100644 --- a/resources/gresource-session.xml +++ b/resources/gresource-session.xml @@ -8,9 +8,12 @@ resources/ui/taskbar.ui - resources/css/session-shell.css + resources/css/session-shell.css resources/css/virtual-keyboard.css + + resources/schemas/dev.iohub.diya.session.gschema.xml + resources/icons/scalable/gear.svg diff --git a/resources/schemas/dev.iohub.diya.session.gschema.xml b/resources/schemas/dev.iohub.diya.session.gschema.xml new file mode 100644 index 0000000..5300091 --- /dev/null +++ b/resources/schemas/dev.iohub.diya.session.gschema.xml @@ -0,0 +1,15 @@ + + + + + 60000 + Idle timeout + The time in milliseconds before the session is considered idle. + + + 'diya' + System theme + The system theme used for the session. + + + diff --git a/resources/ui/taskbar.ui b/resources/ui/taskbar.ui index 0818a63..b365602 100644 --- a/resources/ui/taskbar.ui +++ b/resources/ui/taskbar.ui @@ -28,6 +28,14 @@ + + + 1 + Test + + + + diff --git a/src/files-monior.h b/src/files-monior.h index f797640..e6c6eb4 100644 --- a/src/files-monior.h +++ b/src/files-monior.h @@ -10,5 +10,5 @@ G_DECLARE_FINAL_TYPE (DiyaFilesMonitor, diya_files_monitor, DIYA, FILES_MONITOR, 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); - +gboolean diya_files_monitor_is_watched(DiyaFilesMonitor* monitor, const gchar* file); #endif \ No newline at end of file diff --git a/src/files-monitor.c b/src/files-monitor.c index 854dca8..42ce2a8 100644 --- a/src/files-monitor.c +++ b/src/files-monitor.c @@ -55,37 +55,51 @@ DiyaFilesMonitor* diya_files_monitor_new() return self; } -gboolean diya_files_monitor_watch(DiyaFilesMonitor* self, const gchar* path, GCallback callback, gpointer userdata) +gboolean diya_files_monitor_watch(DiyaFilesMonitor* self, const gchar* any_path, GCallback callback, gpointer userdata) { GFileMonitor* fm = NULL; - if(!g_hash_table_contains(self->watch_table, path)) + char* abs_path = g_canonicalize_filename(any_path, NULL); + if(!g_hash_table_contains(self->watch_table, abs_path)) { GError *err = NULL; - GFile* file = g_file_new_for_path(path); + GFile* file = g_file_new_for_path(abs_path); fm = g_file_monitor(file, G_FILE_MONITOR_WATCH_MOVES, NULL, &err); g_object_unref(file); if (err) { - g_warning("Unable to watch file %s: %s", path, err->message); + g_warning("Unable to watch file %s: %s", abs_path, err->message); g_error_free(err); + g_free(abs_path); return false; } - g_info("DiyaFilesMonitor: watch new file: %s", path); - g_hash_table_insert(self->watch_table, (gpointer)g_strdup(path), fm); + g_info("DiyaFilesMonitor: watch new file: %s", abs_path); + g_hash_table_insert(self->watch_table, (gpointer)g_strdup(abs_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_info("File %s is already watched, register callback handle to the existing watcher", abs_path); + fm = g_hash_table_lookup(self->watch_table, abs_path); } g_signal_connect(G_OBJECT(fm), "changed", callback, userdata); + g_free(abs_path); return true; } -void diya_files_monitor_unwatch(DiyaFilesMonitor* self, const gchar* path) +void diya_files_monitor_unwatch(DiyaFilesMonitor* self, const gchar* any_path) { - if(!g_hash_table_contains(self->watch_table, path)) + gchar* abs_path = g_canonicalize_filename(any_path, NULL); + g_debug("Unwatching: %s", abs_path); + if(g_hash_table_contains(self->watch_table, abs_path)) { - g_hash_table_remove(self->watch_table, (gconstpointer)path); + g_hash_table_remove(self->watch_table, (gconstpointer)abs_path); } + g_free(abs_path); +} + +gboolean diya_files_monitor_is_watched(DiyaFilesMonitor* self, const gchar* file) +{ + gchar* abs_path = g_canonicalize_filename(file, NULL); + gboolean watched = g_hash_table_contains(self->watch_table, abs_path); + g_free(abs_path); + return watched; } \ No newline at end of file diff --git a/src/hook.c b/src/hook.c new file mode 100644 index 0000000..70556a4 --- /dev/null +++ b/src/hook.c @@ -0,0 +1,419 @@ +#include +#include +#include "hook.h" +#include "files-monior.h" +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +struct _DiyaHookHandle +{ + DiyaObject parent; + DiyaHook* hook; + gchar* script; +}; + +G_DEFINE_FINAL_TYPE(DiyaHookHandle, diya_hook_handle, DIYA_TYPE_OBJECT) + +static void diya_hook_handle_finalize(GObject* object) +{ + DiyaHookHandle* self = DIYA_HOOK_HANDLE(object); + g_debug("diya_hook_handle_finalize: %s", diya_object_to_string(object)); + if(self->script) + { + g_free(self->script); + } +} + +static void diya_hook_handle_dispose(GObject* object) +{ + (void) object; + g_debug("diya_hook_disponse: %s", diya_object_to_string(object)); +} + +static void diya_hook_handle_init(DiyaHookHandle* self) +{ + self->hook = NULL; + self->script = NULL; +} + +struct _DiyaHook +{ + DiyaObject parent; + DiyaFilesMonitor *files_watchdog; + GHashTable* lua_hooks; +}; + +static const gchar *diya_hook_handle_to_string(DiyaObject *object) +{ + DiyaHookHandle* self = DIYA_HOOK_HANDLE(object); + return self->script; +} + +static void diya_hook_handle_class_init(DiyaHookHandleClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + DiyaObjectClass *base_class = DIYA_OBJECT_CLASS(class); + + gobject_class->finalize = diya_hook_handle_finalize; + gobject_class->dispose = diya_hook_handle_dispose; + base_class->to_string = diya_hook_handle_to_string; +} + +static DiyaHookHandle* diya_hook_handle_new(DiyaHook* hook, const gchar* script) +{ + DiyaHookHandle* self = DIYA_HOOK_HANDLE(g_object_new(DIYA_TYPE_HOOK_HANDLE, NULL)); + self->hook = hook; + self->script = g_strdup(script); + return self; +} + + +G_DEFINE_FINAL_TYPE(DiyaHook, diya_hook, DIYA_TYPE_OBJECT) + +static void execute_hook_script(DiyaHook* self, const gchar* script, gboolean allow_install_hook); + +static void diya_hook_finalize(GObject *object) +{ + DiyaHook *self = DIYA_HOOK(object); + g_debug("diya_hook_finalize: %s", diya_object_to_string(object)); + if(self->lua_hooks) + { + g_hash_table_destroy(self->lua_hooks); + } + G_OBJECT_CLASS(diya_hook_parent_class)->finalize(object); +} + +static void diya_hook_dispose(GObject *object) +{ + DiyaHook *self = DIYA_HOOK(object); + g_debug("diya_hook_dispose: %s", diya_object_to_string(object)); + if(self->files_watchdog) + { + g_object_unref(self->files_watchdog); + self->files_watchdog = NULL; + } + G_OBJECT_CLASS(diya_hook_parent_class)->dispose(object); +} + +static void diya_hook_init(DiyaHook *self) +{ + self->files_watchdog = diya_files_monitor_new(); + self->lua_hooks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); +} + +static const gchar *diya_hook_to_string(DiyaObject *object) +{ + (void) object; + return "Diya Hook"; +} + +static void diya_hook_class_init(DiyaHookClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + DiyaObjectClass *base_class = DIYA_OBJECT_CLASS(class); + + gobject_class->finalize = diya_hook_finalize; + gobject_class->dispose = diya_hook_dispose; + base_class->to_string = diya_hook_to_string; +} + +DiyaHook* diya_hook_new() +{ + DiyaHook* self = DIYA_HOOK(g_object_new(DIYA_TYPE_HOOK, NULL)); + return self; +} + +static void reset_lua_hook(DiyaHook* self, const gchar* hook, gboolean init) +{ + gchar* abs_path = g_canonicalize_filename(hook, NULL); + if(g_hash_table_contains(self->lua_hooks, abs_path)) + { + GHashTableIter iter; + gchar* target; + DiyaHookHandle* handle; + g_hash_table_iter_init (&iter, g_hash_table_lookup(self->lua_hooks, abs_path)); + while (g_hash_table_iter_next(&iter, (gpointer) &target, (gpointer) &handle)) + { + g_info("Remove lua hook %s for: %s", handle->script, target); + diya_files_monitor_unwatch(self->files_watchdog, target); + } + g_hash_table_remove(self->lua_hooks, abs_path); + } + if(init) + { + g_hash_table_insert(self->lua_hooks,abs_path, g_hash_table_new_full(g_str_hash,g_str_equal,g_free,g_object_unref)); + } +} + +void on_diya_hook_on_file_changed(GFileMonitor *monitor, GFile *file, GFile *other, GFileMonitorEvent evtype, gpointer user_data) +{ + (void)monitor; + (void)other; + DiyaHookHandle* handle = (DiyaHookHandle*) user_data; + char *path = g_file_get_path(file); + g_debug("on_diya_hook_on_file_changed: %s event %x", path, evtype); + switch (evtype) + { + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_MOVED_IN: + // case G_FILE_MONITOR_EVENT_CHANGED: + //case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_DELETED: + case G_FILE_MONITOR_EVENT_RENAMED: + case G_FILE_MONITOR_EVENT_MOVED_OUT: + execute_hook_script(handle->hook,handle->script, false); + default: + break; + } + g_free(path); +} + +static int l_on_file_changed(lua_State *L) +{ + gchar *target = g_canonicalize_filename((const char*)luaL_checkstring(L, 1), NULL); + const char *script = (const char*)luaL_checkstring(L, 2); + gboolean exec_on_start = (gboolean) lua_toboolean(L, 3); + // get global table on top of stack + lua_getglobal(L, "diya"); + + lua_getfield(L, -1, "handle"); + DiyaHook* self = (DiyaHook*)lua_touserdata(L, -1); + // remove the hook object + lua_pop(L, 1); + + lua_getfield(L,-1,"trigger"); + const char* trigger = (const char*) luaL_checkstring(L,-1); + lua_pop(L, 1); + + // remove global table on top of stack + lua_pop(L, 1); + + g_debug("Install file watchdog for %s: script %s from trigger %s", target, script, trigger); + if(!g_hash_table_contains(self->lua_hooks, trigger)) + { + g_critical("Unknow trigger: %s for target %s (%s)", trigger, target, script); + lua_pushboolean(L, 0); + lua_pushstring(L, "Trigger file unknown"); + g_free(target); + return 2; + } + + GHashTable* lookup = g_hash_table_lookup(self->lua_hooks, trigger); + if(g_hash_table_contains(lookup, target)) + { + g_hash_table_remove(lookup, target); + diya_files_monitor_unwatch(self->files_watchdog, target); + } + DiyaHookHandle* handle = diya_hook_handle_new(self, script); + + if(!diya_files_monitor_watch(self->files_watchdog, target, G_CALLBACK(on_diya_hook_on_file_changed), handle)) + { + g_critical("Unable to setup watchdog for target %s (%s)", target, script); + lua_pushboolean(L, 0); + lua_pushstring(L, "Unable to setup watchdog"); + g_free(target); + g_object_unref(handle); + return 2; + } + g_hash_table_insert(lookup, target, handle); + if(exec_on_start) + { + execute_hook_script(self, script, false); + } + lua_pushboolean(L, 1); + return 1; +} + +static const struct luaL_Reg hook_module [] = { + {"changed", l_on_file_changed}, + {NULL,NULL} +}; + +static int l_log_info(lua_State *L) +{ + const char* msg = (const char*)luaL_checkstring(L, 1); + g_info("%s", msg); + return 0; +} + +static int l_log_warning(lua_State *L) +{ + const char* msg = (const char*)luaL_checkstring(L, 1); + g_warning("%s", msg); + return 0; +} + +static int l_log_error(lua_State *L) +{ + const char* msg = (const char*)luaL_checkstring(L, 1); + g_critical("%s", msg); + return 0; +} + +static int l_user_home(lua_State *L) +{ + lua_pushstring(L, g_get_home_dir()); + return 1; +} + +static int l_user_config_dir(lua_State *L) +{ + lua_pushstring(L, g_get_user_config_dir()); + return 1; +} + +static int l_user_data_dir(lua_State *L) +{ + lua_pushstring(L, g_get_user_data_dir()); + return 1; +} + +static int l_save_resource(lua_State *L) +{ + const char* resource = (const char*) luaL_checkstring(L,1); + const char* path = (const char*) luaL_checkstring(L,2); + + GError *error = NULL; + g_info("Saving resource %s to %s", resource, path); + GBytes *bytes = g_resources_lookup_data(resource, 0, &error); + if (error != NULL) + { + g_critical("Unable to read resource %s", error->message); + g_error_free(error); + lua_pushboolean(L, 0); + return 1; + } + g_file_set_contents(path, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), &error); + g_bytes_unref(bytes); + if (error != NULL) + { + g_critical("Unable to read write resource %s to file %s: %s", resource, path, error->message); + g_error_free(error); + lua_pushboolean(L, 0); + return 1; + } + + lua_pushboolean(L, 1); + return 1; +} + +static int l_file_exists(lua_State *L) +{ + const char* path = (const char*) luaL_checkstring(L,1); + lua_pushboolean(L, g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)); + return 1; +} + +static int l_mkdir(lua_State *L) +{ + const char* path = (const char*) luaL_checkstring(L,1); + lua_pushboolean(L, g_mkdir_with_parents((const gchar *)path, 0755) == 0); + return 1; +} + +static const struct luaL_Reg system_module [] = { + {"log_info", l_log_info}, + {"log_warning", l_log_warning}, + {"log_error", l_log_error}, + {"user_home", l_user_home}, + {"user_config_dir", l_user_config_dir}, + {"user_data_dir", l_user_data_dir}, + {"save_resource", l_save_resource}, + {"file_exists", l_file_exists}, + {"mkdir", l_mkdir}, + {NULL, NULL} +}; + +static void execute_hook_script(DiyaHook* self, const gchar* script, gboolean allow_install_hook) +{ + lua_State* L = NULL; + L = luaL_newstate(); + luaL_openlibs(L); + if(L) + { + g_debug("Executing %s", script); + // install custom lib to lua state + lua_newtable(L); + lua_pushstring(L,"system"); + luaL_newlib(L, system_module); + lua_settable(L, -3); + + if(allow_install_hook) + { + lua_pushstring(L,"hook"); + luaL_newlib(L, hook_module); + lua_settable(L, -3); + + lua_pushstring(L, "trigger"); + lua_pushstring(L, script); + lua_settable(L, -3); + } + + lua_pushstring(L,"handle"); + lua_pushlightuserdata(L, self); + lua_settable(L, -3); + + lua_setglobal(L, "diya"); + + if (luaL_loadfile(L, script) || lua_pcall(L, 0, 0, 0)) + { + g_critical("cannot execute hook script: [%s] %s", script, lua_tostring(L, -1)); + } + lua_close(L); + } +} + +void on_diya_hook_lua_script_changed(GFileMonitor *monitor, GFile *file, GFile *other, GFileMonitorEvent evtype, gpointer user_data) +{ + (void)monitor; + (void)other; + DiyaHook *self = user_data; + char *path = g_file_get_path(file); + g_debug("on_diya_hook_lua_script_changed: %s event %x", path, evtype); + switch (evtype) + { + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_MOVED_IN: + // case G_FILE_MONITOR_EVENT_CHANGED: + // case G_FILE_MONITOR_EVENT_CREATED: + // unwatched all watched files installed by this script + reset_lua_hook(self, path, true); + // re-execute the script to install additional hook + execute_hook_script(self,path, true); + break; + case G_FILE_MONITOR_EVENT_DELETED: + case G_FILE_MONITOR_EVENT_RENAMED: + case G_FILE_MONITOR_EVENT_MOVED_OUT: + reset_lua_hook(self, path, false); + break; + default: + break; + } + g_free(path); +} + +gboolean diya_hook_install_lua(DiyaHook* self, const gchar* script) +{ + // unwatch the file + reset_lua_hook(self, script, true); + diya_files_monitor_unwatch(self->files_watchdog, script); + execute_hook_script(self, script, true); + return diya_files_monitor_watch(self->files_watchdog, script, G_CALLBACK(on_diya_hook_lua_script_changed), self); +} + +void diya_hook_uninstall(DiyaHook* self, const gchar* file) +{ + reset_lua_hook(self, file, false); + diya_files_monitor_unwatch(self->files_watchdog, file); +} + +gboolean diya_hook_install(DiyaHook* self, const gchar* file, GCallback callback, gpointer userdata, gboolean singleton) +{ + if(singleton) + { + // unwatch previously watch of the same file + diya_files_monitor_unwatch(self->files_watchdog, file); + } + return diya_files_monitor_watch(self->files_watchdog, file, callback, userdata); +} \ No newline at end of file diff --git a/src/hook.h b/src/hook.h new file mode 100644 index 0000000..387048c --- /dev/null +++ b/src/hook.h @@ -0,0 +1,18 @@ +#ifndef DIYA_HOOK_H +#define DIYA_HOOK_H + +#include "base.h" + + +#define DIYA_TYPE_HOOK (diya_hook_get_type ()) +G_DECLARE_FINAL_TYPE (DiyaHook, diya_hook, DIYA, HOOK, DiyaObject) + +#define DIYA_TYPE_HOOK_HANDLE (diya_hook_handle_get_type()) +G_DECLARE_FINAL_TYPE (DiyaHookHandle, diya_hook_handle, DIYA, HOOK_HANDLE, DiyaObject) + +DiyaHook* diya_hook_new(); +gboolean diya_hook_install(DiyaHook* self, const gchar* file, GCallback callback, gpointer userdata, gboolean singleton); +gboolean diya_hook_install_lua(DiyaHook* self, const gchar* script); +void diya_hook_uninstall(DiyaHook* self, const gchar* file); + +#endif \ No newline at end of file diff --git a/src/idle.c b/src/idle.c index 1806f4f..f6ac1da 100644 --- a/src/idle.c +++ b/src/idle.c @@ -123,6 +123,7 @@ DiyaIdleNotification* diya_shell_get_idle_notification(DiyaShell* shell, guint t DiyaIdleNotification* self = g_object_new(DIYA_TYPE_IDLE_NOTIFICATION, "shell", shell, NULL); struct wl_seat *seat = diya_wayland_get_seat(wayland); self->timeout = timeout_ms; + g_info("Set IDLE notification for timeout %d", timeout_ms); self->notification = ext_idle_notifier_v1_get_idle_notification(mngr, timeout_ms, seat); ext_idle_notification_v1_add_listener(self->notification, &g_idle_listener_impl, self); return self; diff --git a/src/login-shell.c b/src/login-shell.c index 26891f6..9c9a438 100644 --- a/src/login-shell.c +++ b/src/login-shell.c @@ -310,6 +310,11 @@ static void diya_login_on_shell_resume(DiyaShell* shell) diya_shell_power_display(shell, true); } +static void diya_login_shell_active(DiyaShell *shell) +{ + g_object_set(shell, DIYA_PROP_SHELL_IDLE_TIMEOUT, DEFAULT_IDLE_TIMEOUT, NULL); +} + static void diya_login_shell_startup(DiyaShell *shell) { DiyaLoginShell *self = DIYA_LOGIN_SHELL(shell); @@ -340,5 +345,6 @@ static void diya_login_shell_class_init(DiyaLoginShellClass *class) DiyaShellClass *base_shell_class = DIYA_SHELL_CLASS(class); base_shell_class->monitor_changed_handle = on_monitor_changed; base_shell_class->startup_handle = diya_login_shell_startup; + base_shell_class->active_handle = diya_login_shell_active; // base_shell_class->foreign_register = diya_session_shell_foreign_toplevel_register; } \ No newline at end of file diff --git a/src/login.c b/src/login.c index 9739e64..c96d8e0 100644 --- a/src/login.c +++ b/src/login.c @@ -6,6 +6,6 @@ int main(int argc, char *argv[]) { - DiyaLoginShell* shell = DIYA_LOGIN_SHELL(g_object_new(DIYA_TYPE_LOGIN_SHELL, "name", "dev.iohub.diya.login-shell", NULL)); + DiyaLoginShell* shell = DIYA_LOGIN_SHELL(g_object_new(DIYA_TYPE_LOGIN_SHELL, "name", "dev.iohub.diya.login", NULL)); return diya_shell_run(DIYA_SHELL(shell), argc, argv); } diff --git a/src/session-shell.c b/src/session-shell.c index 494fd63..fa04a12 100644 --- a/src/session-shell.c +++ b/src/session-shell.c @@ -10,6 +10,9 @@ #include "launcher.h" #include "session-lock.h" #include "idle.h" + +#define compiled_schema_path() (g_strconcat(g_getenv("GSETTINGS_SCHEMA_DIR"), "/gschemas.compiled", NULL)) + enum { NO_PROP, @@ -29,6 +32,7 @@ struct _DiyaSessionShell DiyaLauncher *launcher; GHashTable *windows; GtkSessionLockInstance *lock; + GSettings *settings; }; G_DEFINE_FINAL_TYPE(DiyaSessionShell, diya_session_shell, DIYA_TYPE_SHELL) @@ -57,6 +61,10 @@ static void diya_session_shell_dispose(GObject *object) { g_object_unref(self->launcher); } + if(self->settings) + { + g_object_unref(self->settings); + } G_OBJECT_CLASS(diya_session_shell_parent_class)->dispose(object); } @@ -150,9 +158,66 @@ static void diya_session_on_shell_resume(DiyaShell* shell) diya_shell_power_display(shell, true); } +void diya_session_init_settings(DiyaSessionShell* self) +{ + g_return_if_fail(DIYA_IS_SESSION_SHELL(self)); + if(self->settings) + { + g_debug("Setting is already initialized. Nothing todo"); + return; + } + + GSettingsSchemaSource* source = g_settings_schema_source_get_default(); + if (!source) + { + g_warning("No gsetting schema source found"); + return; + } + // Look up the schema ID + GSettingsSchema* schema = g_settings_schema_source_lookup(source, SESSION_SHELL_SCHEMA_ID, TRUE); + if (schema) + { + g_debug("Loading setting from compiled schema"); + self->settings = g_settings_new(SESSION_SHELL_SCHEMA_ID); + g_settings_bind(self->settings, DIYA_PROP_SHELL_IDLE_TIMEOUT, DIYA_SHELL(self), DIYA_PROP_SHELL_IDLE_TIMEOUT, G_SETTINGS_BIND_DEFAULT); + g_settings_bind(self->settings, DIYA_PROP_SHELL_THEME, DIYA_SHELL(self), DIYA_PROP_SHELL_THEME, G_SETTINGS_BIND_DEFAULT); + g_settings_schema_unref(schema); + } + else + { + g_warning("Unable to find schema ID %s", SESSION_SHELL_SCHEMA_ID); + } +} + static void diya_session_shell_startup(DiyaShell *shell) { DiyaSessionShell* self = DIYA_SESSION_SHELL(shell); + + const gchar* const* dirs = g_get_system_config_dirs(); + + gchar* path = NULL; + gboolean hook_found = false; + for (int i = 0; dirs[i] != NULL; i++) + { + path = g_strconcat(dirs[0], "/diya/hooks/hooks.lua", NULL); + g_debug("Looking for hook scripts: %s", path); + if (g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)) + { + //hook script path + g_debug("Install LUA hook at: %s", path); + diya_shell_watch_script(shell, path); + hook_found = true; + } + g_free(path); + } + if(!hook_found) + { + path = g_strconcat(g_get_user_config_dir(), "/diya/hooks/hooks.lua", NULL); + g_debug("No system hook found. Install local hook at: %s", path); + diya_shell_watch_script(shell, path); + g_free(path); + } + diya_session_shell_init_background(self); diya_session_shell_launcher_init(self); g_signal_connect(shell, DIYA_SIGNAL_SHELL_KEY_PRESSED, G_CALLBACK(diya_session_on_key_pressed), NULL); @@ -162,6 +227,7 @@ static void diya_session_shell_startup(DiyaShell *shell) static void diya_session_shell_active(DiyaShell *shell) { + diya_session_init_settings(DIYA_SESSION_SHELL(shell)); diya_shell_monitor_input(shell); } @@ -282,6 +348,7 @@ static void diya_session_shell_init(DiyaSessionShell *self) // self->app = NULL; self->background = NULL; self->launcher = NULL; + self->settings = NULL; self->lock = gtk_session_lock_instance_new(); g_signal_connect(self->lock, "locked", G_CALLBACK(on_session_locked), self); // g_signal_connect(lock, "failed", G_CALLBACK(failed), NULL); diff --git a/src/session-shell.h b/src/session-shell.h index fda9f84..2574c5d 100644 --- a/src/session-shell.h +++ b/src/session-shell.h @@ -17,6 +17,8 @@ #define DIYA_PROP_SESSION_LOCK "session-lock" #define DIYA_PROP_SESSION_WINDOWS "windows" +#define SESSION_SHELL_SCHEMA_ID "dev.iohub.diya.session" + #define DIYA_TYPE_SESSION_SHELL (diya_session_shell_get_type ()) G_DECLARE_FINAL_TYPE (DiyaSessionShell, diya_session_shell, DIYA, SESSION_SHELL, DiyaShell) diff --git a/src/session.c b/src/session.c index e74ce15..5d7e9e1 100644 --- a/src/session.c +++ b/src/session.c @@ -18,7 +18,7 @@ static void session_unlocked(DiyaSessionShell* shell, void* data) int main(int argc, char *argv[]) { - DiyaSessionShell *shell = DIYA_SESSION_SHELL(g_object_new(DIYA_TYPE_SESSION_SHELL, "name","dev.iohub.diya.session-shell", NULL)); + DiyaSessionShell *shell = DIYA_SESSION_SHELL(g_object_new(DIYA_TYPE_SESSION_SHELL, "name","dev.iohub.diya.session", NULL)); g_signal_connect(shell, DIYA_SIGNAL_SESSION_LOCKED, G_CALLBACK(session_locked), NULL); g_signal_connect(shell, DIYA_SIGNAL_SESSION_UNLOCKED, G_CALLBACK(session_unlocked), NULL); return diya_shell_run(DIYA_SHELL(shell), argc, argv); diff --git a/src/shell.c b/src/shell.c index 9064ebc..1548326 100644 --- a/src/shell.c +++ b/src/shell.c @@ -2,15 +2,13 @@ #include "shell.h" #include "wayland.h" #include "virtual-keyboard.h" -#include "files-monior.h" #include "input.h" #include "idle.h" #include "wlr-output-power-management-unstable-v1.h" +#include "hook.h" #include #include -#define DEFAULT_IDLE_TIMEOUT 60*1000 // 1m - #define diya_shell_config_theme(priv) (g_strconcat(g_get_home_dir(), "/.themes/", priv->theme ? priv->theme : "default", "/gtk-4.0/gtk.css", NULL)) #define diya_shell_config_theme_dir(priv) (g_strconcat(g_get_home_dir(), "/.themes/", priv->theme ? priv->theme : "default", "/gtk-4.0", NULL)) enum @@ -35,7 +33,7 @@ typedef struct _DiyaShellPrivate GHashTable *vkbs; GtkCssProvider *css_provider; DiyaInput *input; - DiyaFilesMonitor *files_watchdog; + DiyaHook *files_hook; gchar *theme; DiyaIdleNotification *idle_notif; } DiyaShellPrivate; @@ -78,10 +76,10 @@ static void diya_shell_dispose(GObject *object) { g_object_unref(priv->input); } - if (priv->files_watchdog) + if (priv->files_hook) { - g_object_unref(priv->files_watchdog); - priv->files_watchdog = NULL; + g_object_unref(priv->files_hook); + priv->files_hook = NULL; } if (priv->idle_notif) { @@ -137,14 +135,10 @@ void diya_shell_reload(DiyaShell *self) void on_diya_shell_theme_file_changed(GFileMonitor *monitor, GFile *file, GFile *other, GFileMonitorEvent evtype, gpointer user_data) { (void)monitor; + (void)other; 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: @@ -152,17 +146,13 @@ void on_diya_shell_theme_file_changed(GFileMonitor *monitor, GFile *file, GFile 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: + // 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); } @@ -186,18 +176,13 @@ static void on_gtk_app_startup(GtkApplication *app, void *data) 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) + // log all environment variables in debug + for (gchar **env = g_get_environ(); *env != NULL; env++) { - priv->theme = g_strdup(default_theme); + g_debug("ENV: %s", *env); } - gchar *dir = diya_shell_config_theme_dir(priv); - diya_shell_watch_file(shell, dir, G_CALLBACK(on_diya_shell_theme_file_changed), (gpointer)shell); - g_free(dir); - - diya_shell_reload(shell); + // diya_shell_reload(shell); if (class->startup_handle) { class->startup_handle(shell); @@ -208,6 +193,7 @@ static void on_gtk_app_startup(GtkApplication *app, void *data) 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) @@ -215,16 +201,22 @@ static void on_gtk_app_active(GtkApplication *app, void *data) (void)app; DiyaShell *shell = data; DiyaShellClass *class = DIYA_SHELL_GET_CLASS(shell); - // DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); - /** - * TODO: read timeout from setting - */ - g_object_set(shell, DIYA_PROP_SHELL_IDLE_TIMEOUT, DEFAULT_IDLE_TIMEOUT, NULL); - if (class->active_handle) { class->active_handle(shell); } + DiyaShellPrivate *priv = diya_shell_get_instance_private(shell); + if(!priv->theme) + { + // read envar + const char *default_theme = g_getenv(DIYA_ENV_THEME); + if (!default_theme) + { + default_theme = "default"; + } + + g_object_set(shell, DIYA_PROP_SHELL_THEME, default_theme, NULL); + } } static gboolean diya_shell_sigint_handle(gpointer data) @@ -277,6 +269,7 @@ static void diya_shell_set_property(GObject *object, guint property_id, const GV case PROP_SHELL_IDLE_TO: if(priv->idle_notif) { + g_signal_emit_by_name(self, DIYA_SIGNAL_SHELL_RESUME); g_object_unref(priv->idle_notif); } priv->idle_notif = diya_shell_get_idle_notification(self, g_value_get_uint(value)); @@ -286,13 +279,21 @@ static void diya_shell_set_property(GObject *object, guint property_id, const GV case PROP_SHELL_THEME: { const gchar *theme = g_value_get_string(value); + gchar* dir = NULL; if (!priv->theme || !g_str_equal(priv->theme, theme)) { if (priv->theme) { + dir = diya_shell_config_theme_dir(priv); + diya_shell_unwatch_file(self, dir); + g_free(dir); g_free(priv->theme); } priv->theme = g_strdup(theme); + dir = diya_shell_config_theme_dir(priv); + g_mkdir_with_parents((const gchar *)dir, 0755); + diya_shell_watch_file(self, dir, G_CALLBACK(on_diya_shell_theme_file_changed), (gpointer)self); + g_free(dir); diya_shell_reload_theme(self); } break; @@ -346,7 +347,7 @@ static void diya_shell_init(DiyaShell *self) 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->files_hook = NULL; priv->theme = NULL; priv->idle_notif = NULL; } @@ -516,7 +517,14 @@ 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; - + gchar *path = NULL; + if(g_getenv("GSETTINGS_SCHEMA_DIR") == NULL) + { + path = g_strconcat(g_get_user_config_dir(), "/diya/schemas/", NULL); + g_mkdir_with_parents((const gchar *)path, 0755); + g_setenv("GSETTINGS_SCHEMA_DIR", path, FALSE); + g_free(path); + } /* // theme dir gchar *path = g_strconcat(g_get_user_config_dir(), "/diya/themes/", NULL); @@ -525,7 +533,7 @@ int diya_shell_run(DiyaShell *shell, int argc, char **argv) */ // keymap dir - gchar *path = g_strconcat(g_get_user_config_dir(), "/diya/xkb/", NULL); + path = g_strconcat(g_get_user_config_dir(), "/diya/xkb/", NULL); g_mkdir_with_parents((const gchar *)path, 0755); g_free(path); const GOptionEntry cmd_params[] = @@ -541,7 +549,8 @@ int diya_shell_run(DiyaShell *shell, int argc, char **argv) .arg_description = NULL, }, #endif - {NULL}}; + {NULL} + }; g_application_add_main_option_entries(G_APPLICATION(app), cmd_params); g_signal_connect(app, "handle-local-options", G_CALLBACK(diya_shell_handle_local_options), shell); int status = g_application_run(G_APPLICATION(app), argc, argv); @@ -603,19 +612,19 @@ void diya_shell_monitor_input(DiyaShell *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) + if (!priv->files_hook) { - priv->files_watchdog = diya_files_monitor_new(); + priv->files_hook = diya_hook_new(); } - return diya_files_monitor_watch(priv->files_watchdog, file, callback, userdata); + return diya_hook_install(priv->files_hook, file, callback, userdata, true); } void diya_shell_unwatch_file(DiyaShell *self, const gchar *file) { DiyaShellPrivate *priv = diya_shell_get_instance_private(self); - if (priv->files_watchdog) + if (priv->files_hook) { - diya_files_monitor_unwatch(priv->files_watchdog, file); + diya_hook_uninstall(priv->files_hook, file); } } @@ -659,4 +668,14 @@ void diya_shell_power_display(DiyaShell* self, gboolean mode) zwlr_output_power_v1_set_mode(output_power, mode? ZWLR_OUTPUT_POWER_V1_MODE_ON: ZWLR_OUTPUT_POWER_V1_MODE_OFF); zwlr_output_power_v1_destroy(output_power); } +} + +gboolean diya_shell_watch_script(DiyaShell* self, const gchar* script) +{ + DiyaShellPrivate *priv = diya_shell_get_instance_private(self); + if (!priv->files_hook) + { + priv->files_hook = diya_hook_new(); + } + return diya_hook_install_lua(priv->files_hook, script); } \ No newline at end of file diff --git a/src/shell.h b/src/shell.h index 60e4d54..b0eb1c2 100644 --- a/src/shell.h +++ b/src/shell.h @@ -4,6 +4,8 @@ #include #include "base.h" +#define DEFAULT_IDLE_TIMEOUT 60*1000 // 1m + #define DIYA_ENV_THEME "DIYA_DEFAULT_THEME" #define DIYA_ENV_VKB_KEYMAP "DIYA_VKB_KEYMAP" @@ -49,6 +51,7 @@ 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); +gboolean diya_shell_watch_script(DiyaShell* shell, const gchar* script); void diya_shell_unwatch_file(DiyaShell* shell, const gchar* file); void diya_shell_power_display(DiyaShell* shell, gboolean mode); void diya_shell_launch(DiyaShell* shell, GAppInfo* app); diff --git a/src/widgets/taskbar-widget.c b/src/widgets/taskbar-widget.c index b510cb6..99747c1 100644 --- a/src/widgets/taskbar-widget.c +++ b/src/widgets/taskbar-widget.c @@ -116,6 +116,8 @@ struct _DiyaTaskbarWidget DiyaTaskbarListModel *model; GtkListItemFactory *factory; guint selection_changed_sig_id; + + GtkWidget *btn_test; }; enum @@ -278,6 +280,16 @@ static void diya_taskbar_list_model_selection_changed_cb(GtkSelectionModel *mode } } +static void diya_dashboard_btn_test_clicked(GtkWidget *widget, gpointer user_data) +{ + (void) widget; + g_return_if_fail(DIYA_IS_SHELL_WINDOW(user_data)); + DiyaShellWindow* self = user_data; + DiyaSessionShell* shell = DIYA_SESSION_SHELL(diya_shell_window_get_shell(self)); + g_return_if_fail(DIYA_IS_SESSION_SHELL(shell)); + // TODO remove +} + static void diya_taskbar_widget_init(DiyaTaskbarWidget *self) { g_debug("diya_taskbar_widget_init"); @@ -323,6 +335,8 @@ static void diya_taskbar_widget_init(DiyaTaskbarWidget *self) g_signal_connect(controller, "leave", G_CALLBACK(diya_taskbar_applist_leave), self); gtk_widget_add_controller(GTK_WIDGET(self->apps_list), GTK_EVENT_CONTROLLER(controller)); */ + + g_signal_connect(self->btn_test, "clicked", G_CALLBACK(diya_dashboard_btn_test_clicked), self); } static void diya_taskbar_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) @@ -392,6 +406,7 @@ static void diya_taskbar_widget_class_init(DiyaTaskbarWidgetClass *class) gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), DiyaTaskbarWidget, btn_toggle); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), DiyaTaskbarWidget, apps_list); + gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), DiyaTaskbarWidget, btn_test); // gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), diya_dashboard_toggle); gtk_widget_class_set_css_name(GTK_WIDGET_CLASS(class), "diya-taskbar");