diff --git a/docs/meson.build b/docs/meson.build index dab89bf6..e1e6022c 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -51,6 +51,9 @@ all_doc_target = custom_target( '--pkg', 'granite', '--pkg', 'gnome-desktop-3.0', '--pkg', 'gmodule-2.0', + '--pkg', 'wayland-server', + '--pkg', 'pantheon-desktop-shell', + '--vapidir=' + join_paths(meson.global_source_root(), 'protocol'), '-o', '@OUTPUT@', '@INPUT@' ], diff --git a/meson.build b/meson.build index cdeb9043..a583d3e6 100644 --- a/meson.build +++ b/meson.build @@ -182,6 +182,7 @@ if get_option('systemd') endif subdir('data') +subdir('protocol') subdir('lib') subdir('src') subdir('daemon') diff --git a/po/POTFILES b/po/POTFILES index 9db9a2e0..b579e950 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,4 +1,5 @@ src/Dialogs.vala +src/PantheonShell.vala src/ScreenshotManager.vala src/WindowManager.vala daemon/BackgroundMenu.vala diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 00000000..771af1a0 --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,32 @@ +dep_scanner = dependency('wayland-scanner', native: true) +prog_scanner = find_program(dep_scanner.get_variable(pkgconfig: 'wayland_scanner')) + +protocol_file = files('pantheon-desktop-shell-v1.xml') + +pantheon_desktop_shell_sources = [] +pantheon_desktop_shell_sources += custom_target( + 'pantheon-desktop-shell-server-protocol.h', + command: [ prog_scanner, 'server-header', '@INPUT@', '@OUTPUT@' ], + input: protocol_file, + output: 'pantheon-desktop-shell-server-protocol.h', +) + +output_type = 'private-code' +if dep_scanner.version().version_compare('< 1.14.91') + output_type = 'code' +endif +pantheon_desktop_shell_sources += custom_target( + 'pantheon-desktop-shell-protocol.c', + command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ], + input: protocol_file, + output: 'pantheon-desktop-shell-protocol.c', +) + +pantheon_desktop_shell_dep = declare_dependency( + dependencies: [ + vala.find_library('pantheon-desktop-shell', dirs: meson.current_source_dir()), + dependency('wayland-server'), + ], + include_directories: include_directories('.'), + sources: pantheon_desktop_shell_sources +) diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml new file mode 100644 index 00000000..18a7da64 --- /dev/null +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -0,0 +1,115 @@ + + + + + SPDX-License-Identifier: LGPL-2.1-or-later + ]]> + + + + This interface is used by the Pantheon Wayland shell to communicate with + the compositor. + + + + + Create a panel surface from an existing surface. + + + + + + + + Create a desktop widget surface from an existing surface. + + + + + + + + Create a desktop-specific surface from an existing surface. + + + + + + + + + + + + The anchor is a placement hint to the compositor. + + + + + + + + + + How the shell should handle the window. + + + + + + + + + + + Tell the shell which side of the screen the panel is + located. This is so that new windows do not overlap the panel + and maximized windows maximize properly. + + + + + + + + Request keyboard focus, taking it away from any other window. + Keyboard focus must always be manually be requested and is + - in contrast to normal windows - never automatically granted + by the compositor. + + + + + + The given size is only used for exclusive zones and + collision tracking for auto hide. By default and if set + to -1 the size of the surface is used. + + + + + + + + + Tell the shell when to hide the panel. + + + + + + + + + + + + + + + Tell the shell to keep the surface above on all workspaces + + + + diff --git a/protocol/pantheon-desktop-shell.deps b/protocol/pantheon-desktop-shell.deps new file mode 100644 index 00000000..62acb1e0 --- /dev/null +++ b/protocol/pantheon-desktop-shell.deps @@ -0,0 +1 @@ +wayland-server diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi new file mode 100644 index 00000000..b699c45c --- /dev/null +++ b/protocol/pantheon-desktop-shell.vapi @@ -0,0 +1,79 @@ +/* + * Copyright 2023 elementary, Inc. + * Copyright 2023 Corentin Noël + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +namespace Pantheon.Desktop { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_shell_v1_interface")] + public struct ShellInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_shell_v1_interface")] + public static Wl.Interface iface; + public Pantheon.Desktop.GetPanel get_panel; + public Pantheon.Desktop.GetWidget get_widget; + public Pantheon.Desktop.GetExtendedBehavior get_extended_behavior; + + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "enum io_elementary_pantheon_panel_v1_anchor", cprefix="IO_ELEMENTARY_PANTHEON_PANEL_V1_ANCHOR_", has_type_id = false)] + public enum Anchor { + TOP, + BOTTOM, + LEFT, + RIGHT, + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "enum io_elementary_pantheon_panel_v1_hide_mode", cprefix="IO_ELEMENTARY_PANTHEON_PANEL_V1_HIDE_MODE_", has_type_id = false)] + public enum HideMode { + NEVER, + MAXIMIZED_FOCUS_WINDOW, + OVERLAPPING_FOCUS_WINDOW, + OVERLAPPING_WINDOW, + ALWAYS + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_panel_v1_interface")] + public struct PanelInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_panel_v1_interface")] + public static Wl.Interface iface; + public Destroy destroy; + public SetAnchor set_anchor; + public Focus focus; + public SetSize set_size; + public SetHideMode set_hide_mode; + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_widget_v1_interface")] + public struct WidgetInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_widget_v1_interface")] + public static Wl.Interface iface; + public Destroy destroy; + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_extended_behavior_v1_interface")] + public struct ExtendedBehaviorInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_extended_behavior_v1_interface")] + public static Wl.Interface iface; + public Destroy destroy; + public SetKeepAbove set_keep_above; + } + + [CCode (has_target = false, has_typedef = false)] + public delegate void GetPanel (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); + [CCode (has_target = false, has_typedef = false)] + public delegate void GetWidget (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); + [CCode (has_target = false, has_typedef = false)] + public delegate void GetExtendedBehavior (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetAnchor (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] Anchor anchor); + [CCode (has_target = false, has_typedef = false)] + public delegate void Focus (Wl.Client client, Wl.Resource resource); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetSize (Wl.Client client, Wl.Resource resource, int width, int height); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetHideMode (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] HideMode hide_mode); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetKeepAbove (Wl.Client client, Wl.Resource resource); + [CCode (has_target = false, has_typedef = false)] + public delegate void Destroy (Wl.Client client, Wl.Resource resource); +} diff --git a/src/Main.vala b/src/Main.vala index 2bb7070b..30f4ef0b 100644 --- a/src/Main.vala +++ b/src/Main.vala @@ -76,6 +76,9 @@ namespace Gala { try { ctx.start (); + if (ctx.get_compositor_type () == Meta.CompositorType.WAYLAND) { + Gala.init_pantheon_shell (ctx); + } } catch (Error e) { stderr.printf ("Failed to start: %s\n", e.message); return Posix.EXIT_FAILURE; diff --git a/src/PantheonShell.vala b/src/PantheonShell.vala new file mode 100644 index 00000000..55d802e8 --- /dev/null +++ b/src/PantheonShell.vala @@ -0,0 +1,314 @@ +/* + * Copyright 2023 elementary, Inc. + * Copyright 2023 Corentin Noël + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +namespace Gala { +#if !HAS_MUTTER45 + [Compact] + public class FakeMetaWaylandCompositor : GLib.Object { + // It is the third field and Vala adds a FakeMetaWaylandCompositorPrivate *priv + public Wl.Display wayland_display; + + [CCode (cname = "meta_context_get_wayland_compositor")] + public extern static unowned Gala.FakeMetaWaylandCompositor from_context (Meta.Context context); + } +#endif + public static inline unowned Wl.Display? get_display_from_context (Meta.Context context) { +#if HAS_MUTTER45 + unowned Meta.WaylandCompositor? compositor = context.get_wayland_compositor (); + if (compositor == null) { + return null; + } + + return (Wl.Display) compositor.get_wayland_display (); +#else + unowned FakeMetaWaylandCompositor compositor = Gala.FakeMetaWaylandCompositor.from_context (context); + if (compositor == null) { + return null; + } + + return compositor.wayland_display; +#endif + } + + private static Pantheon.Desktop.ShellInterface wayland_pantheon_shell_interface; + private static Pantheon.Desktop.PanelInterface wayland_pantheon_panel_interface; + private static Pantheon.Desktop.WidgetInterface wayland_pantheon_widget_interface; + private static Pantheon.Desktop.ExtendedBehaviorInterface wayland_pantheon_extended_behavior_interface; + private static Wl.Global shell_global; + + public void init_pantheon_shell (Meta.Context context) { + unowned Wl.Display? wl_disp = get_display_from_context (context); + if (wl_disp == null) { + debug ("Not running under Wayland, no Pantheon Shell protocol"); + return; + } + + wayland_pantheon_shell_interface = { + get_panel, + get_widget, + get_extended_behavior, + }; + + wayland_pantheon_panel_interface = { + destroy_panel_surface, + set_anchor, + focus, + set_size, + set_hide_mode, + }; + + wayland_pantheon_widget_interface = { + destroy_widget_surface, + }; + + wayland_pantheon_extended_behavior_interface = { + destroy_extended_behavior_surface, + set_keep_above, + }; + + PanelSurface.quark = GLib.Quark.from_string ("-gala-wayland-panel-surface-data"); + + shell_global = Wl.Global.create (wl_disp, ref Pantheon.Desktop.ShellInterface.iface, 1, (client, version, id) => { + unowned var resource = client.create_resource (ref Pantheon.Desktop.ShellInterface.iface, (int) version, id); + resource.set_implementation (&wayland_pantheon_shell_interface, null, (res) => {}); + }); + } + + public class PanelSurface : GLib.Object { + public static GLib.Quark quark = 0; + public unowned GLib.Object? wayland_surface; + + public PanelSurface (GLib.Object wayland_surface) { + this.wayland_surface = wayland_surface; + } + + ~PanelSurface () { + if (wayland_surface != null) { + wayland_surface.steal_qdata (quark); + } + } + + public void on_wayland_surface_disposed () { + wayland_surface = null; + } + } + + public class WidgetSurface : GLib.Object { + public static GLib.Quark quark = 0; + public unowned GLib.Object? wayland_surface; + + public WidgetSurface (GLib.Object wayland_surface) { + this.wayland_surface = wayland_surface; + } + + ~WidgetSurface () { + if (wayland_surface != null) { + wayland_surface.steal_qdata (quark); + } + } + + public void on_wayland_surface_disposed () { + wayland_surface = null; + } + } + + public class ExtendedBehaviorSurface : GLib.Object { + public static GLib.Quark quark = 0; + public unowned GLib.Object? wayland_surface; + + public ExtendedBehaviorSurface (GLib.Object wayland_surface) { + this.wayland_surface = wayland_surface; + } + + ~ExtendedBehaviorSurface () { + if (wayland_surface != null) { + wayland_surface.steal_qdata (quark); + } + } + + public void on_wayland_surface_disposed () { + wayland_surface = null; + } + } + + static void unref_obj_on_destroy (Wl.Resource resource) { + resource.get_user_data ().unref (); + } + + internal static void get_panel (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { + unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); + PanelSurface? panel_surface = wayland_surface.get_qdata (PanelSurface.quark); + if (panel_surface != null) { + surface_resource.post_error ( + Wl.DisplayError.INVALID_OBJECT, + "io_elementary_pantheon_shell_v1_interface::get_panel already requested" + ); + return; + } + + panel_surface = new PanelSurface (wayland_surface); + unowned var panel_resource = client.create_resource ( + ref Pantheon.Desktop.PanelInterface.iface, + resource.get_version (), + output + ); + panel_resource.set_implementation ( + &wayland_pantheon_panel_interface, + panel_surface.ref (), + unref_obj_on_destroy + ); + wayland_surface.set_qdata_full ( + PanelSurface.quark, + panel_surface, + (GLib.DestroyNotify) PanelSurface.on_wayland_surface_disposed + ); + } + + internal static void get_widget (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { + unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); + WidgetSurface? widget_surface = wayland_surface.get_qdata (WidgetSurface.quark); + if (widget_surface != null) { + surface_resource.post_error ( + Wl.DisplayError.INVALID_OBJECT, + "io_elementary_pantheon_shell_v1_interface::get_widget already requested" + ); + return; + } + + widget_surface = new WidgetSurface (wayland_surface); + unowned var widget_resource = client.create_resource ( + ref Pantheon.Desktop.WidgetInterface.iface, + resource.get_version (), + output + ); + widget_resource.set_implementation ( + &wayland_pantheon_widget_interface, + widget_surface.ref (), + unref_obj_on_destroy + ); + wayland_surface.set_qdata_full ( + WidgetSurface.quark, + widget_surface, + (GLib.DestroyNotify) WidgetSurface.on_wayland_surface_disposed + ); + } + + internal static void get_extended_behavior (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { + unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); + ExtendedBehaviorSurface? eb_surface = wayland_surface.get_qdata (ExtendedBehaviorSurface.quark); + if (eb_surface != null) { + surface_resource.post_error ( + Wl.DisplayError.INVALID_OBJECT, + "io_elementary_pantheon_shell_v1_interface::get_extended_behavior already requested" + ); + return; + } + + eb_surface = new ExtendedBehaviorSurface (wayland_surface); + unowned var eb_resource = client.create_resource ( + ref Pantheon.Desktop.ExtendedBehaviorInterface.iface, + resource.get_version (), + output + ); + eb_resource.set_implementation ( + &wayland_pantheon_extended_behavior_interface, + eb_surface.ref (), + unref_obj_on_destroy + ); + wayland_surface.set_qdata_full ( + ExtendedBehaviorSurface.quark, + eb_surface, + (GLib.DestroyNotify) ExtendedBehaviorSurface.on_wayland_surface_disposed + ); + } + + internal static void set_anchor (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] Pantheon.Desktop.Anchor anchor) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + // TODO + } + + internal static void focus (Wl.Client client, Wl.Resource resource) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + window.focus (window.get_display ().get_current_time ()); + } + + internal static void set_size (Wl.Client client, Wl.Resource resource, int width, int height) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + // TODO + } + + internal static void set_hide_mode (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] Pantheon.Desktop.HideMode hide_mode) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + // TODO + } + + internal static void set_keep_above (Wl.Client client, Wl.Resource resource) { + unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data (); + if (eb_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + eb_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + window.make_above (); + } + + internal static void destroy_panel_surface (Wl.Client client, Wl.Resource resource) { + resource.destroy (); + } + + internal static void destroy_widget_surface (Wl.Client client, Wl.Resource resource) { + resource.destroy (); + } + + internal static void destroy_extended_behavior_surface (Wl.Client client, Wl.Resource resource) { + resource.destroy (); + } +} diff --git a/src/meson.build b/src/meson.build index 452a2457..afe387c9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,6 +9,7 @@ gala_bin_sources = files( 'Main.vala', 'MediaFeedback.vala', 'NotificationStack.vala', + 'PantheonShell.vala', 'PluginManager.vala', 'ScreenSaverManager.vala', 'ScreenshotManager.vala', @@ -69,7 +70,7 @@ gala_bin_sources = files( gala_bin = executable( 'gala', gala_bin_sources, - dependencies: [gala_dep, gala_base_dep], + dependencies: [gala_dep, gala_base_dep, pantheon_desktop_shell_dep], include_directories: config_inc_dir, install_rpath: mutter_typelib_dir, install: true, diff --git a/vapi/wayland-server.deps b/vapi/wayland-server.deps new file mode 100644 index 00000000..b3188f74 --- /dev/null +++ b/vapi/wayland-server.deps @@ -0,0 +1 @@ +posix diff --git a/vapi/wayland-server.vapi b/vapi/wayland-server.vapi new file mode 100644 index 00000000..6fa1885b --- /dev/null +++ b/vapi/wayland-server.vapi @@ -0,0 +1,125 @@ +/* wayland-server.vapi + * + * Copyright 2022 Corentin Noël + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Corentin Noël + */ + +[CCode (cprefix = "wl_", lower_case_cprefix = "wl_", cheader_filename = "wayland-server.h")] +namespace Wl { + [Compact] + [CCode (cname = "struct wl_display", free_function = "wl_display_destroy")] + public class Display { + [CCode (cname = "wl_display_create")] + public Display (); + public int add_socket (string name); + public unowned string add_socket_auto (); + public int add_socket_fd (int sock_fd); + public void terminate (); + public void run (); + public void flush_clients (); + public void destroy_clients (); + public uint32 get_serial (); + public uint32 next_serial (); + } + + [Compact] + [CCode (cname = "struct wl_client", free_function = "wl_client_destroy")] + public class Client { + [CCode (cname = "wl_client_create")] + public Client (Wl.Display display, int fd); + public void flush (); + public void get_credentials (out Posix.pid_t pid, out Posix.uid_t uid, out Posix.gid_t gid); + public int get_fd (); + public unowned Wl.Display get_display (); + [CCode (cname = "wl_resource_create")] + public unowned Wl.Resource? create_resource (ref Wl.Interface interface, int version, uint32 id); + } + + [Compact] + [CCode (cname = "struct wl_resource", free_function = "wl_resource_destroy")] + public class Resource { + public uint32 get_id (); + public unowned Wl.Client get_client (); + [CCode (simple_generics = true)] + public void set_user_data (T? data); + [CCode (simple_generics = true)] + public unowned T? get_user_data (); + public int get_version (); + public unowned string get_class (); + public void destroy (); + public void set_implementation (void* implementation, void* data, [CCode (delegate_target = false)] ResourceDestroyFunc destroy); + [PrintfFormat] + public void post_error(uint32 code, string format, ...); + } + [Compact] + [CCode (cname = "struct wl_interface")] + public class Interface { + public string name; + public int version; + [CCode (array_length = "method_count")] + public Wl.Message[] methods; + [CCode (array_length = "event_count")] + public Wl.Message[] events; + } + + [Compact] + [CCode (cname = "struct wl_message")] + public class Message { + public string name; + public string signature; + [CCode (array_length = false)] + public Wl.Interface?[] types; + } + + [Compact] + [CCode (cname = "struct wl_global", free_function = "wl_global_destroy")] + public class Global { + [CCode (cname = "wl_global_create")] + public static Wl.Global? create (Wl.Display display, ref Wl.Interface interface, int version, [CCode (delegate_target_pos = 3.9) ] Wl.GlobalBindFunc bind); + } + + [CCode (cheader_filename = "wayland-server-protocol.h", cname = "enum wl_display_error", cprefix="WL_DISPLAY_ERROR_", has_type_id = false)] + public enum DisplayError { + INVALID_OBJECT, + INVALID_METHOD, + NO_MEMORY, + IMPLEMENTATION, + } + + [CCode (cname = "wl_global_bind_func_t", instance_pos = 1.9)] + public delegate void GlobalBindFunc (Wl.Client client, uint32 version, uint32 id); + [CCode (cname = "wl_resource_destroy_func_t", has_target = false)] + public delegate void ResourceDestroyFunc (Wl.Resource resource); + [CCode (cname = "WAYLAND_VERSION_MAJOR")] + public const int VERSION_MAJOR; + [CCode (cname = "WAYLAND_VERSION_MINOR")] + public const int VERSION_MINOR; + [CCode (cname = "WAYLAND_VERSION_MICRO")] + public const int VERSION_MICRO; + [CCode (cname = "WAYLAND_VERSION")] + public const string VERSION; +} +