Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:ila.embsys:branches:hardware:xr
gnome-shell-xrdesktop
gnome-shell-xrdesktop.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File gnome-shell-xrdesktop.patch of Package gnome-shell-xrdesktop
diff --git a/.gitmodules b/.gitmodules index bb57dfc6c..5ab4a386d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "subprojects/gvc"] path = subprojects/gvc - url = ../../GNOME/libgnome-volume-control.git + url = https://gitlab.gnome.org/GNOME/libgnome-volume-control.git diff --git a/meson.build b/meson.build index 70ba71106..9fed904d7 100644 --- a/meson.build +++ b/meson.build @@ -95,6 +95,9 @@ x11_dep = dependency('x11') schemas_dep = dependency('gsettings-desktop-schemas', version: schemas_req) gnome_desktop_dep = dependency('gnome-desktop-4', version: gnome_desktop_req) +xrdesktop_dep = dependency('xrdesktop-0.16', required: true) +inputsynth_dep = dependency('libinputsynth-0.16', required: true) + nm_deps = [] if get_option('networkmanager') nm_deps += dependency('libnm', version: nm_req) diff --git a/src/main.c b/src/main.c index dec0efa8c..d65fe31cd 100644 --- a/src/main.c +++ b/src/main.c @@ -26,6 +26,9 @@ #include <elf.h> #endif +#include "shell-vr-mirror.h" +#include "shell-vr-mirror-dbus.h" + #include "shell-global.h" #include "shell-global-private.h" #include "shell-perf-log.h" @@ -689,6 +692,8 @@ main (int argc, char **argv) g_log_set_writer_func (default_log_writer, NULL, NULL); + shell_vr_mirror_dbus_init (); + shell_profiler_init (); if (meta_context_get_compositor_type (context) == META_COMPOSITOR_TYPE_WAYLAND) diff --git a/src/meson.build b/src/meson.build index 070312bd0..3e3a6eff6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -66,6 +66,8 @@ gnome_shell_deps = [ gcr_dep, libsystemd_dep, libpipewire_dep, + xrdesktop_dep, + inputsynth_dep ] gnome_shell_deps += nm_deps @@ -130,7 +132,9 @@ libshell_private_headers = [ 'shell-global-private.h', 'shell-tray-icon-private.h', 'shell-window-tracker-private.h', - 'shell-wm-private.h' + 'shell-wm-private.h', + 'shell-vr-mirror.h', + 'shell-vr-mirror-dbus.h' ] libshell_sources = [ @@ -161,7 +165,9 @@ libshell_sources = [ 'shell-window-preview-layout.c', 'shell-window-tracker.c', 'shell-wm.c', - 'shell-workspace-background.c' + 'shell-workspace-background.c', + 'shell-vr-mirror.c', + 'shell-vr-mirror-dbus.c' ] if have_networkmanager diff --git a/src/shell-vr-mirror-dbus.c b/src/shell-vr-mirror-dbus.c new file mode 100644 index 000000000..261bdbe90 --- /dev/null +++ b/src/shell-vr-mirror-dbus.c @@ -0,0 +1,121 @@ +#include "shell-vr-mirror-dbus.h" +#include "shell-vr-mirror.h" + +#define BUS_NAME "org.gnome.Shell.XR" +static const gchar introspection_xml[] = + "<node>" + " <interface name='org.gnome.Shell.XR'>" + " <property name='enabled' type='b' access='readwrite' />" + " </interface>" + "</node>"; + +static void +_vrshell_set (gboolean enable) +{ + ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance (); + if (enable) + { + if (vr_mirror) + { + g_print ("DBus: Enable XR (already enabled)\n"); + } + else + { + g_print ("DBus: Enable XR\n"); + shell_vr_mirror_create_instance (); + } + } + else + { + g_print ("DBus: Disable XR\n"); + if (vr_mirror) + { + shell_vr_mirror_destroy_instance (); + } + } +} + +static GVariant * +_handle_dbus_property_get (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + if (g_strcmp0 (property_name, "enabled") == 0) + { + ShellVRMirror *instance = shell_vr_mirror_get_instance (); + gboolean vr_running = (instance != NULL); + GVariant *ret = g_variant_new_boolean (vr_running); + return ret; + } + else + { + g_print ("Error, only XR enabled property supported!\n"); + return NULL; + } +} + +static gboolean +_handle_dbus_property_set (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + if (g_strcmp0 (property_name, "enabled") == 0) + { + if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) + { + g_print ("Error, XR enabled property must be bool!\n"); + return FALSE; + } + gboolean setTo = g_variant_get_boolean (value); + _vrshell_set (setTo); + return TRUE; + } + return FALSE; +} + +static const GDBusInterfaceVTable interface_vtable = +{ + NULL, + _handle_dbus_property_get, + _handle_dbus_property_set +}; + +static void +_on_dbus_bus_aquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml ( + introspection_xml, NULL); + + g_dbus_connection_register_object (connection, + "/org/gnome/Shell/XR", + introspection_data->interfaces[0], + &interface_vtable, + NULL, /* user_data */ + NULL, /* user_data_free_func */ + NULL); /* GError** */ +} + +void +shell_vr_mirror_dbus_init (void) +{ + g_bus_own_name (G_BUS_TYPE_SESSION, + BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + _on_dbus_bus_aquired, + NULL, + NULL, + NULL, + NULL); +} diff --git a/src/shell-vr-mirror-dbus.h b/src/shell-vr-mirror-dbus.h new file mode 100644 index 000000000..1ee4da3c7 --- /dev/null +++ b/src/shell-vr-mirror-dbus.h @@ -0,0 +1,3 @@ + +void +shell_vr_mirror_dbus_init (void); diff --git a/src/shell-vr-mirror.c b/src/shell-vr-mirror.c new file mode 100644 index 000000000..6c5dc8a32 --- /dev/null +++ b/src/shell-vr-mirror.c @@ -0,0 +1,1786 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include "config.h" + +#include <clutter/clutter.h> +#include <glib/gprintf.h> + +#include <graphene.h> +#include <glib.h> + +#include <inputsynth.h> + +#include <meta/meta-shaped-texture.h> +#include <meta/display.h> + +#include "shell-vr-mirror.h" +#include "shell-global.h" + +#include <meta/util.h> + +#include <GL/gl.h> + +#include <meta/meta-cursor-tracker.h> +#include <meta/meta-workspace-manager.h> + +/* singleton variable for shell_vr_mirror_get_instance() */ +static ShellVRMirror *shell_vr_mirror_instance = NULL; + +/* 1 pixel/meter = 0.0254 dpi */ +#define SHELL_VR_PIXELS_PER_METER 720.0f +#define SHELL_VR_DESKTOP_PLANE_DISTANCE 3.5f +#define SHELL_VR_LAYER_DISTANCE 0.1f + +#define DEFAULT_LEVEL 0.5 + +#define SHELL_VR_COPY_COMPUTE 1 + +// core gl prototypes +#undef GL_VERSION_1_1 // hack +#include <GL/glcorearb.h> + +// macro to generate a static global declaration like static GLenum (*_glGetError) = NULL; +#define GL_DECL(TYPE, FUNC) static TYPE _ ## FUNC = NULL; + +// macro that invokes the argument with each (opengl function prototype, opengl function name) tuple we load +#define FOR_EACH_GL_FUNC(_) \ + _(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers) \ + _(PFNGLDEBUGMESSAGECALLBACKPROC, glDebugMessageCallback) \ + _(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers) \ + _(PFNGLCREATESHADERPROC, glCreateShader) \ + _(PFNGLSHADERSOURCEPROC, glShaderSource) \ + _(PFNGLCOMPILESHADERPROC, glCompileShader) \ + _(PFNGLGETSHADERIVPROC, glGetShaderiv) \ + _(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog) \ + _(PFNGLCREATEPROGRAMPROC, glCreateProgram) \ + _(PFNGLATTACHSHADERPROC, glAttachShader) \ + _(PFNGLLINKPROGRAMPROC, glLinkProgram) \ + _(PFNGLGETPROGRAMIVPROC, glGetProgramiv) \ + _(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog) \ + _(PFNGLDELETESHADERPROC, glDeleteShader) \ + _(PFNGLGENBUFFERSPROC, glGenBuffers) \ + _(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays) \ + _(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) \ + _(PFNGLBINDBUFFERPROC, glBindBuffer) \ + _(PFNGLBUFFERDATAPROC, glBufferData) \ + _(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) \ + _(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray) \ + _(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation) \ + _(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer) \ + _(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D) \ + _(PFNGLUSEPROGRAMPROC, glUseProgram) \ + _(PFNGLUNIFORMMATRIX4FVPROC, glUniformMatrix4fv) \ + _(PFNGLBLITNAMEDFRAMEBUFFERPROC, glBlitNamedFramebuffer) \ + _(PFNGLUNIFORM3FPROC, glUniform3f) \ + _(PFNGLUNIFORM4FPROC, glUniform4f) \ + _(PFNGLGETERRORPROC, glGetError) \ + _(PFNGLFINISHPROC, glFinish) \ + _(PFNGLFLUSHPROC, glFlush) \ + _(PFNGLGETSTRINGPROC, glGetString) \ + _(PFNGLGETTEXLEVELPARAMETERIVPROC, glGetTexLevelParameteriv) \ + _(PFNGLGENTEXTURESPROC, glGenTextures) \ + _(PFNGLDELETETEXTURESPROC, glDeleteTextures) \ + _(PFNGLTEXPARAMETERIPROC, glTexParameteri) \ + _(PFNGLBINDTEXTUREPROC, glBindTexture) \ + _(PFNGLCREATEMEMORYOBJECTSEXTPROC, glCreateMemoryObjectsEXT) \ + _(PFNGLDELETEMEMORYOBJECTSEXTPROC, glDeleteMemoryObjectsEXT) \ + _(PFNGLMEMORYOBJECTPARAMETERIVEXTPROC, glMemoryObjectParameterivEXT) \ + _(PFNGLGETMEMORYOBJECTPARAMETERIVEXTPROC, glGetMemoryObjectParameterivEXT) \ + _(PFNGLIMPORTMEMORYFDEXTPROC, glImportMemoryFdEXT) \ + _(PFNGLTEXSTORAGEMEM2DEXTPROC, glTexStorageMem2DEXT) \ + _(PFNGLCOPYIMAGESUBDATAPROC, glCopyImageSubData) \ + _(PFNGLNAMEDFRAMEBUFFERTEXTUREPROC, glNamedFramebufferTexture) \ + _(PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC, glNamedFramebufferTextureLayer) \ + _(PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC, glCheckNamedFramebufferStatus) \ + _(PFNGLCREATEFRAMEBUFFERSPROC, glCreateFramebuffers) \ + _(PFNGLDRAWARRAYSPROC, glDrawArrays) \ + _(PFNGLUNIFORM1IPROC, glUniform1i) \ + _(PFNGLUNIFORM1FPROC, glUniform1f) \ + _(PFNGLBINDTEXTUREUNITPROC, glBindTextureUnit) \ + _(PFNGLBINDIMAGETEXTUREPROC, glBindImageTexture) \ + _(PFNGLDISPATCHCOMPUTEPROC, glDispatchCompute) \ + _(PFNGLMEMORYBARRIERPROC, glMemoryBarrier) \ + + +// generates a global declaration for each gl func listed in FOR_EACH_GL_FUNC +FOR_EACH_GL_FUNC(GL_DECL) + +// macro to load an opengl function pointer with cogl_get_proc_address +#define LOAD_GL_FUNC(TYPE, FUNC) \ + _ ## FUNC = (TYPE) cogl_get_proc_address(#FUNC); \ + if (_ ## FUNC == NULL) \ + { \ + g_printerr("Failed to load OpenGL function " #FUNC ); \ + return FALSE; \ + } \ + +static gboolean +_load_gl_functions(void) +{ + FOR_EACH_GL_FUNC(LOAD_GL_FUNC) + return TRUE; +} + +#define ENUM_TO_STR(r) case r: return #r + +static const gchar* +gl_error_string (GLenum code) +{ + switch (code) + { + ENUM_TO_STR(GL_NO_ERROR); + ENUM_TO_STR(GL_INVALID_ENUM); + ENUM_TO_STR(GL_INVALID_VALUE); + ENUM_TO_STR(GL_INVALID_OPERATION); + ENUM_TO_STR(GL_INVALID_FRAMEBUFFER_OPERATION); + ENUM_TO_STR(GL_OUT_OF_MEMORY); + ENUM_TO_STR(GL_STACK_UNDERFLOW); + ENUM_TO_STR(GL_STACK_OVERFLOW); + default: + return "UNKNOWN GL Error"; + } +} + +static gboolean +is_grab_op_valid (MetaGrabOp grab_op) +{ + switch (grab_op) + { + case META_GRAB_OP_NONE: + case META_GRAB_OP_MOVING: + case META_GRAB_OP_RESIZING_NW: + case META_GRAB_OP_RESIZING_N: + case META_GRAB_OP_RESIZING_NE: + case META_GRAB_OP_RESIZING_E: + case META_GRAB_OP_RESIZING_SW: + case META_GRAB_OP_RESIZING_S: + case META_GRAB_OP_RESIZING_SE: + case META_GRAB_OP_RESIZING_W: + case META_GRAB_OP_KEYBOARD_MOVING: + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + case META_GRAB_OP_KEYBOARD_RESIZING_N: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_E: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_S: + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_W: + return TRUE; + default: + return FALSE; + } +} + +static void +gl_check_error_func (const char *prefix, const char *file, const int line) +{ + GLenum err = 0; + while ((err = _glGetError ()) != GL_NO_ERROR) + { + printf ("%s:%i: GL ERROR: %s - %s\n", file, line, prefix, + gl_error_string (err)); + } +} + +#define gl_check_error(PREFIX) gl_check_error_func (PREFIX, __FILE__, __LINE__) + +static const gchar * +gl_get_vendor (void) +{ + static const gchar *vendor = NULL; + if (!vendor) + vendor = (const gchar *) _glGetString (GL_VENDOR); + return vendor; +} + +static const char* shader = + "// Copyright 2021-2023, Collabora Ltd.\n" \ + "// Author: Jakob Bornecrantz <jakob@collabora.com>\n" \ + "// SPDX-License-Identifier: MIT or BSL-1.0 or Apache-2.0\n" \ + + "#version 450\n" \ + + "layout (local_size_x = 16, local_size_y = 16) in;\n" \ + + "layout (binding = 0) uniform sampler2D source;\n" \ + "layout (rgba8, binding = 0) uniform image2D target;\n" \ + + "vec2 position_to_uv(ivec2 extent, uint ix, uint iy)\n" \ + "{\n" \ + " // Turn the index into floating point.\n" \ + " vec2 xy = vec2(float(ix), float(iy));\n" \ + + " // The inverse of the extent of the target image is the pixel size in [0 .. 1] space.\n" \ + " vec2 extent_pixel_size = vec2(1.0 / float(extent.x), 1.0 / float(extent.y));\n" \ + + " // Per-target pixel we move the size of the pixels.\n" \ + " vec2 uv = xy * extent_pixel_size;\n" \ + + " // Emulate a triangle sample position by offset half target pixel size.\n" \ + " uv = uv + extent_pixel_size / 2.0;\n" \ + + " return uv;\n" \ + "}\n" \ + + "void main() {\n" \ + " uint ix = gl_GlobalInvocationID.x;\n" \ + " uint iy = gl_GlobalInvocationID.y;\n" \ + + " ivec2 extent = imageSize(target);\n" \ + " if (ix >= extent.x || iy >= extent.y) {\n" \ + " return;\n" \ + " }\n" \ + + " // Get the UV we should sample from.\n" \ + " vec2 uv = position_to_uv(extent, ix, iy);\n" \ + + " // Do the sample.\n" \ + " vec4 rgba = texture(source, uv);\n" \ + + " // And finally write out.\n" \ + " imageStore(target, ivec2(ix, iy), rgba);\n" \ + "}\n"; + +struct gl_copy_helper +{ + GLuint shader_program_id; +}; + +static gboolean +gl_copy_helper_init (struct gl_copy_helper *self) +{ + GLuint shader_id = _glCreateShader(GL_COMPUTE_SHADER); + const GLchar* shader_source[1]; + shader_source[0] = shader; + // printf("Compute Shader:\n%s\n", shader_source); + _glShaderSource(shader_id, 1, shader_source, NULL); + gl_check_error ("_glShaderSource"); + + _glCompileShader(shader_id); + gl_check_error ("_glCompileShader"); + + int compute_compile_res; + _glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compute_compile_res); + gl_check_error ("_glGetShaderiv"); + + if (!compute_compile_res) { + char info_log[512]; + _glGetShaderInfoLog(shader_id, 512, NULL, info_log); + gl_check_error ("_glGetShaderInfoLog"); + + printf("Compute Shader failed to compile: %s\n", info_log); + return FALSE; + } else { + printf("Successfully compiled compute shader!\n"); + } + + self->shader_program_id = _glCreateProgram(); + _glAttachShader(self->shader_program_id, shader_id); + gl_check_error ("_glAttachShader"); + + _glLinkProgram(self->shader_program_id); + gl_check_error ("_glLinkProgram"); + + GLint shader_program_res; + _glGetProgramiv(self->shader_program_id, GL_LINK_STATUS, &shader_program_res); + gl_check_error ("_glGetProgramiv"); + + if (!shader_program_res) { + char info_log[512]; + _glGetProgramInfoLog(self->shader_program_id, 512, NULL, info_log); + printf("Shader Program failed to link: %s\n", info_log); + return 1; + } else { + printf("Successfully linked shader program!\n"); + } + + _glDeleteShader(shader_id); + + return TRUE; +}; + + +struct _ShellVRMirror +{ + GObject parent; + + InputSynth *vr_input; + + XrdShell *shell; + G3kContext *g3k; + + MetaCursorTracker *cursor_tracker; + GLuint cursor_gl_shared_texture; + + uint32_t top_layer; + + gboolean nvidia; + struct gl_copy_helper gl_copy_helper; + + GSList *grabbed_windows; + + gboolean shutdown; + + bool rendering; + bool framecycle; +}; + +struct _ShellVREffect { + ClutterEffect parent_instance; + + ClutterActor *actor; + XrdWindow *xrd_win; +}; + +G_DEFINE_TYPE (ShellVREffect, shell_vr_effect, CLUTTER_TYPE_EFFECT); + +typedef struct +{ + MetaWindowActor *meta_window_actor; + + bool keep_above_restore; + bool keep_below_restore; + + /* We draw every frame of this window again to this shared GL texture */ + GLuint gl_shared_texture; +} ShellVRWindow; + +G_DEFINE_TYPE (ShellVRMirror, shell_vr_mirror, G_TYPE_OBJECT); + + + +static void +shell_vr_effect_init (ShellVREffect *self) +{ + self->actor = NULL; + self->xrd_win = NULL; +} + +static void +shell_vr_effect_set_actor (ClutterActorMeta *meta, + ClutterActor *actor) +{ + ShellVREffect *self = SHELL_VR_EFFECT (meta); + + // Maintain a pointer to the actor + self->actor = actor; + + // If we've been detached by the actor then we should just bail out here + if (self->actor == NULL) + return; +} + +static gboolean +_upload_window (ShellVRMirror *self, XrdWindow *xrd_win); + + +static void +shell_vr_effect_paint (ClutterEffect *effect, + ClutterPaintNode *node, + ClutterPaintContext *paint_context, + ClutterEffectPaintFlags flags) +{ + ShellVREffect *self = SHELL_VR_EFFECT (effect); + if (!self->actor || !CLUTTER_IS_ACTOR(self->actor)) + return; + + clutter_actor_continue_paint (self->actor, paint_context); + + // XXXXXXX +// if (!(flags & CLUTTER_EFFECT_PAINT_ACTOR_DIRTY)) +// return; + + ShellVRMirror *mirror = shell_vr_mirror_get_instance (); + if (!mirror || !self->xrd_win) + return; + _upload_window (mirror, self->xrd_win); +} + + +static void +shell_vr_effect_pick (ClutterEffect *effect, + ClutterPickContext *pick_context) +{ + ShellVREffect *self = SHELL_VR_EFFECT (effect); + if (self->actor == NULL || !CLUTTER_IS_ACTOR(self->actor)) + return; + + clutter_actor_continue_pick (self->actor, pick_context); +} + +static void +shell_vr_effect_class_init (ShellVREffectClass *klass) +{ + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + + meta_class->set_actor = shell_vr_effect_set_actor; + + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + effect_class->paint = shell_vr_effect_paint; + effect_class->pick = shell_vr_effect_pick; +} + +static GLint +_to_sized_format(GLint format) +{ + if (format == GL_RGBA8) + return GL_RGBA8; + + if (format == GL_RGB) + return GL_RGBA8; // Three component variant not writeable by compute shader. + if (format == GL_RGBA) + return GL_RGBA8; + if (format == GL_BGR) + return GL_RGBA8; + + g_printerr("Format 0x%x not implemented, using GL_RGBA8\n", format); + return GL_RGBA8; +} + +/** + * @brief Creates a new GulkanTexture, OpenGL texture pair that shares video memory. + * + * The new OpenGL texture out_gl_shared_texture will match the properties of gl_texture. + * + * @param self + * @param gulkan + * @param gl_texture + * @param gl_target_type + * @param extent + * @param out_gl_shared_texture + * @return + */ +static GulkanTexture* +_allocate_external_memory (ShellVRMirror *self, + GulkanContext *gulkan, + GLuint gl_texture, + GLenum gl_target_type, + VkExtent2D extent, + GLuint *out_gl_shared_texture) +{ + g_print ("Reallocating %dx%d vulkan texture\n", extent.width, extent.height); + + _glBindTexture (gl_target_type, gl_texture); + char buf[2048]; + snprintf(buf, 2048, "glBindTexture target type %x, texture %d", gl_target_type, gl_texture); + gl_check_error (buf); + + GLint internal_format; + _glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, + GL_TEXTURE_INTERNAL_FORMAT, &internal_format); + gl_check_error ("_glGetTexLevelParameteriv"); + +#if SHELL_VR_COPY_COMPUTE + internal_format = _to_sized_format (internal_format); +#endif + + gsize size; + int fd; + + VkImageLayout layout = g3k_context_get_upload_layout (self->g3k); + GulkanTexture *texture = + gulkan_texture_new_export_fd (gulkan, extent, VK_FORMAT_R8G8B8A8_SRGB, + layout, &size, &fd); + if (texture == NULL) + { + g_printerr ("Error: Unable to initialize Vulkan texture.\n"); + return NULL; + } + gl_check_error ("gulkan_texture_new_export_fd"); + + + GLint gl_dedicated_mem = GL_TRUE; + GLuint gl_mem_object; + _glCreateMemoryObjectsEXT (1, &gl_mem_object); + gl_check_error ("_glCreateMemoryObjectsEXT"); + + _glMemoryObjectParameterivEXT (gl_mem_object, + GL_DEDICATED_MEMORY_OBJECT_EXT, + &gl_dedicated_mem); + gl_check_error ("_glMemoryObjectParameterivEXT"); + + _glGetMemoryObjectParameterivEXT (gl_mem_object, + GL_DEDICATED_MEMORY_OBJECT_EXT, + &gl_dedicated_mem); + gl_check_error ("_glGetMemoryObjectParameterivEXT"); + + _glImportMemoryFdEXT (gl_mem_object, size, + GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); + gl_check_error ("_glImportMemoryFdEXT"); + + _glGenTextures (1, out_gl_shared_texture); + gl_check_error ("_glGenTextures"); + + _glBindTexture (GL_TEXTURE_2D, *out_gl_shared_texture); + gl_check_error ("_glBindTexture"); + + _glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_OPTIMAL_TILING_EXT); + gl_check_error ("glTexParameteri GL_TEXTURE_TILING_EXT"); + _glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl_check_error ("glTexParameteri GL_TEXTURE_MIN_FILTER"); + _glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl_check_error ("glTexParameteri GL_TEXTURE_MAG_FILTER"); + + if (self->nvidia) + internal_format = GL_RGBA8; + + _glTexStorageMem2DEXT (GL_TEXTURE_2D, 1, internal_format, + extent.width, extent.height, gl_mem_object, 0); + gl_check_error ("_glTexStorageMem2DEXT"); + + _glFinish (); + + _glDeleteMemoryObjectsEXT (1, &gl_mem_object); + gl_check_error ("_glDeleteMemoryObjectsEXT"); + + return texture; +} + +#if SHELL_VR_COPY_COMPUTE +/** + * @brief Creates a new GulkanTexture that is shared with a new out_gl_handle OpenGL texture. + * + * If out_gl_handle is != 0, it is destroyed. + * + * @param self + * @param extent + * @param gl_texture + * @param gl_texture_type + * @param out_gl_handle + * @return The new GulkanTexture + */ +static GulkanTexture * +_recreate_texture (ShellVRMirror *self, + VkExtent2D extent, + GLuint gl_texture, + GLenum gl_texture_type, + GLuint *out_gl_handle) +{ + if (*out_gl_handle != 0) + { + _glDeleteTextures (1, out_gl_handle); + gl_check_error ("_glDeleteTextures"); + } + + GulkanContext *gulkan = g3k_context_get_gulkan (self->g3k); + GulkanTexture *texture = _allocate_external_memory (self, gulkan, gl_texture, + gl_texture_type, extent, + out_gl_handle); + gl_check_error ("_allocate_external_memory"); + + _glFinish (); + gl_check_error ("_glFinish"); + + return texture; +} + +/** + * @brief copies gl_texture to target_texture with a compute shader. + * + * @param gch + * @param source_texture + * @param target_texture + * @param extent + */ +static void +_copy_texture (struct gl_copy_helper *gch, + GLuint source_texture, + GLuint target_texture, + VkExtent2D extent) +{ + GLint restore_program; + glGetIntegerv (GL_CURRENT_PROGRAM, &restore_program); + gl_check_error ("glGetIntegerv GL_CURRENT_PROGRAM"); + + GLint restore_active_texture; + glGetIntegerv (GL_ACTIVE_TEXTURE, &restore_active_texture); + gl_check_error ("glGetIntegerv GL_ACTIVE_TEXTURE"); + + glActiveTexture (GL_TEXTURE0); + gl_check_error ("glActiveTexture"); + + // TODO: restore code assumes only 2d textures are bound + + GLint restore_unit0; + glGetIntegerv (GL_TEXTURE_BINDING_2D, &restore_unit0); + gl_check_error ("glGetIntegerv GL_TEXTURE_BINDING_2D 0"); + + + // Set the state we are interested in. + + _glUseProgram (gch->shader_program_id); + gl_check_error ("_glUseProgram"); + + _glBindTextureUnit (0, source_texture); + gl_check_error ("_glBindTextureUnit"); + + _glBindImageTexture (0, target_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + gl_check_error ("_glBindImageTexture"); + + GLuint widthDiv = (extent.width + 15) / 16; + GLuint heightDiv = (extent.height + 15) / 16; + + _glDispatchCompute (widthDiv, heightDiv, 1); + gl_check_error ("glDispatchCompute"); + + _glMemoryBarrier (GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + gl_check_error ("glMemoryBarrier"); + + + // Restore or unbind state. + _glBindImageTexture (0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8); + gl_check_error ("_glBindImageTexture (restore)"); + + _glBindTextureUnit (0, restore_unit0); + gl_check_error ("_glBindTextureUnit (restore)"); + + _glUseProgram (restore_program); + gl_check_error ("glUseProgram (restore)"); + + glActiveTexture (restore_active_texture); + gl_check_error ("glActiveTexture (restore)"); + + // TODO: import semaphore from vulkan and signal + glFinish(); + gl_check_error ("glFinish"); +} +#else +static GulkanTexture* +_recreate_subdata (ShellVRMirror *self, + VkExtent2D extent, + GLuint meta_tex, + GLenum meta_target, + GLuint *out_gl_handle) +{ + if (*out_gl_handle != 0) + _glDeleteTextures (1, out_gl_handle); + + GulkanContext *gulkan = g3k_context_get_gulkan (self->g3k); + GulkanTexture *texture = + _allocate_external_memory (self, gulkan, meta_tex, meta_target, + extent, out_gl_handle); + + return texture; +} + +static void +_update_subdata (VkExtent2D extent, + GLuint meta_tex, + GLenum meta_target, + GLuint dst_name) +{ + _glCopyImageSubData (meta_tex, meta_target, 0, 0, 0, 0, + dst_name, GL_TEXTURE_2D, 0, 0, 0, 0, + extent.width, extent.height, 1); + gl_check_error ("_glCopyImageSubData"); + _glFinish (); +} +#endif + +static void +_cursor_changed_cb (MetaCursorTracker *cursor_tracker, + gpointer _self) +{ + ShellVRMirror *self = _self; + if (!self) + return; + + CoglTexture *cogl_texture = meta_cursor_tracker_get_sprite (cursor_tracker); + int hotspot_x, hotspot_y; + meta_cursor_tracker_get_hot (cursor_tracker, &hotspot_x, &hotspot_y); + + + if (cogl_texture == NULL || !cogl_is_texture (cogl_texture)) + { + g_printerr ("Cursor Error: Could not CoglTexture.\n"); + return; + } + + GLuint meta_tex; + GLenum meta_target_type; + if (!cogl_texture_get_gl_texture (cogl_texture, &meta_tex, &meta_target_type)) + { + g_printerr ("Cursor Error: Could not get GL handle.\n"); + return; + } + + VkExtent2D cursor_extent = { + .width = (uint32_t) cogl_texture_get_width (cogl_texture), + .height = (uint32_t) cogl_texture_get_width (cogl_texture) + }; + + G3kCursor *cursor = xrd_shell_get_desktop_cursor (self->shell); + g3k_cursor_set_hotspot (cursor, hotspot_x, hotspot_y); + + gboolean extent_changed = TRUE; + GulkanTexture *texture = g3k_plane_get_texture (G3K_PLANE (cursor)); + if (texture) + { + VkExtent2D extent = gulkan_texture_get_extent (texture); + extent_changed = (cursor_extent.width != extent.width || + cursor_extent.height != extent.height); + } + + if (extent_changed) + { + GulkanTexture *new_texture; +#if SHELL_VR_COPY_COMPUTE + new_texture = _recreate_texture(self, cursor_extent, meta_tex, meta_target_type, + &self->cursor_gl_shared_texture); +#else + new_texture = _recreate_subdata (self, cursor_extent, meta_tex, + meta_target_type, &self->cursor_gl_shared_texture); +#endif + g3k_plane_set_texture (G3K_PLANE (cursor), new_texture); + } + +#if SHELL_VR_COPY_COMPUTE + _copy_texture (&self->gl_copy_helper, meta_tex, self->cursor_gl_shared_texture, cursor_extent); +#else + _update_subdata (cursor_extent, meta_tex, meta_target_type, + self->cursor_gl_shared_texture); +#endif +} + +static void +_grab_op_begin_cb (MetaDisplay *display1, + MetaDisplay *display2, + MetaWindow *grab_window, + MetaGrabOp op, + gpointer _self) +{ + // yes, this does happen + if (!grab_window) + return; + + if (!is_grab_op_valid(op)) + { + g_warning("_grab_op_begin_cb: Invalid grab op %d received.\n", op); + return; + } + + ShellVRMirror *self = _self; + if (!g_slist_find (self->grabbed_windows, grab_window)) + self->grabbed_windows = g_slist_append (self->grabbed_windows, grab_window); + + g_debug ("Start grab window %s\n", meta_window_get_title (grab_window)); +} + +static void +_grab_op_end_cb (MetaDisplay *display1, + MetaDisplay *display2, + MetaWindow *grab_window, + MetaGrabOp op, + gpointer _self) +{ + if (!grab_window) + return; + + if (!is_grab_op_valid(op)) + { + g_warning("_grab_op_end_cb: Invalid grab op %d received.\n", op); + return; + } + + ShellVRMirror *self = _self; + self->grabbed_windows = g_slist_remove (self->grabbed_windows, grab_window); + + g_debug ("End Grab window %s\n", meta_window_get_title (grab_window)); +} + +static void +shell_vr_mirror_init (ShellVRMirror *self) +{ + self->top_layer = 0; + self->vr_input = NULL; + self->shell = NULL; + self->shutdown = FALSE; +} + +ShellVRMirror * +shell_vr_mirror_new (void) +{ + return g_object_new (SHELL_TYPE_VR_MIRROR, NULL); +} + +ShellVRMirror * +shell_vr_mirror_get_instance (void) +{ + return shell_vr_mirror_instance; +} + +ShellVRMirror * +shell_vr_mirror_create_instance (void) +{ + shell_vr_mirror_instance = shell_vr_mirror_new (); + shell_vr_mirror_initialize (shell_vr_mirror_instance); + return shell_vr_mirror_instance; +} + +/* Coordinate space: 0 == x: left, y == 0: top */ +static graphene_point_t +_window_to_desktop_coords (MetaWindow *meta_win, + graphene_point_t *window_pixels) + { + MetaRectangle window_rect; + meta_window_get_buffer_rect (meta_win, &window_rect); + + graphene_point_t desktop_coords = { + .x = window_rect.x + window_pixels->x, + .y = window_rect.y + window_pixels->y + }; + return desktop_coords; +} + +static MetaWindow * +_get_validated_window (MetaWindowActor *actor) +{ + if (actor == NULL || !META_IS_WINDOW_ACTOR (actor)) + { + g_printerr ("Error: Actor for move cursor not available.\n"); + return NULL; + } + + MetaWindow *meta_win = meta_window_actor_get_meta_window (actor); + if (meta_win == NULL || !META_IS_WINDOW (meta_win)) + { + g_printerr ("Error: No window to move\n"); + return NULL; + } + + if (meta_window_get_display (meta_win) == NULL) + { + g_printerr ("Error: window has no display?!\n"); + return NULL; + } + + return meta_win; +} + +static void +_ensure_focused (MetaWindow *meta_win) +{ + /* mutter asserts that we don't mess with override_redirect windows */ + if (meta_window_is_override_redirect (meta_win)) + return; + + meta_window_raise (meta_win); + if (!meta_window_has_focus (meta_win)) + meta_window_focus (meta_win, CurrentTime); +} + +static void +_ensure_on_workspace (MetaWindow *meta_win) +{ + if (meta_window_is_on_all_workspaces (meta_win)) + return; + + MetaDisplay *display = meta_window_get_display (meta_win); + + MetaWorkspaceManager *manager = meta_display_get_workspace_manager (display); + MetaWorkspace *ws_current = + meta_workspace_manager_get_active_workspace (manager); + + MetaWorkspace *ws_window = meta_window_get_workspace (meta_win); + if (!ws_window || !META_IS_WORKSPACE (ws_window)) + return; + + if (ws_current == ws_window) + return; + + guint32 timestamp = meta_display_get_current_time_roundtrip (display); + meta_workspace_activate_with_focus (ws_window, meta_win, timestamp); +} + +static void +_click_cb (XrdShell *shell, + XrdClickEvent *event, + ShellVRMirror *self) +{ + ShellVRWindow *shell_win; + g_object_get (event->object, "native", &shell_win, NULL); + if (!shell_win) + return; + + MetaWindowActor *actor = (MetaWindowActor*) shell_win->meta_window_actor; + MetaWindow *meta_win = _get_validated_window (actor); + if (!meta_win) + return; + + _ensure_on_workspace (meta_win); + _ensure_focused (meta_win); + + graphene_point_t desktop_coords = + _window_to_desktop_coords (meta_win, event->position); + + input_synth_click (self->vr_input, + desktop_coords.x, desktop_coords.y, + event->button, event->state); +} + +static void +_move_cursor_cb (XrdShell *shell, + XrdMoveCursorEvent *event, + ShellVRMirror *self) +{ + ShellVRWindow *shell_win; + g_object_get (event->window, "native", &shell_win, NULL); + if (!shell_win) + return; + + MetaWindowActor *actor = (MetaWindowActor*) shell_win->meta_window_actor; + MetaWindow *meta_win = _get_validated_window (actor); + if (!meta_win) + return; + + /* do not move mouse cursor while the window is grabbed (in "move" mode) */ + if (g_slist_find (self->grabbed_windows, meta_win)) + return; + + _ensure_on_workspace (meta_win); + _ensure_focused (meta_win); + + graphene_point_t desktop_coords = + _window_to_desktop_coords (meta_win, event->position); + input_synth_move_cursor (self->vr_input, desktop_coords.x, desktop_coords.y); +} + +static void +_keyboard_press_cb (XrdShell *shell, + G3kKeyEvent *event, + ShellVRMirror *self) +{ + XrdWindow* keyboard_xrd_win = xrd_shell_get_keyboard_window (shell); + if (!keyboard_xrd_win) + { + g_print ("ERROR: No keyboard window!\n"); + return; + } + + ShellVRWindow *shell_win; + g_object_get (keyboard_xrd_win, "native", &shell_win, NULL); + if (!shell_win) + return; + + MetaWindowActor *actor = (MetaWindowActor*) shell_win->meta_window_actor; + MetaWindow *meta_win = _get_validated_window (actor); + if (!meta_win) + return; + + _ensure_on_workspace (meta_win); + _ensure_focused (meta_win); + + if (event->keyval == 0) + { + input_synth_characters (self->vr_input, event->string); + } + else + { + // TODO: keyval input + } +} + +static void +_disconnect_signals (ShellVRMirror *self); +static void +_connect_signals (ShellVRMirror *self); + +static void +_destroy_textures(ShellVRMirror *self) +{ + G3kObjectManager *manager = g3k_context_get_manager (self->g3k); + GSList *objects = g3k_object_manager_get_objects (manager); + for (GSList *l = objects; l != NULL; l = l->next) { + XrdWindow *xrd_win = XRD_WINDOW (l->data); + + ShellVRWindow *shell_win; + g_object_get (xrd_win, "native", &shell_win, NULL); + + // external memory texture, has to be recreated + _glDeleteTextures (1, &shell_win->gl_shared_texture); + shell_win->gl_shared_texture = 0; + } + + _glDeleteTextures (1, &self->cursor_gl_shared_texture); + self->cursor_gl_shared_texture = 0; +} + +static void +_detach_paint_cbs (XrdShell *shell) +{ + G3kContext *g3k = xrd_shell_get_g3k (shell); + G3kObjectManager *manager = g3k_context_get_manager (g3k); + GSList *objects = g3k_object_manager_get_objects (manager); + for (GSList *l = objects; l != NULL; l = l->next) + { + XrdWindow *xrd_win = XRD_WINDOW (l->data); + + ShellVRWindow *shell_win; + g_object_get (xrd_win, "native", &shell_win, NULL); + clutter_actor_clear_effects (CLUTTER_ACTOR(shell_win->meta_window_actor)); + } +} + +static void +_state_change_cb (GxrContext *gxr, + GxrStateChangeEvent *event, + gpointer _self) +{ + g_print ("Handling VR state change event\n"); + + ShellVRMirror *self = SHELL_VR_MIRROR (_self); + + switch (event->state_change) + { + case GXR_STATE_SHUTDOWN: + { + g_print("State change event: VR Shutdown\n"); + _detach_paint_cbs (self->shell); + _destroy_textures(self); + shell_vr_mirror_destroy_instance (); + } break; + case GXR_STATE_FRAMECYCLE_START: + self->framecycle = TRUE; + g_debug ("State change event: frame cycle started"); + break; + case GXR_STATE_FRAMECYCLE_STOP: + self->framecycle = FALSE; + g_debug ("State change event: frame cycle stopped"); + break; + case GXR_STATE_RENDERING_START: + self->rendering = TRUE; + g_debug ("State change event: rendering started"); + break; + case GXR_STATE_RENDERING_STOP: + self->rendering = TRUE; + g_debug ("State change event: rendering stopped"); + break; + } +} + +static void +_init_input (ShellVRMirror *self) +{ + if (meta_is_wayland_compositor ()) + self->vr_input = input_synth_new (INPUTSYNTH_BACKEND_WAYLAND_CLUTTER); + else + self->vr_input = input_synth_new (INPUTSYNTH_BACKEND_XDO); +} + +static gboolean +_upload_gl_external_memory (ShellVRMirror *self, + GulkanContext *gulkan, + XrdWindow *xrd_win, + MetaShapedTexture *mst, + MetaRectangle *rect) +{ + CoglTexture *cogl_texture = meta_shaped_texture_get_texture (mst); + + if (cogl_texture == NULL || !cogl_is_texture (cogl_texture)) + { + g_printerr ("Error: Could not CoglTexture from MetaShapedTexture.\n"); + return FALSE; + } + + GLuint meta_tex; + GLenum meta_target; + if (!cogl_texture_get_gl_texture (cogl_texture, &meta_tex, &meta_target)) + { + g_printerr ("Error: Could not get GL handle from CoglTexture.\n"); + return FALSE; + } + + ShellVRWindow *shell_win; + g_object_get (xrd_win, "native", &shell_win, NULL); + if (!shell_win) + return FALSE; + + VkExtent2D window_extent = { + .width = (uint32_t) rect->width, + .height = (uint32_t) rect->height + }; + + GulkanTexture *texture = g3k_plane_get_texture (G3K_PLANE (xrd_win)); + gboolean extent_changed = TRUE; + if (texture) + { + VkExtent2D extent = gulkan_texture_get_extent (texture); + extent_changed = (window_extent.width != extent.width || + window_extent.height != extent.height); + } + + if (extent_changed) + { + GulkanTexture *new_texture; +#if SHELL_VR_COPY_COMPUTE + new_texture = _recreate_texture(self, window_extent, meta_tex, meta_target, + &shell_win->gl_shared_texture); +#else + new_texture = _recreate_subdata (self, window_extent, meta_tex, + meta_target, &shell_win->gl_shared_texture); +#endif + g3k_plane_set_texture (G3K_PLANE (xrd_win), new_texture); + } + +#if SHELL_VR_COPY_COMPUTE + _copy_texture (&self->gl_copy_helper, meta_tex, shell_win->gl_shared_texture, window_extent); +#else + _update_subdata (window_extent, meta_tex, meta_target, shell_win->gl_shared_texture); +#endif + + return TRUE; +} + +static gboolean +_upload_raw_cairo (ShellVRMirror *self, + GulkanContext *gulkan, + XrdWindow *xrd_win, + MetaShapedTexture *mst, + MetaRectangle *rect) +{ + cairo_rectangle_int_t cairo_rect = { + .x = 0, + .y = 0, + .width = rect->width, + .height = rect->height + }; + + cairo_surface_t *sf = meta_shaped_texture_get_image (mst, &cairo_rect); + if (sf == NULL) + { + g_printerr ("Error: Could not get Cairo surface" + " from MetaShapedTexture.\n"); + return FALSE; + } + + ShellVRWindow *shell_win; + g_object_get (xrd_win, "native", &shell_win, NULL); + if (!shell_win) + return FALSE; + + VkImageLayout upload_layout = g3k_context_get_upload_layout (self->g3k); + + int texture_width; + int texture_height; + g_object_get (xrd_win, "texture-width", &texture_width, NULL); + g_object_get (xrd_win, "texture_height", &texture_height, NULL); + + GulkanTexture *texture = g3k_plane_get_texture (G3K_PLANE (xrd_win)); + + if (rect->width != texture_width || + rect->height != texture_height || + texture == NULL) + { + g_print ("Reallocating %dx%d vulkan texture\n", + rect->width, rect->height); + texture = + gulkan_texture_new_from_cairo_surface (gulkan, sf, + VK_FORMAT_B8G8R8A8_SRGB, + upload_layout); + + if (!GULKAN_IS_TEXTURE (texture)) + { + g_printerr ("Error creating texture for window!\n"); + return FALSE; + } + g3k_plane_set_texture (G3K_PLANE (xrd_win), texture); + } + else + { + gulkan_texture_upload_cairo_surface (texture, sf, upload_layout); + } + + cairo_surface_destroy (sf); + + return TRUE; +} + +static gboolean +_upload_window (ShellVRMirror *self, XrdWindow *xrd_win) +{ + ShellVRWindow *shell_win; + g_object_get (xrd_win, "native", &shell_win, NULL); + if (!shell_win) + return FALSE; + + MetaWindowActor* actor = shell_win->meta_window_actor; + + MetaWindow *meta_win = _get_validated_window (actor); + MetaRectangle rect; + meta_window_get_buffer_rect (meta_win, &rect); + + /* skip upload of small buffers */ + if (rect.width <= 10 && rect.height <= 10) + return FALSE; + + MetaShapedTexture* mst = + (MetaShapedTexture*) meta_window_actor_get_texture (actor); + + GulkanContext *gulkan = g3k_context_get_gulkan (self->g3k); + +// CoglTextureComponents components; +// if (self->nvidia) +// { +// CoglTexture *cogl_texture = meta_shaped_texture_get_texture (mst); +// if (cogl_texture == NULL || !cogl_is_texture (cogl_texture)) +// { +// g_printerr ("Error: Could not CoglTexture from MetaShapedTexture.\n"); +// return FALSE; +// } +// components = cogl_texture_get_components (cogl_texture); +// } + + static GMutex upload_mutex; + g_mutex_lock (&upload_mutex); + + gboolean ret; + /* Use cairo upload as fallback on NVIDIA for RGB buffers */ +// if (self->nvidia && components == COGL_TEXTURE_COMPONENTS_RGB && false) +// ret = _upload_raw_cairo (self, gulkan, xrd_win, mst, &rect); +// else + ret = _upload_gl_external_memory (self, gulkan, xrd_win, mst, &rect); + g_mutex_unlock (&upload_mutex); + + return ret; +} + +static gboolean +_is_excluded_from_mirroring (MetaWindow *meta_win) +{ + MetaWindowType type = meta_window_get_window_type (meta_win); + return + type == META_WINDOW_DESKTOP || + type == META_WINDOW_DOCK || + type == META_WINDOW_DND; +} + +static void +_mirror_current_windows (ShellVRMirror *self) +{ + ShellGlobal *global = shell_global_get (); + GList *window = shell_global_get_window_actors (global); + while (window != NULL) + { + MetaWindowActor *actor = window->data; + shell_vr_mirror_map_actor (self, actor); + window = window->next; + } +} + +static void +_apply_desktop_position (MetaWindow *meta_win, + XrdWindow *xrd_win, + uint32_t layer) +{ + ShellGlobal *global = shell_global_get (); + MetaDisplay *display = shell_global_get_display (global); + + int screen_w, screen_h; + meta_display_get_size (display, &screen_w, &screen_h); + + MetaRectangle rect; + meta_window_get_buffer_rect (meta_win, &rect); + + float x = rect.x - screen_w / 2.0f + rect.width / 2.0f; + float y = screen_h - rect.y - screen_h / 4.0f - rect.height / 2.0f; + + graphene_point3d_t point = { + .x = x / SHELL_VR_PIXELS_PER_METER, + .y = y / SHELL_VR_PIXELS_PER_METER + DEFAULT_LEVEL, + .z = -SHELL_VR_DESKTOP_PLANE_DISTANCE + SHELL_VR_LAYER_DISTANCE * layer + }; + + graphene_matrix_t transform; + graphene_matrix_init_translate (&transform, &point); + + g3k_object_set_matrix (G3K_OBJECT (xrd_win), &transform); + + G3kContext *g3k = g3k_object_get_context (G3K_OBJECT (xrd_win)); + G3kObjectManager *manager = g3k_context_get_manager (g3k); + g3k_object_manager_save_reset_transformation (manager, G3K_OBJECT (xrd_win)); +} + +static void +_arrange_windows_by_desktop_position (ShellVRMirror *self) +{ + G3kObjectManager *manager = g3k_context_get_manager (self->g3k); + GSList *objects = g3k_object_manager_get_objects (manager); + + GSList *meta_win_list = NULL; + + for (; objects; objects = objects->next) + { + XrdWindow *xrd_win = XRD_WINDOW (objects->data); + ShellVRWindow *shell_win; + g_object_get (xrd_win, "native", &shell_win, NULL); + if (!shell_win) + continue; + + MetaWindow *meta_win = + meta_window_actor_get_meta_window (shell_win->meta_window_actor); + + if (!_is_excluded_from_mirroring (meta_win)) + meta_win_list = g_slist_append (meta_win_list, meta_win); + } + + ShellGlobal *global = shell_global_get (); + MetaDisplay *display = shell_global_get_display (global); + + GSList *sorted_windows = + meta_display_sort_windows_by_stacking (display, meta_win_list); + + for (self->top_layer = 0; sorted_windows; + sorted_windows = sorted_windows->next) + { + XrdWindow *xrd_window = xrd_shell_lookup_window (self->shell, + META_WINDOW (sorted_windows->data)); + if (!xrd_window) + { + g_printerr ("_arrange_windows_by_desktop_position: Error looking up " + "MetaWindow %p\n", + (void*) sorted_windows->data); + continue; + } + + _apply_desktop_position (sorted_windows->data, + xrd_window, self->top_layer); + self->top_layer++; + } + g_slist_free (sorted_windows); +} + +static void +_connect_signals (ShellVRMirror *self) +{ + g_signal_connect (self->shell, "keyboard-press-event", + (GCallback) _keyboard_press_cb, self); + + g_signal_connect (self->shell, "click-event", + (GCallback) _click_cb, self); + + g_signal_connect (self->shell, "move-cursor-event", + (GCallback) _move_cursor_cb, self); + + GxrContext *gxr = g3k_context_get_gxr (self->g3k); + g_signal_connect (gxr, "state-change-event", + (GCallback) _state_change_cb, self); + + ShellGlobal *global = shell_global_get (); + MetaDisplay *display = shell_global_get_display (global); + + g_signal_connect (display, "grab-op-begin", + (GCallback) _grab_op_begin_cb, self); + + g_signal_connect (display, "grab-op-end", + (GCallback) _grab_op_end_cb, self); + + + self->cursor_tracker = + meta_cursor_tracker_get_for_display (display); + g_signal_connect (self->cursor_tracker, "cursor-changed", + (GCallback) _cursor_changed_cb, self); +} + +static void +_disconnect_signals (ShellVRMirror *self) +{ + g_signal_handlers_disconnect_matched ( + self->shell, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + _keyboard_press_cb, NULL); + g_signal_handlers_disconnect_matched ( + self->shell, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + _click_cb, NULL); + g_signal_handlers_disconnect_matched ( + self->shell, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + _move_cursor_cb, NULL); + g_signal_handlers_disconnect_matched ( + self->shell, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + _state_change_cb, NULL); + + ShellGlobal *global = shell_global_get (); + MetaDisplay *display = shell_global_get_display (global); + + g_signal_handlers_disconnect_matched ( + display, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + _grab_op_begin_cb, NULL); + + g_signal_handlers_disconnect_matched ( + display, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + _grab_op_end_cb, NULL); + + g_signal_handlers_disconnect_matched ( + self->cursor_tracker, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + _cursor_changed_cb, NULL); +} + + +void +shell_vr_mirror_initialize (ShellVRMirror *self) +{ + g_print ("shell: Loading XR with XR_RUNTIME_JSON: %s\n", + g_getenv ("XR_RUNTIME_JSON")); + self->shell = xrd_shell_new (); + if (!self->shell) + { + g_print ("Failed to initialize xrdesktop!\n"); + g_print ("Usually this is caused by a problem with the VR runtime.\n"); + + g_clear_object (&shell_vr_mirror_instance); + + return; + } + + self->g3k = xrd_shell_get_g3k (self->shell); + + g_print ("== Started xrdesktop ==\n"); + + self->cursor_gl_shared_texture = 0; + + if (!_load_gl_functions ()) + g_printerr ("Error: Could not load GL function pointers" + " for external memory upload method.\n"); + + if (!gl_copy_helper_init (&self->gl_copy_helper)) + { + g_printerr ("Failed to init framebuffer renderer\n"); + return; + } + +// self->nvidia = g_strcmp0 (gl_get_vendor (), "NVIDIA Corporation") == 0; + + self->grabbed_windows = NULL; + + _init_input (self); + + _mirror_current_windows (self); + _arrange_windows_by_desktop_position (self); + + + + _connect_signals (self); + + g3k_context_start_renderer (self->g3k); +} + +/* + * Menus, ContextMenus, etc. that should be fixed to a parent window and may + * not be individually moved + */ + +static void +_get_offset (MetaWindow *parent, MetaWindow *child, graphene_point_t *offset) +{ + MetaRectangle parent_rect; + meta_window_get_buffer_rect (parent, &parent_rect); + + MetaRectangle child_rect; + meta_window_get_buffer_rect (child, &child_rect); + + int parent_center_x = parent_rect.x + parent_rect.width / 2; + int parent_center_y = parent_rect.y + parent_rect.height / 2; + + int child_center_x = child_rect.x + child_rect.width / 2; + int child_center_y = child_rect.y + child_rect.height / 2; + + int offset_x = child_center_x - parent_center_x; + int offset_y = child_center_y - parent_center_y; + + offset->x = offset_x; + offset->y = - offset_y; + + g_print ("child at %d,%d to parent at %d,%d, offset %d,%d\n", + child_center_x, child_center_y, + parent_center_x, parent_center_y, + offset_x, offset_y); +} + +static gboolean +_is_child_window (MetaWindow *meta_win) +{ + MetaWindowType t = meta_window_get_window_type (meta_win); + return + t == META_WINDOW_POPUP_MENU || + t == META_WINDOW_DROPDOWN_MENU || + t == META_WINDOW_TOOLTIP || + t == META_WINDOW_MODAL_DIALOG || + t == META_WINDOW_COMBO; +} + +static XrdWindow* +_get_valid_xrd_parent (ShellVRMirror *self, + MetaWindow *meta_parent) +{ + XrdWindow *xrd_parent = NULL; + if (meta_parent != NULL && !_is_excluded_from_mirroring (meta_parent)) + { + xrd_parent = xrd_shell_lookup_window (self->shell, meta_parent); + if (!xrd_parent) + { + g_printerr ("_get_valid_xrd_parent: Could not find XrdWindow for " + "parent MetaWindow %p\n", (void*) meta_parent); + } + } + return xrd_parent; +} + +static bool +_find_valid_parent (ShellVRMirror *self, + MetaWindow *child, + MetaWindow **meta_parent, + XrdWindow **xrd_parent) +{ + /* Try transient first */ + *meta_parent = meta_window_get_transient_for (child); + *xrd_parent = _get_valid_xrd_parent (self, *meta_parent); + if (*xrd_parent != NULL) + return TRUE; + + /* If this doesn't work out try the root ancestor */ + *meta_parent = meta_window_find_root_ancestor (child); + *xrd_parent = _get_valid_xrd_parent (self, *meta_parent); + if (*xrd_parent != NULL) + return TRUE; + + /* Last try, check if anything is focused and make that our parent */ + ShellGlobal *global = shell_global_get (); + MetaDisplay *display = shell_global_get_display (global); + *meta_parent = meta_display_get_focus_window (display); + *xrd_parent = _get_valid_xrd_parent (self, *meta_parent); + if (*xrd_parent != NULL) + return TRUE; + + /* Didn't find anything */ + const gchar *title = meta_window_get_title (child); + g_warning ("Could not find a parent for `%s`", title); + + return FALSE; + +} + +gboolean +shell_vr_mirror_map_actor (ShellVRMirror *self, + MetaWindowActor *actor) +{ + if (!shell_vr_mirror_instance) + return FALSE; + + MetaWindow *meta_win = _get_validated_window (actor); + if (_is_excluded_from_mirroring (meta_win)) + return FALSE; + + MetaRectangle rect; + meta_window_get_buffer_rect (meta_win, &rect); + + gboolean is_child = _is_child_window (meta_win); + XrdWindow *xrd_parent = NULL; + MetaWindow *meta_parent = NULL; + + if (is_child) + { + if (_find_valid_parent (self, meta_win, &meta_parent, &xrd_parent)) + { + // TODO: Port this to new XrdWindow API. + // xrdesktop commit 9354945fa1c94386a72b4ca159d40ebcb09437df + } + } + + const gchar *title = meta_window_get_title (meta_win); + const gchar *description = meta_window_get_description (meta_win); + g_print ("Map window %p: %s (%s)\n", actor, title, description); + + ShellVRWindow *shell_win = g_malloc (sizeof (ShellVRWindow)); + shell_win->meta_window_actor = actor; + shell_win->gl_shared_texture = 0; + + VkExtent2D size_pixels = {rect.width, rect.height}; + graphene_size_t size_meters = g3k_extent_to_size (&size_pixels, + SHELL_VR_PIXELS_PER_METER); + + XrdWindow *xrd_win = xrd_window_new (self->g3k, title, shell_win, size_pixels, + &size_meters); + + gboolean draggable = !(is_child && meta_parent != NULL && xrd_parent != NULL); + + if (is_child && !draggable) + { + graphene_point_t offset; + _get_offset (meta_parent, meta_win, &offset); + + graphene_point3d_t offset_point = { + .x = offset.x, + .y = offset.y, + .z = 0.1 + }; + G3kPose offset_pose = g3k_pose_new (&offset_point, NULL); + + g3k_object_set_local_pose (G3K_OBJECT (xrd_win), &offset_pose); + + xrd_window_add_child (xrd_parent, xrd_win); + } + else if (is_child && xrd_parent == NULL) + g_warning ("Can't add window '%s' as child. No parent candidate!\n", title); + + if (!is_child) + { + _apply_desktop_position (meta_win, xrd_win, self->top_layer); + self->top_layer++; + } + + xrd_shell_add_window (self->shell, xrd_win, G3K_OBJECT (xrd_parent), + draggable, meta_win); + + ShellVREffect *effect = g_object_new (SHELL_TYPE_VR_EFFECT, NULL); + effect->xrd_win = xrd_win; + + clutter_actor_meta_set_name (CLUTTER_ACTOR_META (effect), title); + + clutter_actor_add_effect (CLUTTER_ACTOR(actor), + CLUTTER_EFFECT(effect)); + + return TRUE; +} + +gboolean +shell_vr_mirror_destroy_actor (ShellVRMirror *self, + MetaWindowActor *actor) +{ + if (!shell_vr_mirror_instance) + return FALSE; + + MetaWindow *meta_win = _get_validated_window (actor); + g_print ("WINDOW CLOSED: %s: %s\n", + meta_window_get_wm_class (meta_win), + meta_window_get_title (meta_win)); + + XrdWindow *xrd_win = xrd_shell_lookup_window (self->shell, meta_win); + if (xrd_win) + { + ShellVRWindow *shell_win; + g_object_get (xrd_win, "native", &shell_win, NULL); + if (!shell_win) + return FALSE; + + // TODO: Removing one effect does not work. This could cause problems + // if there are other effects than ours on the window actor. + // The meta of type 'ShellVREffect' with name 'Name' is not attached + // to the actor 'MetaWindowActorX11' + //clutter_actor_remove_effect (CLUTTER_ACTOR(actor), + // CLUTTER_EFFECT(shell_win->effect)); + + clutter_actor_clear_effects (CLUTTER_ACTOR(shell_win->meta_window_actor)); + + if (shell_win->gl_shared_texture != 0) + _glDeleteTextures (1, &shell_win->gl_shared_texture); + + xrd_shell_remove_window (self->shell, xrd_win); + + xrd_window_close (xrd_win); + + g_object_unref (xrd_win); + g_free (shell_win); + } + else + { + g_printerr ("Could not destroy null xrd win.\n"); + return FALSE; + } + + return TRUE; +} + +gboolean +shell_vr_mirror_actor_size_changed (ShellVRMirror *self, + MetaWindowActor *actor) +{ + if (!shell_vr_mirror_instance) + return FALSE; + + MetaWindow *meta_win = _get_validated_window (actor); + + MetaRectangle rect; + meta_window_get_buffer_rect (meta_win, &rect); + g_print ("Window Size Changed: %s: [%d,%d] %dx%d\n", + meta_window_get_title (meta_win), + rect.x, rect.y, rect.width, rect.height); + + if (rect.width < 10 || rect.height < 10) + { + g_debug ("Skipping window '%s', since rect size is below 10x10.", + meta_window_get_title (meta_win)); + return FALSE; + } + + XrdWindow *xrd_win = xrd_shell_lookup_window (self->shell, meta_win); + if (xrd_win == NULL) + { + g_printerr ("actor_size_changed: Could not find XrdWindow for MetaWindow %p\n", + (void*) meta_win); + return FALSE; + } + + return TRUE; +} + +static void +shell_vr_mirror_finalize (GObject *object) +{ + ShellVRMirror *self = SHELL_VR_MIRROR (object); + + g_print ("== Disabling xrdesktop ==\n"); + if (self->vr_input) + g_object_unref (self->vr_input); + if (self->shell) + g_object_unref (self->shell); + + g_slist_free (self->grabbed_windows); + + G_OBJECT_CLASS (shell_vr_mirror_parent_class)->finalize (object); +} + +static void +shell_vr_mirror_class_init (ShellVRMirrorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = shell_vr_mirror_finalize; +} + +static void +_destroy_vr_window (G3kObject *g3k_object, + ShellVRMirror *self) +{ + ShellVRWindow *shell_win; + g_object_get (g3k_object, "native", &shell_win, NULL); + if (shell_win) + shell_vr_mirror_destroy_actor (self, shell_win->meta_window_actor); +} + +void +shell_vr_mirror_destroy_instance (void) +{ + ShellVRMirror *self = shell_vr_mirror_instance; + if (!self) + { + g_printerr ("No ShellVRMirror instance to destroy!\n"); + return; + } + + _disconnect_signals (self); + + self->shutdown = TRUE; + + /* + * We have to clean up windows first because it will only clean up as long + * as there is an active shell_vr_mirror_instance + */ + G3kObjectManager *manager = g3k_context_get_manager (self->g3k); + + GSList *windows = g3k_object_manager_get_objects (manager); + g_slist_foreach (windows, (GFunc) _destroy_vr_window, self); + + /* Let the outside world know that the single instance is gone. */ + shell_vr_mirror_instance = NULL; + + g_object_unref (self); +} + diff --git a/src/shell-vr-mirror.h b/src/shell-vr-mirror.h new file mode 100644 index 000000000..9480e8343 --- /dev/null +++ b/src/shell-vr-mirror.h @@ -0,0 +1,52 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#ifndef __SHELL_VR_MIRROR_H__ +#define __SHELL_VR_MIRROR_H__ + +#include <clutter/clutter.h> +#include "st.h" + +#include "shell-global.h" +#include <xrd.h> + +/* TODO: generate enum header with gschemas */ +typedef enum { + UPLOAD_METHOD_VK_SHARED_GL_MEMORY = 1, + UPLOAD_METHOD_VK_UPLOAD_RAW +} UploadMethod; + +G_BEGIN_DECLS + +#define SHELL_TYPE_VR_MIRROR (shell_vr_mirror_get_type ()) +G_DECLARE_FINAL_TYPE (ShellVRMirror, shell_vr_mirror, + SHELL, VR_MIRROR, GObject) + +#define SHELL_TYPE_VR_EFFECT (shell_vr_effect_get_type ()) +G_DECLARE_FINAL_TYPE (ShellVREffect, shell_vr_effect, + SHELL, VR_EFFECT, ClutterEffect) + +ShellVRMirror *shell_vr_mirror_new (void); +ShellVRMirror *shell_vr_mirror_get_instance (void); +ShellVRMirror *shell_vr_mirror_create_instance (void); +void shell_vr_mirror_destroy_instance (void); + +void shell_vr_mirror_initialize (ShellVRMirror *self); + +gboolean +shell_vr_mirror_map_actor (ShellVRMirror *self, + MetaWindowActor *actor); + +gboolean +shell_vr_mirror_destroy_actor (ShellVRMirror *self, + MetaWindowActor *actor); + +gboolean +shell_vr_mirror_actor_size_changed (ShellVRMirror *self, + MetaWindowActor *actor); + +void +shell_dbus_xr_init (void); + +G_END_DECLS + +#endif /* __SHELL_VR_MIRROR_H__ */ diff --git a/src/shell-wm.c b/src/shell-wm.c index 5663b8647..d7eed62b3 100644 --- a/src/shell-wm.c +++ b/src/shell-wm.c @@ -6,9 +6,11 @@ #include <meta/meta-enum-types.h> #include <meta/keybindings.h> +#include <meta/meta-shaped-texture.h> #include "shell-wm-private.h" #include "shell-global.h" +#include "shell-vr-mirror.h" struct _ShellWM { GObject parent; @@ -273,6 +275,19 @@ void shell_wm_completed_map (ShellWM *wm, MetaWindowActor *actor) { + ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance (); + if (vr_mirror) + shell_vr_mirror_map_actor (vr_mirror, actor); + + MetaWindow *window = meta_window_actor_get_meta_window (actor); + g_print ("shell_wm_completed_map: %s: %s\n", + meta_window_get_wm_class (window), + meta_window_get_title (window)); + + MetaRectangle rect; + meta_window_get_buffer_rect (window, &rect); + g_print ("[%d,%d] %dx%d\n", rect.x, rect.y, rect.width, rect.height); + meta_plugin_map_completed (wm->plugin, actor); } @@ -287,6 +302,10 @@ void shell_wm_completed_destroy (ShellWM *wm, MetaWindowActor *actor) { + g_print ("shell_wm_completed_destroy\n"); + ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance (); + if (vr_mirror) + shell_vr_mirror_destroy_actor (vr_mirror, actor); meta_plugin_destroy_completed (wm->plugin, actor); } @@ -376,6 +395,10 @@ void _shell_wm_size_changed (ShellWM *wm, MetaWindowActor *actor) { + ShellVRMirror *vr_mirror = shell_vr_mirror_get_instance (); + if (vr_mirror) + shell_vr_mirror_actor_size_changed (vr_mirror, actor); + g_signal_emit (wm, shell_wm_signals[SIZE_CHANGED], 0, actor); } diff --git a/tools/gnome-shell-set-vr.py b/tools/gnome-shell-set-vr.py new file mode 100755 index 000000000..bfeffef8f --- /dev/null +++ b/tools/gnome-shell-set-vr.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +available_methods = [ "enable", "disable" ] + +import sys +if len(sys.argv) < 2 or (not sys.argv[1] in available_methods): + print("Usage: {} [enable|disable]".format(sys.argv[0])) + sys.exit() +method = sys.argv[1] + +from gi.repository import Gio, GLib + +bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) +proxy = Gio.DBusProxy.new_sync(bus, + Gio.DBusProxyFlags.DO_NOT_AUTO_START, + None, + 'org.gnome.Shell.XR', + '/org/gnome/Shell/XR', + 'org.gnome.Shell.XR', + None) + +is_enabled = proxy.get_cached_property("enabled") + +# print("Is:", is_enabled) + +prop_proxy = Gio.DBusProxy.new_sync(bus, + Gio.DBusProxyFlags.DO_NOT_AUTO_START, + None, + 'org.gnome.Shell.XR', + '/org/gnome/Shell/XR', + 'org.freedesktop.DBus.Properties', + None) + +to_set = method == "enable" + +# print("Set to:", to_set) + +set_variant = GLib.Variant('(ssv)',("org.gnome.Shell.XR", "enabled", + GLib.Variant.new_boolean(to_set))) + +prop_proxy.call_sync("Set", set_variant, Gio.DBusCallFlags.NONE, -1, None) + +get_variant = GLib.Variant('(ss)',("org.gnome.Shell.XR", "enabled")) + +res = prop_proxy.call_sync("Get", get_variant, Gio.DBusCallFlags.NONE, -1, None) + +# print ("Is:", res)
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor