diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..000d1b2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: LLVM +IndentWidth: 2 +BracedInitializerIndentWidth: 2 +IndentAccessModifiers: true +IndentCaseBlocks: true +IndentCaseLabels: true +AlignOperands: true diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..7f792ef --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,12 @@ +name: clang-format Check +on: [push, pull_request] +jobs: + formatting-check: + name: Formatting Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run clang-format style check. + uses: jidicula/clang-format-action@v4.13.0 + with: + clang-format-version: '18' diff --git a/.gitignore b/.gitignore index c6127b3..73a9d79 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,9 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# Nix + +result +result-dev +result-devdoc diff --git a/build-aux/meson/gen-visibility-macros.py b/build-aux/meson/gen-visibility-macros.py new file mode 100755 index 0000000..ddcf891 --- /dev/null +++ b/build-aux/meson/gen-visibility-macros.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +# +# SPDX-FileCopyrightText: 2022 Collabora Inc. +# 2023 Emmanuele Bassi +# +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Original author: Xavier Claessens + +import argparse +import textwrap +from pathlib import Path + + +# Disable line length warnings as wrapping the C code templates would be hard +# flake8: noqa: E501 + + +def gen_versions_macros(args, current_major_version, current_minor_version, current_micro_version): + ns = args.namespace + with args.out_path.open("w", encoding="utf-8") as ofile, args.in_path.open( + "r", encoding="utf-8" + ) as ifile: + for line in ifile.readlines(): + if f"@{ns}_VERSIONS@" in line: + ofile.write( + textwrap.dedent( + f"""\ + /** + * {ns}_MAJOR_VERSION: + * + * The major version component of the library's version, e.g. "1" for "1.2.3". + */ + #define {ns}_MAJOR_VERSION ({current_major_version}) + + /** + * {ns}_MINOR_VERSION: + * + * The minor version component of the library's version, e.g. "2" for "1.2.3". + */ + #define {ns}_MINOR_VERSION ({current_minor_version}) + + /** + * {ns}_MICRO_VERSION: + * + * The micro version component of the library's version, e.g. "3" for "1.2.3". + */ + #define {ns}_MICRO_VERSION ({current_micro_version}) + """ + ) + ) + for minor in range(0, current_minor_version + 2, 2): + ofile.write( + textwrap.dedent( + f"""\ + /** + * {ns}_VERSION_{current_major_version}_{minor}: + * + * A macro that evaluates to the {current_major_version}.{minor} version, in a format + * that can be used by the C pre-processor. + * + * Since: {current_major_version}.{minor} + */ + #define {ns}_VERSION_{current_major_version}_{minor} (G_ENCODE_VERSION ({current_major_version}, {minor})) + """ + ) + ) + else: + ofile.write(line) + + +def gen_visibility_macros(args, current_major_version, current_minor_version, current_micro_version): + """ + Generates a set of macros for each minor stable version of Shoyu + + - SHOYU_DEPRECATED + - SHOYU_DEPRECATED_IN_… + - SHOYU_DEPRECATED_MACRO_IN_… + - SHOYU_DEPRECATED_ENUMERATOR_IN_… + - SHOYU_DEPRECATED_TYPE_IN_… + + - SHOYU_AVAILABLE_IN_ALL + - SHOYU_AVAILABLE_IN_… + - SHOYU_AVAILABLE_STATIC_INLINE_IN_… + - SHOYU_AVAILABLE_MACRO_IN_… + - SHOYU_AVAILABLE_ENUMERATOR_IN_… + - SHOYU_AVAILABLE_TYPE_IN_… + + - SHOYU_UNAVAILABLE(maj,min) + - SHOYU_UNAVAILABLE_STATIC_INLINE(maj,min) + """ + + ns = args.namespace + with args.out_path.open("w", encoding="utf-8") as f: + f.write( + textwrap.dedent( + f"""\ + #pragma once + + #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined({ns}_STATIC_COMPILATION) + # define _{ns}_EXPORT __declspec(dllexport) + # define _{ns}_IMPORT __declspec(dllimport) + #elif __GNUC__ >= 4 + # define _{ns}_EXPORT __attribute__((visibility("default"))) + # define _{ns}_IMPORT + #else + # define _{ns}_EXPORT + # define _{ns}_IMPORT + #endif + #ifdef {ns}_COMPILATION + # define _{ns}_API _{ns}_EXPORT + #else + # define _{ns}_API _{ns}_IMPORT + #endif + + #define _{ns}_EXTERN _{ns}_API extern + + #define {ns}_VAR _{ns}_EXTERN + #define {ns}_AVAILABLE_IN_ALL _{ns}_EXTERN + + #ifdef {ns}_DISABLE_DEPRECATION_WARNINGS + #define {ns}_DEPRECATED _{ns}_EXTERN + #define {ns}_DEPRECATED_FOR(f) _{ns}_EXTERN + #define {ns}_UNAVAILABLE(maj,min) _{ns}_EXTERN + #define {ns}_UNAVAILABLE_STATIC_INLINE(maj,min) + #else + #define {ns}_DEPRECATED G_DEPRECATED _{ns}_EXTERN + #define {ns}_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _{ns}_EXTERN + #define {ns}_UNAVAILABLE(maj,min) G_UNAVAILABLE(maj,min) _{ns}_EXTERN + #define {ns}_UNAVAILABLE_STATIC_INLINE(maj,min) G_UNAVAILABLE(maj,min) + #endif + """ + ) + ) + for minor in range(0, current_minor_version + 2, 2): + f.write( + textwrap.dedent( + f""" + #if {ns}_VERSION_MIN_REQUIRED >= {ns}_VERSION_0_{minor} + #define {ns}_DEPRECATED_IN_{current_major_version}_{minor} {ns}_DEPRECATED + #define {ns}_DEPRECATED_IN_{current_major_version}_{minor}_FOR(f) {ns}_DEPRECATED_FOR (f) + #define {ns}_DEPRECATED_MACRO_IN_{current_major_version}_{minor} {ns}_DEPRECATED_MACRO + #define {ns}_DEPRECATED_MACRO_IN_{current_major_version}_{minor}_FOR(f) {ns}_DEPRECATED_MACRO_FOR (f) + #define {ns}_DEPRECATED_ENUMERATOR_IN_{current_major_version}_{minor} {ns}_DEPRECATED_ENUMERATOR + #define {ns}_DEPRECATED_ENUMERATOR_IN_{current_major_version}_{minor}_FOR(f) {ns}_DEPRECATED_ENUMERATOR_FOR (f) + #define {ns}_DEPRECATED_TYPE_IN_{current_major_version}_{minor} {ns}_DEPRECATED_TYPE + #define {ns}_DEPRECATED_TYPE_IN_{current_major_version}_{minor}_FOR(f) {ns}_DEPRECATED_TYPE_FOR (f) + #else + #define {ns}_DEPRECATED_IN_{current_major_version}_{minor} _{ns}_EXTERN + #define {ns}_DEPRECATED_IN_{current_major_version}_{minor}_FOR(f) _{ns}_EXTERN + #define {ns}_DEPRECATED_MACRO_IN_{current_major_version}_{minor} + #define {ns}_DEPRECATED_MACRO_IN_{current_major_version}_{minor}_FOR(f) + #define {ns}_DEPRECATED_ENUMERATOR_IN_{current_major_version}_{minor} + #define {ns}_DEPRECATED_ENUMERATOR_IN_{current_major_version}_{minor}_FOR(f) + #define {ns}_DEPRECATED_TYPE_IN_{current_major_version}_{minor} + #define {ns}_DEPRECATED_TYPE_IN_{current_major_version}_{minor}_FOR(f) + #endif + + #if {ns}_VERSION_MAX_ALLOWED < {ns}_VERSION_{current_major_version}_{minor} + #define {ns}_AVAILABLE_IN_{current_major_version}_{minor} {ns}_UNAVAILABLE ({current_major_version}, {minor}) + #define {ns}_AVAILABLE_STATIC_INLINE_IN_{current_major_version}_{minor} {ns}_UNAVAILABLE_STATIC_INLINE ({current_major_version}, {minor}) + #define {ns}_AVAILABLE_MACRO_IN_{current_major_version}_{minor} {ns}_UNAVAILABLE_MACRO ({current_major_version}, {minor}) + #define {ns}_AVAILABLE_ENUMERATOR_IN_{current_major_version}_{minor} {ns}_UNAVAILABLE_ENUMERATOR ({current_major_version}, {minor}) + #define {ns}_AVAILABLE_TYPE_IN_{current_major_version}_{minor} {ns}_UNAVAILABLE_TYPE ({current_major_version}, {minor}) + #else + #define {ns}_AVAILABLE_IN_{current_major_version}_{minor} _{ns}_EXTERN + #define {ns}_AVAILABLE_STATIC_INLINE_IN_{current_major_version}_{minor} + #define {ns}_AVAILABLE_MACRO_IN_{current_major_version}_{minor} + #define {ns}_AVAILABLE_ENUMERATOR_IN_{current_major_version}_{minor} + #define {ns}_AVAILABLE_TYPE_IN_{current_major_version}_{minor} + #endif + """ + ) + ) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("shoyu_version", help="Current GLib version") + subparsers = parser.add_subparsers() + + versions_parser = subparsers.add_parser( + "versions-macros", help="Generate versions macros" + ) + versions_parser.add_argument("namespace", help="Macro namespace") + versions_parser.add_argument("in_path", help="input file", type=Path) + versions_parser.add_argument("out_path", help="output file", type=Path) + versions_parser.set_defaults(func=gen_versions_macros) + + visibility_parser = subparsers.add_parser( + "visibility-macros", help="Generate visibility macros" + ) + visibility_parser.add_argument("namespace", help="Macro namespace") + visibility_parser.add_argument("out_path", help="output file", type=Path) + visibility_parser.set_defaults(func=gen_visibility_macros) + + args = parser.parse_args() + version = [int(i) for i in args.shoyu_version.split(".")] + args.func(args, version[0], version[1], version[2]) + + +if __name__ == "__main__": + main() diff --git a/docs/reference/compositor/meson.build b/docs/reference/compositor/meson.build new file mode 100644 index 0000000..00d27ff --- /dev/null +++ b/docs/reference/compositor/meson.build @@ -0,0 +1,40 @@ +expand_content_md_files = [ +] + +if get_option('documentation') + shoyu_compositor_toml = configure_file( + input: 'shoyu-compositor.toml.in', + output: 'shoyu-compositor.toml', + configuration: toml_conf, + install: true, + install_dir: docs_dir / 'shoyu-compositor') + + custom_target('compositor-doc', + input: [ shoyu_compositor_toml, libcompositor_gir[0] ], + output: 'shoyu-compositor', + command: [ + gidocgen, + 'generate', + gidocgen_common_args, + '--add-include-path=@0@'.format(meson.current_build_dir() / '../../../compositor'), + '--config=@INPUT0@', + '--output-dir=@OUTPUT@', + '--content-dir=@0@'.format(meson.current_source_dir()), + '@INPUT1@', + ], + depend_files: [ expand_content_md_files ], + build_by_default: true, + install: true, + install_dir: docs_dir) + + test('doc-check-compositor', + gidocgen, + args: [ + 'check', + '--config', shoyu_compositor_toml, + '--add-include-path=@0@'.format(meson.current_build_dir() / '../../../compositor'), + libcompositor_gir[0], + ], + depends: libcompositor_gir[0], + suite: ['docs']) +endif diff --git a/docs/reference/compositor/shoyu-compositor.toml.in b/docs/reference/compositor/shoyu-compositor.toml.in new file mode 100644 index 0000000..e5cd729 --- /dev/null +++ b/docs/reference/compositor/shoyu-compositor.toml.in @@ -0,0 +1,31 @@ +[library] +version = "@version@" +browse_url = "https://github.com/MidstallSoftware/shoyu" +repository_url = "https://github.com/MidstallSoftware/shoyu.git" +authors = "Midstall Software" +description = "Shoyu Compositor framework" +license = "AGPL-3.0-only" +dependencies = ["GObject-2.0", "Gio-2.0"] +devhelp = true +search_index = true + + [dependencies."GObject-2.0"] + name = "GObject" + description = "The base type system library" + docs_url = "https://docs.gtk.org/gobject/" + + [dependencies."Gio-2.0"] + name = "Gio" + description = "GObject Interfaces and Objects, Networking, IPC, and I/O" + docs_url = "https://docs.gtk.org/gio/" + +[theme] +name = "basic" +show_index_summary = true +show_class_hierarchy = true + +[source-location] +base_url = "https://github.com/MidstallSoftware/shoyu/tree/master" + +[extra] +content_base_url = "https://github.com/MidstallSoftware/shoyu/tree/master/docs/reference/compositor" diff --git a/docs/reference/meson.build b/docs/reference/meson.build new file mode 100644 index 0000000..96fca54 --- /dev/null +++ b/docs/reference/meson.build @@ -0,0 +1,25 @@ +toml_conf = configuration_data() +toml_conf.set('version', meson.project_version()) + +gidocgen = find_program('gi-docgen', required: get_option('documentation'), native: true) + +gidocgen_common_args = [ + '--quiet', + '--no-namespace-dir', +] + +if get_option('werror') + gidocgen_common_args += ['--fatal-warnings'] +endif + +docs_dir = shoyu_datadir / 'doc' + +if get_option('documentation') and not build_gir + error('API reference requires introspection.') +endif + +subdir('compositor') + +foreach shell : shoyu_shells + subdir('shell-@0@'.format(shell)) +endforeach diff --git a/docs/reference/shell-gtk3/meson.build b/docs/reference/shell-gtk3/meson.build new file mode 100644 index 0000000..49faad7 --- /dev/null +++ b/docs/reference/shell-gtk3/meson.build @@ -0,0 +1,40 @@ +expand_content_md_files = [ +] + +if get_option('documentation') + shoyu_shell_gtk3_toml = configure_file( + input: 'shoyu-shell-gtk3.toml.in', + output: 'shoyu-shell-gtk3.toml', + configuration: toml_conf, + install: true, + install_dir: docs_dir / 'shoyu-shell-gtk3') + + custom_target('shell-gtk3-doc', + input: [ shoyu_shell_gtk3_toml, libshell_gtk3_gir[0] ], + output: 'shoyu-shell-gtk3', + command: [ + gidocgen, + 'generate', + gidocgen_common_args, + '--add-include-path=@0@'.format(meson.current_build_dir() / '../../../shoyu-shell-gtk3'), + '--config=@INPUT0@', + '--output-dir=@OUTPUT@', + '--content-dir=@0@'.format(meson.current_source_dir()), + '@INPUT1@', + ], + depend_files: [ expand_content_md_files ], + build_by_default: true, + install: true, + install_dir: docs_dir) + + test('doc-check-shell-gtk3', + gidocgen, + args: [ + 'check', + '--config', shoyu_shell_gtk3_toml, + '--add-include-path=@0@'.format(meson.current_build_dir() / '../../../shoyu-shell-gtk3'), + libshell_gtk3_gir[0], + ], + depends: libshell_gtk3_gir[0], + suite: ['docs']) +endif diff --git a/docs/reference/shell-gtk3/shoyu-shell-gtk3.toml.in b/docs/reference/shell-gtk3/shoyu-shell-gtk3.toml.in new file mode 100644 index 0000000..231001d --- /dev/null +++ b/docs/reference/shell-gtk3/shoyu-shell-gtk3.toml.in @@ -0,0 +1,18 @@ +[library] +version = "@version@" +repository_url = "https://github.com/MidstallSoftware/shoyu.git" +authors = "Midstall Software" +description = "Shoyu Shell GTK 3" +dependencies = ["GObject-2.0"] +devhelp = true +search_index = true + + [dependencies."GObject-2.0"] + name = "GObject" + description = "The base type system library" + docs_url = "https://docs.gtk.org/gobject/" + +[theme] +name = "basic" +show_index_summary = true +show_class_hierarchy = true diff --git a/docs/reference/shell-gtk4/meson.build b/docs/reference/shell-gtk4/meson.build new file mode 100644 index 0000000..54e52c9 --- /dev/null +++ b/docs/reference/shell-gtk4/meson.build @@ -0,0 +1,40 @@ +expand_content_md_files = [ +] + +if get_option('documentation') + shoyu_shell_gtk4_toml = configure_file( + input: 'shoyu-shell-gtk4.toml.in', + output: 'shoyu-shell-gtk4.toml', + configuration: toml_conf, + install: true, + install_dir: docs_dir / 'shoyu-shell-gtk4') + + custom_target('shell-gtk4-doc', + input: [ shoyu_shell_gtk4_toml, libshell_gtk4_gir[0] ], + output: 'shoyu-shell-gtk4', + command: [ + gidocgen, + 'generate', + gidocgen_common_args, + '--add-include-path=@0@'.format(meson.current_build_dir() / '../../../shoyu-shell-gtk4'), + '--config=@INPUT0@', + '--output-dir=@OUTPUT@', + '--content-dir=@0@'.format(meson.current_source_dir()), + '@INPUT1@', + ], + depend_files: [ expand_content_md_files ], + build_by_default: true, + install: true, + install_dir: docs_dir) + + test('doc-check-shell-gtk4', + gidocgen, + args: [ + 'check', + '--config', shoyu_shell_gtk4_toml, + '--add-include-path=@0@'.format(meson.current_build_dir() / '../../../shoyu-shell-gtk4'), + libshell_gtk4_gir[0], + ], + depends: libshell_gtk4_gir[0], + suite: ['docs']) +endif diff --git a/docs/reference/shell-gtk4/shoyu-shell-gtk4.toml.in b/docs/reference/shell-gtk4/shoyu-shell-gtk4.toml.in new file mode 100644 index 0000000..c15e850 --- /dev/null +++ b/docs/reference/shell-gtk4/shoyu-shell-gtk4.toml.in @@ -0,0 +1,28 @@ +[library] +version = "@version@" +repository_url = "https://github.com/MidstallSoftware/shoyu.git" +authors = "Midstall Software" +description = "Shoyu Shell GTK 4" +dependencies = ["GObject-2.0", "Gdk-4.0", "Gtk-4.0"] +devhelp = true +search_index = true + + [dependencies."GObject-2.0"] + name = "GObject" + description = "The base type system library" + docs_url = "https://docs.gtk.org/gobject/" + + [dependencies."Gdk-4.0"] + name = "Gdk" + description = "The GTK windowing system abstraction" + docs_url = "https://docs.gtk.org/gdk4/" + + [dependencies."Gtk-4.0"] + name = "Gtk" + description = "The GTK toolkit" + docs_url = "https://docs.gtk.org/gtk4/" + +[theme] +name = "basic" +show_index_summary = true +show_class_hierarchy = true diff --git a/example/gtk3.vala b/example/gtk3.vala deleted file mode 100644 index b6ab2d7..0000000 --- a/example/gtk3.vala +++ /dev/null @@ -1,52 +0,0 @@ -namespace ShoyuExampleGtk3 { - public class Application : Shoyu.Application { - private Compositor _compositor; - - public Shoyu.Compositor compositor { - get { return _compositor; } - } - - public Application() { - Object(application_id: "com.midstall.shoyu.example"); - } - - public override void activate() { - _compositor = new Compositor.with_application(this.wl_display, this); - } - - public static int main(string[] args) { - var app = new Application(); - return app.run(args); - } - } - - public class Compositor : Shoyu.Compositor { - public Shoyu.Output primary { - owned get { - return get_outputs().nth_data(0); - } - } - - public Compositor(void* wl_display) { - Object(wl_display: wl_display); - - this.create_output.connect((output) => new Output(this, output)); - } - - public Compositor.with_application(void* wl_display, GLib.Application application) { - Object(wl_display: wl_display, application: application); - } - } - - public class Output : Shoyu.Output { - public Output(Shoyu.Compositor compositor, void* output) { - Object(compositor: compositor, wlr_output: output); - - var label = new Gtk.Label("Hello"); - this.add(label); - - label.show(); - label.set_mapped(true); - } - } -} diff --git a/example/simple.c b/example/simple.c deleted file mode 100644 index d6b2a4a..0000000 --- a/example/simple.c +++ /dev/null @@ -1,12 +0,0 @@ -#include - -static void activate_cb(ShoyuApplication* application) { - struct wl_display* wl_display = shoyu_application_get_wl_display(application); - ShoyuCompositor* compositor = shoyu_compositor_new_with_application(wl_display, G_APPLICATION(application)); -} - -int main(int argc, char** argv) { - g_autoptr(ShoyuApplication) app = shoyu_application_new("com.midstall.shoyu.example", G_APPLICATION_NON_UNIQUE); - g_signal_connect(app, "activate", G_CALLBACK(activate_cb), NULL); - return g_application_run(G_APPLICATION(app), argc, argv); -} diff --git a/examples/meson.build b/examples/meson.build new file mode 100644 index 0000000..0e14d7e --- /dev/null +++ b/examples/meson.build @@ -0,0 +1,7 @@ +if build_gtk3 + subdir('shell-gtk3') +endif + +if build_gtk4 + subdir('shell-gtk4') +endif diff --git a/examples/shell-gtk3/hello-world/main.c b/examples/shell-gtk3/hello-world/main.c new file mode 100644 index 0000000..8fb8436 --- /dev/null +++ b/examples/shell-gtk3/hello-world/main.c @@ -0,0 +1,33 @@ +#include +#include + +static void realize(GtkWidget *widget) { + GdkWindow *window = gtk_widget_get_window(widget); + GdkDisplay *display = gdk_window_get_display(window); + + shoyu_shell_gtk_monitor_set_window(gdk_display_get_monitor(display, 0), + window); +} + +static void activate(GApplication *application) { + GtkApplicationWindow *win = + gtk_application_window_new(GTK_APPLICATION(application)); + gtk_window_set_decorated(GTK_WINDOW(win), FALSE); + g_signal_connect(win, "realize", G_CALLBACK(realize), NULL); + + gtk_container_add(GTK_CONTAINER(win), gtk_label_new("Hello, world")); + gtk_widget_show_all(GTK_WIDGET(win)); +} + +int main(int argc, char **argv) { + gtk_init(&argc, &argv); + shoyu_shell_gtk_init(); + + GtkApplication *app = + gtk_application_new("com.midstall.shoyu.Shell", G_APPLICATION_NON_UNIQUE); + 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/examples/shell-gtk3/hello-world/meson.build b/examples/shell-gtk3/hello-world/meson.build new file mode 100644 index 0000000..e9a968c --- /dev/null +++ b/examples/shell-gtk3/hello-world/meson.build @@ -0,0 +1,4 @@ +executable( + 'shoyu-shell-gtk3-example-hello-world', + sources: ['main.c'], + dependencies: [libshell_gtk3_dep]) diff --git a/examples/shell-gtk3/meson.build b/examples/shell-gtk3/meson.build new file mode 100644 index 0000000..0cd0f1d --- /dev/null +++ b/examples/shell-gtk3/meson.build @@ -0,0 +1 @@ +subdir('hello-world') diff --git a/examples/shell-gtk4/hello-world/main.c b/examples/shell-gtk4/hello-world/main.c new file mode 100644 index 0000000..b6294e8 --- /dev/null +++ b/examples/shell-gtk4/hello-world/main.c @@ -0,0 +1,34 @@ +#include +#include + +static void realize(GtkWidget *widget) { + GdkSurface *surface = gtk_native_get_surface(GTK_NATIVE(widget)); + GdkDisplay *display = gdk_surface_get_display(surface); + GListModel *monitors = gdk_display_get_monitors(display); + + shoyu_shell_gtk_monitor_set_surface( + GDK_MONITOR(g_list_model_get_object(monitors, 0)), surface); +} + +static void activate(GApplication *application) { + GtkApplicationWindow *win = + gtk_application_window_new(GTK_APPLICATION(application)); + gtk_window_set_decorated(GTK_WINDOW(win), FALSE); + g_signal_connect(win, "realize", G_CALLBACK(realize), NULL); + gtk_window_present(GTK_WINDOW(win)); + + gtk_window_set_child(GTK_WINDOW(win), gtk_label_new("Hello, world")); +} + +int main(int argc, char **argv) { + gtk_init(); + shoyu_shell_gtk_init(); + + GtkApplication *app = + gtk_application_new("com.midstall.shoyu.Shell", G_APPLICATION_NON_UNIQUE); + 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/examples/shell-gtk4/hello-world/meson.build b/examples/shell-gtk4/hello-world/meson.build new file mode 100644 index 0000000..53caa7d --- /dev/null +++ b/examples/shell-gtk4/hello-world/meson.build @@ -0,0 +1,4 @@ +executable( + 'shoyu-shell-gtk4-example-hello-world', + sources: ['main.c'], + dependencies: [libshell_gtk4_dep]) diff --git a/examples/shell-gtk4/meson.build b/examples/shell-gtk4/meson.build new file mode 100644 index 0000000..0cd0f1d --- /dev/null +++ b/examples/shell-gtk4/meson.build @@ -0,0 +1 @@ +subdir('hello-world') diff --git a/flake.lock b/flake.lock index c2cdb7d..eba3b6b 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1726560853, - "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -42,27 +42,27 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1721441154, - "narHash": "sha256-LHZtBq31ViF1lkZ8Hnp2Y0ypUMIhVzgp2ZCVCOmO0Do=", + "lastModified": 1731473366, + "narHash": "sha256-sE2WfD3YyNrCROfRZKqMDR77g3KV4FXUaJ7NWe+A7ro=", "owner": "tpwrules", "repo": "nixos-apple-silicon", - "rev": "d3fed6f02e05aee529c95efd402ebb259463f1a6", + "rev": "3eee753e4b074790342fadb1c4e7183d037ddac4", "type": "github" }, "original": { "owner": "tpwrules", - "ref": "release-2024-07-19", + "ref": "release-2024-11-12", "repo": "nixos-apple-silicon", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1731042213, - "narHash": "sha256-1DrYyIw/gR6Sfdm5eN/rH4rofYbNo2l8TKSvxX00IIY=", + "lastModified": 1731694126, + "narHash": "sha256-TP9Z8iJsM9ij7QHv9K2DmPkOXnzG2/8jm4aKUsD0Sxo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "031c0841b473073ef9d5786b7a7ff336d836c8ae", + "rev": "f937a3a20cd3d2dddcd3163272cef14c9e1bd154", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6e5c40e..d5aab11 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,7 @@ systems.url = "github:nix-systems/default-linux"; flake-utils.url = "github:numtide/flake-utils"; nixos-apple-silicon = { - url = "github:tpwrules/nixos-apple-silicon/release-2024-07-19"; + url = "github:tpwrules/nixos-apple-silicon/release-2024-11-12"; inputs.nixpkgs.follows = "nixpkgs"; }; }; @@ -22,70 +22,105 @@ }: let inherit (nixpkgs) lib; + + defaultOverlay = + pkgs: prev: with pkgs; { + shoyu = stdenv.mkDerivation { + pname = "shoyu"; + version = self.shortRev or "dirty"; + + outputs = [ + "out" + "dev" + "devdoc" + ]; + + src = lib.cleanSource self; + + nativeBuildInputs = with pkgs; [ + ninja + meson + python3 + gobject-introspection + gi-docgen + pkg-config + vala + wayland-scanner + ]; + + buildInputs = [ + glib + wlroots_0_18 + gtk3 + gtk4 + libxkbcommon + ]; + + mesonFlags = [ + (lib.mesonBool "documentation" true) + ]; + + postPatch = '' + files=( + build-aux/meson/gen-visibility-macros.py + ) + + chmod +x ''${files[@]} + patchShebangs ''${files[@]} + ''; + + postFixup = '' + moveToOutput "share/doc" "$devdoc" + ''; + }; + }; in flake-utils.lib.eachSystem (import systems) ( system: let - pkgs = nixpkgs.legacyPackages.${system}.extend ( - pkgs: prev: with pkgs; { - pkgsAsahi = ( - if stdenv.targetPlatform.isAarch64 then - pkgsCross.aarch64-multiplatform.appendOverlays [ - nixos-apple-silicon.overlays.default - (pkgsAsahi: prev: { - mesa-asahi-edge = prev.mesa-asahi-edge.overrideAttrs ( - super: prev: { - meta = prev.meta // { - platforms = [ - "i686-linux" - "x86_64-linux" - "x86_64-darwin" - "armv5tel-linux" - "armv6l-linux" - "armv7l-linux" - "armv7a-linux" - "aarch64-linux" - "powerpc64-linux" - "powerpc64le-linux" - "aarch64-darwin" - "riscv64-linux" - ]; - }; - } - ); - - mesa = if pkgsAsahi.targetPlatform.isAarch64 then pkgsAsahi.mesa-asahi-edge else prev.mesa; - }) - ] - else - null - ); - } - ); + pkgs = nixpkgs.legacyPackages.${system}.appendOverlays [ + ( + pkgs: prev: with pkgs; { + pkgsAsahi = ( + if stdenv.hostPlatform.isAarch64 then + pkgs.appendOverlays [ + nixos-apple-silicon.overlays.default + (pkgsAsahi: prev: { + mesa = pkgsAsahi.mesa-asahi-edge; + }) + ] + else + null + ); + } + ) + defaultOverlay + ]; in { + packages = + { + default = pkgs.shoyu; + } + // lib.optionalAttrs (pkgs.pkgsAsahi != null) { + asahi = pkgs.pkgsAsahi.shoyu; + }; + devShells = - let - mkShell = - pkgs: - pkgs.mkShell { - packages = with pkgs; [ - pkg-config - gtk3 - gtk4 - wlroots_0_18 - meson - ninja - libdrm - gobject-introspection - vala - ]; - }; - in { - default = mkShell pkgs; + default = pkgs.shoyu; } - // lib.optionalAttrs (pkgs.pkgsAsahi != null) { asahi = mkShell pkgs.pkgsAsahi; }; + // lib.optionalAttrs (pkgs.pkgsAsahi != null) { + asahi = pkgs.pkgsAsahi.shoyu; + }; + + legacyPackages = pkgs; } - ); + ) + // { + overlays = { + default = defaultOverlay; + shoyu = defaultOverlay; + }; + }; } diff --git a/include/shoyu/application.h b/include/shoyu/application.h deleted file mode 100644 index b485c2f..0000000 --- a/include/shoyu/application.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - -#include "compositor.h" - -G_BEGIN_DECLS; - -G_DECLARE_DERIVABLE_TYPE(ShoyuApplication, shoyu_application, SHOYU, APPLICATION, GApplication); - -struct _ShoyuApplicationClass { - GApplicationClass parent_class; -}; - -ShoyuApplication* shoyu_application_new(const gchar* application_id, GApplicationFlags flags); -struct wl_display* shoyu_application_get_wl_display(ShoyuApplication* self); - -G_END_DECLS; diff --git a/include/shoyu/compositor.h b/include/shoyu/compositor.h deleted file mode 100644 index a090bd6..0000000 --- a/include/shoyu/compositor.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "output.h" - -G_BEGIN_DECLS; - -G_DECLARE_DERIVABLE_TYPE(ShoyuCompositor, shoyu_compositor, SHOYU, COMPOSITOR, GObject); - -struct _ShoyuCompositorClass { - GObjectClass parent_class; - - /** - * ShoyuCompositorClass:create_output: - * - * Returns: (transfer full) (nullable): a #ShoyuOutput - */ - ShoyuOutput* (*create_output)(ShoyuCompositor* compositor, struct wlr_output* wlr_output); - - /*< private >*/ - gpointer padding[12]; -}; - -struct wlr_backend* shoyu_compositor_get_wlr_backend(ShoyuCompositor* self); - -ShoyuCompositor* shoyu_compositor_new(struct wl_display* wl_display); -ShoyuCompositor* shoyu_compositor_new_with_application(struct wl_display* wl_display, GApplication* application); -ShoyuCompositor* shoyu_compositor_new_with_wlr_backend(struct wl_display* wl_display, struct wlr_backend* wlr_backend); -ShoyuCompositor* shoyu_compositor_new_with_wlr_backend_with_application(struct wl_display* wl_display, struct wlr_backend* wlr_backend, GApplication* application); - -GList* shoyu_compositor_get_outputs(ShoyuCompositor* self); - -G_END_DECLS; diff --git a/include/shoyu/output.h b/include/shoyu/output.h deleted file mode 100644 index bc31446..0000000 --- a/include/shoyu/output.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -G_BEGIN_DECLS; - -#if GTK_MAJOR_VERSION == 3 -G_DECLARE_DERIVABLE_TYPE(ShoyuOutput, shoyu_output, SHOYU, OUTPUT, GtkBin); -#elif GTK_MAJOR_VERSION == 4 -G_DECLARE_DERIVABLE_TYPE(ShoyuOutput, shoyu_output, SHOYU, OUTPUT, GtkWidget); -#endif - -struct _ShoyuCompositor; - -struct _ShoyuOutputClass { -#if GTK_MAJOR_VERSION == 3 - GtkBinClass parent_class; -#elif GTK_MAJOR_VERSION == 4 - GtkWidgetClass parent_class; -#endif - - gboolean (*request_state)(ShoyuOutput* self, struct wlr_output_state* state); - - gpointer padding[12]; -}; - -ShoyuOutput* shoyu_output_new(struct _ShoyuCompositor* compositor, struct wlr_output* wlr_output); - -G_END_DECLS; diff --git a/include/shoyu/shoyu.h b/include/shoyu/shoyu.h deleted file mode 100644 index 16d011d..0000000 --- a/include/shoyu/shoyu.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include "application.h" -#include "compositor.h" diff --git a/include/shoyu/wayland-event-source.h b/include/shoyu/wayland-event-source.h deleted file mode 100644 index 2c382e6..0000000 --- a/include/shoyu/wayland-event-source.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include - -GSource* shoyu_wayland_event_source_new(struct wl_display* wl_display, struct wl_event_loop* event_loop); diff --git a/meson.build b/meson.build index 597f501..230a2d9 100644 --- a/meson.build +++ b/meson.build @@ -1,127 +1,297 @@ project('shoyu', 'c', license: 'AGPL-3.0-only', license_files: 'LICENSE', - version: '0.1.0') + version: '0.1.0', + default_options: [ + 'buildtype=debugoptimized', + 'warning_level=1', + 'c_std=gnu99', + ], + meson_version: '>= 1.2.0') gnome = import('gnome') +wayland = import('unstable-wayland') -libdrm = dependency('libdrm') -wayland_server = dependency('wayland-server') -wlroots = dependency('wlroots-0.18') +add_project_arguments('-DG_LOG_USE_STRUCTURED=1', language: 'c') +add_project_arguments('-DGLIB_DISABLE_DEPRECATION_WARNINGS', language: 'c') -gobject = dependency('gobject-2.0') -gtk3 = dependency('gtk+-3.0', required: false) -gtk4 = dependency('gtk4', required: false) +shoyu_version = meson.project_version() +shoyu_major_version = shoyu_version.split('.')[0].to_int() +shoyu_minor_version = shoyu_version.split('.')[1].to_int() +shoyu_micro_version = shoyu_version.split('.')[2].to_int() -gobject_introspection = dependency('gobject-introspection-1.0', required: false) -vapigen = find_program('vapigen', required: false) -valac = find_program('valac', required: false) +shoyu_interface_age = shoyu_minor_version.is_odd() ? 0 : shoyu_micro_version -if valac.found() - add_languages('vala') - add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi')], language: 'vala') +add_project_arguments('-DSHOYU_VERSION="@0@"'.format(shoyu_version), language: 'c') + +add_project_arguments('-D_GNU_SOURCE', language: 'c') + +shoyu_debug_cflags = [] +debug = get_option('debug') +optimization = get_option('optimization') +if debug + shoyu_debug_cflags += '-DG_ENABLE_DEBUG' + if optimization in ['0', 'g'] + shoyu_debug_cflags += '-DG_ENABLE_CONSISTENCY_CHECKS' + endif +elif optimization in ['2', '3', 's'] + shoyu_debug_cflags += ['-DG_DISABLE_CAST_CHECKS', '-DG_DISABLE_ASSERT'] endif -libshoyu_deps = [wlroots, gobject, libdrm, wayland_server] -libshoyu_sources = [ - 'src/application.c', 'src/compositor.c', - 'src/output.c', 'src/wayland-event-source.c' -] -libshoyu_headers = [ - 'include/shoyu/application.h', 'include/shoyu/compositor.h', - 'include/shoyu/output.h', 'include/shoyu/shoyu.h', - 'include/shoyu/wayland-event-source.h' -] - -if gtk3.found() - libshoyu_gtk3_deps = libshoyu_deps + [gtk3] - libshoyu_gtk3 = shared_library('shoyu-gtk3', - libshoyu_sources, - c_args: ['-DWLR_USE_UNSTABLE=1'], - include_directories: ['include'], - dependencies: libshoyu_gtk3_deps, - install: true) - - if gobject_introspection.found() - libshoyu_gtk3_gir = gnome.generate_gir( - libshoyu_gtk3, - dependencies: [libshoyu_deps, gtk3], - sources: libshoyu_sources + libshoyu_headers, - extra_args: ['-DWLR_USE_UNSTABLE=1'], - namespace: 'ShoyuGtk3', - nsversion: '0.1', - identifier_prefix: 'Shoyu', - symbol_prefix: 'shoyu', - export_packages: 'shoyu-gtk3', - includes: ['Gtk-3.0'], - header: 'shoyu/shoyu.h', - install: true) +add_project_arguments(shoyu_debug_cflags, language: 'c') + +shoyu_binary_version = '0.1.0' + +shoyu_binary_age = 100 * shoyu_minor_version + shoyu_micro_version + +shoyu_soversion = '0' +shoyu_library_version = '1.@0@.@1@'.format(shoyu_binary_age - shoyu_interface_age, shoyu_interface_age) + +shoyu_api_version = '0.1' + +os_unix = false +os_linux = false +os_win32 = false +os_darwin = false + +cc = meson.get_compiler('c') + +if host_machine.system() == 'darwin' + if not cc.compiles(''' + #include + #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101500L + # error message "Minimal macOS SDK version not met" + #endif + ''', + name: 'macOS SDK version >= 10.15', + ) + error('macOS SDK 10.15 or newer is required to build GTK') endif - if vapigen.found() - libshoyu_gtk3_vapi = gnome.generate_vapi( - 'shoyu-gtk3', - sources: libshoyu_gtk3_gir[0], - packages: 'gtk+-3.0', - install: true) + os_darwin = true +elif host_machine.system() == 'windows' + os_win32 = true +elif host_machine.system() == 'linux' + os_linux = true +endif +os_unix = not os_win32 + +shoyu_prefix = get_option('prefix') +shoyu_includedir = join_paths(shoyu_prefix, get_option('includedir')) +shoyu_libdir = join_paths(shoyu_prefix, get_option('libdir')) +shoyu_datadir = join_paths(shoyu_prefix, get_option('datadir')) +shoyu_localedir = join_paths(shoyu_prefix, get_option('localedir')) +shoyu_sysconfdir = join_paths(shoyu_prefix, get_option('sysconfdir')) + +cdata = configuration_data() +cdata.set_quoted('PACKAGE_VERSION', meson.project_version()) +cdata.set_quoted('SHOYU_LOCALEDIR', shoyu_localedir) +cdata.set_quoted('SHOYU_DATADIR', shoyu_datadir) +cdata.set_quoted('SHOYU_LIBDIR', shoyu_libdir) +cdata.set_quoted('SHOYU_SYSCONFDIR', shoyu_sysconfdir) +cdata.set_quoted('GETTEXT_PACKAGE', 'shoyu') + +if shoyu_minor_version.is_even() + cdata.set('GLIB_DISABLE_DEPRECATION_WARNINGS', 1) +endif + +if cc.get_id() == 'gcc' or cc.get_id() == 'clang' + test_cflags = [ + '-fno-strict-aliasing', + '-Wno-c++11-extensions', + '-Wno-missing-include-dirs', + '-Wno-typedef-redefinition', + '-Wno-tautological-constant-out-of-range-compare', + '-Wduplicated-branches', + '-Wduplicated-cond', + '-Wformat=2', + '-Wformat-nonliteral', + '-Wformat-security', + '-Wignored-qualifiers', + '-Wimplicit-function-declaration', + '-Wlogical-op', + '-Wmisleading-indentation', + '-Wmissing-format-attribute', + '-Wmissing-include-dirs', + '-Wmissing-noreturn', + '-Wnested-externs', + '-Wold-style-definition', + '-Wpointer-arith', + '-Wshadow', + '-Wstrict-prototypes', + '-Wswitch-default', + '-Wswitch-enum', + '-Wundef', + '-Wuninitialized', + '-Wunused', + ] + + extra_warnings = [ + 'address', + 'array-bounds', + 'empty-body', + 'enum-int-mismatch', + 'implicit', + 'implicit-fallthrough', # For non-gcc + 'implicit-fallthrough=5', # For GCC, only recognize the attribute and no comments + 'init-self', + 'int-to-pointer-cast', + 'main', + 'missing-braces', + 'missing-declarations', + 'missing-prototypes', + 'nonnull', + 'override-init', + 'pointer-to-int-cast', + 'redundant-decls', + 'return-type', + 'sequence-point', + 'trigraphs', + 'vla', + 'write-strings', + ] + + + if get_option('buildtype').startswith('debug') + foreach warning: extra_warnings + test_cflags += '-Werror=@0@'.format(warning) + endforeach + else + foreach warning: extra_warnings + test_cflags += '-W@0@'.format(warning) + endforeach endif - libshoyu_gtk3_dep = declare_dependency( - link_with: [libshoyu_gtk3], - include_directories: ['include'], - dependencies: libshoyu_gtk3_deps) - - exe_simple_example_gtk3 = executable('shoyu-simple-example-gtk3', - 'example/simple.c', - c_args: ['-DWLR_USE_UNSTABLE=1'], - dependencies: [libshoyu_gtk3_dep]) - - if valac.found() - exe_vala_example_gtk3 = executable('shoyu-vala-example-gtk3', - 'example/gtk3.vala', - c_args: ['-DWLR_USE_UNSTABLE=1'], - dependencies: [libshoyu_gtk3_dep, libshoyu_gtk3_vapi]) + if cc.get_id() == 'gcc' + test_cflags += ['-Wcast-align'] # This warns too much on clang endif -endif -if gtk4.found() - libshoyu_gtk4 = shared_library('shoyu-gtk4', - libshoyu_sources, - c_args: ['-DWLR_USE_UNSTABLE=1'], - include_directories: ['include'], - dependencies: [libshoyu_deps, gtk4], - install: true) - libshoyu_gtk4_dep = declare_dependency( - link_with: [libshoyu_gtk4], - include_directories: ['include'], - dependencies: [libshoyu_deps, gtk4]) - - if gobject_introspection.found() - libshoyu_gtk4_gir = gnome.generate_gir( - libshoyu_gtk4, - dependencies: [libshoyu_deps, gtk4], - sources: libshoyu_sources + libshoyu_headers, - extra_args: ['-DWLR_USE_UNSTABLE=1'], - namespace: 'ShoyuGtk4', - nsversion: '0.1', - identifier_prefix: 'Shoyu', - symbol_prefix: 'shoyu', - export_packages: 'shoyu-gtk4', - includes: ['Gtk-4.0'], - header: 'shoyu/shoyu.h', - install: true) + if not shoyu_debug_cflags.contains('-DG_DISABLE_ASSERT') + test_cflags += ['-Wnull-dereference'] # Too noisy when assertions are disabled endif +else + test_cflags = [] +endif - if vapigen.found() - libshoyu_gtk4_vapi = gnome.generate_vapi( - 'shoyu-gtk4', - sources: libshoyu_gtk4_gir[0], - packages: 'gtk4', - install: true) +if get_option('default_library') != 'static' + if os_win32 + cdata.set('DLL_EXPORT', true) + else + test_cflags += ['-fvisibility=hidden'] endif +endif + +common_cflags = cc.get_supported_arguments(test_cflags) + +common_ldflags = cc.get_supported_link_arguments([ + '-Wl,-Bsymbolic', + '-Wl,-z,relro', + '-Wl,-z,now', +]) + +conf_inc = include_directories('.') +proto_inc = include_directories('protocols') +libcompositor_inc = include_directories('shoyu-compositor') +libshell_gtk3_inc = include_directories('shoyu-shell-gtk3') +libshell_gtk4_inc = include_directories('shoyu-shell-gtk4') + +gen_visibility_macros = find_program('build-aux/meson/gen-visibility-macros.py') + +# GObject Introspection +gir = find_program('g-ir-scanner', required : get_option('introspection')) + +if not gir.found() and get_option('introspection').enabled() + error('Introspection enabled, but g-ir-scanner not found.') +endif - exe_simple_example_gtk4 = executable('shoyu-simple-example-gtk4', - 'example/simple.c', - c_args: ['-DWLR_USE_UNSTABLE=1'], - dependencies: [libshoyu_gtk4_dep]) +build_gir = gir.found() and (get_option('introspection').enabled() or + (get_option('introspection').allowed() and get_option('documentation'))) + +# Vala API Generation +vapigen = find_program('vapigen', required: get_option('vapigen')) + +if not vapigen.found() and get_option('vapigen').enabled() + error('Vala API generation enabled, but vapigen not found.') +endif + +build_vapi = vapigen.found() and (get_option('introspection').enabled() or + (get_option('introspection').allowed() and build_gir)) + +# Base dependencies +gobject = dependency('gobject-2.0', version: '>= 2.81.0') +gio = dependency('gio-2.0', version: '>= 2.81.0') +wlroots = dependency('wlroots-0.18', version: '>= 0.18.0') +wayland_server = dependency('wayland-server', version: '>= 1.23.0') +wayland_client = dependency('wayland-client', version: '>= 1.23.0') + +shoyu_shells = [] + +# GTK 3 +gtk3 = dependency('gtk+-3.0', required: get_option('gtk3')) + +if not gtk3.found() and get_option('gtk3').enabled() + error('GTK 3 enabled, but the library was not found.') endif + +build_gtk3 = gtk3.found() and (get_option('gtk3').enabled() or get_option('gtk3').allowed()) +gdk3_wayland = dependency('gdk-wayland-3.0', required: build_gtk3) + +# GTK 4 +if build_gtk3 + shoyu_shells += 'gtk3' +endif + +gtk4 = dependency('gtk4', required: get_option('gtk4')) + +if not gtk4.found() and get_option('gtk4').enabled() + error('GTK 3 enabled, but the library was not found.') +endif + +build_gtk4 = gtk4.found() and (get_option('gtk4').enabled() or get_option('gtk4').allowed()) +gtk4_wayland = dependency('gtk4-wayland', required: build_gtk4) + +if build_gtk4 + shoyu_shells += 'gtk4' +endif + +configure_file(output: 'shoyu-config.h', + configuration: cdata) + +# Add subprojects +subdir('protocols') + +subdir('shoyu-compositor/version') +subdir('shoyu-compositor') +subdir('shoyu-compositor-runner') + +foreach shell : shoyu_shells + subdir('shoyu-shell-@0@/version'.format(shell)) + subdir('shoyu-shell-@0@'.format(shell)) +endforeach + +if get_option('build-examples') + subdir('examples') +endif + +subdir('po') + +# Documentation +subdir('docs/reference') + +# Summary +summary('Shells', shoyu_shells, section: 'Components') + +summary('Compiler', cc.get_id(), section: 'Toolchain') +summary('Linker', cc.get_linker_id(), section: 'Toolchain') + +summary('Debugging', get_option('debug'), section: 'Build') +summary('Optimization', get_option('optimization'), section: 'Build') +summary('Introspection', build_gir, section: 'Build') +summary('Vala API Generation', build_vapi, section: 'Build') +summary('Documentation', get_option('documentation'), section: 'Build') +summary('Examples', get_option('build-examples'), section: 'Build') + +summary('prefix', shoyu_prefix, section: 'Directories') +summary('includedir', shoyu_includedir, section: 'Directories') +summary('libdir', shoyu_libdir, section: 'Directories') +summary('datadir', shoyu_datadir, section: 'Directories') diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..b31f097 --- /dev/null +++ b/meson.options @@ -0,0 +1,33 @@ +option('introspection', + type: 'feature', + value: 'auto', + yield: true, + description: 'Build introspection data (requires gobject-introspection)') + +option('vapigen', + type: 'feature', + value: 'auto', + yield: true, + description: 'Generate vapi files') + +option('documentation', + type: 'boolean', + value: false, + description: 'Build API reference and tools documentation') + +option('gtk3', + type: 'feature', + value: 'auto', + yield: true, + description: 'Build the GTK 3 shell library') + +option('gtk4', + type: 'feature', + value: 'auto', + yield: true, + description: 'Build the GTK 4 shell library') + +option('build-examples', + type: 'boolean', + value: true, + description: 'Build examples') diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..c574d07 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1 @@ +en diff --git a/po/POTFILES b/po/POTFILES new file mode 100644 index 0000000..9cfee5d --- /dev/null +++ b/po/POTFILES @@ -0,0 +1,9 @@ +shoyu-compositor-runner/main.c +shoyu-shell-gtk3/version.c +shoyu-shell-gtk4/version.c +shoyu-compositor/compositor.c +shoyu-compositor/wayland-event-source.c +shoyu-compositor/version.c +shoyu-compositor/main.c +shoyu-compositor/output.c +shoyu-compositor/input.c diff --git a/po/en.po b/po/en.po new file mode 100644 index 0000000..1d79901 --- /dev/null +++ b/po/en.po @@ -0,0 +1,42 @@ +# English translation of Shoyu. +# Copyright (C) 2024 Midstall Software. +# This file is distributed under the same license as the shoyu package. +# Tristan Ross , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: shoyu\n" +"Report-Msgid-Bugs-To: \"https://github.com/MidstallSoftware/shoyu/issues/\"\n" +"POT-Creation-Date: 2024-11-15 23:08-0800\n" +"PO-Revision-Date: 2024-11-15 23:08-0800\n" +"Last-Translator: Tristan Ross \n" +"Language-Team: en\n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: shoyu-compositor/compositor.c:42 shoyu-compositor/compositor.c:100 +#, c-format +msgid "Outputs changed (old: %u, new: %u)" +msgstr "" + +#: shoyu-compositor/compositor.c:49 +#, c-format +msgid "Destroyed ShoyuOutput#%p" +msgstr "" + +#: shoyu-compositor/compositor.c:60 +#, c-format +msgid "Inputs changed (old: %u, new: %u)" +msgstr "" + +#: shoyu-compositor/compositor.c:63 +#, c-format +msgid "Destroyed ShoyuInput#%p" +msgstr "" + +#: shoyu-compositor/compositor.c:103 +#, c-format +msgid "Created ShoyuOutput#%p" +msgstr "" diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 0000000..7503cc8 --- /dev/null +++ b/po/meson.build @@ -0,0 +1,30 @@ +i18n = import('i18n') + +xgettext_args = [ + '--msgid-bugs-address="https://github.com/MidstallSoftware/shoyu/issues/"', + '--add-comments', + '--from-code=utf-8', + '--flag=g_dngettext:2:pass-c-format', + '--flag=g_strdup_printf:1:c-format', + '--flag=g_string_printf:2:c-format', + '--flag=g_string_append_printf:2:c-format', + '--flag=g_error_new:3:c-format', + '--flag=g_set_error:4:c-format', + '--flag=g_markup_printf_escaped:1:c-format', + '--flag=g_log:3:c-format', + '--flag=g_warning:1:c-format', + '--flag=g_error:1:c-format', + '--flag=g_critical:1:c-format', + '--flag=g_message:1:c-format', + '--flag=g_debug:1:c-format', + '--flag=g_print:1:c-format', + '--flag=g_printerr:1:c-format', + '--flag=g_printf:1:c-format', + '--flag=g_fprintf:2:c-format', + '--flag=g_sprintf:2:c-format', + '--flag=g_snprintf:3:c-format', + '--flag=g_scanner_error:2:c-format', + '--flag=g_scanner_warn:2:c-format', +] + +i18n.gettext('shoyu', args: xgettext_args) diff --git a/po/shoyu.pot b/po/shoyu.pot new file mode 100644 index 0000000..5789ebe --- /dev/null +++ b/po/shoyu.pot @@ -0,0 +1,43 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the shoyu package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: shoyu\n" +"Report-Msgid-Bugs-To: \"https://github.com/MidstallSoftware/shoyu/issues/\"\n" +"POT-Creation-Date: 2024-11-15 23:08-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: shoyu-compositor/compositor.c:42 shoyu-compositor/compositor.c:100 +#, c-format +msgid "Outputs changed (old: %u, new: %u)" +msgstr "" + +#: shoyu-compositor/compositor.c:49 +#, c-format +msgid "Destroyed ShoyuOutput#%p" +msgstr "" + +#: shoyu-compositor/compositor.c:60 +#, c-format +msgid "Inputs changed (old: %u, new: %u)" +msgstr "" + +#: shoyu-compositor/compositor.c:63 +#, c-format +msgid "Destroyed ShoyuInput#%p" +msgstr "" + +#: shoyu-compositor/compositor.c:103 +#, c-format +msgid "Created ShoyuOutput#%p" +msgstr "" diff --git a/protocols/meson.build b/protocols/meson.build new file mode 100644 index 0000000..8ee2eb5 --- /dev/null +++ b/protocols/meson.build @@ -0,0 +1,32 @@ +protocols = [ + wayland.find_protocol('xdg-shell'), + join_paths(meson.current_source_dir(), 'shoyu-shell.xml'), +] + +libwayland_client_sources = [] +libwayland_server_sources = [] + +foreach protocol : protocols + generated = wayland.scan_xml(protocol, + client: true, + server: true, + public: true) + + libwayland_client_sources += [ + generated[0], + generated[1] + ] + + libwayland_server_sources += [ + generated[0], + generated[2] + ] +endforeach + +libwayland_client = static_library( + 'shoyu-wayland-client', + sources: libwayland_client_sources) + +libwayland_server = static_library( + 'shoyu-wayland-server', + sources: libwayland_server_sources) diff --git a/protocols/shoyu-shell.xml b/protocols/shoyu-shell.xml new file mode 100644 index 0000000..52fc33d --- /dev/null +++ b/protocols/shoyu-shell.xml @@ -0,0 +1,60 @@ + + + + + The global interface exposing Shoyu Shell capabilities and control. + + + + + + + + + + + + + + + + + + + + + + + + + + Sets the surface to render as the output + + + + + + + + + + + + + + + + + + An event which says the surface is ready for capture. + + + + + + + Emitted when the toplevel is destroyed + + + + diff --git a/shoyu-compositor-runner/main.c b/shoyu-compositor-runner/main.c new file mode 100644 index 0000000..7d93678 --- /dev/null +++ b/shoyu-compositor-runner/main.c @@ -0,0 +1,20 @@ +#include + +static void activate(GApplication *application) { + ShoyuCompositor *compositor = + shoyu_compositor_new_with_application(application); + shoyu_compositor_start(compositor); +} + +int main(int argc, char **argv) { + shoyu_init(); + + GApplication *app = g_application_new("com.midstall.shoyu.Compositor", + G_APPLICATION_NON_UNIQUE); + + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + + int status = g_application_run(app, argc, argv); + g_object_unref(app); + return status; +} diff --git a/shoyu-compositor-runner/meson.build b/shoyu-compositor-runner/meson.build new file mode 100644 index 0000000..e085102 --- /dev/null +++ b/shoyu-compositor-runner/meson.build @@ -0,0 +1,4 @@ +shoyu_compositor_runner_exe = executable('shoyu-compositor-runner', + sources: ['main.c'], + dependencies: [libcompositor_dep], + install: true) diff --git a/shoyu-compositor/compositor-private.h b/shoyu-compositor/compositor-private.h new file mode 100644 index 0000000..949004b --- /dev/null +++ b/shoyu-compositor/compositor-private.h @@ -0,0 +1,241 @@ +#pragma once + +#include "compositor.h" +#include "input.h" +#include "output.h" +#include "shell.h" +#include "surface.h" +#include "xdg-toplevel.h" + +#include +#include +#include +#include +#include + +struct _ShoyuCompositor { + GObject parent_instance; + + GApplication *application; + ShoyuShell *shell; + + struct wl_display *wl_display; + GSource *wl_source; + + struct wlr_backend *wlr_backend; + struct wlr_renderer *wlr_renderer; + struct wlr_allocator *wlr_allocator; + + struct wlr_xdg_shell *wlr_xdg_shell; + struct wlr_output_layout *output_layout; + + struct wlr_compositor *wlr_compositor; + + GList *surfaces; + struct wl_listener new_surface; + + GList *outputs; + struct wl_listener new_output; + + GList *inputs; + struct wl_listener new_input; + + GList *xdg_toplevels; + struct wl_listener new_xdg_toplevel; + + const char *socket; +}; + +struct _ShoyuCompositorClass { + GObjectClass parent_class; + + /** + * ShoyuCompositor:output_type: + * + * The type to create when a #ShoyuOutput is being created. + */ + GType output_type; + + /** + * ShoyuCompositor:input_type: + * + * The type to create when a #ShoyuInput is being created. + */ + GType input_type; + + /** + * ShoyuCompositor:surface_type: + * + * The type to create when a #ShoyuSurface is being created. + */ + GType surface_type; + + /** + * ShoyuCompositor:xdg_toplevel_type: + * + * The type to create when a #ShoyuXdgToplevel is being created. + */ + GType xdg_toplevel_type; + + /** + * ShoyuCompositor:create_backend: + * @self: (not nullable): The object instance + * @event_loop: (not nullable): The Wayland event loop for the display + * server. + * + * Creates a wlroots backend for the compositor. + * + * Returns: (not nullable) (transfer full): The wlroots backend to use. + */ + struct wlr_backend *(*create_backend)(ShoyuCompositor *self, + struct wl_event_loop *event_loop); + + /** + * ShoyuCompositor:create_renderer: + * @self: (not nullable): The object instance + * @backend: (not nullable): The wlroots backend being used + * + * Creates a wlroots renderer for the compositor. + * + * Returns: (not nullable) (transfer full): The wlroots renderer to use. + */ + struct wlr_renderer *(*create_renderer)(ShoyuCompositor *self, + struct wlr_backend *backend); + + /** + * ShoyuCompositor:create_allocator: + * @self: (not nullable): The object instance + * @backend: (not nullable): The wlroots backend being used + * @renderer: (not nullable): The wlroots renderer being used + * + * Creates a wlroots allocator for the compositor. + * + * Returns: (not nullable) (transfer full): The wlroots allocator to use. + */ + struct wlr_allocator *(*create_allocator)(ShoyuCompositor *self, + struct wlr_backend *backend, + struct wlr_renderer *renderer); + + /** + * ShoyuOutput:create_output: + * @self: (not nullable): The object instance + * @output: (not nullable): The wlroots output + * + * Creates a #ShoyuOutput for the compositor. + * + * Returns: (nullable) (transfer full): A #ShoyuOutput. + */ + ShoyuOutput *(*create_output)(ShoyuCompositor *self, + struct wlr_output *output); + + /** + * ShoyuOutput:create_input: + * @self: (not nullable): The object instance + * @output: (not nullable): The wlroots input + * + * Creates a #ShoyuInput for the compositor. + * + * Returns: (nullable) (transfer full): A #ShoyuInput. + */ + ShoyuInput *(*create_input)(ShoyuCompositor *self, + struct wlr_input_device *device); + + /** + * ShoyuOutput:create_surface: + * @self: (not nullable): The object instance + * @surface: (not nullable): The wlroots surface + * + * Creates a #ShoyuSurface for the compositor. + * + * Returns: (nullable) (transfer full): A #ShoyuSurface. + */ + ShoyuSurface *(*create_surface)(ShoyuCompositor *self, + struct wlr_surface *surface); + + /** + * ShoyuOutput:create_xdg_toplevel: + * @self: (not nullable): The object instance + * @toplevel: (not nullable): The wlroots XDG toplevel + * + * Creates a #ShoyuXdgToplevel for the compositor. + * + * Returns: (nullable) (transfer full): A #ShoyuXdgToplevel. + */ + ShoyuXdgToplevel *(*create_xdg_toplevel)(ShoyuCompositor *self, + struct wlr_xdg_toplevel *toplevel); + + /** + * ShoyuCompositor:output_added: + * @self: (not nullable): The object instance + * @output: (not nullable): The output which was added + */ + void (*output_added)(ShoyuCompositor *self, ShoyuOutput *output); + + /** + * ShoyuCompositor:output_removed: + * @self: (not nullable): The object instance + * @output: (not nullable): The output which was removed + */ + void (*output_removed)(ShoyuCompositor *self, ShoyuOutput *output); + + /** + * ShoyuCompositor:input_added: + * @self: (not nullable): The object instance + * @input: (not nullable): The input which was added + */ + void (*input_added)(ShoyuCompositor *self, ShoyuInput *input); + + /** + * ShoyuCompositor:input_removed: + * @self: (not nullable): The object instance + * @input: (not nullable): The input which was removed + */ + void (*input_removed)(ShoyuCompositor *self, ShoyuInput *input); + + /** + * ShoyuCompositor:surface_added: + * @self: (not nullable): The object instance + * @surface: (not nullable): The surface which was added + */ + void (*surface_added)(ShoyuCompositor *self, ShoyuSurface *surface); + + /** + * ShoyuCompositor:surface_removed: + * @self: (not nullable): The object instance + * @surface: (not nullable): The surface which was removed + */ + void (*surface_removed)(ShoyuCompositor *self, ShoyuSurface *surface); + + /** + * ShoyuCompositor:input_added: + * @self: (not nullable): The object instance + * @toplevel: (not nullable): The toplevel which was added + */ + void (*xdg_toplevel_added)(ShoyuCompositor *self, + ShoyuXdgToplevel *toplevel); + + /** + * ShoyuCompositor:output_removed: + * @self: (not nullable): The object instance + * @toplevel: (not nullable): The toplevel which was removed + */ + void (*xdg_toplevel_removed)(ShoyuCompositor *self, + ShoyuXdgToplevel *toplevel); + + /** + * ShoyuCompositor::started: + * @self: (not nullable): The object instance + */ + void (*started)(ShoyuCompositor *self); +}; + +ShoyuOutput *shoyu_compositor_get_output(ShoyuCompositor *self, + struct wlr_output *wlr_output); +ShoyuSurface *shoyu_compositor_get_surface(ShoyuCompositor *self, + struct wlr_surface *wlr_surface); + +gboolean +shoyu_compositor_is_xdg_toplevel_claimed(ShoyuCompositor *self, + struct xdg_toplevel *xdg_toplevel); +ShoyuOutput *shoyu_compositor_get_xdg_toplevel_claimed_output( + ShoyuCompositor *self, struct xdg_toplevel *xdg_toplevel); diff --git a/shoyu-compositor/compositor.c b/shoyu-compositor/compositor.c new file mode 100644 index 0000000..051a020 --- /dev/null +++ b/shoyu-compositor/compositor.c @@ -0,0 +1,719 @@ +#include "shoyu-config.h" + +#include "compositor-private.h" +#include "input-private.h" +#include "output-private.h" +#include "shell-private.h" +#include "surface-private.h" +#include "wayland-event-source.h" +#include "xdg-toplevel-private.h" + +#include +#include +#include +#include + +/** + * ShoyuCompositor: + * + * A wlroots based Wayland compositor. + */ + +enum { + PROP_0 = 0, + PROP_APPLICATION, + PROP_SOCKET, + PROP_SHELL, + N_PROPERTIES, + + SIG_OUTPUT_ADDED = 0, + SIG_OUTPUT_REMOVED, + SIG_INPUT_ADDED, + SIG_INPUT_REMOVED, + SIG_SURFACE_ADDED, + SIG_SURFACE_REMOVED, + SIG_XDG_TOPLEVEL_ADDED, + SIG_XDG_TOPLEVEL_REMOVED, + SIG_STARTED, + N_SIGNALS, +}; + +static GParamSpec *shoyu_compositor_props[N_PROPERTIES] = { + NULL, +}; +static guint shoyu_compositor_sigs[N_SIGNALS]; + +G_DEFINE_TYPE(ShoyuCompositor, shoyu_compositor, G_TYPE_OBJECT) + +static void shoyu_compositor_destroy_output(ShoyuOutput *output, + ShoyuCompositor *self) { + g_signal_emit(self, shoyu_compositor_sigs[SIG_OUTPUT_REMOVED], 0, output); + + guint len = g_list_length(self->outputs); + self->outputs = g_list_remove(self->outputs, output); + + guint new_len = g_list_length(self->outputs); + g_debug(_("Outputs changed (old: %u, new: %u)"), len, new_len); + g_assert(new_len < len); + + if (self->application != NULL) { + g_application_release(self->application); + } + + g_debug(_("Destroyed ShoyuOutput#%p"), output); + g_object_unref(output); +} + +static void shoyu_compositor_destroy_input(ShoyuInput *input, + ShoyuCompositor *self) { + g_signal_emit(self, shoyu_compositor_sigs[SIG_INPUT_REMOVED], 0, input); + + guint len = g_list_length(self->inputs); + self->inputs = g_list_remove(self->inputs, input); + + guint new_len = g_list_length(self->inputs); + g_debug(_("Inputs changed (old: %u, new: %u)"), len, new_len); + g_assert(new_len < len); + + g_debug(_("Destroyed ShoyuInput#%p"), input); + g_object_unref(input); +} + +static void shoyu_compositor_destroy_surface(ShoyuSurface *surface, + ShoyuCompositor *self) { + g_signal_emit(self, shoyu_compositor_sigs[SIG_SURFACE_REMOVED], 0, surface); + + guint len = g_list_length(self->surfaces); + self->surfaces = g_list_remove(self->surfaces, surface); + + guint new_len = g_list_length(self->surfaces); + g_debug(_("Surfaces changed (old: %u, new: %u)"), len, new_len); + g_assert(new_len < len); + + g_debug(_("Destroyed ShoyuSurface#%p"), surface); + g_object_unref(surface); +} + +static void shoyu_compositor_destroy_xdg_toplevel(ShoyuXdgToplevel *toplevel, + ShoyuCompositor *self) { + g_signal_emit(self, shoyu_compositor_sigs[SIG_XDG_TOPLEVEL_REMOVED], 0, + toplevel); + + guint len = g_list_length(self->xdg_toplevels); + self->xdg_toplevels = g_list_remove(self->xdg_toplevels, toplevel); + + guint new_len = g_list_length(self->xdg_toplevels); + g_debug(_("XDG toplevels changed (old: %u, new: %u)"), len, new_len); + g_assert(new_len < len); + + g_debug(_("Destroyed ShoyuXdgToplevel#%p"), toplevel); + g_object_unref(toplevel); +} + +static void shoyu_compositor_new_output(struct wl_listener *listener, + void *data) { + ShoyuCompositor *self = wl_container_of(listener, self, new_output); + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + struct wlr_output *wlr_output = data; + + wlr_output_init_render(wlr_output, self->wlr_allocator, self->wlr_renderer); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + + struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); + if (mode != NULL) { + wlr_output_state_set_mode(&state, mode); + } + + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + g_return_if_fail(class->create_output != NULL); + + ShoyuOutput *output = class->create_output(self, wlr_output); + if (output == NULL) + return; + + g_signal_connect(output, "destroy", + G_CALLBACK(shoyu_compositor_destroy_output), self); + + shoyu_output_realize(output, wlr_output); + + guint len = g_list_length(self->outputs); + self->outputs = g_list_append(self->outputs, output); + + guint new_len = g_list_length(self->outputs); + g_debug(_("Outputs changed (old: %u, new: %u)"), len, new_len); + g_assert(new_len > len); + + g_debug(_("Created ShoyuOutput#%p"), output); + + if (self->application != NULL) { + g_application_hold(self->application); + } + + g_signal_emit(self, shoyu_compositor_sigs[SIG_OUTPUT_ADDED], 0, output); +} + +static void shoyu_compositor_new_input(struct wl_listener *listener, + void *data) { + ShoyuCompositor *self = wl_container_of(listener, self, new_input); + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + struct wlr_input_device *wlr_input_device = data; + + g_return_if_fail(class->create_input != NULL); + + ShoyuInput *input = class->create_input(self, wlr_input_device); + if (input == NULL) + return; + + g_signal_connect(input, "destroy", G_CALLBACK(shoyu_compositor_destroy_input), + self); + + shoyu_input_realize(input, wlr_input_device); + + guint len = g_list_length(self->inputs); + self->inputs = g_list_append(self->inputs, input); + + guint new_len = g_list_length(self->inputs); + g_debug("Inputs changed (old: %u, new: %u)", len, new_len); + g_assert(new_len > len); + + g_debug("Created ShoyuInput#%p", input); + g_signal_emit(self, shoyu_compositor_sigs[SIG_INPUT_ADDED], 0, input); +} + +static void shoyu_compositor_new_surface(struct wl_listener *listener, + void *data) { + ShoyuCompositor *self = wl_container_of(listener, self, new_surface); + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + struct wlr_surface *wlr_surface = data; + + g_return_if_fail(class->create_surface != NULL); + + ShoyuSurface *surface = class->create_surface(self, wlr_surface); + if (surface == NULL) + return; + + g_signal_connect(surface, "destroy", + G_CALLBACK(shoyu_compositor_destroy_surface), self); + + shoyu_surface_realize(surface, wlr_surface); + + guint len = g_list_length(self->surfaces); + self->surfaces = g_list_append(self->surfaces, surface); + + guint new_len = g_list_length(self->surfaces); + g_debug("Surfaces changed (old: %u, new: %u)", len, new_len); + g_assert(new_len > len); + + g_debug("Created ShoyuSurface#%p", surface); + g_signal_emit(self, shoyu_compositor_sigs[SIG_SURFACE_ADDED], 0, surface); +} + +static void shoyu_compositor_new_xdg_toplevel(struct wl_listener *listener, + void *data) { + ShoyuCompositor *self = wl_container_of(listener, self, new_xdg_toplevel); + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + struct wlr_xdg_toplevel *wlr_xdg_toplevel = data; + + g_return_if_fail(class->create_xdg_toplevel != NULL); + + ShoyuXdgToplevel *toplevel = + class->create_xdg_toplevel(self, wlr_xdg_toplevel); + if (toplevel == NULL) + return; + + g_signal_connect(toplevel, "destroy", + G_CALLBACK(shoyu_compositor_destroy_xdg_toplevel), self); + + shoyu_xdg_toplevel_realize(toplevel, wlr_xdg_toplevel); + + guint len = g_list_length(self->xdg_toplevels); + self->xdg_toplevels = g_list_append(self->xdg_toplevels, toplevel); + + guint new_len = g_list_length(self->xdg_toplevels); + g_debug("XDG toplevels changed (old: %u, new: %u)", len, new_len); + g_assert(new_len > len); + + g_debug("Created ShoyuXdgToplevel#%p", toplevel); + g_signal_emit(self, shoyu_compositor_sigs[SIG_XDG_TOPLEVEL_ADDED], 0, + toplevel); +} + +static void shoyu_compositor_constructed(GObject *object) { + G_OBJECT_CLASS(shoyu_compositor_parent_class)->constructed(object); + + ShoyuCompositor *self = SHOYU_COMPOSITOR(object); + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + g_assert(class->create_backend != NULL); + self->wlr_backend = + class->create_backend(self, wl_display_get_event_loop(self->wl_display)); + g_assert(self->wlr_backend != NULL); + + g_assert(class->create_renderer != NULL); + self->wlr_renderer = class->create_renderer(self, self->wlr_backend); + g_assert(self->wlr_renderer != NULL); + + wlr_renderer_init_wl_display(self->wlr_renderer, self->wl_display); + + g_assert(class->create_allocator != NULL); + self->wlr_allocator = + class->create_allocator(self, self->wlr_backend, self->wlr_renderer); + g_assert(self->wlr_allocator != NULL); + + self->wlr_compositor = + wlr_compositor_create(self->wl_display, 5, self->wlr_renderer); + g_assert(self->wlr_compositor != NULL); + + self->new_surface.notify = shoyu_compositor_new_surface; + wl_signal_add(&self->wlr_compositor->events.new_surface, &self->new_surface); + + self->new_output.notify = shoyu_compositor_new_output; + wl_signal_add(&self->wlr_backend->events.new_output, &self->new_output); + + self->new_input.notify = shoyu_compositor_new_input; + wl_signal_add(&self->wlr_backend->events.new_input, &self->new_input); + + self->new_xdg_toplevel.notify = shoyu_compositor_new_xdg_toplevel; + wl_signal_add(&self->wlr_xdg_shell->events.new_toplevel, + &self->new_xdg_toplevel); +} + +static void shoyu_compositor_finalize(GObject *object) { + ShoyuCompositor *self = SHOYU_COMPOSITOR(object); + + g_clear_list(&self->outputs, (GDestroyNotify)g_object_unref); + g_clear_list(&self->inputs, (GDestroyNotify)g_object_unref); + g_clear_list(&self->surfaces, (GDestroyNotify)g_object_unref); + g_clear_list(&self->xdg_toplevels, (GDestroyNotify)g_object_unref); + + g_clear_object(&self->shell); + + g_clear_pointer(&self->wlr_allocator, (GDestroyNotify)wlr_allocator_destroy); + g_clear_pointer(&self->wlr_renderer, (GDestroyNotify)wlr_renderer_destroy); + g_clear_pointer(&self->wlr_backend, (GDestroyNotify)wlr_backend_destroy); + g_clear_pointer(&self->wl_source, (GDestroyNotify)g_source_destroy); + g_clear_pointer(&self->wl_display, (GDestroyNotify)wl_display_destroy); + + g_clear_object(&self->application); + + G_OBJECT_CLASS(shoyu_compositor_parent_class)->finalize(object); +} + +static void shoyu_compositor_set_property(GObject *object, guint prop_id, + const GValue *value, + GParamSpec *pspec) { + ShoyuCompositor *self = SHOYU_COMPOSITOR(object); + + switch (prop_id) { + case PROP_APPLICATION: + self->application = g_value_dup_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_compositor_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) { + ShoyuCompositor *self = SHOYU_COMPOSITOR(object); + + switch (prop_id) { + case PROP_APPLICATION: + g_value_set_object(value, G_OBJECT(self->application)); + break; + case PROP_SOCKET: + g_value_set_string(value, self->socket); + break; + case PROP_SHELL: + g_value_set_object(value, G_OBJECT(self->shell)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static struct wlr_backend * +shoyu_compositor_real_create_backend(ShoyuCompositor *self, + struct wl_event_loop *event_loop) { + return wlr_backend_autocreate(event_loop, NULL); +} + +static struct wlr_renderer * +shoyu_compositor_real_create_renderer(ShoyuCompositor *self, + struct wlr_backend *backend) { + return wlr_renderer_autocreate(backend); +} + +static struct wlr_allocator * +shoyu_compositor_real_create_allocator(ShoyuCompositor *self, + struct wlr_backend *backend, + struct wlr_renderer *renderer) { + return wlr_allocator_autocreate(backend, renderer); +} + +static ShoyuOutput * +shoyu_compositor_real_create_output(ShoyuCompositor *self, + struct wlr_output *wlr_output) { + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + ShoyuOutput *output = + g_object_new(class->output_type, "compositor", self, NULL); + g_return_val_if_fail(output != NULL, NULL); + return output; +} + +static ShoyuInput * +shoyu_compositor_real_create_input(ShoyuCompositor *self, + struct wlr_input_device *input_device) { + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + ShoyuInput *input = g_object_new(class->input_type, "compositor", self, NULL); + g_return_val_if_fail(input != NULL, NULL); + return input; +} + +static ShoyuSurface * +shoyu_compositor_real_create_surface(ShoyuCompositor *self, + struct wlr_surface *wlr_surface) { + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + ShoyuSurface *surface = + g_object_new(class->surface_type, "compositor", self, NULL); + g_return_val_if_fail(surface != NULL, NULL); + return surface; +} + +static ShoyuXdgToplevel *shoyu_compositor_real_create_xdg_toplevel( + ShoyuCompositor *self, struct wlr_xdg_toplevel *wlr_xdg_toplevel) { + ShoyuCompositorClass *class = SHOYU_COMPOSITOR_GET_CLASS(self); + + ShoyuXdgToplevel *toplevel = + g_object_new(class->xdg_toplevel_type, "compositor", self, NULL); + g_return_val_if_fail(toplevel != NULL, NULL); + return toplevel; +} + +static void shoyu_compositor_class_init(ShoyuCompositorClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->constructed = shoyu_compositor_constructed; + object_class->finalize = shoyu_compositor_finalize; + object_class->set_property = shoyu_compositor_set_property; + object_class->get_property = shoyu_compositor_get_property; + + class->output_type = SHOYU_TYPE_OUTPUT; + class->input_type = SHOYU_TYPE_INPUT; + class->surface_type = SHOYU_TYPE_SURFACE; + class->xdg_toplevel_type = SHOYU_TYPE_XDG_TOPLEVEL; + + class->create_backend = shoyu_compositor_real_create_backend; + class->create_renderer = shoyu_compositor_real_create_renderer; + class->create_allocator = shoyu_compositor_real_create_allocator; + class->create_output = shoyu_compositor_real_create_output; + class->create_input = shoyu_compositor_real_create_input; + class->create_surface = shoyu_compositor_real_create_surface; + class->create_xdg_toplevel = shoyu_compositor_real_create_xdg_toplevel; + + shoyu_compositor_props[PROP_APPLICATION] = g_param_spec_object( + "application", "Gio Application", + "The application to run the compositor on.", G_TYPE_APPLICATION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + shoyu_compositor_props[PROP_SOCKET] = g_param_spec_string( + "socket", "Wayland Socket", "The name of the Wayland display socket.", + NULL, G_PARAM_READABLE); + + shoyu_compositor_props[PROP_SHELL] = g_param_spec_object( + "shell", "Shoyu Shell", "The shell to run with the compositor.", + SHOYU_TYPE_SHELL, G_PARAM_READABLE); + + g_object_class_install_properties(object_class, N_PROPERTIES, + shoyu_compositor_props); + + /** + * ShoyuCompositor::output-added: + * @compositor: the object which received the signal + * @output: a #ShoyuOutput + */ + shoyu_compositor_sigs[SIG_OUTPUT_ADDED] = + g_signal_new("output-added", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, output_added), NULL, + NULL, NULL, G_TYPE_NONE, 1, SHOYU_TYPE_OUTPUT); + + /** + * ShoyuCompositor::output-removed: + * @compositor: the object which received the signal + * @output: a #ShoyuOutput + */ + shoyu_compositor_sigs[SIG_OUTPUT_REMOVED] = + g_signal_new("output-removed", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, output_removed), NULL, + NULL, NULL, G_TYPE_NONE, 1, SHOYU_TYPE_OUTPUT); + + /** + * ShoyuCompositor::input-added: + * @compositor: the object which received the signal + * @output: a #ShoyuInput + */ + shoyu_compositor_sigs[SIG_INPUT_ADDED] = + g_signal_new("input-added", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, input_added), NULL, + NULL, NULL, G_TYPE_NONE, 1, SHOYU_TYPE_INPUT); + + /** + * ShoyuCompositor::input-removed: + * @compositor: the object which received the signal + * @output: a #ShoyuInput + */ + shoyu_compositor_sigs[SIG_INPUT_REMOVED] = + g_signal_new("input-removed", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, input_removed), NULL, + NULL, NULL, G_TYPE_NONE, 1, SHOYU_TYPE_INPUT); + + /** + * ShoyuCompositor::surface-added: + * @compositor: the object which received the signal + * @output: a #ShoyuXdgToplevel + */ + shoyu_compositor_sigs[SIG_SURFACE_ADDED] = + g_signal_new("surface-added", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, surface_added), NULL, + NULL, NULL, G_TYPE_NONE, 1, SHOYU_TYPE_SURFACE); + + /** + * ShoyuCompositor::surface-removed: + * @compositor: the object which received the signal + * @output: a #ShoyuXdgToplevel + */ + shoyu_compositor_sigs[SIG_SURFACE_REMOVED] = + g_signal_new("surface-removed", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, surface_removed), NULL, + NULL, NULL, G_TYPE_NONE, 1, SHOYU_TYPE_SURFACE); + + /** + * ShoyuCompositor::xdg-toplevel-added: + * @compositor: the object which received the signal + * @output: a #ShoyuXdgToplevel + */ + shoyu_compositor_sigs[SIG_XDG_TOPLEVEL_ADDED] = g_signal_new( + "xdg-toplevel-added", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, xdg_toplevel_added), NULL, NULL, + NULL, G_TYPE_NONE, 1, SHOYU_TYPE_XDG_TOPLEVEL); + + /** + * ShoyuCompositor::xdg-toplevel-removed: + * @compositor: the object which received the signal + * @output: a #ShoyuXdgToplevel + */ + shoyu_compositor_sigs[SIG_XDG_TOPLEVEL_REMOVED] = g_signal_new( + "xdg-toplevel-removed", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, xdg_toplevel_removed), NULL, NULL, + NULL, G_TYPE_NONE, 1, SHOYU_TYPE_XDG_TOPLEVEL); + + /** + * ShoyuCompositor::started: + * @compositor: the object which received the signal + */ + shoyu_compositor_sigs[SIG_STARTED] = + g_signal_new("started", SHOYU_TYPE_COMPOSITOR, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuCompositorClass, started), NULL, NULL, + NULL, G_TYPE_NONE, 0); +} + +static void shoyu_compositor_init(ShoyuCompositor *self) { + self->wl_display = wl_display_create(); + g_assert(self->wl_display != NULL); + + self->socket = wl_display_add_socket_auto(self->wl_display); + g_assert(self->socket != NULL); + + g_debug("ShoyuCompositor#%p has socket %s", self, self->socket); + + self->wl_source = shoyu_wayland_event_source_new( + self->wl_display, wl_display_get_event_loop(self->wl_display)); + g_assert(self->wl_source != NULL); + + self->outputs = NULL; + + self->wlr_xdg_shell = wlr_xdg_shell_create(self->wl_display, 3); + g_assert(self->wlr_xdg_shell != NULL); + + self->output_layout = wlr_output_layout_create(self->wl_display); + g_assert(self->output_layout != NULL); + + self->shell = shoyu_shell_new(self); + g_assert(self->shell != NULL); + + wlr_subcompositor_create(self->wl_display); + wlr_data_device_manager_create(self->wl_display); +} + +/** + * shoyu_compositor_new: (constructor) + * + * Creates a #ShoyuCompositor + * + * Returns: (transfer full): A #ShoyuCompositor + */ +ShoyuCompositor *shoyu_compositor_new(void) { + return SHOYU_COMPOSITOR(g_object_new(SHOYU_TYPE_COMPOSITOR, NULL)); +} + +/** + * shoyu_compositor_new_with_application: (constructor) + * @application: A #GApplication + * + * Creates a #ShoyuCompositor which binds to a #GApplication. + * + * By binding to a #GApplication, the compositor will hold the + * application open as long as there are monitors. Once the last + * monitor is removed, the compositor will then shutdown the + * application. + * + * Returns: (transfer full): A #ShoyuCompositor + */ +ShoyuCompositor * +shoyu_compositor_new_with_application(GApplication *application) { + return SHOYU_COMPOSITOR( + g_object_new(SHOYU_TYPE_COMPOSITOR, "application", application, NULL)); +} + +/** + * shoyu_compositor_get_application: + * @self: A #ShoyuCompositor + * + * Gets the #GApplication associated with the compositor. + * + * Returns: (transfer none) (nullable): A #GApplication + */ +GApplication *shoyu_compositor_get_application(ShoyuCompositor *self) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), NULL); + return self->application; +} + +/** + * shoyu_compositor_get_socket: + * @self: A #ShoyuCompositor + * + * Gets the Wayland display socket name for the compositor. + * + * Returns: (transfer none): A string + */ +const char *shoyu_compositor_get_socket(ShoyuCompositor *self) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), NULL); + return self->socket; +} + +/** + * shoyu_compositor_start: + * @self: A #ShoyuCompositor + * + * Starts processing events for the Wayland server. + * + * Returns: %TRUE if the server has started, %FALSE if + * it failed to start. + */ +gboolean shoyu_compositor_start(ShoyuCompositor *self) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), FALSE); + + if (!wlr_backend_start(self->wlr_backend)) + return FALSE; + + g_signal_emit(self, shoyu_compositor_sigs[SIG_STARTED], 0); + return TRUE; +} + +/** + * shoyu_compositor_get_shell: + * @self: A #ShoyuCompositor + * + * Returns: (transfer none): A #ShoyuShell + */ +ShoyuShell *shoyu_compositor_get_shell(ShoyuCompositor *self) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), FALSE); + return self->shell; +} + +ShoyuOutput *shoyu_compositor_get_output(ShoyuCompositor *self, + struct wlr_output *wlr_output) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), NULL); + + for (GList *item = self->outputs; item != NULL; item = item->next) { + ShoyuOutput *output = SHOYU_OUTPUT(item->data); + + if (output->is_invalidated) + continue; + if (output->wlr_output == wlr_output) + return output; + } + + return NULL; +} + +ShoyuSurface *shoyu_compositor_get_surface(ShoyuCompositor *self, + struct wlr_surface *wlr_surface) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), NULL); + + for (GList *item = self->surfaces; item != NULL; item = item->next) { + ShoyuSurface *surface = SHOYU_SURFACE(item->data); + + if (surface->is_invalidated) + continue; + if (surface->wlr_surface == wlr_surface) + return surface; + } + + return NULL; +} + +gboolean +shoyu_compositor_is_xdg_toplevel_claimed(ShoyuCompositor *self, + struct xdg_toplevel *xdg_toplevel) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), FALSE); + + for (GList *item = self->outputs; item != NULL; item = item->next) { + ShoyuOutput *output = SHOYU_OUTPUT(item->data); + + if (output->is_invalidated) + continue; + if (output->wlr_surface != NULL) { + struct wlr_xdg_toplevel *output_xdg_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(output->wlr_surface); + if (output_xdg_toplevel == xdg_toplevel) + return TRUE; + } + } + + return FALSE; +} + +ShoyuOutput *shoyu_compositor_get_xdg_toplevel_claimed_output( + ShoyuCompositor *self, struct xdg_toplevel *xdg_toplevel) { + g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), FALSE); + + for (GList *item = self->outputs; item != NULL; item = item->next) { + ShoyuOutput *output = SHOYU_OUTPUT(item->data); + + if (output->is_invalidated) + continue; + if (output->wlr_surface != NULL) { + struct wlr_xdg_toplevel *output_xdg_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(output->wlr_surface); + if (output_xdg_toplevel == xdg_toplevel) + return output; + } + } + + return FALSE; +} diff --git a/shoyu-compositor/compositor.h b/shoyu-compositor/compositor.h new file mode 100644 index 0000000..1ec582b --- /dev/null +++ b/shoyu-compositor/compositor.h @@ -0,0 +1,57 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#ifdef SHOYU_COMPILATION +typedef struct _ShoyuCompositor ShoyuCompositor; +#else +typedef GObject ShoyuCompositor; +#endif +typedef struct _ShoyuCompositorClass ShoyuCompositorClass; + +#define SHOYU_TYPE_COMPOSITOR (shoyu_compositor_get_type()) +#define SHOYU_COMPOSITOR(object) \ + (G_TYPE_CHECK_INSTANCE_CAST((object), SHOYU_TYPE_COMPOSITOR, ShoyuCompositor)) +#define SHOYU_COMPOSITOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), SHOYU_TYPE_COMPOSITOR, \ + ShoyuCompositorClass)) +#define SHOYU_IS_COMPOSITOR(object) \ + (G_TYPE_CHECK_INSTANCE_TYPE((object), SHOYU_TYPE_COMPOSITOR)) +#define SHOYU_IS_COMPOSITOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), SHOYU_TYPE_COMPOSITOR)) +#define SHOYU_COMPOSITOR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), SHOYU_TYPE_COMPOSITOR, \ + ShoyuCompositorClass)) + +SHOYU_AVAILABLE_IN_ALL +GType shoyu_compositor_get_type(void) G_GNUC_CONST; + +SHOYU_AVAILABLE_IN_ALL +ShoyuCompositor *shoyu_compositor_new(void); + +SHOYU_AVAILABLE_IN_ALL +ShoyuCompositor * +shoyu_compositor_new_with_application(GApplication *application); + +SHOYU_AVAILABLE_IN_ALL +GApplication *shoyu_compositor_get_application(ShoyuCompositor *self); + +SHOYU_AVAILABLE_IN_ALL +const char *shoyu_compositor_get_socket(ShoyuCompositor *self); + +SHOYU_AVAILABLE_IN_ALL +gboolean shoyu_compositor_start(ShoyuCompositor *self); + +SHOYU_AVAILABLE_IN_ALL +ShoyuShell *shoyu_compositor_get_shell(ShoyuCompositor *self); + +G_END_DECLS diff --git a/shoyu-compositor/config.h.meson b/shoyu-compositor/config.h.meson new file mode 100644 index 0000000..138d13a --- /dev/null +++ b/shoyu-compositor/config.h.meson @@ -0,0 +1,11 @@ +#pragma once + +#if !defined (__SHOYU_COMPOSITOR_H_INSIDE__) && !defined (SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +G_END_DECLS diff --git a/shoyu-compositor/input-private.h b/shoyu-compositor/input-private.h new file mode 100644 index 0000000..ddf3f48 --- /dev/null +++ b/shoyu-compositor/input-private.h @@ -0,0 +1,29 @@ +#pragma once + +#include "input.h" + +#include + +struct _ShoyuInput { + GObject parent_instance; + + ShoyuCompositor *compositor; + + struct wlr_input_device *wlr_input_device; + + bool is_invalidated; + + struct wl_listener destroy; +}; + +struct _ShoyuInputClass { + GObjectClass parent_class; + + void (*realized)(ShoyuInput *self, + struct wlr_input_device *wlr_input_device); + void (*unrealized)(ShoyuInput *self); +}; + +void shoyu_input_realize(ShoyuInput *self, + struct wlr_input_device *wlr_input_device); +void shoyu_input_unrealize(ShoyuInput *self); diff --git a/shoyu-compositor/input.c b/shoyu-compositor/input.c new file mode 100644 index 0000000..c30e423 --- /dev/null +++ b/shoyu-compositor/input.c @@ -0,0 +1,175 @@ +#include "input-private.h" + +/** + * ShoyuInput: + * + * An input which represents something like a keyboard, mouse, + * or other input device for #ShoyuCompositor. + */ + +enum { + PROP_0 = 0, + PROP_COMPOSITOR, + N_PROPERTIES, + + SIG_DESTROY = 0, + SIG_REALIZED, + SIG_UNREALIZED, + N_SIGNALS, +}; + +static GParamSpec *shoyu_input_props[N_PROPERTIES] = { + NULL, +}; +static guint shoyu_input_sigs[N_SIGNALS]; + +G_DEFINE_TYPE(ShoyuInput, shoyu_input, G_TYPE_OBJECT) + +static void shoyu_input_destroy(struct wl_listener *listener, void *data) { + ShoyuInput *self = wl_container_of(listener, self, destroy); + + if (!self->is_invalidated) { + shoyu_input_unrealize(self); + g_signal_emit(self, shoyu_input_sigs[SIG_DESTROY], 0); + } +} + +static void shoyu_input_finalize(GObject *object) { + ShoyuInput *self = SHOYU_INPUT(object); + + g_clear_object(&self->compositor); + + G_OBJECT_CLASS(shoyu_input_parent_class)->finalize(object); +} + +static void shoyu_input_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) { + ShoyuInput *self = SHOYU_INPUT(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + self->compositor = g_value_dup_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_input_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) { + ShoyuInput *self = SHOYU_INPUT(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + g_value_set_object(value, G_OBJECT(self->compositor)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_input_class_init(ShoyuInputClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->finalize = shoyu_input_finalize; + object_class->set_property = shoyu_input_set_property; + object_class->get_property = shoyu_input_get_property; + + shoyu_input_props[PROP_COMPOSITOR] = g_param_spec_object( + "compositor", "Shoyu Compositor", "The compositor the input comes from.", + SHOYU_TYPE_COMPOSITOR, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties(object_class, N_PROPERTIES, + shoyu_input_props); + + /** + * ShoyuInput::destroy: + * @input: a #ShoyuInput + */ + shoyu_input_sigs[SIG_DESTROY] = + g_signal_new("destroy", SHOYU_TYPE_INPUT, G_SIGNAL_RUN_LAST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0); + + /** + * ShoyuInput::realized: + * @input: a #ShoyuInput + * @wlr_input_device: A wlroots input device + */ + shoyu_input_sigs[SIG_REALIZED] = + g_signal_new("realized", SHOYU_TYPE_INPUT, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuInputClass, realized), NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * ShoyuInput::unrealized: + * @input: a #ShoyuInput + */ + shoyu_input_sigs[SIG_UNREALIZED] = + g_signal_new("unrealized", SHOYU_TYPE_INPUT, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuInputClass, unrealized), NULL, NULL, + NULL, G_TYPE_NONE, 0); +} + +static void shoyu_input_init(ShoyuInput *self) { self->is_invalidated = TRUE; } + +/** + * shoyu_input_new: (constructor) + * + * Creates a #ShoyuInput + * + * Returns: (transfer full): A #ShoyuInput + */ +ShoyuInput *shoyu_input_new(ShoyuCompositor *compositor) { + return SHOYU_INPUT( + g_object_new(SHOYU_TYPE_INPUT, "compositor", compositor, NULL)); +} + +/** + * shoyu_input_get_compositor: + * @self: A #ShoyuInput + * + * Gets the #ShoyuCompositor which the input comes from. + * + * Returns: (transfer none) (nullable): A #ShoyuCompositor + */ +ShoyuCompositor *shoyu_input_get_compositor(ShoyuInput *self) { + g_return_val_if_fail(SHOYU_IS_INPUT(self), NULL); + return self->compositor; +} + +/** + * shoyu_input_realize: + * @self: A #ShoyuInput + * @wlr_input: The wlroots input + */ +void shoyu_input_realize(ShoyuInput *self, + struct wlr_input_device *wlr_input_device) { + g_return_if_fail(SHOYU_IS_INPUT(self)); + g_return_if_fail(self->wlr_input_device == NULL && self->is_invalidated); + + self->wlr_input_device = wlr_input_device; + self->is_invalidated = FALSE; + + self->destroy.notify = shoyu_input_destroy; + wl_signal_add(&self->wlr_input_device->events.destroy, &self->destroy); + + g_signal_emit(self, shoyu_input_sigs[SIG_REALIZED], 0, wlr_input_device); +} + +/** + * shoyu_input_unrealize: + * @self: A #ShoyuInput + */ +void shoyu_input_unrealize(ShoyuInput *self) { + g_return_if_fail(SHOYU_IS_INPUT(self)); + g_return_if_fail(self->wlr_input_device != NULL && !self->is_invalidated); + + wl_list_remove(&self->destroy.link); + + self->wlr_input_device = NULL; + self->is_invalidated = TRUE; + + g_signal_emit(self, shoyu_input_sigs[SIG_UNREALIZED], 0); +} diff --git a/shoyu-compositor/input.h b/shoyu-compositor/input.h new file mode 100644 index 0000000..fe30d59 --- /dev/null +++ b/shoyu-compositor/input.h @@ -0,0 +1,41 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#ifdef SHOYU_COMPILATION +typedef struct _ShoyuInput ShoyuInput; +#else +typedef GObject ShoyuInput; +#endif +typedef struct _ShoyuInputClass ShoyuInputClass; + +#define SHOYU_TYPE_INPUT (shoyu_input_get_type()) +#define SHOYU_INPUT(object) \ + (G_TYPE_CHECK_INSTANCE_CAST((object), SHOYU_TYPE_INPUT, ShoyuInput)) +#define SHOYU_INPUT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), SHOYU_TYPE_INPUT, ShoyuInputClass)) +#define SHOYU_IS_INPUT(object) \ + (G_TYPE_CHECK_INSTANCE_TYPE((object), SHOYU_TYPE_INPUT)) +#define SHOYU_IS_INPUT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), SHOYU_TYPE_INPUT)) +#define SHOYU_INPUT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), SHOYU_TYPE_INPUT, ShoyuInputClass)) + +SHOYU_AVAILABLE_IN_ALL +GType shoyu_input_get_type(void) G_GNUC_CONST; + +SHOYU_AVAILABLE_IN_ALL +ShoyuInput *shoyu_input_new(ShoyuCompositor *compositor); + +SHOYU_AVAILABLE_IN_ALL +ShoyuCompositor *shoyu_input_get_compositor(ShoyuInput *self); + +G_END_DECLS diff --git a/shoyu-compositor/main.c b/shoyu-compositor/main.c new file mode 100644 index 0000000..e322325 --- /dev/null +++ b/shoyu-compositor/main.c @@ -0,0 +1,67 @@ +#include "main.h" +#include "shoyu-config.h" + +#include +#include + +static gboolean shoyu_initialized = FALSE; + +static void log_handler(enum wlr_log_importance importance, const char *fmt, + va_list args) { + switch (importance) { + case WLR_SILENT: + case WLR_LOG_IMPORTANCE_LAST: + break; + case WLR_ERROR: + g_logv("wlroots", G_LOG_LEVEL_ERROR, fmt, args); + break; + case WLR_INFO: + g_logv("wlroots", G_LOG_LEVEL_INFO, fmt, args); + break; + case WLR_DEBUG: + g_logv("wlroots", G_LOG_LEVEL_DEBUG, fmt, args); + break; + } +} + +static void setlocale_initialization(void) { + static gboolean initialized = FALSE; + + if (initialized) + return; + initialized = TRUE; + + if (!setlocale(LC_ALL, "")) { + g_warning( + "Locale not supported by C library.\n\tUsing the fallback 'C' locale."); + } +} + +static void gettext_initialization(void) { + setlocale_initialization(); + + bindtextdomain(GETTEXT_PACKAGE, SHOYU_LOCALEDIR); +#ifdef HAVE_BIND_TEXTDOMAIN_CODESET + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); +#endif +} + +void shoyu_init(void) { + if (!shoyu_init_check()) { + g_warning("Failed to initialize"); + exit(1); + } +} + +gboolean shoyu_init_check(void) { + if (shoyu_initialized) + return TRUE; + + gettext_initialization(); + wlr_log_init(WLR_DEBUG, log_handler); + + shoyu_initialized = TRUE; + return TRUE; +} + +gboolean shoyu_is_initialized(void) { return shoyu_initialized; } diff --git a/shoyu-compositor/main.h b/shoyu-compositor/main.h new file mode 100644 index 0000000..6463225 --- /dev/null +++ b/shoyu-compositor/main.h @@ -0,0 +1,21 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +SHOYU_AVAILABLE_IN_ALL +void shoyu_init(void); + +SHOYU_AVAILABLE_IN_ALL +gboolean shoyu_init_check(void); + +SHOYU_AVAILABLE_IN_ALL +gboolean shoyu_is_initialized(void); + +G_END_DECLS diff --git a/shoyu-compositor/meson.build b/shoyu-compositor/meson.build new file mode 100644 index 0000000..ea9e8a1 --- /dev/null +++ b/shoyu-compositor/meson.build @@ -0,0 +1,174 @@ +libcompositor_cargs = [ + '-DSHOYU_COMPILATION', + '-DG_LOG_DOMAIN="ShoyuCompositor"', + '-DSHOYU_COMPOSITOR_BINARY_VERSION="@0@"'.format(shoyu_binary_version), + '-DSHOYU_COMPOSITOR_HOST="@0@"'.format(host_machine.system()), + '-DSHOYU_COMPOSITOR_DATA_PREFIX="@0@"'.format(shoyu_prefix), + '-DWLR_USE_UNSTABLE', +] + +libcompositor_private_sources = files([ + 'shell-output.c', + 'shell-toplevel.c', +]) + +libcompositor_private_h_sources = files([ + 'compositor-private.h', + 'input-private.h', + 'output-private.h', + 'shell-private.h', + 'shell-output-private.h', + 'shell-toplevel-private.h', + 'surface-private.h', + 'wayland-event-source-private.h', + 'xdg-toplevel-private.h', +]) + +libcompositor_public_sources = files([ + 'compositor.c', + 'input.c', + 'main.c', + 'output.c', + 'shell.c', + 'surface.c', + 'version.c', + 'wayland-event-source.c', + 'xdg-toplevel.c', +]) + +libcompositor_public_headers = files([ + 'compositor.h', + 'input.h', + 'main.h', + 'output.h', + 'shell.h', + 'shoyu-compositor.h', + 'surface.h', + 'wayland-event-source.h', + 'xdg-toplevel.h', +]) + +install_headers(libcompositor_public_headers, subdir: 'shoyu-compositor/') +libcompositor_sources = libcompositor_public_sources + libcompositor_private_sources + +libcompositor_enums = gnome.mkenums_simple('compositorenumtypes', + sources: libcompositor_public_headers, + decorator: 'SHOYU_AVAILABLE_IN_ALL', + body_prefix: '#include "config.h"', + header_prefix: '#include "version/versionmacros.h"\n', + install_dir: shoyu_includedir / 'shoyu-compositor', + install_header: true) + +libcompositor_enums_h = libcompositor_enums[1] + +libcompositor_config_cdata = configuration_data() + +libcompositor_config = configure_file( + input: 'config.h.meson', + output: 'config.h', + configuration: libcompositor_config_cdata, + install_dir: shoyu_includedir / 'shoyu-compositor', +) + +libcompositor_version_cdata = configuration_data() +libcompositor_version_cdata.set('SHOYU_COMPOSITOR_MAJOR_VERSION', shoyu_major_version) +libcompositor_version_cdata.set('SHOYU_COMPOSITOR_MINOR_VERSION', shoyu_minor_version) +libcompositor_version_cdata.set('SHOYU_COMPOSITOR_MICRO_VERSION', shoyu_micro_version) +libcompositor_version_cdata.set('SHOYU_COMPOSITOR_BINARY_AGE', shoyu_binary_age) +libcompositor_version_cdata.set('SHOYU_COMPOSITOR_INTERFACE_AGE', shoyu_interface_age) +libcompositor_version_cdata.set('SHOYU_COMPOSITOR_VERSION', shoyu_version) +libcompositor_version_cdata.set('SHOYU_COMPOSITOR_API_VERSION', shoyu_api_version) + +libcompositor_version = configure_file(input: 'version.h.in', + output: 'version.h', + configuration: libcompositor_version_cdata, + install: true, + install_dir: shoyu_includedir / 'shoyu-compositor') + +libcompositor_gen_headers = [ + libcompositor_enums_h, + libcompositor_config, + libcompositor_version, +] + +libcompositor_deps = [ + gobject, + gio, + wlroots, + wayland_server, +] + +darwin_versions = [ + # compatibility version + 1 + '@0@'.format(shoyu_binary_age - shoyu_interface_age).to_int(), + # current version + '@0@.@1@'.format(1 + '@0@'.format(shoyu_binary_age - shoyu_interface_age).to_int(), shoyu_interface_age), +] + +libcompositor_sources += [ + libcompositor_config, + libcompositor_enums, + libcompositor_version_macros_h, + libcompositor_visibility_h, + libcompositor_private_h_sources, + libcompositor_public_headers, +] + +libcompositor = shared_library('shoyu-compositor', + sources: libcompositor_sources, + c_args: libcompositor_cargs + common_cflags, + include_directories: [conf_inc, proto_inc, libcompositor_inc], + link_whole: [libwayland_server], + dependencies: libcompositor_deps, + link_args: common_ldflags, + soversion: shoyu_soversion, + version: shoyu_library_version, + darwin_versions: darwin_versions, + gnu_symbol_visibility: 'hidden', + install: true) + +libcompositor_dep_sources = [libcompositor_config, libcompositor_version] + +if build_gir + gir_args = [ + '-DSHOYU_COMPILATION', + '--quiet', + ] + + libcompositor_gir_inc = [ 'GObject-2.0', 'Gio-2.0' ] + + libcompositor_gir = gnome.generate_gir(libcompositor, + sources: [ + libcompositor_enums_h, + libcompositor_public_headers, + libcompositor_public_sources, + libcompositor_version, + libcompositor_config, + ], + namespace: 'Shoyu', + nsversion: shoyu_api_version, + identifier_prefix: 'Shoyu', + symbol_prefix: 'shoyu', + includes: libcompositor_gir_inc, + header: 'shoyu-compositor/shoyu-compositor.h', + install: true, + dependencies: libcompositor_deps, + extra_args: gir_args, + fatal_warnings: get_option('werror'), + ) + libcompositor_dep_sources += libcompositor_gir +endif + +if build_vapi + libcompositor_vapi = gnome.generate_vapi('shoyu-compositor', + sources: libcompositor_gir[0], + packages: ['gobject-2.0', 'gio-2.0'], + install: true) +endif + +libcompositor_dep = declare_dependency( + sources: libcompositor_dep_sources, + include_directories: [conf_inc, libcompositor_inc], + dependencies: libcompositor_deps, + link_with: libcompositor, + link_args: common_ldflags) diff --git a/shoyu-compositor/output-private.h b/shoyu-compositor/output-private.h new file mode 100644 index 0000000..386dbd1 --- /dev/null +++ b/shoyu-compositor/output-private.h @@ -0,0 +1,34 @@ +#pragma once + +#include "output.h" + +#include + +struct _ShoyuOutput { + GObject parent_instance; + + ShoyuCompositor *compositor; + + struct wlr_output *wlr_output; + struct wlr_output_layout_output *wlr_output_layout_output; + + struct wlr_surface *wlr_surface; + + bool is_invalidated; + + struct wl_listener destroy; + struct wl_listener frame; + struct wl_listener request_state; +}; + +struct _ShoyuOutputClass { + GObjectClass parent_class; + + void (*realized)(ShoyuOutput *self, struct wlr_output *wlr_output); + void (*unrealized)(ShoyuOutput *self); +}; + +void shoyu_output_realize(ShoyuOutput *self, struct wlr_output *wlr_output); +void shoyu_output_unrealize(ShoyuOutput *self); +void shoyu_output_set_surface(ShoyuOutput *self, + struct wlr_surface *wlr_surface); diff --git a/shoyu-compositor/output.c b/shoyu-compositor/output.c new file mode 100644 index 0000000..aa0de52 --- /dev/null +++ b/shoyu-compositor/output.c @@ -0,0 +1,271 @@ +#include "compositor-private.h" +#include "output-private.h" + +/** + * ShoyuOutput: + * + * An output which represents a monitor for #ShoyuCompositor. + */ + +enum { + PROP_0 = 0, + PROP_COMPOSITOR, + PROP_SURFACE, + N_PROPERTIES, + + SIG_DESTROY = 0, + SIG_REALIZED, + SIG_UNREALIZED, + N_SIGNALS, +}; + +static GParamSpec *shoyu_output_props[N_PROPERTIES] = { + NULL, +}; +static guint shoyu_output_sigs[N_SIGNALS]; + +G_DEFINE_TYPE(ShoyuOutput, shoyu_output, G_TYPE_OBJECT) + +static void shoyu_output_destroy(struct wl_listener *listener, void *data) { + ShoyuOutput *self = wl_container_of(listener, self, destroy); + + if (!self->is_invalidated) { + shoyu_output_unrealize(self); + g_signal_emit(self, shoyu_output_sigs[SIG_DESTROY], 0); + } +} + +static void shoyu_output_frame(struct wl_listener *listener, void *data) { + ShoyuOutput *self = wl_container_of(listener, self, frame); + + struct wlr_output_state state; + wlr_output_state_init(&state); + + struct wlr_render_pass *pass = + wlr_output_begin_render_pass(self->wlr_output, &state, NULL, NULL); + + if (self->wlr_surface != NULL) { + ShoyuSurface *surface = + shoyu_compositor_get_surface(self->compositor, self->wlr_surface); + if (surface != NULL) { + struct wlr_texture *texture = wlr_surface_get_texture(self->wlr_surface); + if (texture != NULL) { + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ + .texture = texture, + .dst_box = {0, 0}, + }); + } + } + } else { + wlr_render_pass_add_rect( + pass, &(struct wlr_render_rect_options){ + .box = {self->wlr_output->width, self->wlr_output->height}, + .color = {0, 0, 0, 1.0}, + }); + } + + wlr_render_pass_submit(pass); + wlr_output_commit_state(self->wlr_output, &state); + wlr_output_state_finish(&state); + + if (self->wlr_surface != NULL) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_surface_send_frame_done(self->wlr_surface, &now); + } +} + +static void shoyu_output_request_state(struct wl_listener *listener, + void *data) { + ShoyuOutput *self = wl_container_of(listener, self, request_state); + + const struct wlr_output_event_request_state *event = data; + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_copy(&state, event->state); + + // TODO: pass this to a signal to determine what should be done. + + wlr_output_commit_state(self->wlr_output, &state); + + if (self->wlr_surface != NULL) { + struct wlr_xdg_toplevel *xdg_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(self->wlr_surface); + if (xdg_toplevel != NULL) { + wlr_xdg_toplevel_set_size(xdg_toplevel, self->wlr_output->width, + self->wlr_output->height); + } + } +} + +static void shoyu_output_finalize(GObject *object) { + ShoyuOutput *self = SHOYU_OUTPUT(object); + + if (!self->is_invalidated) + g_clear_pointer(&self->wlr_output, (GDestroyNotify)wlr_output_destroy); + g_clear_object(&self->compositor); + + G_OBJECT_CLASS(shoyu_output_parent_class)->finalize(object); +} + +static void shoyu_output_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) { + ShoyuOutput *self = SHOYU_OUTPUT(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + self->compositor = g_value_dup_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_output_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) { + ShoyuOutput *self = SHOYU_OUTPUT(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + g_value_set_object(value, G_OBJECT(self->compositor)); + break; + case PROP_SURFACE: + g_value_set_pointer(value, self->wlr_surface); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_output_class_init(ShoyuOutputClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->finalize = shoyu_output_finalize; + object_class->set_property = shoyu_output_set_property; + object_class->get_property = shoyu_output_get_property; + + shoyu_output_props[PROP_COMPOSITOR] = g_param_spec_object( + "compositor", "Shoyu Compositor", "The compositor the output comes from.", + SHOYU_TYPE_COMPOSITOR, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + shoyu_output_props[PROP_SURFACE] = g_param_spec_pointer( + "surface", "Wayland surface", + "The surface which is to be rendered onto the output.", G_PARAM_READABLE); + + g_object_class_install_properties(object_class, N_PROPERTIES, + shoyu_output_props); + + /** + * ShoyuOutput::destroy: + * @output: a #ShoyuOutput + */ + shoyu_output_sigs[SIG_DESTROY] = + g_signal_new("destroy", SHOYU_TYPE_OUTPUT, G_SIGNAL_RUN_LAST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0); + + /** + * ShoyuOutput::realized: + * @output: a #ShoyuOutput + * @wlr_output: A wlroots output + */ + shoyu_output_sigs[SIG_REALIZED] = + g_signal_new("realized", SHOYU_TYPE_OUTPUT, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuOutputClass, realized), NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * ShoyuOutput::unrealized: + * @output: a #ShoyuOutput + */ + shoyu_output_sigs[SIG_UNREALIZED] = + g_signal_new("unrealized", SHOYU_TYPE_OUTPUT, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuOutputClass, unrealized), NULL, NULL, + NULL, G_TYPE_NONE, 0); +} + +static void shoyu_output_init(ShoyuOutput *self) { + self->is_invalidated = TRUE; +} + +/** + * shoyu_output_new: (constructor) + * + * Creates a #ShoyuOutput + * + * Returns: (transfer full): A #ShoyuOutput + */ +ShoyuOutput *shoyu_output_new(ShoyuCompositor *compositor) { + return SHOYU_OUTPUT( + g_object_new(SHOYU_TYPE_OUTPUT, "compositor", compositor, NULL)); +} + +/** + * shoyu_output_get_compositor: + * @self: A #ShoyuOutput + * + * Gets the #ShoyuCompositor which the output comes from. + * + * Returns: (transfer none) (nullable): A #ShoyuCompositor + */ +ShoyuCompositor *shoyu_output_get_compositor(ShoyuOutput *self) { + g_return_val_if_fail(SHOYU_IS_OUTPUT(self), NULL); + return self->compositor; +} + +/** + * shoyu_output_realize: + * @self: A #ShoyuOutput + * @wlr_output: The wlroots output + */ +void shoyu_output_realize(ShoyuOutput *self, struct wlr_output *wlr_output) { + g_return_if_fail(SHOYU_IS_OUTPUT(self)); + g_return_if_fail(self->wlr_output == NULL && self->is_invalidated); + + self->wlr_output = wlr_output; + self->is_invalidated = FALSE; + + self->destroy.notify = shoyu_output_destroy; + wl_signal_add(&self->wlr_output->events.destroy, &self->destroy); + + self->frame.notify = shoyu_output_frame; + wl_signal_add(&self->wlr_output->events.frame, &self->frame); + + self->request_state.notify = shoyu_output_request_state; + wl_signal_add(&self->wlr_output->events.request_state, &self->request_state); + + self->wlr_output_layout_output = + wlr_output_layout_add_auto(self->compositor->output_layout, wlr_output); + g_assert(self->wlr_output_layout_output != NULL); + + g_signal_emit(self, shoyu_output_sigs[SIG_REALIZED], 0, wlr_output); +} + +/** + * shoyu_output_unrealize: + * @self: A #ShoyuOutput + */ +void shoyu_output_unrealize(ShoyuOutput *self) { + g_return_if_fail(SHOYU_IS_OUTPUT(self)); + g_return_if_fail(self->wlr_output != NULL && !self->is_invalidated); + + wl_list_remove(&self->destroy.link); + wl_list_remove(&self->frame.link); + wl_list_remove(&self->request_state.link); + + self->wlr_output = NULL; + self->is_invalidated = TRUE; + + g_signal_emit(self, shoyu_output_sigs[SIG_UNREALIZED], 0); +} + +void shoyu_output_set_surface(ShoyuOutput *self, + struct wlr_surface *wlr_surface) { + g_return_if_fail(SHOYU_IS_OUTPUT(self)); + + self->wlr_surface = wlr_surface; + + g_object_notify_by_pspec(G_OBJECT(self), shoyu_output_props[PROP_SURFACE]); +} diff --git a/shoyu-compositor/output.h b/shoyu-compositor/output.h new file mode 100644 index 0000000..5f89263 --- /dev/null +++ b/shoyu-compositor/output.h @@ -0,0 +1,41 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#ifdef SHOYU_COMPILATION +typedef struct _ShoyuOutput ShoyuOutput; +#else +typedef GObject ShoyuOutput; +#endif +typedef struct _ShoyuOutputClass ShoyuOutputClass; + +#define SHOYU_TYPE_OUTPUT (shoyu_output_get_type()) +#define SHOYU_OUTPUT(object) \ + (G_TYPE_CHECK_INSTANCE_CAST((object), SHOYU_TYPE_OUTPUT, ShoyuOutput)) +#define SHOYU_OUTPUT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), SHOYU_TYPE_OUTPUT, ShoyuOutputClass)) +#define SHOYU_IS_OUTPUT(object) \ + (G_TYPE_CHECK_INSTANCE_TYPE((object), SHOYU_TYPE_OUTPUT)) +#define SHOYU_IS_OUTPUT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), SHOYU_TYPE_OUTPUT)) +#define SHOYU_OUTPUT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), SHOYU_TYPE_OUTPUT, ShoyuOutputClass)) + +SHOYU_AVAILABLE_IN_ALL +GType shoyu_output_get_type(void) G_GNUC_CONST; + +SHOYU_AVAILABLE_IN_ALL +ShoyuOutput *shoyu_output_new(ShoyuCompositor *compositor); + +SHOYU_AVAILABLE_IN_ALL +ShoyuCompositor *shoyu_output_get_compositor(ShoyuOutput *self); + +G_END_DECLS diff --git a/shoyu-compositor/shell-output-private.h b/shoyu-compositor/shell-output-private.h new file mode 100644 index 0000000..c0f212c --- /dev/null +++ b/shoyu-compositor/shell-output-private.h @@ -0,0 +1,8 @@ +#pragma once + +#include "shell.h" +#include + +void shoyu_shell_get_output(struct wl_client *wl_client, + struct wl_resource *shell_resource, uint32_t id, + struct wl_resource *output); diff --git a/shoyu-compositor/shell-output.c b/shoyu-compositor/shell-output.c new file mode 100644 index 0000000..c3c2141 --- /dev/null +++ b/shoyu-compositor/shell-output.c @@ -0,0 +1,97 @@ +#include "compositor-private.h" +#include "output-private.h" +#include "shell-output-private.h" +#include "shell-private.h" + +#include +#include +#include +#include + +typedef struct { + struct wl_resource *resource; + struct wlr_addon addon; + struct wlr_output *wlr_output; + ShoyuShell *shell; +} ShellOutput; + +static void shoyu_shell_output_set_surface(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *surface) { + ShellOutput *self = wl_resource_get_user_data(resource); + struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface); + + ShoyuOutput *output = + shoyu_compositor_get_output(self->shell->compositor, self->wlr_output); + g_return_if_fail(output != NULL); + + shoyu_output_set_surface(output, wlr_surface); +} + +static const struct shoyu_shell_output_interface shoyu_shell_output_impl = { + .set_surface = shoyu_shell_output_set_surface, +}; + +static void shoyu_shell_output_destroy(ShellOutput *self) { + if (self == NULL) + return; + + wlr_addon_finish(&self->addon); + wl_resource_set_user_data(self->resource, NULL); + free(self); +} + +static void shoyu_shell_output_resource_destroy(struct wl_resource *resource) { + ShellOutput *self = wl_resource_get_user_data(resource); + shoyu_shell_output_destroy(self); +} + +static void shoyu_shell_output_addon_destroy(struct wlr_addon *addon) { + ShellOutput *self = wl_container_of(addon, self, addon); + shoyu_shell_output_destroy(self); +} + +static const struct wlr_addon_interface shoyu_shell_output_addon_impl = { + .name = "shoyu_shell_output", + .destroy = shoyu_shell_output_addon_destroy, +}; + +void shoyu_shell_get_output(struct wl_client *wl_client, + struct wl_resource *shell_resource, uint32_t id, + struct wl_resource *output) { + ShoyuShell *shell = wl_resource_get_user_data(shell_resource); + struct wlr_output *wlr_output = wlr_output_from_resource(output); + + if (wlr_addon_find(&wlr_output->addons, shell, + &shoyu_shell_output_addon_impl) != NULL) { + wl_resource_post_error( + shell_resource, SHOYU_SHELL_ERROR_OUTPUT_ALREADY_CONSTRUCTED, + "shoyu_shell_output already constructed for this output"); + return; + } + + ShellOutput *shell_output = malloc(sizeof(ShellOutput)); + if (shell_output == NULL) { + wl_resource_post_no_memory(shell_resource); + return; + } + + shell_output->wlr_output = wlr_output; + shell_output->shell = SHOYU_SHELL(g_object_ref(shell)); + + uint32_t version = wl_resource_get_version(shell_resource); + shell_output->resource = + wl_resource_create(wl_client, &shoyu_shell_output_interface, version, id); + if (shell_output == NULL) { + g_object_unref(shell_output->shell); + free(shell_output); + wl_resource_post_no_memory(shell_resource); + return; + } + + wl_resource_set_implementation(shell_output->resource, + &shoyu_shell_output_impl, shell_output, + shoyu_shell_output_resource_destroy); + wlr_addon_init(&shell_output->addon, &wlr_output->addons, shell, + &shoyu_shell_output_addon_impl); +} diff --git a/shoyu-compositor/shell-private.h b/shoyu-compositor/shell-private.h new file mode 100644 index 0000000..5fe37da --- /dev/null +++ b/shoyu-compositor/shell-private.h @@ -0,0 +1,23 @@ +#pragma once + +#include "shell.h" +#include + +struct _ShoyuShell { + GObject parent_instance; + + ShoyuCompositor *compositor; + + struct wl_global *global; + struct wl_listener display_destroy; + + struct wl_resource *resource; + struct wl_client *client; + uint32_t version; +}; + +struct _ShoyuShellClass { + GObjectClass parent_class; +}; + +ShoyuShell *shoyu_shell_new(ShoyuCompositor *compositor); diff --git a/shoyu-compositor/shell-toplevel-private.h b/shoyu-compositor/shell-toplevel-private.h new file mode 100644 index 0000000..df80598 --- /dev/null +++ b/shoyu-compositor/shell-toplevel-private.h @@ -0,0 +1,11 @@ +#pragma once + +#include "shell.h" +#include +#include + +void shoyu_shell_toplevel_create(struct wl_client *wl_client, + struct wl_resource *shell_resource, + struct wlr_xdg_toplevel *xdg_toplevel); +void shoyu_shell_toplevel_delete(ShoyuShell *shell, + struct wlr_xdg_toplevel *xdg_toplevel); diff --git a/shoyu-compositor/shell-toplevel.c b/shoyu-compositor/shell-toplevel.c new file mode 100644 index 0000000..4f4d6a2 --- /dev/null +++ b/shoyu-compositor/shell-toplevel.c @@ -0,0 +1,90 @@ +#include "shell-toplevel-private.h" +#include +#include +#include + +typedef struct { + struct wl_resource *resource; + struct wlr_addon addon; + struct wlr_xdg_toplevel *wlr_xdg_toplevel; + ShoyuShell *shell; +} ShellToplevel; + +static const struct shoyu_shell_toplevel_interface shoyu_shell_toplevel_impl = + {}; + +static void shoyu_shell_toplevel_destroy(ShellToplevel *self) { + if (self == NULL) + return; + + wlr_addon_finish(&self->addon); + wl_resource_set_user_data(self->resource, NULL); + shoyu_shell_toplevel_send_destroy(self->resource); + free(self); +} + +static void +shoyu_shell_toplevel_resource_destroy(struct wl_resource *resource) { + ShellToplevel *self = wl_resource_get_user_data(resource); + shoyu_shell_toplevel_destroy(self); +} + +static void shoyu_shell_toplevel_addon_destroy(struct wlr_addon *addon) { + ShellToplevel *self = wl_container_of(addon, self, addon); + shoyu_shell_toplevel_destroy(self); +} + +static const struct wlr_addon_interface shoyu_shell_toplevel_addon_impl = { + .name = "shoyu_shell_toplevel", + .destroy = shoyu_shell_toplevel_addon_destroy, +}; + +void shoyu_shell_toplevel_create(struct wl_client *wl_client, + struct wl_resource *shell_resource, + struct wlr_xdg_toplevel *xdg_toplevel) { + ShoyuShell *shell = wl_resource_get_user_data(shell_resource); + + if (wlr_addon_find(&xdg_toplevel->base->surface->addons, shell, + &shoyu_shell_toplevel_addon_impl) != NULL) { + wl_resource_post_error( + shell_resource, SHOYU_SHELL_ERROR_TOPLEVEL_ALREADY_CONSTRUCTED, + "shoyu_shell_toplevel already constructed for this xdg_toplevel"); + return; + } + + ShellToplevel *shell_toplevel = malloc(sizeof(ShellToplevel)); + if (shell_toplevel == NULL) { + wl_resource_post_no_memory(shell_resource); + return; + } + + shell_toplevel->wlr_xdg_toplevel = xdg_toplevel; + shell_toplevel->shell = SHOYU_SHELL(g_object_ref(shell)); + + uint32_t version = wl_resource_get_version(shell_resource); + shell_toplevel->resource = wl_resource_create( + wl_client, &shoyu_shell_toplevel_interface, version, 0); + if (shell_toplevel == NULL) { + g_object_unref(shell_toplevel->shell); + free(shell_toplevel); + wl_resource_post_no_memory(shell_resource); + return; + } + + wl_resource_set_implementation(shell_toplevel->resource, + &shoyu_shell_toplevel_impl, shell_toplevel, + shoyu_shell_toplevel_resource_destroy); + wlr_addon_init(&shell_toplevel->addon, &xdg_toplevel->base->surface->addons, + shell, &shoyu_shell_toplevel_addon_impl); + + shoyu_shell_send_toplevel_added(shell_resource, shell_toplevel->resource); +} + +void shoyu_shell_toplevel_delete(ShoyuShell *shell, + struct wlr_xdg_toplevel *xdg_toplevel) { + struct wlr_addon *addon = + wlr_addon_find(&xdg_toplevel->base->surface->addons, shell, + &shoyu_shell_toplevel_addon_impl); + g_return_if_fail(addon != NULL); + shoyu_shell_toplevel_addon_destroy(addon); +} diff --git a/shoyu-compositor/shell.c b/shoyu-compositor/shell.c new file mode 100644 index 0000000..1624ca0 --- /dev/null +++ b/shoyu-compositor/shell.c @@ -0,0 +1,169 @@ +#include "compositor-private.h" +#include "shell-output-private.h" +#include "shell-private.h" +#include "shell-toplevel-private.h" +#include "xdg-toplevel-private.h" + +/** + * ShoyuShell: + * + * An object which provides the Shoyu Shell Wayland protocol. + */ + +enum { + PROP_0 = 0, + PROP_COMPOSITOR, + N_PROPERTIES, +}; + +static GParamSpec *shoyu_shell_props[N_PROPERTIES] = { + NULL, +}; + +G_DEFINE_TYPE(ShoyuShell, shoyu_shell, G_TYPE_OBJECT) + +static const struct shoyu_shell_interface shoyu_shell_impl = { + .get_output = shoyu_shell_get_output, +}; + +static void shoyu_shell_resource_destroy(struct wl_resource *resource) { + ShoyuShell *self = wl_resource_get_user_data(resource); + + for (GList *item = self->compositor->xdg_toplevels; item != NULL; + item = item->next) { + ShoyuXdgToplevel *xdg_toplevel = SHOYU_XDG_TOPLEVEL(item->data); + if (xdg_toplevel->is_invalidated) + continue; + + shoyu_shell_xdg_toplevel_unbind_shell(xdg_toplevel); + } + + self->client = NULL; + self->resource = NULL; + self->version = 0; +} + +static void shoyu_shell_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + ShoyuShell *self = SHOYU_SHELL(data); + + g_debug("wl_client#%p wants to bind to ShoyuShell#%p", wl_client, self); + + struct wl_resource *resource = + wl_resource_create(wl_client, &shoyu_shell_interface, version, id); + + if (self->resource != NULL) { + wl_resource_post_error( + resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "Only a single client can bind to the Shoyu Shell protocol"); + return; + } + + wl_resource_set_implementation(resource, &shoyu_shell_impl, self, + shoyu_shell_resource_destroy); + + self->resource = resource; + self->version = version; + self->client = wl_client; + + for (GList *item = self->compositor->xdg_toplevels; item != NULL; + item = item->next) { + ShoyuXdgToplevel *xdg_toplevel = SHOYU_XDG_TOPLEVEL(item->data); + if (xdg_toplevel->is_invalidated) + continue; + + shoyu_shell_xdg_toplevel_bind_shell(xdg_toplevel); + } +} + +static void shoyu_shell_display_destroy(struct wl_listener *listener, + void *data) { + ShoyuShell *self = wl_container_of(listener, self, display_destroy); + g_object_unref(self); +} + +static void shoyu_shell_constructed(GObject *object) { + G_OBJECT_CLASS(shoyu_shell_parent_class)->constructed(object); + + ShoyuShell *self = SHOYU_SHELL(object); + + g_assert(self->compositor != NULL); + + self->global = + wl_global_create(self->compositor->wl_display, &shoyu_shell_interface, 1, + g_object_ref(self), shoyu_shell_bind); + g_assert(self->global); + + self->display_destroy.notify = shoyu_shell_display_destroy; + wl_display_add_destroy_listener(self->compositor->wl_display, + &self->display_destroy); +} + +static void shoyu_shell_finalize(GObject *object) { + ShoyuShell *self = SHOYU_SHELL(object); + + wl_list_remove(&self->display_destroy.link); + + g_clear_pointer(&self->global, (GDestroyNotify)wl_global_destroy); + g_clear_object(&self->compositor); + + G_OBJECT_CLASS(shoyu_shell_parent_class)->finalize(object); +} + +static void shoyu_shell_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) { + ShoyuShell *self = SHOYU_SHELL(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + self->compositor = g_value_dup_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_shell_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) { + ShoyuShell *self = SHOYU_SHELL(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + g_value_set_object(value, G_OBJECT(self->compositor)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_shell_class_init(ShoyuShellClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->constructed = shoyu_shell_constructed; + object_class->finalize = shoyu_shell_finalize; + object_class->set_property = shoyu_shell_set_property; + object_class->get_property = shoyu_shell_get_property; + + shoyu_shell_props[PROP_COMPOSITOR] = g_param_spec_object( + "compositor", "Shoyu Compositor", "The compositor the shell comes from.", + SHOYU_TYPE_COMPOSITOR, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties(object_class, N_PROPERTIES, + shoyu_shell_props); +} + +static void shoyu_shell_init(ShoyuShell *self) {} + +/** + * shoyu_shell_new: (constructor) + * + * Creates a #ShoyuShell + * + * Returns: (transfer full): A #ShoyuShell + */ +ShoyuShell *shoyu_shell_new(ShoyuCompositor *compositor) { + return SHOYU_SHELL( + g_object_new(SHOYU_TYPE_SHELL, "compositor", compositor, NULL)); +} diff --git a/shoyu-compositor/shell.h b/shoyu-compositor/shell.h new file mode 100644 index 0000000..3a23086 --- /dev/null +++ b/shoyu-compositor/shell.h @@ -0,0 +1,34 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#ifdef SHOYU_COMPILATION +typedef struct _ShoyuShell ShoyuShell; +#else +typedef GObject ShoyuShell; +#endif +typedef struct _ShoyuShellClass ShoyuShellClass; + +#define SHOYU_TYPE_SHELL (shoyu_shell_get_type()) +#define SHOYU_SHELL(object) \ + (G_TYPE_CHECK_INSTANCE_CAST((object), SHOYU_TYPE_SHELL, ShoyuShell)) +#define SHOYU_SHELL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), SHOYU_TYPE_SHELL, ShoyuShellClass)) +#define SHOYU_IS_SHELL(object) \ + (G_TYPE_CHECK_INSTANCE_TYPE((object), SHOYU_TYPE_SHELL)) +#define SHOYU_IS_SHELL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), SHOYU_TYPE_SHELL)) +#define SHOYU_SHELL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), SHOYU_TYPE_SHELL, ShoyuShellClass)) + +SHOYU_AVAILABLE_IN_ALL +GType shoyu_shell_get_type(void) G_GNUC_CONST; + +G_END_DECLS diff --git a/shoyu-compositor/shoyu-compositor.h b/shoyu-compositor/shoyu-compositor.h new file mode 100644 index 0000000..2e0eeaa --- /dev/null +++ b/shoyu-compositor/shoyu-compositor.h @@ -0,0 +1,16 @@ +#pragma once + +#define __SHOYU_COMPOSITOR_H_INSIDE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef __SHOYU_COMPOSITOR_H_INSIDE__ diff --git a/shoyu-compositor/surface-private.h b/shoyu-compositor/surface-private.h new file mode 100644 index 0000000..ee780eb --- /dev/null +++ b/shoyu-compositor/surface-private.h @@ -0,0 +1,30 @@ +#pragma once + +#include "surface.h" + +#include + +struct _ShoyuSurface { + GObject parent_instance; + + ShoyuCompositor *compositor; + + struct wlr_surface *wlr_surface; + struct wlr_buffer *buffer; + + bool is_invalidated; + + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener map; +}; + +struct _ShoyuSurfaceClass { + GObjectClass parent_class; + + void (*realized)(ShoyuSurface *self, struct wlr_surface *wlr_surface); + void (*unrealized)(ShoyuSurface *self); +}; + +void shoyu_surface_realize(ShoyuSurface *self, struct wlr_surface *wlr_surface); +void shoyu_surface_unrealize(ShoyuSurface *self); diff --git a/shoyu-compositor/surface.c b/shoyu-compositor/surface.c new file mode 100644 index 0000000..daa5700 --- /dev/null +++ b/shoyu-compositor/surface.c @@ -0,0 +1,252 @@ +#include "compositor-private.h" +#include "output-private.h" +#include "surface-private.h" + +/** + * ShoyuSurface: + * + * An object which represents a surface for #ShoyuCompositor. + */ + +enum { + PROP_0 = 0, + PROP_COMPOSITOR, + N_PROPERTIES, + + SIG_DESTROY = 0, + SIG_REALIZED, + SIG_UNREALIZED, + SIG_COMMIT, + SIG_MAP, + N_SIGNALS, +}; + +static GParamSpec *shoyu_surface_props[N_PROPERTIES] = { + NULL, +}; +static guint shoyu_surface_sigs[N_SIGNALS]; + +G_DEFINE_TYPE(ShoyuSurface, shoyu_surface, G_TYPE_OBJECT) + +static void shoyu_surface_destroy(struct wl_listener *listener, void *data) { + ShoyuSurface *self = wl_container_of(listener, self, destroy); + + if (!self->is_invalidated) { + shoyu_surface_unrealize(self); + g_signal_emit(self, shoyu_surface_sigs[SIG_DESTROY], 0); + } +} + +static void shoyu_surface_commit(struct wl_listener *listener, void *data) { + ShoyuSurface *self = wl_container_of(listener, self, commit); + + struct wlr_xdg_toplevel *wlr_xdg_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(self->wlr_surface); + if (wlr_xdg_toplevel != NULL) { + if (wlr_xdg_toplevel->base->initial_commit) { + ShoyuOutput *output = shoyu_compositor_get_xdg_toplevel_claimed_output( + self->compositor, wlr_xdg_toplevel); + if (output != NULL) { + wlr_xdg_toplevel_set_size(wlr_xdg_toplevel, output->wlr_output->width, + output->wlr_output->height); + } else { + wlr_xdg_toplevel_set_size(wlr_xdg_toplevel, 0, 0); + } + } + } + + struct wlr_buffer *buffer = NULL; + if (self->wlr_surface->buffer != NULL) { + buffer = wlr_buffer_lock(&self->wlr_surface->buffer->base); + } + + wlr_buffer_unlock(self->buffer); + self->buffer = buffer; + + g_signal_emit(self, shoyu_surface_sigs[SIG_COMMIT], 0); +} + +static void shoyu_surface_map(struct wl_listener *listener, void *data) { + ShoyuSurface *self = wl_container_of(listener, self, map); + + g_signal_emit(self, shoyu_surface_sigs[SIG_MAP], 0); +} + +static void shoyu_surface_finalize(GObject *object) { + ShoyuSurface *self = SHOYU_SURFACE(object); + + g_clear_object(&self->compositor); + + G_OBJECT_CLASS(shoyu_surface_parent_class)->finalize(object); +} + +static void shoyu_surface_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) { + ShoyuSurface *self = SHOYU_SURFACE(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + self->compositor = g_value_dup_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_surface_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) { + ShoyuSurface *self = SHOYU_SURFACE(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + g_value_set_object(value, G_OBJECT(self->compositor)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_surface_class_init(ShoyuSurfaceClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->finalize = shoyu_surface_finalize; + object_class->set_property = shoyu_surface_set_property; + object_class->get_property = shoyu_surface_get_property; + + shoyu_surface_props[PROP_COMPOSITOR] = g_param_spec_object( + "compositor", "Shoyu Compositor", + "The compositor the surface comes from.", SHOYU_TYPE_COMPOSITOR, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties(object_class, N_PROPERTIES, + shoyu_surface_props); + + /** + * ShoyuSurface::destroy: + * @surface: a #ShoyuSurface + */ + shoyu_surface_sigs[SIG_DESTROY] = + g_signal_new("destroy", SHOYU_TYPE_SURFACE, G_SIGNAL_RUN_LAST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0); + + /** + * ShoyuSurface::realized: + * @surface: a #ShoyuSurface + * @wlr_surface: A wlroots surface + */ + shoyu_surface_sigs[SIG_REALIZED] = + g_signal_new("realized", SHOYU_TYPE_SURFACE, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuSurfaceClass, realized), NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * ShoyuSurface::unrealized: + * @surface: a #ShoyuSurface + */ + shoyu_surface_sigs[SIG_UNREALIZED] = + g_signal_new("unrealized", SHOYU_TYPE_SURFACE, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuSurfaceClass, unrealized), NULL, NULL, + NULL, G_TYPE_NONE, 0); + + /** + * ShoyuSurface::commit: + * @surface: a #ShoyuSurface + */ + shoyu_surface_sigs[SIG_COMMIT] = + g_signal_new("commit", SHOYU_TYPE_SURFACE, G_SIGNAL_RUN_LAST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0); + + /** + * ShoyuSurface::map: + * @surface: a #ShoyuSurface + */ + shoyu_surface_sigs[SIG_MAP] = + g_signal_new("map", SHOYU_TYPE_SURFACE, G_SIGNAL_RUN_LAST, 0, NULL, NULL, + NULL, G_TYPE_NONE, 0); +} + +static void shoyu_surface_init(ShoyuSurface *self) { + self->is_invalidated = TRUE; +} + +/** + * shoyu_surface_new: (constructor) + * + * Creates a #ShoyuSurface + * + * Returns: (transfer full): A #ShoyuSurface + */ +ShoyuSurface *shoyu_surface_new(ShoyuCompositor *compositor) { + return SHOYU_SURFACE( + g_object_new(SHOYU_TYPE_SURFACE, "compositor", compositor, NULL)); +} + +/** + * shoyu_surface_get_compositor: + * @self: A #ShoyuSurface + * + * Gets the #ShoyuCompositor which the surface comes from. + * + * Returns: (transfer none) (nullable): A #ShoyuCompositor + */ +ShoyuCompositor *shoyu_surface_get_compositor(ShoyuSurface *self) { + g_return_val_if_fail(SHOYU_IS_SURFACE(self), NULL); + return self->compositor; +} + +/** + * shoyu_surface_realize: + * @self: A #ShoyuSurface + * @wlr_surface: The wlroots surface + */ +void shoyu_surface_realize(ShoyuSurface *self, + struct wlr_surface *wlr_surface) { + g_return_if_fail(SHOYU_IS_SURFACE(self)); + g_return_if_fail(self->wlr_surface == NULL && self->is_invalidated); + + self->wlr_surface = wlr_surface; + self->is_invalidated = FALSE; + + self->destroy.notify = shoyu_surface_destroy; + wl_signal_add(&self->wlr_surface->events.destroy, &self->destroy); + + self->commit.notify = shoyu_surface_commit; + wl_signal_add(&self->wlr_surface->events.commit, &self->commit); + + self->map.notify = shoyu_surface_map; + wl_signal_add(&self->wlr_surface->events.map, &self->map); + + g_signal_emit(self, shoyu_surface_sigs[SIG_REALIZED], 0, wlr_surface); +} + +/** + * shoyu_surface_unrealize: + * @self: A #ShoyuSurface + */ +void shoyu_surface_unrealize(ShoyuSurface *self) { + g_return_if_fail(SHOYU_IS_SURFACE(self)); + g_return_if_fail(self->wlr_surface != NULL && !self->is_invalidated); + + struct wlr_xdg_toplevel *wlr_xdg_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(self->wlr_surface); + if (wlr_xdg_toplevel != NULL) { + ShoyuOutput *output = shoyu_compositor_get_xdg_toplevel_claimed_output( + self->compositor, wlr_xdg_toplevel); + if (output != NULL) { + output->wlr_surface = NULL; + } + } + + wl_list_remove(&self->destroy.link); + wl_list_remove(&self->commit.link); + wl_list_remove(&self->map.link); + + g_clear_pointer(&self->buffer, (GDestroyNotify)wlr_buffer_unlock); + + self->wlr_surface = NULL; + self->is_invalidated = TRUE; + + g_signal_emit(self, shoyu_surface_sigs[SIG_UNREALIZED], 0); +} diff --git a/shoyu-compositor/surface.h b/shoyu-compositor/surface.h new file mode 100644 index 0000000..c06d329 --- /dev/null +++ b/shoyu-compositor/surface.h @@ -0,0 +1,41 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#ifdef SHOYU_COMPILATION +typedef struct _ShoyuSurface ShoyuSurface; +#else +typedef GObject ShoyuSurface; +#endif +typedef struct _ShoyuSurfaceClass ShoyuSurfaceClass; + +#define SHOYU_TYPE_SURFACE (shoyu_surface_get_type()) +#define SHOYU_SURFACE(object) \ + (G_TYPE_CHECK_INSTANCE_CAST((object), SHOYU_TYPE_SURFACE, ShoyuSurface)) +#define SHOYU_SURFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), SHOYU_TYPE_SURFACE, ShoyuSurfaceClass)) +#define SHOYU_IS_SURFACE(object) \ + (G_TYPE_CHECK_INSTANCE_TYPE((object), SHOYU_TYPE_SURFACE)) +#define SHOYU_IS_SURFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), SHOYU_TYPE_SURFACE)) +#define SHOYU_SURFACE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), SHOYU_TYPE_SURFACE, ShoyuSurfaceClass)) + +SHOYU_AVAILABLE_IN_ALL +GType shoyu_surface_get_type(void) G_GNUC_CONST; + +SHOYU_AVAILABLE_IN_ALL +ShoyuSurface *shoyu_surface_new(ShoyuCompositor *compositor); + +SHOYU_AVAILABLE_IN_ALL +ShoyuCompositor *shoyu_surface_get_compositor(ShoyuSurface *self); + +G_END_DECLS diff --git a/shoyu-compositor/version.c b/shoyu-compositor/version.c new file mode 100644 index 0000000..2dbe30d --- /dev/null +++ b/shoyu-compositor/version.c @@ -0,0 +1,80 @@ +#include "version.h" +#include "config.h" + +/** + * shoyu_get_major_version: + * + * Returns the major version number of the Shoyu Compositor library. + * + * Returns: the major version number of the Shoyu Compositor library + */ +guint shoyu_get_major_version(void) { return SHOYU_MAJOR_VERSION; } + +/** + * shoyu_get_minor_version: + * + * Returns the minor version number of the Shoyu Compositor library. + * + * Returns: the minor version number of the Shoyu Compositor library + */ +guint shoyu_get_minor_version(void) { return SHOYU_MINOR_VERSION; } + +/** + * shoyu_get_micro_version: + * + * Returns the micro version number of the Shoyu Compositor library. + * + * Returns: the micro version number of the Shoyu Compositor library + */ +guint shoyu_get_micro_version(void) { return SHOYU_MICRO_VERSION; } + +/** + * shoyu_get_binary_age: + * + * Returns the binary age as passed to `libtool`. + * + * If `libtool` means nothing to you, don't worry about it. + * + * Returns: the binary age of the Shoyu Compositor library + */ +guint shoyu_get_binary_age(void) { return SHOYU_MICRO_VERSION; } + +/** + * shoyu_get_interface_age: + * + * Returns the interface age as passed to `libtool`. + * + * If `libtool` means nothing to you, don't worry about it. + * + * Returns: the interface age of the Shoyu Compositor library + */ +guint shoyu_get_interface_age(void) { return SHOYU_MICRO_VERSION; } + +/** + * shoyu_check_version: + * @required_major: the required major version + * @required_minor: the required minor version + * @required_micro: the required micro version + * + * Checks that the Shoyu Compositor library in use is compatible with the + * given version. + * + * Returns: (nullable): %NULL if the Shoyu Compositor library is compatible with + * the given version, or a string describing the version mismatch. The returned + * string is owned by Shoyu Compositor and should not be modified or freed. + */ +const char *shoyu_check_version(guint required_major, guint required_minor, + guint required_micro) { + int shoyu_effective_micro = 100 * SHOYU_MINOR_VERSION + SHOYU_MICRO_VERSION; + int required_effective_micro = 100 * required_minor + required_micro; + + if (required_major > SHOYU_MAJOR_VERSION) + return "Shoyu Compositor version too old (major mismatch)"; + if (required_major < SHOYU_MAJOR_VERSION) + return "Shoyu Compositor version too new (major mismatch)"; + if (required_effective_micro < shoyu_effective_micro - SHOYU_BINARY_AGE) + return "Shoyu Compositor version too new (micro mismatch)"; + if (required_effective_micro > shoyu_effective_micro) + return "Shoyu Compositor version too old (micro mismatch)"; + return NULL; +} diff --git a/shoyu-compositor/version.h.in b/shoyu-compositor/version.h.in new file mode 100644 index 0000000..a96262b --- /dev/null +++ b/shoyu-compositor/version.h.in @@ -0,0 +1,88 @@ +#pragma once + +#if !defined (__SHOYU_COMPOSITOR_H_INSIDE__) && !defined (SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * SHOYU_MAJOR_VERSION: + * + * Like [func@get_major_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_MAJOR_VERSION (@SHOYU_COMPOSITOR_MAJOR_VERSION@) + +/** + * SHOYU_MINOR_VERSION: + * + * Like [func@get_minor_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_MINOR_VERSION (@SHOYU_COMPOSITOR_MINOR_VERSION@) + +/** + * SHOYU_MICRO_VERSION: + * + * Like [func@get_micro_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_MICRO_VERSION (@SHOYU_COMPOSITOR_MICRO_VERSION@) + +/** + * SHOYU_BINARY_AGE: + * + * Like [func@get_binary_age], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_BINARY_AGE (@SHOYU_COMPOSITOR_BINARY_AGE@) + +/** + * SHOYU_INTERFACE_AGE: + * + * Like [func@get_interface_age], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_INTERFACE_AGE (@SHOYU_COMPOSITOR_INTERFACE_AGE@) + +/** + * SHOYU_CHECK_VERSION: + * @major: major version (e.g. 1 for version 1.2.5) + * @minor: minor version (e.g. 2 for version 1.2.5) + * @micro: micro version (e.g. 5 for version 1.2.5) + * + * Returns %TRUE if the version of the SHOYU_COMPOSITOR header files + * is the same as or newer than the passed-in version. + * + * Returns: %TRUE if SHOYU_COMPOSITOR headers are new enough + */ +#define SHOYU_CHECK_VERSION(major,minor,micro) \ + (SHOYU_MAJOR_VERSION > (major) || \ + (SHOYU_MAJOR_VERSION == (major) && SHOYU_MINOR_VERSION > (minor)) || \ + (SHOYU_MAJOR_VERSION == (major) && SHOYU_MINOR_VERSION == (minor) && \ + SHOYU_MICRO_VERSION >= (micro))) + +SHOYU_AVAILABLE_IN_ALL +guint shoyu_get_major_version(void) G_GNUC_CONST; +SHOYU_AVAILABLE_IN_ALL +guint shoyu_get_minor_version(void) G_GNUC_CONST; +SHOYU_AVAILABLE_IN_ALL +guint shoyu_get_micro_version(void) G_GNUC_CONST; +SHOYU_AVAILABLE_IN_ALL +guint shoyu_get_binary_age(void) G_GNUC_CONST; +SHOYU_AVAILABLE_IN_ALL +guint shoyu_get_interface_age(void) G_GNUC_CONST; + +SHOYU_AVAILABLE_IN_ALL +const char* shoyu_check_version(guint required_major, guint required_minor, guint required_micro); + +G_END_DECLS diff --git a/shoyu-compositor/version/meson.build b/shoyu-compositor/version/meson.build new file mode 100644 index 0000000..adfa160 --- /dev/null +++ b/shoyu-compositor/version/meson.build @@ -0,0 +1,16 @@ +libcompositor_version_macros_h = custom_target( + input: 'versionmacros.h.in', + output: 'versionmacros.h', + command: [gen_visibility_macros, meson.project_version(), 'versions-macros', 'SHOYU', '@INPUT@', '@OUTPUT@'], + install: true, + install_dir: shoyu_includedir / 'shoyu-compositor/version', + # FIXME: Not needed with Meson >= 0.64.0 + install_tag: 'devel') + +libcompositor_visibility_h = custom_target( + output: 'visibility.h', + command: [gen_visibility_macros, meson.project_version(), 'visibility-macros', 'SHOYU', '@OUTPUT@'], + install: true, + install_dir: shoyu_includedir / 'shoyu-compositor/version', + # FIXME: Not needed with Meson >= 0.64.0 + install_tag: 'devel') diff --git a/shoyu-compositor/version/versionmacros.h.in b/shoyu-compositor/version/versionmacros.h.in new file mode 100644 index 0000000..f2327db --- /dev/null +++ b/shoyu-compositor/version/versionmacros.h.in @@ -0,0 +1,123 @@ +#if !defined (__SHOYU_COMPOSITOR_H_INSIDE__) && !defined (SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#pragma once + +#include + +#if !defined(SHOYU_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(4, 6) || \ + __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 4)) +#define _SHOYU_GNUC_DO_PRAGMA(x) _Pragma(G_STRINGIFY (x)) +#define SHOYU_DEPRECATED_MACRO _SHOYU_GNUC_DO_PRAGMA(GCC warning "Deprecated pre-processor symbol") +#define SHOYU_DEPRECATED_MACRO_FOR(f) \ + _SHOYU_GNUC_DO_PRAGMA(GCC warning G_STRINGIFY (Deprecated pre-processor symbol: replace with #f)) +#define SHOYU_UNAVAILABLE_MACRO(maj,min) \ + _SHOYU_GNUC_DO_PRAGMA(GCC warning G_STRINGIFY (Not available before maj.min)) +#else +#define SHOYU_DEPRECATED_MACRO +#define SHOYU_DEPRECATED_MACRO_FOR(f) +#define SHOYU_UNAVAILABLE_MACRO(maj,min) +#endif + +#if !defined(SHOYU_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(6, 1) || \ + (defined (__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 0)))) +#define SHOYU_DEPRECATED_ENUMERATOR G_DEPRECATED +#define SHOYU_DEPRECATED_ENUMERATOR_FOR(f) G_DEPRECATED_FOR(f) +#define SHOYU_UNAVAILABLE_ENUMERATOR(maj,min) G_UNAVAILABLE(maj,min) +#else +#define SHOYU_DEPRECATED_ENUMERATOR +#define SHOYU_DEPRECATED_ENUMERATOR_FOR(f) +#define SHOYU_UNAVAILABLE_ENUMERATOR(maj,min) +#endif + +#if !defined(SHOYU_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(3, 1) || \ + (defined (__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 0)))) +#define SHOYU_DEPRECATED_TYPE G_DEPRECATED +#define SHOYU_DEPRECATED_TYPE_FOR(f) G_DEPRECATED_FOR(f) +#define SHOYU_UNAVAILABLE_TYPE(maj,min) G_UNAVAILABLE(maj,min) +#else +#define SHOYU_DEPRECATED_TYPE +#define SHOYU_DEPRECATED_TYPE_FOR(f) +#define SHOYU_UNAVAILABLE_TYPE(maj,min) +#endif + +@SHOYU_VERSIONS@ + +/* evaluates to the current stable version; for development cycles, + * this means the next stable target, with a hard backstop to the + * beginning of the stable series + */ +#if SHOYU_MAJOR_VERSION >= 4 && (SHOYU_MINOR_VERSION % 2) +# define SHOYU_VERSION_CUR_STABLE (G_ENCODE_VERSION (SHOYU_MAJOR_VERSION, SHOYU_MINOR_VERSION + 1)) +#elif G_ENCODE_VERSION (SHOYU_MAJOR_VERSION, SHOYU_MINOR_VERSION) > SHOYU_VERSION_0_0 +# define SHOYU_VERSION_CUR_STABLE (G_ENCODE_VERSION (SHOYU_MAJOR_VERSION, SHOYU_MINOR_VERSION)) +#else +# define SHOYU_VERSION_CUR_STABLE SHOYU_VERSION_0_0 +#endif + +/* evaluates to the previous stable version, with a hard backstop + * to the beginning of the stable series + */ +#if SHOYU_MAJOR_VERSION >= 4 && (SHOYU_MINOR_VERSION % 2) +# define SHOYU_VERSION_PREV_STABLE (G_ENCODE_VERSION (SHOYU_MAJOR_VERSION, SHOYU_MINOR_VERSION - 1)) +#elif SHOYU_MAJOR_VERSION >= 4 && SHOYU_MINOR_VERSION > 2 +# define SHOYU_VERSION_PREV_STABLE (G_ENCODE_VERSION (SHOYU_MAJOR_VERSION, SHOYU_MINOR_VERSION - 2)) +#else +# define SHOYU_VERSION_PREV_STABLE SHOYU_VERSION_0_0 +#endif + +/** + * SHOYU_VERSION_MIN_REQUIRED: + * + * A macro that should be defined by the user prior to including + * the `gdk.h` header. + * + * The definition should be one of the predefined SHOYU version + * macros: %SHOYU_VERSION_0_0, %SHOYU_VERSION_4_2,... + * + * This macro defines the lower bound for the SHOYU API to use. + * + * If a function has been deprecated in a newer version of SHOYU, + * it is possible to use this symbol to avoid the compiler warnings + * without disabling warning for every deprecated function. + */ +#ifndef SHOYU_VERSION_MIN_REQUIRED +# define SHOYU_VERSION_MIN_REQUIRED (SHOYU_VERSION_CUR_STABLE) +#endif + +/** + * SHOYU_VERSION_MAX_ALLOWED: + * + * A macro that should be defined by the user prior to including + * the `gdk.h` header. + * + * The definition should be one of the predefined SHOYU version + * macros: %SHOYU_VERSION_0_0, %SHOYU_VERSION_4_2,... + * + * This macro defines the upper bound for the SHOYU API to use. + * + * If a function has been introduced in a newer version of SHOYU, + * it is possible to use this symbol to get compiler warnings when + * trying to use that function. + */ +#ifndef SHOYU_VERSION_MAX_ALLOWED +# if SHOYU_VERSION_MIN_REQUIRED > SHOYU_VERSION_PREV_STABLE +# define SHOYU_VERSION_MAX_ALLOWED SHOYU_VERSION_MIN_REQUIRED +# else +# define SHOYU_VERSION_MAX_ALLOWED SHOYU_VERSION_CUR_STABLE +# endif +#endif + +/* sanity checks */ +#if SHOYU_VERSION_MAX_ALLOWED < SHOYU_VERSION_MIN_REQUIRED +# error "SHOYU_VERSION_MAX_ALLOWED must be >= SHOYU_VERSION_MIN_REQUIRED" +#endif +#if SHOYU_VERSION_MIN_REQUIRED < SHOYU_VERSION_0_0 +# error "SHOYU_VERSION_MIN_REQUIRED must be >= SHOYU_VERSION_0_0" +#endif + +#include diff --git a/shoyu-compositor/wayland-event-source-private.h b/shoyu-compositor/wayland-event-source-private.h new file mode 100644 index 0000000..7f7f9cd --- /dev/null +++ b/shoyu-compositor/wayland-event-source-private.h @@ -0,0 +1,9 @@ +#pragma once + +#include "wayland-event-source.h" + +struct _ShoyuWaylandEventSource { + GSource source; + struct wl_display *wl_display; + struct wl_event_loop *event_loop; +}; diff --git a/shoyu-compositor/wayland-event-source.c b/shoyu-compositor/wayland-event-source.c new file mode 100644 index 0000000..5582f2c --- /dev/null +++ b/shoyu-compositor/wayland-event-source.c @@ -0,0 +1,67 @@ +#include "wayland-event-source-private.h" + +/** + * ShoyuWaylandEventSource: + * + * A #GSource which handles the dispatching of Wayland server events. + */ + +static gboolean shoyu_wayland_event_source_prepare(GSource *source, + int *timeout) { + ShoyuWaylandEventSource *self = (ShoyuWaylandEventSource *)source; + + *timeout = -1; + + if (self->wl_display != NULL) + wl_display_flush_clients(self->wl_display); + + return FALSE; +} + +static gboolean shoyu_wayland_event_source_dispatch(GSource *source, + GSourceFunc callback, + gpointer data) { + ShoyuWaylandEventSource *self = (ShoyuWaylandEventSource *)source; + + wl_event_loop_dispatch(self->event_loop, 0); + return TRUE; +} + +static GSourceFuncs shoyu_wayland_event_source_funcs = { + shoyu_wayland_event_source_prepare, + NULL, + shoyu_wayland_event_source_dispatch, + NULL, +}; + +/** + * shoyu_wayland_event_source_new: (constructor) + * @wl_display: (not nullable): a Wayland display + * @event_loop: (not nullable): a Wayland event loop + * + * Creates a new #GSource which is responsible for dispatching Wayland + * server events. + * + * Returns: a #GSource + */ +GSource *shoyu_wayland_event_source_new(struct wl_display *wl_display, + struct wl_event_loop *event_loop) { + GSource *source = g_source_new(&shoyu_wayland_event_source_funcs, + sizeof(ShoyuWaylandEventSource)); + + char *name = g_strdup_printf("Wayland Event Loop source (%p)", event_loop); + g_source_set_name(source, name); + g_free(name); + + ShoyuWaylandEventSource *self = (ShoyuWaylandEventSource *)source; + + self->wl_display = wl_display; + self->event_loop = event_loop; + + g_source_add_unix_fd(source, wl_event_loop_get_fd(event_loop), + G_IO_IN | G_IO_ERR); + g_source_set_can_recurse(source, TRUE); + g_source_attach(source, NULL); + + return source; +} diff --git a/shoyu-compositor/wayland-event-source.h b/shoyu-compositor/wayland-event-source.h new file mode 100644 index 0000000..f3890c7 --- /dev/null +++ b/shoyu-compositor/wayland-event-source.h @@ -0,0 +1,23 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#ifdef SHOYU_COMPILATION +typedef struct _ShoyuWaylandEventSource ShoyuWaylandEventSource; +#else +typedef GSource ShoyuWaylandEventSource; +#endif + +SHOYU_AVAILABLE_IN_ALL +GSource *shoyu_wayland_event_source_new(struct wl_display *wl_display, + struct wl_event_loop *event_loop); + +G_END_DECLS diff --git a/shoyu-compositor/xdg-shell-protocol.h b/shoyu-compositor/xdg-shell-protocol.h new file mode 100644 index 0000000..1099686 --- /dev/null +++ b/shoyu-compositor/xdg-shell-protocol.h @@ -0,0 +1 @@ +#include diff --git a/shoyu-compositor/xdg-toplevel-private.h b/shoyu-compositor/xdg-toplevel-private.h new file mode 100644 index 0000000..dda4f4a --- /dev/null +++ b/shoyu-compositor/xdg-toplevel-private.h @@ -0,0 +1,32 @@ +#pragma once + +#include "xdg-toplevel.h" + +#include + +struct _ShoyuXdgToplevel { + GObject parent_instance; + + ShoyuCompositor *compositor; + + struct wlr_xdg_toplevel *wlr_xdg_toplevel; + + bool is_invalidated; + + struct wl_listener destroy; +}; + +struct _ShoyuXdgToplevelClass { + GObjectClass parent_class; + + void (*realized)(ShoyuXdgToplevel *self, + struct wlr_xdg_toplevel *wlr_xdg_toplevel); + void (*unrealized)(ShoyuXdgToplevel *self); +}; + +void shoyu_xdg_toplevel_realize(ShoyuXdgToplevel *self, + struct wlr_xdg_toplevel *wlr_xdg_toplevel); +void shoyu_xdg_toplevel_unrealize(ShoyuXdgToplevel *self); + +void shoyu_shell_xdg_toplevel_bind_shell(ShoyuXdgToplevel *self); +void shoyu_shell_xdg_toplevel_unbind_shell(ShoyuXdgToplevel *self); diff --git a/shoyu-compositor/xdg-toplevel.c b/shoyu-compositor/xdg-toplevel.c new file mode 100644 index 0000000..9202b97 --- /dev/null +++ b/shoyu-compositor/xdg-toplevel.c @@ -0,0 +1,201 @@ +#include "compositor-private.h" +#include "shell-private.h" +#include "shell-toplevel-private.h" +#include "xdg-toplevel-private.h" + +enum { + PROP_0 = 0, + PROP_COMPOSITOR, + N_PROPERTIES, + + SIG_DESTROY = 0, + SIG_REALIZED, + SIG_UNREALIZED, + N_SIGNALS, +}; + +static GParamSpec *shoyu_xdg_toplevel_props[N_PROPERTIES] = { + NULL, +}; +static guint shoyu_xdg_toplevel_sigs[N_SIGNALS]; + +G_DEFINE_TYPE(ShoyuXdgToplevel, shoyu_xdg_toplevel, G_TYPE_OBJECT) + +static void shoyu_xdg_toplevel_destroy(struct wl_listener *listener, + void *data) { + ShoyuXdgToplevel *self = wl_container_of(listener, self, destroy); + + if (!self->is_invalidated) { + shoyu_xdg_toplevel_unrealize(self); + g_signal_emit(self, shoyu_xdg_toplevel_sigs[SIG_DESTROY], 0); + } +} + +static void shoyu_xdg_toplevel_finalize(GObject *object) { + ShoyuXdgToplevel *self = SHOYU_XDG_TOPLEVEL(object); + + g_clear_object(&self->compositor); + + G_OBJECT_CLASS(shoyu_xdg_toplevel_parent_class)->finalize(object); +} + +static void shoyu_xdg_toplevel_set_property(GObject *object, guint prop_id, + const GValue *value, + GParamSpec *pspec) { + ShoyuXdgToplevel *self = SHOYU_XDG_TOPLEVEL(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + self->compositor = g_value_dup_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_xdg_toplevel_get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) { + ShoyuXdgToplevel *self = SHOYU_XDG_TOPLEVEL(object); + + switch (prop_id) { + case PROP_COMPOSITOR: + g_value_set_object(value, G_OBJECT(self->compositor)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void shoyu_xdg_toplevel_class_init(ShoyuXdgToplevelClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS(class); + + object_class->finalize = shoyu_xdg_toplevel_finalize; + object_class->set_property = shoyu_xdg_toplevel_set_property; + object_class->get_property = shoyu_xdg_toplevel_get_property; + + shoyu_xdg_toplevel_props[PROP_COMPOSITOR] = g_param_spec_object( + "compositor", "Shoyu Compositor", "The compositor the output comes from.", + SHOYU_TYPE_COMPOSITOR, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties(object_class, N_PROPERTIES, + shoyu_xdg_toplevel_props); + + /** + * ShoyuXdgToplevel::destroy: + * @output: a #ShoyuXdgToplevel + */ + shoyu_xdg_toplevel_sigs[SIG_DESTROY] = + g_signal_new("destroy", SHOYU_TYPE_XDG_TOPLEVEL, G_SIGNAL_RUN_LAST, 0, + NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * ShoyuXdgToplevel::realized: + * @output: a #ShoyuXdgToplevel + * @wlr_output: A wlroots output + */ + shoyu_xdg_toplevel_sigs[SIG_REALIZED] = + g_signal_new("realized", SHOYU_TYPE_XDG_TOPLEVEL, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuXdgToplevelClass, realized), NULL, NULL, + NULL, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * ShoyuXdgToplevel::unrealized: + * @output: a #ShoyuXdgToplevel + */ + shoyu_xdg_toplevel_sigs[SIG_UNREALIZED] = + g_signal_new("unrealized", SHOYU_TYPE_XDG_TOPLEVEL, G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(ShoyuXdgToplevelClass, unrealized), NULL, + NULL, NULL, G_TYPE_NONE, 0); +} + +static void shoyu_xdg_toplevel_init(ShoyuXdgToplevel *self) { + self->is_invalidated = TRUE; +} + +/** + * shoyu_xdg_toplevel_new: (constructor) + * + * Creates a #ShoyuXdgToplevel + * + * Returns: (transfer full): A #ShoyuXdgToplevel + */ +ShoyuXdgToplevel *shoyu_xdg_toplevel_new(ShoyuCompositor *compositor) { + return SHOYU_XDG_TOPLEVEL( + g_object_new(SHOYU_TYPE_XDG_TOPLEVEL, "compositor", compositor, NULL)); +} + +/** + * shoyu_xdg_toplevel_get_compositor: + * @self: A #ShoyuXdgToplevel + * + * Gets the #ShoyuCompositor which the XDG toplevel comes from. + * + * Returns: (transfer none) (nullable): A #ShoyuCompositor + */ +ShoyuCompositor *shoyu_xdg_toplevel_get_compositor(ShoyuXdgToplevel *self) { + g_return_val_if_fail(SHOYU_IS_XDG_TOPLEVEL(self), NULL); + return self->compositor; +} + +/** + * shoyu_xdg_toplevel_realize: + * @self: A #ShoyuXdgToplevel + * @wlr_output: The wlroots output + */ +void shoyu_xdg_toplevel_realize(ShoyuXdgToplevel *self, + struct wlr_xdg_toplevel *wlr_xdg_toplevel) { + g_return_if_fail(SHOYU_IS_XDG_TOPLEVEL(self)); + g_return_if_fail(self->wlr_xdg_toplevel == NULL && self->is_invalidated); + + self->wlr_xdg_toplevel = wlr_xdg_toplevel; + self->is_invalidated = FALSE; + + self->destroy.notify = shoyu_xdg_toplevel_destroy; + wl_signal_add(&self->wlr_xdg_toplevel->events.destroy, &self->destroy); + + g_signal_emit(self, shoyu_xdg_toplevel_sigs[SIG_REALIZED], 0, + wlr_xdg_toplevel); + + if (!shoyu_compositor_is_xdg_toplevel_claimed(self->compositor, + wlr_xdg_toplevel) && + self->compositor->shell->resource != NULL) { + shoyu_shell_xdg_toplevel_bind_shell(self); + } +} + +/** + * shoyu_xdg_toplevel_unrealize: + * @self: A #ShoyuXdgToplevel + */ +void shoyu_xdg_toplevel_unrealize(ShoyuXdgToplevel *self) { + g_return_if_fail(SHOYU_IS_XDG_TOPLEVEL(self)); + g_return_if_fail(self->wlr_xdg_toplevel != NULL && !self->is_invalidated); + + wl_list_remove(&self->destroy.link); + + self->wlr_xdg_toplevel = NULL; + self->is_invalidated = TRUE; + + g_signal_emit(self, shoyu_xdg_toplevel_sigs[SIG_UNREALIZED], 0); + + if (!shoyu_compositor_is_xdg_toplevel_claimed(self->compositor, + self->wlr_xdg_toplevel) && + self->compositor->shell->resource != NULL) { + shoyu_shell_xdg_toplevel_unbind_shell(self); + } +} + +void shoyu_shell_xdg_toplevel_bind_shell(ShoyuXdgToplevel *self) { + shoyu_shell_toplevel_create(self->compositor->shell->client, + self->compositor->shell->resource, + self->wlr_xdg_toplevel); +} + +void shoyu_shell_xdg_toplevel_unbind_shell(ShoyuXdgToplevel *self) { + if (self->wlr_xdg_toplevel != NULL) { + shoyu_shell_toplevel_delete(self->compositor->shell, + self->wlr_xdg_toplevel); + } +} diff --git a/shoyu-compositor/xdg-toplevel.h b/shoyu-compositor/xdg-toplevel.h new file mode 100644 index 0000000..d40f904 --- /dev/null +++ b/shoyu-compositor/xdg-toplevel.h @@ -0,0 +1,44 @@ +#pragma once + +#if !defined(__SHOYU_COMPOSITOR_H_INSIDE__) && !defined(SHOYU_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#ifdef SHOYU_COMPILATION +typedef struct _ShoyuXdgToplevel ShoyuXdgToplevel; +#else +typedef GObject ShoyuXdgToplevel; +#endif +typedef struct _ShoyuXdgToplevelClass ShoyuXdgToplevelClass; + +#define SHOYU_TYPE_XDG_TOPLEVEL (shoyu_xdg_toplevel_get_type()) +#define SHOYU_XDG_TOPLEVEL(object) \ + (G_TYPE_CHECK_INSTANCE_CAST((object), SHOYU_TYPE_XDG_TOPLEVEL, \ + ShoyuXdgToplevel)) +#define SHOYU_XDG_TOPLEVEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), SHOYU_TYPE_XDG_TOPLEVEL, \ + ShoyuXdgToplevelClass)) +#define SHOYU_IS_XDG_TOPLEVEL(object) \ + (G_TYPE_CHECK_INSTANCE_TYPE((object), SHOYU_TYPE_XDG_TOPLEVEL)) +#define SHOYU_IS_XDG_TOPLEVEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), SHOYU_TYPE_XDG_TOPLEVEL)) +#define SHOYU_XDG_TOPLEVEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), SHOYU_TYPE_XDG_TOPLEVEL, \ + ShoyuXdgToplevelClass)) + +SHOYU_AVAILABLE_IN_ALL +GType shoyu_xdg_toplevel_get_type(void) G_GNUC_CONST; + +SHOYU_AVAILABLE_IN_ALL +ShoyuXdgToplevel *shoyu_xdg_toplevel_new(ShoyuCompositor *compositor); + +SHOYU_AVAILABLE_IN_ALL +ShoyuCompositor *shoyu_xdg_toplevel_get_compositor(ShoyuXdgToplevel *self); + +G_END_DECLS diff --git a/shoyu-shell-gtk3/config.h.meson b/shoyu-shell-gtk3/config.h.meson new file mode 100644 index 0000000..9f89b36 --- /dev/null +++ b/shoyu-shell-gtk3/config.h.meson @@ -0,0 +1,11 @@ +#pragma once + +#if !defined (__SHOYU_SHELL_GTK_H_INSIDE__) && !defined (SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +G_END_DECLS diff --git a/shoyu-shell-gtk3/main-private.h b/shoyu-shell-gtk3/main-private.h new file mode 100644 index 0000000..ac0c302 --- /dev/null +++ b/shoyu-shell-gtk3/main-private.h @@ -0,0 +1,19 @@ +#pragma once + +#include "main.h" +#include + +#define SHOYU_SHELL_GTK_DISPLAY_KEY "shoyu-shell-gtk3-display" +#define SHOYU_SHELL_GTK_MONITOR_KEY "shoyu-shell-gtk3-monitor" + +typedef struct _ShoyuShellGtkDisplay { + struct shoyu_shell *shoyu_shell; + GList *surfaces; +} ShoyuShellGtkDisplay; + +typedef struct _ShoyuShellGtkToplevel { + GdkDisplay *display; + struct shoyu_shell_toplevel *shoyu_shell_toplevel; +} ShoyuShellGtkToplevel; + +struct shoyu_shell_output *shoyu_shell_gtk_get_output(GdkMonitor *monitor); diff --git a/shoyu-shell-gtk3/main.c b/shoyu-shell-gtk3/main.c new file mode 100644 index 0000000..f208974 --- /dev/null +++ b/shoyu-shell-gtk3/main.c @@ -0,0 +1,163 @@ +#include "main.h" +#include "main-private.h" +#include "shoyu-config.h" + +#include +#include +#include + +static gboolean shoyu_shell_gtk_initialized = FALSE; + +static void setlocale_initialization(void) { + static gboolean initialized = FALSE; + + if (initialized) + return; + initialized = TRUE; + + if (!setlocale(LC_ALL, "")) { + g_warning( + "Locale not supported by C library.\n\tUsing the fallback 'C' locale."); + } +} + +static void gettext_initialization(void) { + setlocale_initialization(); + + bindtextdomain(GETTEXT_PACKAGE, SHOYU_LOCALEDIR); +#ifdef HAVE_BIND_TEXTDOMAIN_CODESET + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); +#endif +} + +void shoyu_shell_gtk_init(void) { + if (!shoyu_shell_gtk_init_check()) { + g_warning("Failed to initialize"); + } +} + +gboolean shoyu_shell_gtk_init_check(void) { + if (shoyu_shell_gtk_initialized) + return TRUE; + + gettext_initialization(); + + GdkDisplay *display = gdk_display_get_default(); + g_return_val_if_fail(display != NULL, FALSE); + g_return_val_if_fail(shoyu_shell_gtk_init_display(display), FALSE); + + shoyu_shell_gtk_initialized = TRUE; + return TRUE; +} + +gboolean shoyu_shell_gtk_is_initialized(void) { + return shoyu_shell_gtk_initialized; +} + +static void shoyu_shell_toplevel_added(void *data, + struct shoyu_shell *shoyu_shell, + struct shoyu_shell_toplevel *toplevel) { + g_debug("Toplevel added %p", toplevel); + + // TODO: listen for toplevel destroy +} + +static const struct shoyu_shell_listener shoyu_shell_listener = { + .toplevel_added = shoyu_shell_toplevel_added, +}; + +static void shoyu_shell_gtk_wl_registry_global(void *data, + struct wl_registry *wl_registry, + uint32_t id, const char *iface, + uint32_t version) { + GdkDisplay *display = GDK_DISPLAY(data); + ShoyuShellGtkDisplay *self = + g_object_get_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY); + + g_debug("Found global %s.%d#%d", iface, version, id); + + if (g_strcmp0(iface, shoyu_shell_interface.name) == 0) { + self->shoyu_shell = + wl_registry_bind(wl_registry, id, &shoyu_shell_interface, + MIN(shoyu_shell_interface.version, version)); + shoyu_shell_add_listener(self->shoyu_shell, &shoyu_shell_listener, self); + } +} + +static void shoyu_shell_gtk_wl_registry_global_remove( + void *data, struct wl_registry *wl_registry, uint32_t id) { + // GdkDisplay* display = GDK_DISPLAY(data); + // ShoyuShellGtkDisplay* self = g_object_get_data(G_OBJECT(display), + // SHOYU_SHELL_GTK_DISPLAY_KEY); +} + +static const struct wl_registry_listener shoyu_shell_gtk_wl_registry_listener = + { + .global = shoyu_shell_gtk_wl_registry_global, + .global_remove = shoyu_shell_gtk_wl_registry_global_remove, +}; + +gboolean shoyu_shell_gtk_init_display(GdkDisplay *display) { + if (g_object_get_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY) != + NULL) { + return TRUE; + } + + g_return_val_if_fail(GDK_IS_WAYLAND_DISPLAY(display), FALSE); + + ShoyuShellGtkDisplay *self = g_malloc0(sizeof(ShoyuShellGtkDisplay)); + g_assert(self != NULL); + + g_object_set_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY, self); + + struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); + struct wl_registry *wl_registry = wl_display_get_registry(wl_display); + + wl_registry_add_listener(wl_registry, &shoyu_shell_gtk_wl_registry_listener, + display); + wl_display_roundtrip(wl_display); + + if (self->shoyu_shell == NULL) { + g_object_set_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY, NULL); + free(self); + return FALSE; + } + + return TRUE; +} + +struct shoyu_shell_output *shoyu_shell_gtk_get_output(GdkMonitor *monitor) { + struct shoyu_shell_output *shoyu_shell_output = + g_object_get_data(G_OBJECT(monitor), SHOYU_SHELL_GTK_MONITOR_KEY); + if (shoyu_shell_output != NULL) { + return shoyu_shell_output; + } + + GdkDisplay *display = gdk_monitor_get_display(monitor); + g_return_val_if_fail(display != NULL, NULL); + + ShoyuShellGtkDisplay *self = + g_object_get_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY); + g_return_val_if_fail(self != NULL, NULL); + + struct wl_output *wl_output = gdk_wayland_monitor_get_wl_output(monitor); + + shoyu_shell_output = shoyu_shell_get_output(self->shoyu_shell, wl_output); + g_object_set_data(G_OBJECT(monitor), SHOYU_SHELL_GTK_MONITOR_KEY, + shoyu_shell_output); + return shoyu_shell_output; +} + +gboolean shoyu_shell_gtk_monitor_set_window(GdkMonitor *monitor, + GdkWindow *window) { + struct shoyu_shell_output *shoyu_shell_output = + shoyu_shell_gtk_get_output(monitor); + g_return_val_if_fail(shoyu_shell_output != NULL, FALSE); + + g_return_val_if_fail(GDK_IS_WAYLAND_WINDOW(window), FALSE); + struct wl_surface *wl_surface = gdk_wayland_window_get_wl_surface(window); + g_return_val_if_fail(wl_surface != NULL, FALSE); + + shoyu_shell_output_set_surface(shoyu_shell_output, wl_surface); + return TRUE; +} diff --git a/shoyu-shell-gtk3/main.h b/shoyu-shell-gtk3/main.h new file mode 100644 index 0000000..e5b6707 --- /dev/null +++ b/shoyu-shell-gtk3/main.h @@ -0,0 +1,30 @@ +#pragma once + +#if !defined(__SHOYU_SHELL_GTK_H_INSIDE__) && \ + !defined(SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +void shoyu_shell_gtk_init(void); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_init_check(void); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_is_initialized(void); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_init_display(GdkDisplay *display); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_monitor_set_window(GdkMonitor *monitor, + GdkWindow *window); + +G_END_DECLS diff --git a/shoyu-shell-gtk3/meson.build b/shoyu-shell-gtk3/meson.build new file mode 100644 index 0000000..f5f9279 --- /dev/null +++ b/shoyu-shell-gtk3/meson.build @@ -0,0 +1,150 @@ +libshell_gtk3_cargs = [ + '-DSHOYU_SHELL_GTK_COMPILATION', + '-DG_LOG_DOMAIN="ShoyuShellGtk"', + '-DSHOYU_SHELL_GTK_BINARY_VERSION="@0@"'.format(shoyu_binary_version), + '-DSHOYU_SHELL_GTK_HOST="@0@"'.format(host_machine.system()), + '-DSHOYU_SHELL_GTK_DATA_PREFIX="@0@"'.format(shoyu_prefix), +] + +libshell_gtk3_private_sources = files([]) + +libshell_gtk3_private_h_sources = files([ + 'main-private.h', +]) + +libshell_gtk3_public_sources = files([ + 'main.c', + 'version.c', +]) + +libshell_gtk3_public_headers = files([ + 'main.h', + 'shoyu-shell-gtk3.h', +]) + +install_headers(libshell_gtk3_public_headers, subdir: 'shoyu-shell-gtk3/') +libshell_gtk3_sources = libshell_gtk3_public_sources + libshell_gtk3_private_sources + +libshell_gtk3_enums = gnome.mkenums_simple('shell-gtk3-enumtypes', + sources: libshell_gtk3_public_headers, + decorator: 'SHOYU_SHELL_GTK_AVAILABLE_IN_ALL', + body_prefix: '#include "config.h"', + header_prefix: '#include "version/versionmacros.h"\n', + install_dir: shoyu_includedir / 'shoyu-shell-gtk3', + install_header: true) + +libshell_gtk3_enums_h = libshell_gtk3_enums[1] + +libshell_gtk3_config_cdata = configuration_data() + +libshell_gtk3_config = configure_file( + input: 'config.h.meson', + output: 'config.h', + configuration: libshell_gtk3_config_cdata, + install_dir: shoyu_includedir / 'shoyu-shell-gtk3', +) + +libshell_gtk3_version_cdata = configuration_data() +libshell_gtk3_version_cdata.set('SHOYU_SHELL_GTK_MAJOR_VERSION', shoyu_major_version) +libshell_gtk3_version_cdata.set('SHOYU_SHELL_GTK_MINOR_VERSION', shoyu_minor_version) +libshell_gtk3_version_cdata.set('SHOYU_SHELL_GTK_MICRO_VERSION', shoyu_micro_version) +libshell_gtk3_version_cdata.set('SHOYU_SHELL_GTK_BINARY_AGE', shoyu_binary_age) +libshell_gtk3_version_cdata.set('SHOYU_SHELL_GTK_INTERFACE_AGE', shoyu_interface_age) +libshell_gtk3_version_cdata.set('SHOYU_SHELL_GTK_VERSION', shoyu_version) +libshell_gtk3_version_cdata.set('SHOYU_SHELL_GTK_API_VERSION', shoyu_api_version) + +libshell_gtk3_version = configure_file(input: 'version.h.in', + output: 'version.h', + configuration: libshell_gtk3_version_cdata, + install: true, + install_dir: shoyu_includedir / 'shoyu-shell-gtk3', +) + +libshell_gtk3_gen_headers = [ + libshell_gtk3_enums_h, + libshell_gtk3_config, + libshell_gtk3_version, +] + +libshell_gtk3_deps = [ + gobject, + gdk3_wayland, + gtk3, + wayland_client, +] + +darwin_versions = [ + # compatibility version + 1 + '@0@'.format(shoyu_binary_age - shoyu_interface_age).to_int(), + # current version + '@0@.@1@'.format(1 + '@0@'.format(shoyu_binary_age - shoyu_interface_age).to_int(), shoyu_interface_age), +] + +libshell_gtk3_sources += [ + libshell_gtk3_config, + libshell_gtk3_enums, + libshell_gtk3_version_macros_h, + libshell_gtk3_visibility_h, + libshell_gtk3_private_h_sources, + libshell_gtk3_public_headers, +] + +libshell_gtk3 = shared_library('shoyu-shell-gtk3', + sources: libshell_gtk3_sources, + c_args: libshell_gtk3_cargs + common_cflags, + include_directories: [conf_inc, libshell_gtk3_inc, proto_inc], + dependencies: libshell_gtk3_deps, + link_whole: [libwayland_client], + link_args: common_ldflags, + soversion: shoyu_soversion, + version: shoyu_library_version, + darwin_versions: darwin_versions, + gnu_symbol_visibility: 'hidden', + install: true) + +libshell_gtk3_dep_sources = [libshell_gtk3_config, libshell_gtk3_version] + +if build_gir + gir_args = [ + '-DSHOYU_SHELL_GTK_COMPILATION', + '--quiet', + ] + + libshell_gtk3_gir_inc = [ 'GObject-2.0', 'Gtk-3.0' ] + + libshell_gtk3_gir = gnome.generate_gir(libshell_gtk3, + sources: [ + libshell_gtk3_enums_h, + libshell_gtk3_public_headers, + libshell_gtk3_public_sources, + libshell_gtk3_version, + libshell_gtk3_config, + ], + namespace: 'ShoyuShellGtk3', + nsversion: shoyu_api_version, + identifier_prefix: 'ShoyuShellGtk', + symbol_prefix: 'shoyu_shell_gtk', + includes: libshell_gtk3_gir_inc, + header: 'shoyu-shell-gtk3/shoyu-shell-gtk3.h', + export_packages: 'shoyu-shell-gtk3', + install: true, + dependencies: libshell_gtk3_deps, + extra_args: gir_args, + fatal_warnings: get_option('werror'), + ) + libshell_gtk3_dep_sources += libshell_gtk3_gir +endif + +if build_vapi + libshell_gtk3_vapi = gnome.generate_vapi('shoyu-shell-gtk3', + sources: libshell_gtk3_gir[0], + packages: ['gobject-2.0', 'gtk+-3.0'], + install: true) +endif + +libshell_gtk3_dep = declare_dependency( + sources: libshell_gtk3_dep_sources, + include_directories: [conf_inc, libshell_gtk3_inc], + dependencies: libshell_gtk3_deps, + link_with: libshell_gtk3, + link_args: common_ldflags) diff --git a/shoyu-shell-gtk3/shoyu-shell-gtk3.h b/shoyu-shell-gtk3/shoyu-shell-gtk3.h new file mode 100644 index 0000000..a4a6cfc --- /dev/null +++ b/shoyu-shell-gtk3/shoyu-shell-gtk3.h @@ -0,0 +1,10 @@ +#pragma once + +#define __SHOYU_SHELL_GTK_H_INSIDE__ + +#include +#include +#include +#include + +#undef __SHOYU_SHELL_GTK_H_INSIDE__ diff --git a/shoyu-shell-gtk3/version.c b/shoyu-shell-gtk3/version.c new file mode 100644 index 0000000..ecacefb --- /dev/null +++ b/shoyu-shell-gtk3/version.c @@ -0,0 +1,93 @@ +#include "version.h" +#include "config.h" + +/** + * shoyu_shell_gtk_get_major_version: + * + * Returns the major version number of the Shoyu Compositor library. + * + * Returns: the major version number of the Shoyu Compositor library + */ +guint shoyu_shell_gtk_get_major_version(void) { + return SHOYU_SHELL_GTK_MAJOR_VERSION; +} + +/** + * shoyu_shell_gtk_get_minor_version: + * + * Returns the minor version number of the Shoyu Compositor library. + * + * Returns: the minor version number of the Shoyu Compositor library + */ +guint shoyu_shell_gtk_get_minor_version(void) { + return SHOYU_SHELL_GTK_MINOR_VERSION; +} + +/** + * shoyu_shell_gtk_get_micro_version: + * + * Returns the micro version number of the Shoyu Compositor library. + * + * Returns: the micro version number of the Shoyu Compositor library + */ +guint shoyu_shell_gtk_get_micro_version(void) { + return SHOYU_SHELL_GTK_MICRO_VERSION; +} + +/** + * shoyu_shell_gtk_get_binary_age: + * + * Returns the binary age as passed to `libtool`. + * + * If `libtool` means nothing to you, don't worry about it. + * + * Returns: the binary age of the Shoyu Shell GTK 3 library + */ +guint shoyu_shell_gtk_get_binary_age(void) { + return SHOYU_SHELL_GTK_MICRO_VERSION; +} + +/** + * shoyu_shell_gtk_get_interface_age: + * + * Returns the interface age as passed to `libtool`. + * + * If `libtool` means nothing to you, don't worry about it. + * + * Returns: the interface age of the Shoyu Shell GTK 3 library + */ +guint shoyu_shell_gtk_get_interface_age(void) { + return SHOYU_SHELL_GTK_MICRO_VERSION; +} + +/** + * shoyu_shell_gtk_check_version: + * @required_major: the required major version + * @required_minor: the required minor version + * @required_micro: the required micro version + * + * Checks that the Shoyu Shell GTK 3 library in use is compatible with the + * given version. + * + * Returns: (nullable): %NULL if the Shoyu Compositor library is compatible with + * the given version, or a string describing the version mismatch. The returned + * string is owned by Shoyu Compositor and should not be modified or freed. + */ +const char *shoyu_shell_gtk_check_version(guint required_major, + guint required_minor, + guint required_micro) { + int shoyu_shell_gtk_effective_micro = + 100 * SHOYU_SHELL_GTK_MINOR_VERSION + SHOYU_SHELL_GTK_MICRO_VERSION; + int required_effective_micro = 100 * required_minor + required_micro; + + if (required_major > SHOYU_SHELL_GTK_MAJOR_VERSION) + return "Shoyu Compositor version too old (major mismatch)"; + if (required_major < SHOYU_SHELL_GTK_MAJOR_VERSION) + return "Shoyu Compositor version too new (major mismatch)"; + if (required_effective_micro < + shoyu_shell_gtk_effective_micro - SHOYU_SHELL_GTK_BINARY_AGE) + return "Shoyu Compositor version too new (micro mismatch)"; + if (required_effective_micro > shoyu_shell_gtk_effective_micro) + return "Shoyu Compositor version too old (micro mismatch)"; + return NULL; +} diff --git a/shoyu-shell-gtk3/version.h.in b/shoyu-shell-gtk3/version.h.in new file mode 100644 index 0000000..26e306d --- /dev/null +++ b/shoyu-shell-gtk3/version.h.in @@ -0,0 +1,88 @@ +#pragma once + +#if !defined (__SHOYU_SHELL_GTK_H_INSIDE__) && !defined (SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * SHOYU_SHELL_GTK_MAJOR_VERSION: + * + * Like [func@get_major_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_MAJOR_VERSION (@SHOYU_SHELL_GTK_MAJOR_VERSION@) + +/** + * SHOYU_SHELL_GTK_MINOR_VERSION: + * + * Like [func@get_minor_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_MINOR_VERSION (@SHOYU_SHELL_GTK_MINOR_VERSION@) + +/** + * SHOYU_SHELL_GTK_MICRO_VERSION: + * + * Like [func@get_micro_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_MICRO_VERSION (@SHOYU_SHELL_GTK_MICRO_VERSION@) + +/** + * SHOYU_SHELL_GTK_BINARY_AGE: + * + * Like [func@get_binary_age], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_BINARY_AGE (@SHOYU_SHELL_GTK_BINARY_AGE@) + +/** + * SHOYU_SHELL_GTK_INTERFACE_AGE: + * + * Like [func@get_interface_age], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_INTERFACE_AGE (@SHOYU_SHELL_GTK_INTERFACE_AGE@) + +/** + * SHOYU_SHELL_GTK_CHECK_VERSION: + * @major: major version (e.g. 1 for version 1.2.5) + * @minor: minor version (e.g. 2 for version 1.2.5) + * @micro: micro version (e.g. 5 for version 1.2.5) + * + * Returns %TRUE if the version of the SHOYU_SHELL_GTK header files + * is the same as or newer than the passed-in version. + * + * Returns: %TRUE if SHOYU_SHELL_GTK headers are new enough + */ +#define SHOYU_SHELL_GTK_CHECK_VERSION(major,minor,micro) \ + (SHOYU_SHELL_GTK_MAJOR_VERSION > (major) || \ + (SHOYU_SHELL_GTK_MAJOR_VERSION == (major) && SHOYU_SHELL_GTK_MINOR_VERSION > (minor)) || \ + (SHOYU_SHELL_GTK_MAJOR_VERSION == (major) && SHOYU_SHELL_GTK_MINOR_VERSION == (minor) && \ + SHOYU_SHELL_GTK_MICRO_VERSION >= (micro))) + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_major_version(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_minor_version(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_micro_version(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_binary_age(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_interface_age(void) G_GNUC_CONST; + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +const char* shoyu_shell_gtk_check_version (guint required_major, guint required_minor, guint required_micro); + +G_END_DECLS diff --git a/shoyu-shell-gtk3/version/meson.build b/shoyu-shell-gtk3/version/meson.build new file mode 100644 index 0000000..248d8c9 --- /dev/null +++ b/shoyu-shell-gtk3/version/meson.build @@ -0,0 +1,16 @@ +libshell_gtk3_version_macros_h = custom_target( + input: 'versionmacros.h.in', + output: 'versionmacros.h', + command: [gen_visibility_macros, meson.project_version(), 'versions-macros', 'SHOYU_SHELL_GTK', '@INPUT@', '@OUTPUT@'], + install: true, + install_dir: shoyu_includedir / 'shoyu-shell-gtk3/version', + # FIXME: Not needed with Meson >= 0.64.0 + install_tag: 'devel') + +libshell_gtk3_visibility_h = custom_target( + output: 'visibility.h', + command: [gen_visibility_macros, meson.project_version(), 'visibility-macros', 'SHOYU_SHELL_GTK', '@OUTPUT@'], + install: true, + install_dir: shoyu_includedir / 'shoyu-shell-gtk3/version', + # FIXME: Not needed with Meson >= 0.64.0 + install_tag: 'devel') diff --git a/shoyu-shell-gtk3/version/versionmacros.h.in b/shoyu-shell-gtk3/version/versionmacros.h.in new file mode 100644 index 0000000..69a8330 --- /dev/null +++ b/shoyu-shell-gtk3/version/versionmacros.h.in @@ -0,0 +1,123 @@ +#if !defined (__SHOYU_SHELL_GTK_H_INSIDE__) && !defined (SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#pragma once + +#include + +#if !defined(SHOYU_SHELL_GTK_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(4, 6) || \ + __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 4)) +#define _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(x) _Pragma(G_STRINGIFY (x)) +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(GCC warning "Deprecated pre-processor symbol") +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO_FOR(f) \ + _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(GCC warning G_STRINGIFY (Deprecated pre-processor symbol: replace with #f)) +#define SHOYU_SHELL_GTK_UNAVAILABLE_MACRO(maj,min) \ + _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(GCC warning G_STRINGIFY (Not available before maj.min)) +#else +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_MACRO(maj,min) +#endif + +#if !defined(SHOYU_SHELL_GTK_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(6, 1) || \ + (defined (__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 0)))) +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR G_DEPRECATED +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR_FOR(f) G_DEPRECATED_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_ENUMERATOR(maj,min) G_UNAVAILABLE(maj,min) +#else +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_ENUMERATOR(maj,min) +#endif + +#if !defined(SHOYU_SHELL_GTK_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(3, 1) || \ + (defined (__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 0)))) +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE G_DEPRECATED +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE_FOR(f) G_DEPRECATED_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_TYPE(maj,min) G_UNAVAILABLE(maj,min) +#else +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_TYPE(maj,min) +#endif + +@SHOYU_SHELL_GTK_VERSIONS@ + +/* evaluates to the current stable version; for development cycles, + * this means the next stable target, with a hard backstop to the + * beginning of the stable series + */ +#if SHOYU_SHELL_GTK_MAJOR_VERSION >= 4 && (SHOYU_SHELL_GTK_MINOR_VERSION % 2) +# define SHOYU_SHELL_GTK_VERSION_CUR_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION + 1)) +#elif G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION) > SHOYU_SHELL_GTK_VERSION_0_0 +# define SHOYU_SHELL_GTK_VERSION_CUR_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION)) +#else +# define SHOYU_SHELL_GTK_VERSION_CUR_STABLE SHOYU_SHELL_GTK_VERSION_0_0 +#endif + +/* evaluates to the previous stable version, with a hard backstop + * to the beginning of the stable series + */ +#if SHOYU_SHELL_GTK_MAJOR_VERSION >= 4 && (SHOYU_SHELL_GTK_MINOR_VERSION % 2) +# define SHOYU_SHELL_GTK_VERSION_PREV_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION - 1)) +#elif SHOYU_SHELL_GTK_MAJOR_VERSION >= 4 && SHOYU_SHELL_GTK_MINOR_VERSION > 2 +# define SHOYU_SHELL_GTK_VERSION_PREV_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION - 2)) +#else +# define SHOYU_SHELL_GTK_VERSION_PREV_STABLE SHOYU_SHELL_GTK_VERSION_0_0 +#endif + +/** + * SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED: + * + * A macro that should be defined by the user prior to including + * the `gdk.h` header. + * + * The definition should be one of the predefined SHOYU version + * macros: %SHOYU_SHELL_GTK_VERSION_0_0, %SHOYU_SHELL_GTK_VERSION_4_2,... + * + * This macro defines the lower bound for the SHOYU API to use. + * + * If a function has been deprecated in a newer version of SHOYU, + * it is possible to use this symbol to avoid the compiler warnings + * without disabling warning for every deprecated function. + */ +#ifndef SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED +# define SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED (SHOYU_SHELL_GTK_VERSION_CUR_STABLE) +#endif + +/** + * SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED: + * + * A macro that should be defined by the user prior to including + * the `gdk.h` header. + * + * The definition should be one of the predefined SHOYU version + * macros: %SHOYU_SHELL_GTK_VERSION_0_0, %SHOYU_SHELL_GTK_VERSION_4_2,... + * + * This macro defines the upper bound for the SHOYU API to use. + * + * If a function has been introduced in a newer version of SHOYU, + * it is possible to use this symbol to get compiler warnings when + * trying to use that function. + */ +#ifndef SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED +# if SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED > SHOYU_SHELL_GTK_VERSION_PREV_STABLE +# define SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED +# else +# define SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED SHOYU_SHELL_GTK_VERSION_CUR_STABLE +# endif +#endif + +/* sanity checks */ +#if SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED < SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED +# error "SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED must be >= SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED" +#endif +#if SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED < SHOYU_SHELL_GTK_VERSION_0_0 +# error "SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED must be >= SHOYU_SHELL_GTK_VERSION_0_0" +#endif + +#include diff --git a/shoyu-shell-gtk4/config.h.meson b/shoyu-shell-gtk4/config.h.meson new file mode 100644 index 0000000..3eac2e1 --- /dev/null +++ b/shoyu-shell-gtk4/config.h.meson @@ -0,0 +1,11 @@ +#pragma once + +#if !defined (__SHOYU_SHELL_GTK_H_INSIDE__) && !defined (SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +G_END_DECLS diff --git a/shoyu-shell-gtk4/main-private.h b/shoyu-shell-gtk4/main-private.h new file mode 100644 index 0000000..a14c5d7 --- /dev/null +++ b/shoyu-shell-gtk4/main-private.h @@ -0,0 +1,19 @@ +#pragma once + +#include "main.h" +#include + +#define SHOYU_SHELL_GTK_DISPLAY_KEY "shoyu-shell-gtk4-display" +#define SHOYU_SHELL_GTK_MONITOR_KEY "shoyu-shell-gtk4-monitor" + +typedef struct _ShoyuShellGtkDisplay { + struct shoyu_shell *shoyu_shell; + GList *surfaces; +} ShoyuShellGtkDisplay; + +typedef struct _ShoyuShellGtkToplevel { + GdkDisplay *display; + struct shoyu_shell_toplevel *shoyu_shell_toplevel; +} ShoyuShellGtkToplevel; + +struct shoyu_shell_output *shoyu_shell_gtk_get_output(GdkMonitor *monitor); diff --git a/shoyu-shell-gtk4/main.c b/shoyu-shell-gtk4/main.c new file mode 100644 index 0000000..61ddca8 --- /dev/null +++ b/shoyu-shell-gtk4/main.c @@ -0,0 +1,163 @@ +#include "main.h" +#include "main-private.h" +#include "shoyu-config.h" + +#include +#include +#include + +static gboolean shoyu_shell_gtk_initialized = FALSE; + +static void setlocale_initialization(void) { + static gboolean initialized = FALSE; + + if (initialized) + return; + initialized = TRUE; + + if (!setlocale(LC_ALL, "")) { + g_warning( + "Locale not supported by C library.\n\tUsing the fallback 'C' locale."); + } +} + +static void gettext_initialization(void) { + setlocale_initialization(); + + bindtextdomain(GETTEXT_PACKAGE, SHOYU_LOCALEDIR); +#ifdef HAVE_BIND_TEXTDOMAIN_CODESET + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); +#endif +} + +void shoyu_shell_gtk_init(void) { + if (!shoyu_shell_gtk_init_check()) { + g_warning("Failed to initialize"); + } +} + +gboolean shoyu_shell_gtk_init_check(void) { + if (shoyu_shell_gtk_initialized) + return TRUE; + + gettext_initialization(); + + GdkDisplay *display = gdk_display_get_default(); + g_return_val_if_fail(display != NULL, FALSE); + g_return_val_if_fail(shoyu_shell_gtk_init_display(display), FALSE); + + shoyu_shell_gtk_initialized = TRUE; + return TRUE; +} + +gboolean shoyu_shell_gtk_is_initialized(void) { + return shoyu_shell_gtk_initialized; +} + +static void shoyu_shell_toplevel_added(void *data, + struct shoyu_shell *shoyu_shell, + struct shoyu_shell_toplevel *toplevel) { + g_debug("Toplevel added %p", toplevel); + + // TODO: listen for toplevel destroy +} + +static const struct shoyu_shell_listener shoyu_shell_listener = { + .toplevel_added = shoyu_shell_toplevel_added, +}; + +static void shoyu_shell_gtk_wl_registry_global(void *data, + struct wl_registry *wl_registry, + uint32_t id, const char *iface, + uint32_t version) { + GdkDisplay *display = GDK_DISPLAY(data); + ShoyuShellGtkDisplay *self = + g_object_get_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY); + + g_debug("Found global %s.%d#%d", iface, version, id); + + if (g_strcmp0(iface, shoyu_shell_interface.name) == 0) { + self->shoyu_shell = + wl_registry_bind(wl_registry, id, &shoyu_shell_interface, + MIN(shoyu_shell_interface.version, version)); + shoyu_shell_add_listener(self->shoyu_shell, &shoyu_shell_listener, self); + } +} + +static void shoyu_shell_gtk_wl_registry_global_remove( + void *data, struct wl_registry *wl_registry, uint32_t id) { + // GdkDisplay* display = GDK_DISPLAY(data); + // ShoyuShellGtkDisplay* self = g_object_get_data(G_OBJECT(display), + // SHOYU_SHELL_GTK_DISPLAY_KEY); +} + +static const struct wl_registry_listener shoyu_shell_gtk_wl_registry_listener = + { + .global = shoyu_shell_gtk_wl_registry_global, + .global_remove = shoyu_shell_gtk_wl_registry_global_remove, +}; + +gboolean shoyu_shell_gtk_init_display(GdkDisplay *display) { + if (g_object_get_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY) != + NULL) { + return TRUE; + } + + g_return_val_if_fail(GDK_IS_WAYLAND_DISPLAY(display), FALSE); + + ShoyuShellGtkDisplay *self = g_malloc0(sizeof(ShoyuShellGtkDisplay)); + g_assert(self != NULL); + + g_object_set_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY, self); + + struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); + struct wl_registry *wl_registry = wl_display_get_registry(wl_display); + + wl_registry_add_listener(wl_registry, &shoyu_shell_gtk_wl_registry_listener, + display); + wl_display_roundtrip(wl_display); + + if (self->shoyu_shell == NULL) { + g_object_set_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY, NULL); + free(self); + return FALSE; + } + + return TRUE; +} + +struct shoyu_shell_output *shoyu_shell_gtk_get_output(GdkMonitor *monitor) { + struct shoyu_shell_output *shoyu_shell_output = + g_object_get_data(G_OBJECT(monitor), SHOYU_SHELL_GTK_MONITOR_KEY); + if (shoyu_shell_output != NULL) { + return shoyu_shell_output; + } + + GdkDisplay *display = gdk_monitor_get_display(monitor); + g_return_val_if_fail(display != NULL, NULL); + + ShoyuShellGtkDisplay *self = + g_object_get_data(G_OBJECT(display), SHOYU_SHELL_GTK_DISPLAY_KEY); + g_return_val_if_fail(self != NULL, NULL); + + struct wl_output *wl_output = gdk_wayland_monitor_get_wl_output(monitor); + + shoyu_shell_output = shoyu_shell_get_output(self->shoyu_shell, wl_output); + g_object_set_data(G_OBJECT(monitor), SHOYU_SHELL_GTK_MONITOR_KEY, + shoyu_shell_output); + return shoyu_shell_output; +} + +gboolean shoyu_shell_gtk_monitor_set_surface(GdkMonitor *monitor, + GdkSurface *surface) { + struct shoyu_shell_output *shoyu_shell_output = + shoyu_shell_gtk_get_output(monitor); + g_return_val_if_fail(shoyu_shell_output != NULL, FALSE); + + g_return_val_if_fail(GDK_IS_WAYLAND_SURFACE(surface), FALSE); + struct wl_surface *wl_surface = gdk_wayland_surface_get_wl_surface(surface); + g_return_val_if_fail(wl_surface != NULL, FALSE); + + shoyu_shell_output_set_surface(shoyu_shell_output, wl_surface); + return TRUE; +} diff --git a/shoyu-shell-gtk4/main.h b/shoyu-shell-gtk4/main.h new file mode 100644 index 0000000..96949e8 --- /dev/null +++ b/shoyu-shell-gtk4/main.h @@ -0,0 +1,30 @@ +#pragma once + +#if !defined(__SHOYU_SHELL_GTK_H_INSIDE__) && \ + !defined(SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +void shoyu_shell_gtk_init(void); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_init_check(void); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_is_initialized(void); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_init_display(GdkDisplay *display); + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +gboolean shoyu_shell_gtk_monitor_set_surface(GdkMonitor *monitor, + GdkSurface *surface); + +G_END_DECLS diff --git a/shoyu-shell-gtk4/meson.build b/shoyu-shell-gtk4/meson.build new file mode 100644 index 0000000..7f0d9e3 --- /dev/null +++ b/shoyu-shell-gtk4/meson.build @@ -0,0 +1,147 @@ +libshell_gtk4_cargs = [ + '-DSHOYU_SHELL_GTK_COMPILATION', + '-DG_LOG_DOMAIN="ShoyuShellGtk"', + '-DSHOYU_SHELL_GTK_BINARY_VERSION="@0@"'.format(shoyu_binary_version), + '-DSHOYU_SHELL_GTK_HOST="@0@"'.format(host_machine.system()), + '-DSHOYU_SHELL_GTK_DATA_PREFIX="@0@"'.format(shoyu_prefix), +] + +libshell_gtk4_private_sources = files([]) + +libshell_gtk4_private_h_sources = files([]) + +libshell_gtk4_public_sources = files([ + 'main.c', + 'version.c', +]) + +libshell_gtk4_public_headers = files([ + 'main.h', + 'shoyu-shell-gtk4.h', +]) + +install_headers(libshell_gtk4_public_headers, subdir: 'shoyu-shell-gtk4/') +libshell_gtk4_sources = libshell_gtk4_public_sources + libshell_gtk4_private_sources + +libshell_gtk4_enums = gnome.mkenums_simple('shell-gtk4-enumtypes', + sources: libshell_gtk4_public_headers, + decorator: 'SHOYU_SHELL_GTK_AVAILABLE_IN_ALL', + body_prefix: '#include "config.h"', + header_prefix: '#include "version/versionmacros.h"\n', + install_dir: shoyu_includedir / 'shoyu-shell-gtk4', + install_header: true) + +libshell_gtk4_enums_h = libshell_gtk4_enums[1] + +libshell_gtk4_config_cdata = configuration_data() + +libshell_gtk4_config = configure_file( + input: 'config.h.meson', + output: 'config.h', + configuration: libshell_gtk4_config_cdata, + install_dir: shoyu_includedir / 'shoyu-shell-gtk4', +) + +libshell_gtk4_version_cdata = configuration_data() +libshell_gtk4_version_cdata.set('SHOYU_SHELL_GTK_MAJOR_VERSION', shoyu_major_version) +libshell_gtk4_version_cdata.set('SHOYU_SHELL_GTK_MINOR_VERSION', shoyu_minor_version) +libshell_gtk4_version_cdata.set('SHOYU_SHELL_GTK_MICRO_VERSION', shoyu_micro_version) +libshell_gtk4_version_cdata.set('SHOYU_SHELL_GTK_BINARY_AGE', shoyu_binary_age) +libshell_gtk4_version_cdata.set('SHOYU_SHELL_GTK_INTERFACE_AGE', shoyu_interface_age) +libshell_gtk4_version_cdata.set('SHOYU_SHELL_GTK_VERSION', shoyu_version) +libshell_gtk4_version_cdata.set('SHOYU_SHELL_GTK_API_VERSION', shoyu_api_version) + +libshell_gtk4_version = configure_file(input: 'version.h.in', + output: 'version.h', + configuration: libshell_gtk4_version_cdata, + install: true, + install_dir: shoyu_includedir / 'shoyu-shell-gtk4', +) + +libshell_gtk4_gen_headers = [ + libshell_gtk4_enums_h, + libshell_gtk4_config, + libshell_gtk4_version, +] + +libshell_gtk4_deps = [ + gobject, + gtk4, + gtk4_wayland, +] + +darwin_versions = [ + # compatibility version + 1 + '@0@'.format(shoyu_binary_age - shoyu_interface_age).to_int(), + # current version + '@0@.@1@'.format(1 + '@0@'.format(shoyu_binary_age - shoyu_interface_age).to_int(), shoyu_interface_age), +] + +libshell_gtk4_sources += [ + libshell_gtk4_config, + libshell_gtk4_enums, + libshell_gtk4_version_macros_h, + libshell_gtk4_visibility_h, + libshell_gtk4_private_h_sources, + libshell_gtk4_public_headers, +] + +libshell_gtk4 = shared_library('shoyu-shell-gtk4', + sources: libshell_gtk4_sources, + c_args: libshell_gtk4_cargs + common_cflags, + include_directories: [conf_inc, libshell_gtk4_inc, proto_inc], + dependencies: libshell_gtk4_deps, + link_whole: [libwayland_client], + link_args: common_ldflags, + soversion: shoyu_soversion, + version: shoyu_library_version, + darwin_versions: darwin_versions, + gnu_symbol_visibility: 'hidden', + install: true) + +libshell_gtk4_dep_sources = [libshell_gtk4_config, libshell_gtk4_version] + +if build_gir + gir_args = [ + '-DSHOYU_SHELL_GTK_COMPILATION', + '--quiet', + ] + + libshell_gtk4_gir_inc = [ 'GObject-2.0', 'Gdk-4.0', 'Gtk-4.0' ] + + libshell_gtk4_gir = gnome.generate_gir(libshell_gtk4, + sources: [ + libshell_gtk4_enums_h, + libshell_gtk4_public_headers, + libshell_gtk4_public_sources, + libshell_gtk4_version, + libshell_gtk4_config, + ], + namespace: 'ShoyuShellGtk4', + nsversion: shoyu_api_version, + identifier_prefix: 'ShoyuShellGtk', + symbol_prefix: 'shoyu_shell_gtk', + includes: libshell_gtk4_gir_inc, + header: 'shoyu-shell-gtk4/shoyu-shell-gtk4.h', + export_packages: 'shoyu-shell-gtk4', + install: true, + dependencies: libshell_gtk4_deps, + extra_args: gir_args, + fatal_warnings: get_option('werror'), + ) + libshell_gtk4_dep_sources += libshell_gtk4_gir +endif + +if build_vapi + libshell_gtk4_vapi = gnome.generate_vapi('shoyu-shell-gtk4', + sources: libshell_gtk4_gir[0], + packages: ['gobject-2.0', 'gtk4'], + install: true) +endif + +libshell_gtk4_dep = declare_dependency( + sources: libshell_gtk4_dep_sources, + include_directories: [conf_inc, libshell_gtk4_inc], + dependencies: libshell_gtk4_deps, + link_with: libshell_gtk4, + link_args: common_ldflags) diff --git a/shoyu-shell-gtk4/shoyu-shell-gtk4.h b/shoyu-shell-gtk4/shoyu-shell-gtk4.h new file mode 100644 index 0000000..f2bf427 --- /dev/null +++ b/shoyu-shell-gtk4/shoyu-shell-gtk4.h @@ -0,0 +1,10 @@ +#pragma once + +#define __SHOYU_SHELL_GTK_H_INSIDE__ + +#include +#include +#include +#include + +#undef __SHOYU_SHELL_GTK_H_INSIDE__ diff --git a/shoyu-shell-gtk4/version.c b/shoyu-shell-gtk4/version.c new file mode 100644 index 0000000..c6fa579 --- /dev/null +++ b/shoyu-shell-gtk4/version.c @@ -0,0 +1,93 @@ +#include "version.h" +#include "config.h" + +/** + * shoyu_shell_gtk_get_major_version: + * + * Returns the major version number of the Shoyu Compositor library. + * + * Returns: the major version number of the Shoyu Compositor library + */ +guint shoyu_shell_gtk_get_major_version(void) { + return SHOYU_SHELL_GTK_MAJOR_VERSION; +} + +/** + * shoyu_shell_gtk_get_minor_version: + * + * Returns the minor version number of the Shoyu Compositor library. + * + * Returns: the minor version number of the Shoyu Compositor library + */ +guint shoyu_shell_gtk_get_minor_version(void) { + return SHOYU_SHELL_GTK_MINOR_VERSION; +} + +/** + * shoyu_shell_gtk_get_micro_version: + * + * Returns the micro version number of the Shoyu Compositor library. + * + * Returns: the micro version number of the Shoyu Compositor library + */ +guint shoyu_shell_gtk_get_micro_version(void) { + return SHOYU_SHELL_GTK_MICRO_VERSION; +} + +/** + * shoyu_shell_gtk_get_binary_age: + * + * Returns the binary age as passed to `libtool`. + * + * If `libtool` means nothing to you, don't worry about it. + * + * Returns: the binary age of the Shoyu Shell GTK 4 library + */ +guint shoyu_shell_gtk_get_binary_age(void) { + return SHOYU_SHELL_GTK_MICRO_VERSION; +} + +/** + * shoyu_shell_gtk_get_interface_age: + * + * Returns the interface age as passed to `libtool`. + * + * If `libtool` means nothing to you, don't worry about it. + * + * Returns: the interface age of the Shoyu Shell GTK 4 library + */ +guint shoyu_shell_gtk_get_interface_age(void) { + return SHOYU_SHELL_GTK_MICRO_VERSION; +} + +/** + * shoyu_shell_gtk_check_version: + * @required_major: the required major version + * @required_minor: the required minor version + * @required_micro: the required micro version + * + * Checks that the Shoyu Shell GTK 4 library in use is compatible with the + * given version. + * + * Returns: (nullable): %NULL if the Shoyu Compositor library is compatible with + * the given version, or a string describing the version mismatch. The returned + * string is owned by Shoyu Compositor and should not be modified or freed. + */ +const char *shoyu_shell_gtk_check_version(guint required_major, + guint required_minor, + guint required_micro) { + int shoyu_shell_gtk_effective_micro = + 100 * SHOYU_SHELL_GTK_MINOR_VERSION + SHOYU_SHELL_GTK_MICRO_VERSION; + int required_effective_micro = 100 * required_minor + required_micro; + + if (required_major > SHOYU_SHELL_GTK_MAJOR_VERSION) + return "Shoyu Compositor version too old (major mismatch)"; + if (required_major < SHOYU_SHELL_GTK_MAJOR_VERSION) + return "Shoyu Compositor version too new (major mismatch)"; + if (required_effective_micro < + shoyu_shell_gtk_effective_micro - SHOYU_SHELL_GTK_BINARY_AGE) + return "Shoyu Compositor version too new (micro mismatch)"; + if (required_effective_micro > shoyu_shell_gtk_effective_micro) + return "Shoyu Compositor version too old (micro mismatch)"; + return NULL; +} diff --git a/shoyu-shell-gtk4/version.h.in b/shoyu-shell-gtk4/version.h.in new file mode 100644 index 0000000..385ba69 --- /dev/null +++ b/shoyu-shell-gtk4/version.h.in @@ -0,0 +1,88 @@ +#pragma once + +#if !defined (__SHOYU_SHELL_GTK_H_INSIDE__) && !defined (SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * SHOYU_SHELL_GTK_MAJOR_VERSION: + * + * Like [func@get_major_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_MAJOR_VERSION (@SHOYU_SHELL_GTK_MAJOR_VERSION@) + +/** + * SHOYU_SHELL_GTK_MINOR_VERSION: + * + * Like [func@get_minor_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_MINOR_VERSION (@SHOYU_SHELL_GTK_MINOR_VERSION@) + +/** + * SHOYU_SHELL_GTK_MICRO_VERSION: + * + * Like [func@get_micro_version], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_MICRO_VERSION (@SHOYU_SHELL_GTK_MICRO_VERSION@) + +/** + * SHOYU_SHELL_GTK_BINARY_AGE: + * + * Like [func@get_binary_age], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_BINARY_AGE (@SHOYU_SHELL_GTK_BINARY_AGE@) + +/** + * SHOYU_SHELL_GTK_INTERFACE_AGE: + * + * Like [func@get_interface_age], but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + */ +#define SHOYU_SHELL_GTK_INTERFACE_AGE (@SHOYU_SHELL_GTK_INTERFACE_AGE@) + +/** + * SHOYU_SHELL_GTK_CHECK_VERSION: + * @major: major version (e.g. 1 for version 1.2.5) + * @minor: minor version (e.g. 2 for version 1.2.5) + * @micro: micro version (e.g. 5 for version 1.2.5) + * + * Returns %TRUE if the version of the SHOYU_SHELL_GTK header files + * is the same as or newer than the passed-in version. + * + * Returns: %TRUE if SHOYU_SHELL_GTK headers are new enough + */ +#define SHOYU_SHELL_GTK_CHECK_VERSION(major,minor,micro) \ + (SHOYU_SHELL_GTK_MAJOR_VERSION > (major) || \ + (SHOYU_SHELL_GTK_MAJOR_VERSION == (major) && SHOYU_SHELL_GTK_MINOR_VERSION > (minor)) || \ + (SHOYU_SHELL_GTK_MAJOR_VERSION == (major) && SHOYU_SHELL_GTK_MINOR_VERSION == (minor) && \ + SHOYU_SHELL_GTK_MICRO_VERSION >= (micro))) + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_major_version(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_minor_version(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_micro_version(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_binary_age(void) G_GNUC_CONST; +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +guint shoyu_shell_gtk_get_interface_age(void) G_GNUC_CONST; + +SHOYU_SHELL_GTK_AVAILABLE_IN_ALL +const char* shoyu_shell_gtk_check_version (guint required_major, guint required_minor, guint required_micro); + +G_END_DECLS diff --git a/shoyu-shell-gtk4/version/meson.build b/shoyu-shell-gtk4/version/meson.build new file mode 100644 index 0000000..05fca31 --- /dev/null +++ b/shoyu-shell-gtk4/version/meson.build @@ -0,0 +1,16 @@ +libshell_gtk4_version_macros_h = custom_target( + input: 'versionmacros.h.in', + output: 'versionmacros.h', + command: [gen_visibility_macros, meson.project_version(), 'versions-macros', 'SHOYU_SHELL_GTK', '@INPUT@', '@OUTPUT@'], + install: true, + install_dir: shoyu_includedir / 'shoyu-shell-gtk4/version', + # FIXME: Not needed with Meson >= 0.64.0 + install_tag: 'devel') + +libshell_gtk4_visibility_h = custom_target( + output: 'visibility.h', + command: [gen_visibility_macros, meson.project_version(), 'visibility-macros', 'SHOYU_SHELL_GTK', '@OUTPUT@'], + install: true, + install_dir: shoyu_includedir / 'shoyu-shell-gtk4/version', + # FIXME: Not needed with Meson >= 0.64.0 + install_tag: 'devel') diff --git a/shoyu-shell-gtk4/version/versionmacros.h.in b/shoyu-shell-gtk4/version/versionmacros.h.in new file mode 100644 index 0000000..6f76b22 --- /dev/null +++ b/shoyu-shell-gtk4/version/versionmacros.h.in @@ -0,0 +1,123 @@ +#if !defined (__SHOYU_SHELL_GTK_H_INSIDE__) && !defined (SHOYU_SHELL_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#pragma once + +#include + +#if !defined(SHOYU_SHELL_GTK_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(4, 6) || \ + __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 4)) +#define _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(x) _Pragma(G_STRINGIFY (x)) +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(GCC warning "Deprecated pre-processor symbol") +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO_FOR(f) \ + _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(GCC warning G_STRINGIFY (Deprecated pre-processor symbol: replace with #f)) +#define SHOYU_SHELL_GTK_UNAVAILABLE_MACRO(maj,min) \ + _SHOYU_SHELL_GTK_GNUC_DO_PRAGMA(GCC warning G_STRINGIFY (Not available before maj.min)) +#else +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO +#define SHOYU_SHELL_GTK_DEPRECATED_MACRO_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_MACRO(maj,min) +#endif + +#if !defined(SHOYU_SHELL_GTK_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(6, 1) || \ + (defined (__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 0)))) +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR G_DEPRECATED +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR_FOR(f) G_DEPRECATED_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_ENUMERATOR(maj,min) G_UNAVAILABLE(maj,min) +#else +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR +#define SHOYU_SHELL_GTK_DEPRECATED_ENUMERATOR_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_ENUMERATOR(maj,min) +#endif + +#if !defined(SHOYU_SHELL_GTK_DISABLE_DEPRECATION_WARNINGS) && \ + (G_GNUC_CHECK_VERSION(3, 1) || \ + (defined (__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 0)))) +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE G_DEPRECATED +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE_FOR(f) G_DEPRECATED_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_TYPE(maj,min) G_UNAVAILABLE(maj,min) +#else +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE +#define SHOYU_SHELL_GTK_DEPRECATED_TYPE_FOR(f) +#define SHOYU_SHELL_GTK_UNAVAILABLE_TYPE(maj,min) +#endif + +@SHOYU_SHELL_GTK_VERSIONS@ + +/* evaluates to the current stable version; for development cycles, + * this means the next stable target, with a hard backstop to the + * beginning of the stable series + */ +#if SHOYU_SHELL_GTK_MAJOR_VERSION >= 4 && (SHOYU_SHELL_GTK_MINOR_VERSION % 2) +# define SHOYU_SHELL_GTK_VERSION_CUR_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION + 1)) +#elif G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION) > SHOYU_SHELL_GTK_VERSION_0_0 +# define SHOYU_SHELL_GTK_VERSION_CUR_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION)) +#else +# define SHOYU_SHELL_GTK_VERSION_CUR_STABLE SHOYU_SHELL_GTK_VERSION_0_0 +#endif + +/* evaluates to the previous stable version, with a hard backstop + * to the beginning of the stable series + */ +#if SHOYU_SHELL_GTK_MAJOR_VERSION >= 4 && (SHOYU_SHELL_GTK_MINOR_VERSION % 2) +# define SHOYU_SHELL_GTK_VERSION_PREV_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION - 1)) +#elif SHOYU_SHELL_GTK_MAJOR_VERSION >= 4 && SHOYU_SHELL_GTK_MINOR_VERSION > 2 +# define SHOYU_SHELL_GTK_VERSION_PREV_STABLE (G_ENCODE_VERSION (SHOYU_SHELL_GTK_MAJOR_VERSION, SHOYU_SHELL_GTK_MINOR_VERSION - 2)) +#else +# define SHOYU_SHELL_GTK_VERSION_PREV_STABLE SHOYU_SHELL_GTK_VERSION_0_0 +#endif + +/** + * SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED: + * + * A macro that should be defined by the user prior to including + * the `gdk.h` header. + * + * The definition should be one of the predefined SHOYU version + * macros: %SHOYU_SHELL_GTK_VERSION_0_0, %SHOYU_SHELL_GTK_VERSION_4_2,... + * + * This macro defines the lower bound for the SHOYU API to use. + * + * If a function has been deprecated in a newer version of SHOYU, + * it is possible to use this symbol to avoid the compiler warnings + * without disabling warning for every deprecated function. + */ +#ifndef SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED +# define SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED (SHOYU_SHELL_GTK_VERSION_CUR_STABLE) +#endif + +/** + * SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED: + * + * A macro that should be defined by the user prior to including + * the `gdk.h` header. + * + * The definition should be one of the predefined SHOYU version + * macros: %SHOYU_SHELL_GTK_VERSION_0_0, %SHOYU_SHELL_GTK_VERSION_4_2,... + * + * This macro defines the upper bound for the SHOYU API to use. + * + * If a function has been introduced in a newer version of SHOYU, + * it is possible to use this symbol to get compiler warnings when + * trying to use that function. + */ +#ifndef SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED +# if SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED > SHOYU_SHELL_GTK_VERSION_PREV_STABLE +# define SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED +# else +# define SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED SHOYU_SHELL_GTK_VERSION_CUR_STABLE +# endif +#endif + +/* sanity checks */ +#if SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED < SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED +# error "SHOYU_SHELL_GTK_VERSION_MAX_ALLOWED must be >= SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED" +#endif +#if SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED < SHOYU_SHELL_GTK_VERSION_0_0 +# error "SHOYU_SHELL_GTK_VERSION_MIN_REQUIRED must be >= SHOYU_SHELL_GTK_VERSION_0_0" +#endif + +#include diff --git a/src/application-private.h b/src/application-private.h deleted file mode 100644 index d29d564..0000000 --- a/src/application-private.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -typedef struct _ShoyuApplicationPrivate { - ShoyuCompositor* compositor; - struct wl_display* wl_display; - - GSource* wl_source; -} ShoyuApplicationPrivate; - -#define SHOYU_APPLICATION(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), shoyu_application_get_type(), \ - ShoyuApplication)) - -#define SHOYU_APPLICATION_GET_PRIVATE(app) \ - ((ShoyuApplicationPrivate*)shoyu_application_get_instance_private( \ - SHOYU_APPLICATION(app))) diff --git a/src/application.c b/src/application.c deleted file mode 100644 index 5d881da..0000000 --- a/src/application.c +++ /dev/null @@ -1,105 +0,0 @@ -#define G_LOG_DOMAIN "ShoyuApplication" - -#include "application-private.h" - -#include -#include -#include - -enum { - PROP_0 = 0, - PROP_WL_DISPLAY, - N_PROPERTIES, -}; - -static GParamSpec* shoyu_application_props[N_PROPERTIES] = { NULL, }; - -G_DEFINE_TYPE_WITH_PRIVATE(ShoyuApplication, shoyu_application, g_application_get_type()); - -static void shoyu_wlroots_log_handler(enum wlr_log_importance importance, const char* fmt, va_list args) { - switch (importance) { - case WLR_SILENT: - case WLR_LOG_IMPORTANCE_LAST: - break; - case WLR_ERROR: - g_logv("wlroots", G_LOG_LEVEL_ERROR, fmt, args); - break; - case WLR_INFO: - g_logv("wlroots", G_LOG_LEVEL_INFO, fmt, args); - break; - case WLR_DEBUG: - g_logv("wlroots", G_LOG_LEVEL_DEBUG, fmt, args); - break; - } -} - -static void shoyu_application_constructed(GObject* object) { - G_OBJECT_CLASS(shoyu_application_parent_class)->constructed(object); - - ShoyuApplication* self = SHOYU_APPLICATION(object); - ShoyuApplicationPrivate* priv = SHOYU_APPLICATION_GET_PRIVATE(self); - - wlr_log_init(WLR_DEBUG, shoyu_wlroots_log_handler); - -#if GTK_MAJOR_VERSION == 3 - gtk_init(NULL, NULL); -#elif GTK_MAJOR_VERSION == 4 - gtk_init(); -#endif - - priv->wl_display = wl_display_create(); - - priv->wl_source = shoyu_wayland_event_source_new(priv->wl_display, wl_display_get_event_loop(priv->wl_display)); -} - -static void shoyu_application_dispose(GObject* object) { - ShoyuApplication* self = SHOYU_APPLICATION(object); - ShoyuApplicationPrivate* priv = SHOYU_APPLICATION_GET_PRIVATE(self); - - g_clear_object(&priv->compositor); - g_clear_pointer(&priv->wl_display, wl_display_destroy); - g_clear_pointer(&priv->wl_source, g_source_destroy); - - G_OBJECT_CLASS(shoyu_application_parent_class)->dispose(object); -} - -static void shoyu_application_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { - ShoyuApplication* self = SHOYU_APPLICATION(object); - ShoyuApplicationPrivate* priv = SHOYU_APPLICATION_GET_PRIVATE(self); - - switch (prop_id) { - case PROP_WL_DISPLAY: - g_value_set_pointer(value, priv->wl_display); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; - } -} - -static void shoyu_application_class_init(ShoyuApplicationClass* klass) { - GObjectClass* object_class = G_OBJECT_CLASS(klass); - - object_class->constructed = shoyu_application_constructed; - object_class->dispose = shoyu_application_dispose; - object_class->get_property = shoyu_application_get_property; - - /** - * ShoyuApplication:wl-display: (getter get_wl_display) - * - * The Wayland display server instance associated with the application. - */ - shoyu_application_props[PROP_WL_DISPLAY] = g_param_spec_pointer("wl-display", "Wayland Display", "The Wayland display server instance.", G_PARAM_READABLE); - g_object_class_install_properties(object_class, N_PROPERTIES, shoyu_application_props); -} - -static void shoyu_application_init(ShoyuApplication* self) {} - -ShoyuApplication* shoyu_application_new(const gchar* application_id, GApplicationFlags flags) { - return SHOYU_APPLICATION(g_object_new(shoyu_application_get_type(), "application-id", application_id, "flags", flags, NULL)); -} - -struct wl_display* shoyu_application_get_wl_display(ShoyuApplication* self) { - ShoyuApplicationPrivate* priv = SHOYU_APPLICATION_GET_PRIVATE(self); - return priv->wl_display; -} diff --git a/src/compositor-private.h b/src/compositor-private.h deleted file mode 100644 index b4f4cc0..0000000 --- a/src/compositor-private.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -typedef struct _ShoyuCompositorPrivate { - ShoyuCompositor* self; - - GApplication* application; - GList* outputs; - - struct wl_display* wl_display; - struct wlr_backend* wlr_backend; - struct wlr_renderer* wlr_renderer; - - struct wlr_allocator* allocator; - struct wlr_compositor* compositor; - - struct wl_listener new_output; -} ShoyuCompositorPrivate; - -#define SHOYU_COMPOSITOR(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), shoyu_compositor_get_type(), \ - ShoyuCompositor)) - -#define SHOYU_COMPOSITOR_GET_PRIVATE(app) \ - ((ShoyuCompositorPrivate*)shoyu_compositor_get_instance_private( \ - SHOYU_COMPOSITOR(app))) - -struct wlr_renderer* shoyu_compositor_get_wlr_renderer(ShoyuCompositor* self); diff --git a/src/compositor.c b/src/compositor.c deleted file mode 100644 index 8612af5..0000000 --- a/src/compositor.c +++ /dev/null @@ -1,240 +0,0 @@ -#define G_LOG_DOMAIN "ShoyuCompositor" - -#include - -#include -#include -#include -#include - -#include "compositor-private.h" - -enum { - kPropApplication = 1, - kPropWlDisplay, - kPropWlrBackend, - kPropLast, - - kSignalCreateOutput = 0, - kSignalLastSignal, -}; - -static GParamSpec* shoyu_compositor_properties[kPropLast] = { NULL, }; -static guint shoyu_compositor_signals[kSignalLastSignal]; - -G_DEFINE_TYPE_WITH_CODE(ShoyuCompositor, shoyu_compositor, G_TYPE_OBJECT, - G_ADD_PRIVATE(ShoyuCompositor)); - -static void shoyu_compositor_destroy_output(ShoyuOutput* output, ShoyuCompositor* self) { - ShoyuCompositorPrivate* priv = SHOYU_COMPOSITOR_GET_PRIVATE(self); - - guint len = g_list_length(priv->outputs); - priv->outputs = g_list_remove(priv->outputs, output); - - guint new_len = g_list_length(priv->outputs); - g_debug("Outputs changed (old: %u, new: %u)", len, new_len); - g_assert(new_len < len); - - if (priv->application != NULL) { - g_application_release(priv->application); - } - - g_debug("Destroyed ShoyuOutput#%p", output); - g_object_unref(output); -} - -static void shoyu_compositor_new_output(struct wl_listener* listener, void* data) { - ShoyuCompositorPrivate* priv = wl_container_of(listener, priv, new_output); - ShoyuCompositor* self = priv->self; - - struct wlr_output* wlr_output = (struct wlr_output*)data; - - wlr_output_init_render(wlr_output, priv->allocator, priv->wlr_renderer); - - struct wlr_output_state state; - wlr_output_state_init(&state); - wlr_output_state_set_enabled(&state, true); - - struct wlr_output_mode* mode = wlr_output_preferred_mode(wlr_output); - if (mode != NULL) { - wlr_output_state_set_mode(&state, mode); - } - - wlr_output_commit_state(wlr_output, &state); - wlr_output_state_finish(&state); - - guint len = g_list_length(priv->outputs); - - ShoyuOutput* output = NULL; - g_signal_emit(self, shoyu_compositor_signals[kSignalCreateOutput], 0, wlr_output, &output); - g_return_if_fail(output != NULL); - - priv->outputs = g_list_append(priv->outputs, output); - - guint new_len = g_list_length(priv->outputs); - g_debug("Outputs changed (old: %u, new: %u)", len, new_len); - g_assert(new_len > len); - - g_signal_connect(output, "wl-destroy", G_CALLBACK(shoyu_compositor_destroy_output), self); - - g_debug("Created ShoyuOutput#%p", output); - - if (priv->application != NULL) { - g_application_hold(priv->application); - } -} - -static void shoyu_compositor_constructed(GObject* object) { - G_OBJECT_CLASS(shoyu_compositor_parent_class)->constructed(object); - - ShoyuCompositor* self = SHOYU_COMPOSITOR(object); - ShoyuCompositorPrivate* priv = SHOYU_COMPOSITOR_GET_PRIVATE(self); - priv->self = self; - - g_assert(priv->wl_display != NULL); - - if (priv->wlr_backend == NULL) { - priv->wlr_backend = wlr_backend_autocreate(wl_display_get_event_loop(priv->wl_display), NULL); - } - - g_assert(priv->wlr_backend != NULL); - - priv->wlr_renderer = wlr_renderer_autocreate(priv->wlr_backend); - g_assert(priv->wlr_renderer != NULL); - - wlr_renderer_init_wl_display(priv->wlr_renderer, priv->wl_display); - - priv->allocator = wlr_allocator_autocreate(priv->wlr_backend, priv->wlr_renderer); - g_assert(priv->allocator != NULL); - - priv->outputs = NULL; - priv->new_output.notify = shoyu_compositor_new_output; - wl_signal_add(&priv->wlr_backend->events.new_output, &priv->new_output); - - if (!wlr_backend_start(priv->wlr_backend)) { - g_critical("Failed to start the wlroots backend"); - } - - priv->compositor = wlr_compositor_create(priv->wl_display, 5, NULL); - wlr_subcompositor_create(priv->wl_display); -} - -static void shoyu_compositor_dispose(GObject* object) { - ShoyuCompositor* self = SHOYU_COMPOSITOR(object); - ShoyuCompositorPrivate* priv = SHOYU_COMPOSITOR_GET_PRIVATE(self); - - g_clear_list(&priv->outputs, g_object_unref); - g_clear_pointer(&priv->wlr_backend, wlr_backend_destroy); - - G_OBJECT_CLASS(shoyu_compositor_parent_class)->dispose(object); -} - -static void shoyu_compositor_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) { - ShoyuCompositor* self = SHOYU_COMPOSITOR(object); - ShoyuCompositorPrivate* priv = SHOYU_COMPOSITOR_GET_PRIVATE(self); - - switch (prop_id) { - case kPropApplication: - priv->application = g_value_dup_object(value); - break; - case kPropWlDisplay: - priv->wl_display = g_value_get_pointer(value); - break; - case kPropWlrBackend: - priv->wlr_backend = g_value_get_pointer(value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; - } -} - -static void shoyu_compositor_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { - ShoyuCompositor* self = SHOYU_COMPOSITOR(object); - ShoyuCompositorPrivate* priv = SHOYU_COMPOSITOR_GET_PRIVATE(self); - - switch (prop_id) { - case kPropApplication: - g_value_set_object(value, G_OBJECT(priv->application)); - break; - case kPropWlDisplay: - g_value_set_pointer(value, priv->wl_display); - break; - case kPropWlrBackend: - g_value_set_pointer(value, priv->wlr_backend); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; - } -} - -static void shoyu_compositor_class_init(ShoyuCompositorClass* klass) { - GObjectClass* object_class = G_OBJECT_CLASS(klass); - - klass->create_output = shoyu_output_new; - - object_class->constructed = shoyu_compositor_constructed; - object_class->dispose = shoyu_compositor_dispose; - object_class->set_property = shoyu_compositor_set_property; - object_class->get_property = shoyu_compositor_get_property; - - shoyu_compositor_properties[kPropApplication] = g_param_spec_object("application", "Gio Application", "The application to run the compositor on.", g_application_get_type(), G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - shoyu_compositor_properties[kPropWlDisplay] = g_param_spec_pointer("wl-display", "Wayland Display", "The Wayland display server instance.", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - shoyu_compositor_properties[kPropWlrBackend] = g_param_spec_pointer("wlr-backend", "Wlroots Backend", "The wlroots backend for the compositor.", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - - g_object_class_install_properties(object_class, kPropLast, shoyu_compositor_properties); - - /** - * ShoyuCompositor::create-output: - * @compositor: the object which received the signal - * @wlr_output: the wlroots output which was recently created - * - * Returns: (transfer full): a #ShoyuOutput - */ - shoyu_compositor_signals[kSignalCreateOutput] = g_signal_new( - "create-output", shoyu_compositor_get_type(), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET(ShoyuCompositorClass, create_output), - g_signal_accumulator_first_wins, NULL, NULL, shoyu_output_get_type(), 1, G_TYPE_POINTER); -} - -static void shoyu_compositor_init(ShoyuCompositor* self) {} - -ShoyuCompositor* shoyu_compositor_new(struct wl_display* wl_display) { - return shoyu_compositor_new_with_application(wl_display, NULL); -} - -ShoyuCompositor* shoyu_compositor_new_with_application(struct wl_display* wl_display, GApplication* application) { - return shoyu_compositor_new_with_wlr_backend_with_application(wl_display, wlr_backend_autocreate(wl_display_get_event_loop(wl_display), NULL), application); -} - -ShoyuCompositor* shoyu_compositor_new_with_wlr_backend(struct wl_display* wl_display, struct wlr_backend* wlr_backend) { - return shoyu_compositor_new_with_wlr_backend_with_application(wl_display, wlr_backend, NULL); -} - -ShoyuCompositor* shoyu_compositor_new_with_wlr_backend_with_application(struct wl_display* wl_display, struct wlr_backend* wlr_backend, GApplication* application) { - return SHOYU_COMPOSITOR(g_object_new(shoyu_compositor_get_type(), "wl-display", wl_display, "wlr-backend", wlr_backend, "application", application, NULL)); -} - -struct wlr_renderer* shoyu_compositor_get_wlr_renderer(ShoyuCompositor* self) { - g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), NULL); - - ShoyuCompositorPrivate* priv = SHOYU_COMPOSITOR_GET_PRIVATE(self); - g_return_val_if_fail(priv != NULL, NULL); - - return priv->wlr_renderer; -} - -/** - * shoyu_compositor_get_outputs: - * - * Returns: (element-type ShoyuOutput) (transfer full): a list of #ShoyuOutput - */ -GList* shoyu_compositor_get_outputs(ShoyuCompositor* self) { - g_return_val_if_fail(SHOYU_IS_COMPOSITOR(self), NULL); - - ShoyuCompositorPrivate* priv = SHOYU_COMPOSITOR_GET_PRIVATE(self); - g_return_val_if_fail(priv != NULL, NULL); - - return g_list_copy_deep(priv->outputs, (GCopyFunc)g_object_ref, NULL); -} diff --git a/src/output-private.h b/src/output-private.h deleted file mode 100644 index b590d1c..0000000 --- a/src/output-private.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -typedef struct _ShoyuOutputPrivate { - ShoyuOutput* self; - - ShoyuCompositor* compositor; - struct wlr_output* wlr_output; - - gboolean is_destroying; - - struct wl_listener destroy; - struct wl_listener frame; - struct wl_listener request_state; -} ShoyuOutputPrivate; - -#define SHOYU_OUTPUT(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), shoyu_output_get_type(), \ - ShoyuOutput)) - -#define SHOYU_OUTPUT_GET_PRIVATE(app) \ - ((ShoyuOutputPrivate*)shoyu_output_get_instance_private( \ - SHOYU_OUTPUT(app))) diff --git a/src/output.c b/src/output.c deleted file mode 100644 index 93cd705..0000000 --- a/src/output.c +++ /dev/null @@ -1,287 +0,0 @@ -#define G_LOG_DOMAIN "ShoyuOutput" - -#include -#include -#include -#include -#include "compositor-private.h" -#include "output-private.h" - -enum { - kPropCompositor = 1, - kPropWlrOutput, - kPropLast, - - kSignalDestroy = 0, - kSignalRequestState, - kSignalLastSignal, -}; - -static GParamSpec* shoyu_output_properties[kPropLast] = { NULL, }; -static guint shoyu_output_signals[kSignalLastSignal]; - -#if GTK_MAJOR_VERSION == 4 -// NOTE: keep in sync with "gtk/gtknativeprivate.h" -struct _GtkNativeInterface { - GTypeInterface g_iface; - - GdkSurface* (*get_surface)(GtkNative* self); - GskRenderer* (*get_renderer)(GtkNative* self); - void (*get_surface_transform)(GtkNative* self, double* x, double* y); - void (*layout)(GtkNative* self, int width, int height); -}; - -static void shoyu_output_native_iface_init(GtkNativeInterface* iface); - -G_DEFINE_TYPE_WITH_CODE(ShoyuOutput, shoyu_output, GTK_TYPE_WIDGET, - G_ADD_PRIVATE(ShoyuOutput) - G_IMPLEMENT_INTERFACE(GTK_TYPE_NATIVE, shoyu_output_native_iface_init)); -#elif GTK_MAJOR_VERSION == 3 -G_DEFINE_TYPE_WITH_PRIVATE(ShoyuOutput, shoyu_output, GTK_TYPE_BIN); -#endif - -static gboolean shoyu_output_request_state_default(ShoyuOutput* self, struct wlr_output_state* event) { - return TRUE; -} - -#if GTK_MAJOR_VERSION == 4 -static void shoyu_output_native_iface_init(GtkNativeInterface* iface) {} -#elif GTK_MAJOR_VERSION == 3 -static gboolean shoyu_output_draw(GtkWidget* widget, cairo_t* cr) { - gboolean ret = FALSE; - - GtkStyleContext* context = gtk_widget_get_style_context(widget); - - GtkAllocation allocation; - gtk_widget_get_allocation(widget, &allocation); - - gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height); - gtk_render_frame(context, cr, 0, 0, allocation.width, allocation.height); - - if (GTK_WIDGET_CLASS(shoyu_output_parent_class)->draw) { - ret = GTK_WIDGET_CLASS(shoyu_output_parent_class)->draw(widget, cr); - } - - return ret; -} -#endif - -static void shoyu_output_destroy(struct wl_listener* listener, void* data) { - ShoyuOutputPrivate* priv = wl_container_of(listener, priv, destroy); - ShoyuOutput* self = priv->self; - - if (!priv->is_destroying) { - priv->is_destroying = TRUE; - g_signal_emit(self, shoyu_output_signals[kSignalDestroy], 0); - } -} - -static void shoyu_output_frame(struct wl_listener* listener, void* data) { - ShoyuOutputPrivate* priv = wl_container_of(listener, priv, frame); - ShoyuOutput* self = priv->self; - - struct wlr_output_state state; - wlr_output_state_init(&state); - - struct wlr_render_pass* pass = wlr_output_begin_render_pass(priv->wlr_output, &state, NULL, NULL); - -#if GTK_MAJOR_VERSION == 3 - cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, priv->wlr_output->width, priv->wlr_output->height); - cairo_t* cr = cairo_create(surface); - - cairo_set_source_rgb(cr, 0, 0, 0); - cairo_rectangle(cr, 0, 0, priv->wlr_output->width * 1.0, priv->wlr_output->height * 1.0); - cairo_fill(cr); - - GtkAllocation alloc = { - .x = 0, - .y = 0, - .width = priv->wlr_output->width, - .height = priv->wlr_output->height, - }; - - gtk_widget_size_allocate(GTK_WIDGET(self), &alloc); - gtk_widget_draw(GTK_WIDGET(self), cr); - - cairo_destroy(cr); -#elif GTK_MAJOR_VERSION == 4 - graphene_rect_t bounds; - graphene_rect_init(&bounds, 0, 0, priv->wlr_output->width, priv->wlr_output->height); - - gtk_widget_queue_draw(GTK_WIDGET(self)); - - GtkSnapshot* snapshot = gtk_snapshot_new(); - - GdkRGBA color = { 0, 1.0, 0, 1.0 }; - gtk_snapshot_append_color(snapshot, &color, &bounds); - - GTK_WIDGET_GET_CLASS(self)->snapshot(GTK_WIDGET(self), snapshot); - - GskRenderNode* node = gtk_snapshot_free_to_node(snapshot); - - cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, priv->wlr_output->width, priv->wlr_output->height); - cairo_t* cr = cairo_create(surface); - - cairo_set_source_rgb(cr, 0, 0, 0); - cairo_rectangle(cr, 0, 0, priv->wlr_output->width * 1.0, priv->wlr_output->height * 1.0); - cairo_fill(cr); - - gsk_render_node_draw(node, cr); - gsk_render_node_unref(node); - - cairo_destroy(cr); -#endif - - struct wlr_texture* texture = wlr_texture_from_pixels(shoyu_compositor_get_wlr_renderer(priv->compositor), DRM_FORMAT_ARGB8888, cairo_image_surface_get_stride(surface), priv->wlr_output->width, priv->wlr_output->height, (const void*)cairo_image_surface_get_data(surface)); - -#if GTK_MAJOR_VERSION == 3 - cairo_surface_destroy(surface); -#endif - - wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ - .texture = texture, - .dst_box = { .x = 0, .y = 0 }, - }); - - wlr_render_pass_submit(pass); - - wlr_output_commit_state(priv->wlr_output, &state); - wlr_output_state_finish(&state); - - wlr_texture_destroy(texture); -} - -static void shoyu_output_request_state(struct wl_listener* listener, void* data) { - ShoyuOutputPrivate* priv = wl_container_of(listener, priv, request_state); - ShoyuOutput* self = priv->self; - - const struct wlr_output_event_request_state* event = data; - - struct wlr_output_state state; - wlr_output_state_init(&state); - wlr_output_state_copy(&state, event->state); - - gboolean should_commit = TRUE; - g_signal_emit(self, shoyu_output_signals[kSignalRequestState], 0, &state, &should_commit); - - if (should_commit) wlr_output_commit_state(priv->wlr_output, &state); -} - -static void shoyu_output_constructed(GObject* object) { - G_OBJECT_CLASS(shoyu_output_parent_class)->constructed(object); - - ShoyuOutput* self = SHOYU_OUTPUT(object); - ShoyuOutputPrivate* priv = SHOYU_OUTPUT_GET_PRIVATE(self); - priv->self = self; - - priv->destroy.notify = shoyu_output_destroy; - wl_signal_add(&priv->wlr_output->events.destroy, &priv->destroy); - - priv->frame.notify = shoyu_output_frame; - wl_signal_add(&priv->wlr_output->events.frame, &priv->frame); - - priv->request_state.notify = shoyu_output_request_state; - wl_signal_add(&priv->wlr_output->events.request_state, &priv->request_state); - -#if GTK_MAJOR_VERSION == 3 - GtkWidgetPath* widget_path = gtk_widget_get_path(GTK_WIDGET(self)); - gtk_widget_path_append_type(widget_path, GTK_TYPE_WINDOW); -#endif - - GtkStyleContext* context = gtk_widget_get_style_context(GTK_WIDGET(self)); - -#if GTK_MAJOR_VERSION == 3 - gtk_style_context_add_class(context, GTK_STYLE_CLASS_BACKGROUND); -#elif GTK_MAJOR_VERSION == 4 - gtk_style_context_add_class(context, "background"); -#endif -} - -static void shoyu_output_dispose(GObject* object) { - ShoyuOutput* self = SHOYU_OUTPUT(object); - ShoyuOutputPrivate* priv = SHOYU_OUTPUT_GET_PRIVATE(self); - - g_clear_object(&priv->compositor); - - if (!priv->is_destroying) { - g_clear_pointer(&priv->wlr_output, wlr_output_destroy); - } - - G_OBJECT_CLASS(shoyu_output_parent_class)->dispose(object); -} - -static void shoyu_output_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) { - ShoyuOutput* self = SHOYU_OUTPUT(object); - ShoyuOutputPrivate* priv = SHOYU_OUTPUT_GET_PRIVATE(self); - - switch (prop_id) { - case kPropCompositor: - priv->compositor = g_value_dup_object(value); - break; - case kPropWlrOutput: - priv->wlr_output = g_value_get_pointer(value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; - } -} - -static void shoyu_output_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { - ShoyuOutput* self = SHOYU_OUTPUT(object); - ShoyuOutputPrivate* priv = SHOYU_OUTPUT_GET_PRIVATE(self); - - switch (prop_id) { - case kPropCompositor: - g_value_set_object(value, G_OBJECT(priv->compositor)); - break; - case kPropWlrOutput: - g_value_set_pointer(value, priv->wlr_output); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; - } -} - -static void shoyu_output_class_init(ShoyuOutputClass* klass) { - GObjectClass* object_class = G_OBJECT_CLASS(klass); - GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); - - klass->request_state = shoyu_output_request_state_default; - -#if GTK_MAJOR_VERSION == 3 - widget_class->draw = shoyu_output_draw; -#endif - - object_class->constructed = shoyu_output_constructed; - object_class->dispose = shoyu_output_dispose; - object_class->set_property = shoyu_output_set_property; - object_class->get_property = shoyu_output_get_property; - - shoyu_output_properties[kPropCompositor] = g_param_spec_object("compositor", "Shoyu Compositor", "The compositor for the output.", shoyu_compositor_get_type(), G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - shoyu_output_properties[kPropWlrOutput] = g_param_spec_pointer("wlr-output", "Wayland Output", "The wlroots output instance.", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - - g_object_class_install_properties(object_class, kPropLast, shoyu_output_properties); - - shoyu_output_signals[kSignalDestroy] = g_signal_new("wl-destroy", shoyu_output_get_type(), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); - shoyu_output_signals[kSignalRequestState] = g_signal_new( - "request-state", shoyu_output_get_type(), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET(ShoyuOutputClass, request_state), - g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); - - gtk_widget_class_set_css_name(widget_class, "output"); -} - -static void shoyu_output_init(ShoyuOutput* self) { -#if GTK_MAJOR_VERSION == 3 - gtk_widget_set_has_window(GTK_WIDGET(self), TRUE); - gtk_widget_set_mapped(GTK_WIDGET(self), TRUE); -#endif - - gtk_widget_set_visible(GTK_WIDGET(self), TRUE); -} - -ShoyuOutput* shoyu_output_new(ShoyuCompositor* compositor, struct wlr_output* wlr_output) { - return SHOYU_OUTPUT(g_object_new(shoyu_output_get_type(), "compositor", compositor, "wlr-output", wlr_output, NULL)); -} diff --git a/src/wayland-event-source-private.h b/src/wayland-event-source-private.h deleted file mode 100644 index a54998c..0000000 --- a/src/wayland-event-source-private.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include - -typedef struct _ShoyuWaylandEventSource { - GSource source; - struct wl_display* wl_display; - struct wl_event_loop* event_loop; -} ShoyuWaylandEventSource; diff --git a/src/wayland-event-source.c b/src/wayland-event-source.c deleted file mode 100644 index ce43cbc..0000000 --- a/src/wayland-event-source.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "wayland-event-source-private.h" - -static gboolean shoyu_wayland_event_source_prepare(GSource* source, int* timeout) { - ShoyuWaylandEventSource* self = (ShoyuWaylandEventSource*)source; - - *timeout = -1; - - if (self->wl_display != NULL) wl_display_flush_clients(self->wl_display); - - return FALSE; -} - -static gboolean shoyu_wayland_event_source_dispatch(GSource* source, GSourceFunc callback, gpointer data) { - ShoyuWaylandEventSource* self = (ShoyuWaylandEventSource*)source; - - wl_event_loop_dispatch(self->event_loop, 0); - return TRUE; -} - -static GSourceFuncs shoyu_wayland_event_source_funcs = { - shoyu_wayland_event_source_prepare, - NULL, - shoyu_wayland_event_source_dispatch, - NULL, -}; - -GSource* shoyu_wayland_event_source_new(struct wl_display* wl_display, struct wl_event_loop* event_loop) { - GSource* source = g_source_new(&shoyu_wayland_event_source_funcs, sizeof (ShoyuWaylandEventSource)); - - char* name = g_strdup_printf("Wayland Event Loop source (%p)", event_loop); - g_source_set_name(source, name); - g_free(name); - - ShoyuWaylandEventSource* self = (ShoyuWaylandEventSource*)source; - - self->wl_display = wl_display; - self->event_loop = event_loop; - - g_source_add_unix_fd(source, wl_event_loop_get_fd(event_loop), G_IO_IN | G_IO_ERR); - g_source_set_can_recurse(source, TRUE); - g_source_attach(source, NULL); - - return source; -} diff --git a/vapi/libdrm.vapi b/vapi/libdrm.vapi deleted file mode 100644 index da8a2af..0000000 --- a/vapi/libdrm.vapi +++ /dev/null @@ -1,2 +0,0 @@ -[CCode(cprefix = "drm", lower_case_cprefix = "drm_")] -namespace DRM {} diff --git a/vapi/wayland-server.vapi b/vapi/wayland-server.vapi deleted file mode 100644 index 4999ddd..0000000 --- a/vapi/wayland-server.vapi +++ /dev/null @@ -1,2 +0,0 @@ -[CCode(cprefix = "wl_", lower_case_cprefix = "wl_")] -namespace WaylandServer {} diff --git a/vapi/wlroots-0.18.vapi b/vapi/wlroots-0.18.vapi deleted file mode 100644 index 0bc2701..0000000 --- a/vapi/wlroots-0.18.vapi +++ /dev/null @@ -1,2 +0,0 @@ -[CCode(cprefix = "wlr_", lower_case_cprefix = "wlr_")] -namespace Wlroots {}