459 lines
14 KiB
C
459 lines
14 KiB
C
#include <glib-unix.h>
|
|
#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);
|
|
}
|
|
} |