From ac300c32cf9dc1d3dce1c63902f54e0a0e76b3f1 Mon Sep 17 00:00:00 2001 From: DanyLE Date: Sun, 14 Apr 2024 16:29:02 +0200 Subject: [PATCH] Initial commit: add code - using meson build - implement shell using gobject API - support wlr-foreign-toplevel protocol - shell base UI (WIP) --- .gitignore | 3 +- meson.build | 59 ++++ ...oreign-toplevel-management-unstable-v1.xml | 270 +++++++++++++++ src/background.c | 33 ++ src/background.h | 8 + src/base.c | 21 ++ src/base.h | 24 ++ src/foreign.c | 313 ++++++++++++++++++ src/foreign.h | 7 + src/launcher.c | 78 +++++ src/launcher.h | 9 + src/log.c | 0 src/log.h | 0 src/shell.c | 230 +++++++++++++ src/shell.h | 27 ++ src/wayland.c | 57 ++++ src/wayland.h | 8 + 17 files changed, 1146 insertions(+), 1 deletion(-) create mode 100644 meson.build create mode 100644 protocols/wlr-foreign-toplevel-management-unstable-v1.xml create mode 100644 src/background.c create mode 100644 src/background.h create mode 100644 src/base.c create mode 100644 src/base.h create mode 100644 src/foreign.c create mode 100644 src/foreign.h create mode 100644 src/launcher.c create mode 100644 src/launcher.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/shell.c create mode 100644 src/shell.h create mode 100644 src/wayland.c create mode 100644 src/wayland.h diff --git a/.gitignore b/.gitignore index acf5dd9..ab0c578 100644 --- a/.gitignore +++ b/.gitignore @@ -85,4 +85,5 @@ dkms.conf *.exe *.out *.app - +build +.vscode \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..60433b1 --- /dev/null +++ b/meson.build @@ -0,0 +1,59 @@ +project('diya-shell', + ['c'], + version: '0.1.0', + license: 'MIT', + meson_version: '>=0.51.0', + default_options: ['c_std=gnu11', 'warning_level=3']) + +lib_so_version = '0' + +add_project_arguments( + [ + '-Wno-pedantic', + '-Werror=implicit-function-declaration', + '-Werror=return-type', + ], + language: 'c') + +gtk = dependency('gtk4') +wayland_client = dependency('wayland-client', version: '>=1.10.0') + +# wayland_scanner is required, but we can find it without pkg-config +wayland_scanner = find_program('wayland-scanner') + +# use system xdg-shell protocol when available +#wayland_protocols = dependency('wayland-protocols', version: '>=1.16') + +# pkg_config = import('pkgconfig') +# gnome = import('gnome') + +gtk_layer_shell = dependency('gtk4-layer-shell-0', version: '>=1.0.2') +wayland_targets=[] + +wayland_protos = [ + 'protocols/wlr-foreign-toplevel-management-unstable-v1' +] + +foreach proto : wayland_protos + xml = ''.join([proto,'.xml']) + header = ''.join([proto.split('/').get(-1),'.h']) + cfile = ''.join([proto.split('/').get(-1),'.c']) + wayland_targets += custom_target(header,output:header,input:xml, + command: [ wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ] ) + wayland_targets += custom_target(cfile,output:cfile,input:xml, + command: [ wayland_scanner, 'public-code', '@INPUT@', '@OUTPUT@' ] ) +endforeach + +src = [ + 'src/base.c', + 'src/launcher.c', + 'src/background.c', + 'src/wayland.c', + 'src/shell.c', + 'src/foreign.c', + wayland_targets] + +executable( + 'diya-shell', + src, + dependencies: [gtk, gtk_layer_shell, wayland_client]) \ No newline at end of file diff --git a/protocols/wlr-foreign-toplevel-management-unstable-v1.xml b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 0000000..1081337 --- /dev/null +++ b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/src/background.c b/src/background.c new file mode 100644 index 0000000..ef87bb9 --- /dev/null +++ b/src/background.c @@ -0,0 +1,33 @@ +#include "background.h" +#include +#define NAMESPACE "background" + +static void on_background_destroy(GtkWindow *window, GApplication *_data) +{ + (void) window; + (void)_data; + //g_application_quit (G_APPLICATION (gtk_window_get_application (window))); +} + +void diya_shell_init_background(DiyaShell * shell) +{ + GtkWindow *gtk_window; + g_object_get(shell, "background", >k_window, NULL); + assert(gtk_window); + g_signal_connect (gtk_window, "destroy", G_CALLBACK (on_background_destroy), NULL); + // int layer shell for window + gtk_layer_init_for_window (gtk_window); + // anchor window to all edges + for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) + gtk_layer_set_anchor (gtk_window, i, true); + // set margin on window + for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) + gtk_layer_set_margin (gtk_window, i, 0); + gtk_layer_set_layer (gtk_window, GTK_LAYER_SHELL_LAYER_BACKGROUND); + gtk_layer_set_keyboard_mode (gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); + gtk_layer_set_namespace (gtk_window, NAMESPACE); + gtk_widget_set_name(GTK_WIDGET(gtk_window),NAMESPACE); + //gtk_layer_auto_exclusive_zone_enable (gtk_window); + //g_signal_connect (gtk_window, "orientation-changed", G_CALLBACK (on_orientation_changed), /*data*/NULL); + gtk_window_present (GTK_WINDOW (gtk_window)); +} \ No newline at end of file diff --git a/src/background.h b/src/background.h new file mode 100644 index 0000000..f3e5fe3 --- /dev/null +++ b/src/background.h @@ -0,0 +1,8 @@ +#ifndef DIYA_SHELL_BACKGROUND_H +#define DIYA_SHELL_BACKGROUND_H + +#include "shell.h" + +void diya_shell_init_background(DiyaShell * shell); + +#endif \ No newline at end of file diff --git a/src/base.c b/src/base.c new file mode 100644 index 0000000..546e690 --- /dev/null +++ b/src/base.c @@ -0,0 +1,21 @@ +#include "base.h" + +G_DEFINE_ABSTRACT_TYPE(DiyaObject, diya_object, G_TYPE_OBJECT) + +static void diya_object_class_init(DiyaObjectClass *class) +{ + class->to_string = NULL; +} + +static void diya_object_init(DiyaObject *self) +{ + (void) self; +} + +const gchar * diya_object_to_string(gpointer object) +{ + g_return_val_if_fail(DIYA_IS_OBJECT(object), NULL); + DiyaObject * self = DIYA_OBJECT(object); + DiyaObjectClass *class = DIYA_OBJECT_GET_CLASS(self); + return class->to_string ? class->to_string(self) : NULL; +} \ No newline at end of file diff --git a/src/base.h b/src/base.h new file mode 100644 index 0000000..7efcc69 --- /dev/null +++ b/src/base.h @@ -0,0 +1,24 @@ +#ifndef DIYA_OBJECT_H +#define DIYA_OBJECT_H + +#include +/** + * Base class object + * + */ +#define DIYA_TYPE_OBJECT (diya_object_get_type ()) +G_DECLARE_DERIVABLE_TYPE (DiyaObject, diya_object, DIYA, OBJECT, GObject) + +struct _DiyaObjectClass { + GObjectClass parent_class; + const gchar* (*to_string) (DiyaObject *self); + /** + * @brief reserve for futur use to + * define common API for descendants + * + */ +}; + +const gchar* diya_object_to_string(gpointer object); + +#endif \ No newline at end of file diff --git a/src/foreign.c b/src/foreign.c new file mode 100644 index 0000000..f7050b4 --- /dev/null +++ b/src/foreign.c @@ -0,0 +1,313 @@ +#include +#include +#include "foreign.h" + +/** + * @DiyaWindow Object definition + * + */ +enum +{ + NO_PROP, + WIN_APP_ID, + WIN_TITLE, + WIN_HANDLE, + WIN_STATE, + WIN_PARENT, + SHELL, + N_PROPERTIES +}; +static GParamSpec *win_properties[N_PROPERTIES] = {0}; +struct _DiyaWindow +{ + DiyaObject parent; + gchar * appid; + gpointer handle; + gchar* title; + enum diya_win_state state; + DiyaWindow * parent_win; + DiyaShell * shell; + gchar string[128]; +}; +G_DEFINE_TYPE(DiyaWindow, diya_window, DIYA_TYPE_OBJECT) + +static void diya_window_finalize(GObject* object) +{ + DiyaWindow * self = DIYA_WINDOW(object); + g_debug("diya_window_finalize: %s", diya_object_to_string(self)); + if(self->appid) + { + g_free(self->appid); + } + if(self->title) + { + g_free(self->title); + } +} + +static void diya_window_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) +{ + DiyaWindow * self = DIYA_WINDOW(object); + switch (property_id) + { + case WIN_APP_ID: + if(self->appid) + { + g_free(self->appid); + } + self->appid = g_strdup(g_value_get_string(value)); + break; + case WIN_TITLE: + if(self->title) + { + g_free(self->title); + } + self->title = g_strdup(g_value_get_string(value)); + break; + + case WIN_HANDLE: + self->handle = g_value_get_pointer(value); + break; + case WIN_PARENT: + self->parent_win = g_value_get_pointer(value); + break; + case SHELL: + self->shell = g_value_get_pointer(value); + break; + case WIN_STATE: + self->state = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void diya_window_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) +{ + DiyaWindow * self = DIYA_WINDOW(object); + switch (property_id) + { + case WIN_APP_ID: + g_value_set_string(value, self->appid); + break; + case WIN_TITLE: + g_value_set_string(value, self->title); + break; + case WIN_HANDLE: + g_value_set_pointer(value, self->handle); + break; + case WIN_PARENT: + g_value_set_pointer(value, self->parent_win); + break; + case SHELL: + g_value_set_pointer(value, self->shell); + break; + case WIN_STATE: + g_value_set_uint(value, self->state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void diya_window_init(DiyaWindow *self) +{ + self->appid = NULL; + //self->handle = NULL; + self->parent_win = NULL; + //self->shell = NULL; + self->state = DIYA_WIN_STATE_NONE; + self->title = NULL; +} + +static const gchar* diya_window_to_string(DiyaObject* object) +{ + DiyaWindow* self = DIYA_WINDOW(object); + g_snprintf(self->string, sizeof(self->string), "Window 0x%" PRIXPTR ": %s (%s)", (uintptr_t) self->handle, self->appid?self->appid:"", self->title?self->title:""); + return self->string; +} + +static void diya_window_class_init(DiyaWindowClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + DiyaObjectClass *base_class = DIYA_OBJECT_CLASS(class); + + gobject_class->finalize = diya_window_finalize; + gobject_class->set_property = diya_window_set_property; + gobject_class->get_property = diya_window_get_property; + base_class->to_string = diya_window_to_string; + + win_properties[WIN_APP_ID] = g_param_spec_string("appid", NULL, "Window application id", "", G_PARAM_READWRITE); + win_properties[WIN_TITLE] = g_param_spec_string("title", NULL, "Window title","", G_PARAM_READWRITE ); + win_properties[WIN_HANDLE] = g_param_spec_pointer("handle", NULL, "Foreign window handle", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY ); + win_properties[WIN_STATE] = g_param_spec_uint("state", NULL, "Window state",0, UINT_MAX , DIYA_WIN_STATE_NONE,G_PARAM_READWRITE); + win_properties[WIN_PARENT] = g_param_spec_pointer("parent", NULL, "Window parent", G_PARAM_READWRITE); + win_properties[SHELL] = g_param_spec_pointer("shell", NULL, "Reference to shell", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY ); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, win_properties); +} + +static struct zwlr_foreign_toplevel_manager_v1 *g_toplevel_manager; + +static void toplevel_handle_output_leave(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct wl_output *output) +{ + (void) data; + (void) handle; + (void) output; +} +static void toplevel_handle_title(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + const char *title) +{ + DiyaShell *shell = (DiyaShell *)data; + assert(shell); + DiyaWindow *win = diya_shell_get_window(shell, handle); + assert(win); + g_object_set(win, "title", title, NULL); + g_debug("New title for: %s", diya_object_to_string(win)); +} +static void toplevel_handle_app_id(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + const char *app_id) +{ + DiyaShell *shell = (DiyaShell *)data; + assert(shell); + DiyaWindow *win = diya_shell_get_window(shell, handle); + assert(win); + g_object_set(win, "appid", app_id, NULL); + g_debug("New appid for: %s", diya_object_to_string(win)); +} +static void toplevel_handle_output_enter(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct wl_output *output) +{ + (void) data; + (void) handle; + (void) output; +} +static void toplevel_handle_state(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct wl_array *state) +{ + uint32_t *entry; + DiyaShell *shell = (DiyaShell *)data; + assert(shell); + DiyaWindow *win = diya_shell_get_window(shell, handle); + assert(win); + enum diya_win_state wstate = DIYA_WIN_STATE_NONE; + wl_array_for_each(entry, state) switch (*entry) + { + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: + wstate |= DIYA_WIN_STATE_MINIMIZE; + break; + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: + wstate |= DIYA_WIN_STATE_MAXIMIZE; + break; + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: + wstate |= DIYA_WIN_STATE_FULLSCREEN; + break; + case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: + wstate |= DIYA_WIN_STATE_FOCUS; + break; + } + g_object_set(win, "state", wstate, NULL); +} +static void toplevel_handle_done(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle) +{ + DiyaShell *shell = (DiyaShell *)data; + assert(shell); + DiyaWindow *win = diya_shell_get_window(shell, handle); + assert(win); + g_signal_emit_by_name(shell, "foreign-window-changed", (void *)win); +} +static void toplevel_handle_closed(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle) +{ + DiyaShell *shell = (DiyaShell *)data; + assert(shell); + DiyaWindow *win = diya_shell_get_window(shell, handle); + assert(win); + g_signal_emit_by_name(shell, "foreign-window-removed", (void *)win); + diya_shell_remove_window(shell, win); + zwlr_foreign_toplevel_handle_v1_destroy(handle); +} +static void toplevel_handle_parent(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct zwlr_foreign_toplevel_handle_v1 *parent) +{ + if (!parent) + { + return; + } + assert(handle != parent); + DiyaShell *shell = (DiyaShell *)data; + assert(shell); + DiyaWindow *child_win = diya_shell_get_window(shell, handle); + DiyaWindow *parent_win = diya_shell_get_window(shell, parent); + assert(child_win); + assert(parent_win); + assert(child_win != parent_win); + g_object_set(child_win, "parent", parent_win, NULL); + g_debug("toplevel_handle_parent: %s is child of %s", + diya_object_to_string(child_win), + diya_object_to_string(parent_win)); +} +static const struct zwlr_foreign_toplevel_handle_v1_listener g_toplevel_impl = { + .title = toplevel_handle_title, + .app_id = toplevel_handle_app_id, + .output_enter = toplevel_handle_output_enter, + .output_leave = toplevel_handle_output_leave, + .state = toplevel_handle_state, + .done = toplevel_handle_done, + .closed = toplevel_handle_closed, + .parent = toplevel_handle_parent}; + +static void toplevel_manager_handle_toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager, struct zwlr_foreign_toplevel_handle_v1 *tl) +{ + (void) toplevel_manager; + DiyaShell *shell = (DiyaShell *)data; + assert(shell); + DiyaWindow *win = diya_shell_get_window(shell, tl); + + if (win) + { + g_debug("[%s] already exists",diya_object_to_string(win)); + return; + } + // TODO: different between windows + win = diya_window_new(shell, tl); + zwlr_foreign_toplevel_handle_v1_add_listener(tl, &g_toplevel_impl, data); +} + +static void toplevel_manager_handle_finished(void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager) +{ + (void) data; + // remove table entry + zwlr_foreign_toplevel_manager_v1_destroy(toplevel_manager); +} + +static const struct zwlr_foreign_toplevel_manager_v1_listener g_toplevel_manager_impl = + { + .toplevel = toplevel_manager_handle_toplevel, + .finished = toplevel_manager_handle_finished, +}; + +void diya_shell_foreign_toplevel_register(struct wl_registry *registry, uint32_t name, DiyaShell *shell) +{ + g_toplevel_manager = wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, 3); + zwlr_foreign_toplevel_manager_v1_add_listener(g_toplevel_manager, &g_toplevel_manager_impl, (void *)shell); +} + +DiyaWindow * diya_window_new(DiyaShell* shell, gpointer handle) +{ + DiyaWindow *win = DIYA_WINDOW(g_object_new(DIYA_TYPE_WINDOW,"shell", shell, "handle", handle, NULL)); + diya_shell_add_window(shell, win); + g_debug("Add new window 0x%" PRIXPTR, (uintptr_t)handle); + return win; +} \ No newline at end of file diff --git a/src/foreign.h b/src/foreign.h new file mode 100644 index 0000000..1a370ca --- /dev/null +++ b/src/foreign.h @@ -0,0 +1,7 @@ +#ifndef DIYA_SHELL_FOREIGN_H +#define DIYA_SHELL_FOREIGN_H +#include "wlr-foreign-toplevel-management-unstable-v1.h" +#include "shell.h" + +void diya_shell_foreign_toplevel_register(struct wl_registry *registry, uint32_t name, DiyaShell * shell); +#endif \ No newline at end of file diff --git a/src/launcher.c b/src/launcher.c new file mode 100644 index 0000000..40a7a60 --- /dev/null +++ b/src/launcher.c @@ -0,0 +1,78 @@ +#include "launcher.h" +#include "foreign.h" +#include + +#define NAMESPACE "launcher" + +static void on_launcher_destroy(GtkWindow *window, GApplication *_data) +{ + (void) window; + (void)_data; + //g_application_quit (G_APPLICATION (gtk_window_get_application (window))); +} + +static void on_foreign_window_change(GtkApplication* app, DiyaWindow * win, gpointer data) +{ + (void) app; + (void) data; + assert(win); + g_warning("WINDOW CHANGEEEEEEEEE %s", diya_object_to_string(DIYA_OBJECT(win))); +} + +static void on_foreign_window_removed(GtkApplication* app, DiyaWindow * win, gpointer data) +{ + (void) app; + (void) data; + assert(win); + g_warning("WINDOW removed %s", diya_object_to_string(DIYA_OBJECT(win))); +} + +void diya_launcher_init(DiyaShell * shell) +{ + assert(shell); + GtkWindow *gtk_window; + g_object_get(shell, "launchpad", >k_window, NULL); + assert(gtk_window); + g_signal_connect (gtk_window, "destroy", G_CALLBACK (on_launcher_destroy), NULL); + // int layer shell for window + gtk_layer_init_for_window (gtk_window); + // anchor window to all edges + gtk_layer_set_anchor (gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, true); + gtk_layer_set_anchor (gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, true); + gtk_layer_set_anchor (gtk_window, GTK_LAYER_SHELL_EDGE_TOP, true); + gtk_layer_set_anchor (gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, false); + + // set margin on window + for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) + gtk_layer_set_margin (gtk_window, i, 0); + gtk_layer_set_layer (gtk_window, GTK_LAYER_SHELL_LAYER_TOP); + gtk_layer_set_keyboard_mode (gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); + gtk_layer_set_namespace (gtk_window, NAMESPACE); + // the top launcher shall be exclusive + gtk_layer_auto_exclusive_zone_enable (gtk_window); + + gtk_widget_set_name(GTK_WIDGET(gtk_window),NAMESPACE); + gtk_window_set_default_size(gtk_window, 48, 48); + + GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); + gtk_box_set_homogeneous (GTK_BOX (box), TRUE); + gtk_window_set_child (GTK_WINDOW (gtk_window), box); + + //g_signal_connect (gtk_window, "orientation-changed", G_CALLBACK (on_orientation_changed), /*data*/NULL); + gtk_window_present (GTK_WINDOW (gtk_window)); + + g_signal_connect (shell, "foreign-window-changed", G_CALLBACK (on_foreign_window_change), NULL); + g_signal_connect (shell, "foreign-window-removed", G_CALLBACK (on_foreign_window_removed), NULL); + + GList *apps = g_app_info_get_all (); + GList * l; + for (l = apps; l != NULL; l = l->next) + { + gpointer element_data = l->data; + g_warning("%s", g_app_info_get_display_name(element_data)); /*print out all of the display names of the .desktop files */ + g_warning("%s", g_app_info_get_id(element_data)); + g_warning("%s", g_app_info_get_name(element_data)); + //g_warning("%s", g_app_info_get_icon(element_data)); + } + g_list_free_full(apps, g_object_unref); +} \ No newline at end of file diff --git a/src/launcher.h b/src/launcher.h new file mode 100644 index 0000000..c7059f1 --- /dev/null +++ b/src/launcher.h @@ -0,0 +1,9 @@ +#ifndef DIYA_SHELL_LAUNCHER_H +#define DIYA_SHELL_LAUNCHER_H + +#include "shell.h" + + +void diya_launcher_init(DiyaShell * shell); + +#endif \ No newline at end of file diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..e69de29 diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..e69de29 diff --git a/src/shell.c b/src/shell.c new file mode 100644 index 0000000..a04ec0c --- /dev/null +++ b/src/shell.c @@ -0,0 +1,230 @@ +#include + +#include "background.h" +#include "launcher.h" +#include "wayland.h" +#include "foreign.h" + +#define SHELL_DESCRIPTION "Diya GTK shell for wayland (diyac)" + +enum +{ + NO_PROP, + SHELL_APP, + SHELL_BACKGROUND_WIDGET, + SHELL_LAUNCHPAD_WIDGET, + SHELL_WINDOWS, + N_PROPERTIES +}; + +static GParamSpec *shell_properties[N_PROPERTIES] = {0}; + +struct _DiyaShell +{ + DiyaObject parent; + GtkApplication* app; + GtkWindow* background; + GtkWindow* launcher; + GHashTable* windows; +}; +G_DEFINE_TYPE(DiyaShell, diya_shell, DIYA_TYPE_OBJECT) + +static void diya_shell_finalize(GObject* object) +{ + DiyaShell * self = DIYA_SHELL(object); + g_hash_table_destroy(self->windows); + // TODO: free element in each entry of the table + // g_object_unref(self->app); + g_warning("diya_shell_finalize"); +} + +static void diya_shell_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) +{ + DiyaShell * self = DIYA_SHELL(object); + switch (property_id) + { + case SHELL_APP: + self->app = g_value_get_pointer(value); + assert(self->app); + self->background = GTK_WINDOW (gtk_application_window_new (self->app)); + self->launcher = GTK_WINDOW (gtk_application_window_new (self->app)); + break; + case SHELL_BACKGROUND_WIDGET: + case SHELL_LAUNCHPAD_WIDGET: + case SHELL_WINDOWS: + //self->windows = g_value_get_pointer(value); + 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); + switch (property_id) + { + case SHELL_APP: + g_value_set_pointer(value, self->app); + break; + case SHELL_BACKGROUND_WIDGET: + g_value_set_pointer(value, self->background); + break; + case SHELL_LAUNCHPAD_WIDGET: + assert(self->launcher); + g_value_set_pointer(value, self->launcher); + break; + case SHELL_WINDOWS: + g_value_set_pointer(value, self->windows); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static const gchar* diya_shell_to_string(DiyaObject* object) +{ + (void) object; + return SHELL_DESCRIPTION; +} + +static void diya_shell_class_init(DiyaShellClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + DiyaObjectClass *base_class = DIYA_OBJECT_CLASS(class); + + base_class->to_string = diya_shell_to_string; + gobject_class->finalize = diya_shell_finalize; + gobject_class->set_property = diya_shell_set_property; + gobject_class->get_property = diya_shell_get_property; + + shell_properties[SHELL_APP] = g_param_spec_pointer("application", NULL, "Shell application", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + shell_properties[SHELL_BACKGROUND_WIDGET] = g_param_spec_pointer("background", NULL, "Shell background widget", G_PARAM_READABLE ); + shell_properties[SHELL_LAUNCHPAD_WIDGET] = g_param_spec_pointer("launchpad", NULL, "Shell launchpad", G_PARAM_READABLE ); + shell_properties[SHELL_WINDOWS] = g_param_spec_pointer("windows", NULL, "Shell foreign windows", G_PARAM_READABLE); + g_object_class_install_properties (gobject_class, N_PROPERTIES, shell_properties); + + g_signal_new("foreign-window-changed", + DIYA_TYPE_SHELL, + G_SIGNAL_DETAILED | + G_SIGNAL_ACTION | + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + g_signal_new("foreign-window-removed", + DIYA_TYPE_SHELL, + G_SIGNAL_DETAILED | + G_SIGNAL_ACTION | + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); +} + +static void diya_shell_init(DiyaShell *self) +{ + //self->app = NULL; + self->background = NULL; + self->launcher = NULL; + self->windows = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); +} + +gpointer diya_shell_get_window(DiyaShell * shell, gpointer handle) +{ + return g_hash_table_lookup(shell->windows, handle); +} +gboolean diya_shell_add_window(DiyaShell * shell, DiyaWindow * win) +{ + gpointer handle; + g_object_get(win, "handle", &handle, NULL); + assert(handle); + return g_hash_table_insert(shell->windows,handle, win); +} + +gboolean diya_shell_remove_window(DiyaShell * shell, DiyaWindow * win) +{ + gpointer handle; + g_object_get(win, "handle", &handle, NULL); + assert(handle); + return g_hash_table_remove(shell->windows,handle); +} + +DiyaShell * diya_shell_new(GtkApplication * app) +{ + return DIYA_SHELL(g_object_new(DIYA_TYPE_SHELL,"application",app, NULL)); +} +/* +static void on_orientation_changed (GtkWindow *window, WindowOrientation orientation, ToplevelData *data) +{ + (void)window; + GtkOrientation orient_toplevel, orient_sub; + orient_toplevel = GTK_ORIENTATION_HORIZONTAL; + orient_sub = GTK_ORIENTATION_VERTICAL; + + switch (orientation) { + case WINDOW_ORIENTATION_HORIZONTAL: + orient_toplevel = GTK_ORIENTATION_HORIZONTAL; + orient_sub = GTK_ORIENTATION_HORIZONTAL; + break; + case WINDOW_ORIENTATION_VERTICAL: + orient_toplevel = GTK_ORIENTATION_VERTICAL; + orient_sub = GTK_ORIENTATION_VERTICAL; + break; + case WINDOW_ORIENTATION_NONE: + orient_toplevel = GTK_ORIENTATION_HORIZONTAL; + orient_sub = GTK_ORIENTATION_VERTICAL; + break; + } + gtk_orientable_set_orientation (GTK_ORIENTABLE (data->toplevel_box), orient_toplevel); + gtk_orientable_set_orientation (GTK_ORIENTABLE (data->first_box), orient_sub); + gtk_orientable_set_orientation (GTK_ORIENTABLE (data->second_box), orient_sub); + //gtk_window_resize (window, 1, 1); // force the window to shrink to the smallest size it can +} +*/ +static void shutdown(GtkApplication *app, void *data) +{ + (void) app; + g_warning("Application shutdown"); + DiyaShell * shell = data; + assert(shell); + g_object_unref(shell); +} +static void activate(GtkApplication *app, void *data) +{ + (void)data; + DiyaShell * shell = diya_shell_new(app); + assert(shell); + assert(DIYA_IS_SHELL(shell)); + g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), (void*)shell); + + diya_launcher_init(shell); + diya_shell_wayland_init(shell); + diya_shell_init_background(shell); + // CSS support + // set color for it + GtkCssProvider *provider = gtk_css_provider_new(); + const char *css = "#launcher {background-color: red;} #background {background-image:url(\"file:///etc/xdg/labwc/wpp.jpg\");background-size: cover;}"; + gtk_css_provider_load_from_string(provider, css); + gtk_style_context_add_provider_for_display( + gdk_display_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); +} + +int main(int argc, char *argv[]) +{ + GtkApplication* app = gtk_application_new("dev.iohub.diya-shell", G_APPLICATION_DEFAULT_FLAGS); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + int status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + return status; +} diff --git a/src/shell.h b/src/shell.h new file mode 100644 index 0000000..1ba6234 --- /dev/null +++ b/src/shell.h @@ -0,0 +1,27 @@ +#ifndef DIYA_SHELL_H +#define DIYA_SHELL_H +#include +#include "base.h" + +enum diya_win_state +{ + DIYA_WIN_STATE_NONE = 0, + DIYA_WIN_STATE_MINIMIZE = 1 << 0, + DIYA_WIN_STATE_MAXIMIZE = 1 << 1, + DIYA_WIN_STATE_FULLSCREEN = 1 << 2, + DIYA_WIN_STATE_FOCUS = 1 << 3, +}; + +#define DIYA_TYPE_SHELL (diya_shell_get_type ()) +G_DECLARE_FINAL_TYPE (DiyaShell, diya_shell, DIYA, SHELL, DiyaObject) + +#define DIYA_TYPE_WINDOW (diya_window_get_type ()) +G_DECLARE_FINAL_TYPE (DiyaWindow, diya_window, DIYA, WINDOW, DiyaObject) + +gpointer diya_shell_get_window(DiyaShell * shell, gpointer handle); +gboolean diya_shell_add_window(DiyaShell * shell, DiyaWindow * win); +gboolean diya_shell_remove_window(DiyaShell * shell, DiyaWindow * win); +DiyaShell * diya_shell_new(GtkApplication * app); + +DiyaWindow * diya_window_new(DiyaShell* shell, gpointer handle); +#endif \ No newline at end of file diff --git a/src/wayland.c b/src/wayland.c new file mode 100644 index 0000000..58dc171 --- /dev/null +++ b/src/wayland.c @@ -0,0 +1,57 @@ +#include +#include +#include "wayland.h" +#include "foreign.h" + +static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const gchar *interface, uint32_t version) +{ + /*if (!g_strcmp0(interface, zxdg_output_manager_v1_interface.name)) + xdg_output_register(registry, name); + else if (!g_strcmp0(interface, wl_shm_interface.name)) + shm_register(registry, name); + else if (!g_strcmp0(interface, zwlr_layer_shell_v1_interface.name)) + layer_shell_register(registry, name, version); + if (!g_strcmp0(interface,zwlr_foreign_toplevel_manager_v1_interface.name)) + foreign_toplevel_register(registry,name); + */ + //g_warning("register global: %s VS %s", interface, zwlr_foreign_toplevel_manager_v1_interface.name); + (void) version; + if (!g_strcmp0(interface, zwlr_foreign_toplevel_manager_v1_interface.name)) + { + g_warning("register global: %s", interface); + diya_shell_foreign_toplevel_register(registry, name, data); + } + +} + +static void handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + (void) data; + (void) registry; + (void) name; +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove +}; + +void diya_shell_wayland_init(DiyaShell *shell) +{ + struct wl_display *display; + struct wl_registry *registry; + display = gdk_wayland_display_get_wl_display(gdk_display_get_default()); + if (!display) + g_error("Can't get wayland display\n"); + + registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, (void*)shell); + wl_display_roundtrip(display); + // wayland_monitor_probe(); + // GdkMonitor *mon = wayland_monitor_get_default(); + // g_info("default output: %s", (gchar *)g_object_get_data(G_OBJECT(mon), "xdg_name")); + + // wl_display_roundtrip(display); + // wl_display_roundtrip(display); +} diff --git a/src/wayland.h b/src/wayland.h new file mode 100644 index 0000000..7e46cf6 --- /dev/null +++ b/src/wayland.h @@ -0,0 +1,8 @@ +#ifndef DIYA_SHELL_WAYLAND_H +#define DIYA_SHELL_WAYLAND_H + +#include "shell.h" + +void diya_shell_wayland_init(DiyaShell *shell); + +#endif \ No newline at end of file