diff --git a/gnome/src/extension.js b/gnome/src/extension.js index 1383d5c..d306de1 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -1,4 +1,3 @@ -import Clutter from 'gi://Clutter' import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Meta from 'gi://Meta'; @@ -10,7 +9,7 @@ import { DeviceDataStream } from './devicedatastream.js'; import Globals from './globals.js'; import { Logger } from './logger.js'; import { MonitorManager } from './monitormanager.js'; -import { VirtualMonitorsActor } from './virtualmonitorsactor.js'; +import { VirtualDisplaysActor } from './virtualdisplaysactor.js'; import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; @@ -43,36 +42,16 @@ export default class BreezyDesktopExtension extends Extension { // Set/destroyed by enable/disable this._cursor_manager = null; this._monitor_manager = null; - this._overlay_content = null; - this._overlay = null; + this._virtual_displays_actor = null; + this._virtual_displays_overlay = null; this._target_monitor = null; this._is_effect_running = false; - this._distance_binding = null; - this._show_banner_binding = null; + this._effect_settings_bindings = []; + this._data_stream_bindings = []; this._show_banner_connection = null; - this._custom_banner_enabled_binding = null; - this._monitor_wrapping_scheme_binding = null; - this._viewport_offset_x_binding = null; - this._viewport_offset_y_binding = null; - this._monitor_spacing_binding = null; this._distance_connection = null; this._follow_threshold_connection = null; - this._widescreen_mode_settings_connection = null; - this._widescreen_mode_effect_state_connection = null; this._breezy_desktop_running_connection = null; - this._debug_no_device_binding = null; - this._start_binding = null; - this._end_binding = null; - this._curved_display_binding = null; - this._display_size_binding = null; - this._look_ahead_override_binding = null; - this._disable_anti_aliasing_binding = null; - this._framerate_cap_binding = null; - this._optimal_monitor_config_binding = null; - this._headset_as_primary_binding = null; - this._actor_added_connection = null; - this._actor_removed_connection = null; - this._data_stream_connection = null; if (!Globals.logger) { Globals.logger = new Logger({ @@ -94,7 +73,6 @@ export default class BreezyDesktopExtension extends Extension { try { Globals.extension_dir = this.path; - this.settings.bind('debug', Globals.logger, 'debug', Gio.SettingsBindFlags.DEFAULT); Globals.data_stream.start(); @@ -107,12 +85,11 @@ export default class BreezyDesktopExtension extends Extension { this._monitor_manager.setChangeHook(this._handle_monitor_change.bind(this)); this._monitor_manager.enable(); - this._optimal_monitor_config_binding = this.settings.bind('use-optimal-monitor-config', - this._monitor_manager, 'use-optimal-monitor-config', Gio.SettingsBindFlags.DEFAULT); - this._headset_as_primary_binding = this.settings.bind('headset-as-primary', - this._monitor_manager, 'headset-as-primary', Gio.SettingsBindFlags.DEFAULT); - this._debug_no_device_binding = this.settings.bind('debug-no-device', - Globals.data_stream, 'debug-no-device', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('debug', Globals.logger, 'debug', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('use-optimal-monitor-config',this._monitor_manager, 'use-optimal-monitor-config', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('headset-as-primary', this._monitor_manager, 'headset-as-primary', Gio.SettingsBindFlags.DEFAULT); + this.settings.bind('debug-no-device', Globals.data_stream, 'debug-no-device', Gio.SettingsBindFlags.DEFAULT); + this._breezy_desktop_running_connection = Globals.data_stream.connect('notify::breezy-desktop-running', this._handle_breezy_desktop_running_change.bind(this)); @@ -251,22 +228,22 @@ export default class BreezyDesktopExtension extends Extension { const refreshRate = targetMonitor.refreshRate ?? 60; // use rgba(255, 4, 144, 1) for chroma key background - this._overlay = new St.Bin({ style: 'background-color: rgba(0, 0, 0, 1);', clip_to_allocation: true }); - this._overlay.opacity = 255; - this._overlay.set_position(targetMonitor.x, targetMonitor.y); - this._overlay.set_size(targetMonitor.width, targetMonitor.height); + this._virtual_displays_overlay = new St.Bin({ style: 'background-color: rgba(0, 0, 0, 1);', clip_to_allocation: true }); + this._virtual_displays_overlay.opacity = 255; + this._virtual_displays_overlay.set_position(targetMonitor.x, targetMonitor.y); + this._virtual_displays_overlay.set_size(targetMonitor.width, targetMonitor.height); // const textureSourceActor = Main.layoutManager.uiGroup; Globals.data_stream.refresh_data(); - this._overlay_content = new VirtualMonitorsActor({ + this._virtual_displays_actor = new VirtualDisplaysActor({ width: targetMonitor.width, height: targetMonitor.height, + target_monitor: targetMonitor, virtual_monitors: virtualMonitors, monitor_wrapping_scheme: this.settings.get_string('monitor-wrapping-scheme'), monitor_spacing: this.settings.get_int('monitor-spacing'), viewport_offset_x: this.settings.get_double('viewport-offset-x'), viewport_offset_y: this.settings.get_double('viewport-offset-y'), - target_monitor: targetMonitor, display_distance: this.settings.get_double('display-distance'), toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'), toggle_display_distance_end: this.settings.get_double('toggle-display-distance-end'), @@ -276,13 +253,13 @@ export default class BreezyDesktopExtension extends Extension { custom_banner_enabled: Globals.data_stream.custom_banner_enabled }); - this._overlay.set_child(this._overlay_content); - this._overlay_content.renderMonitors(); + this._virtual_displays_overlay.set_child(this._virtual_displays_actor); + this._virtual_displays_actor.renderMonitors(); - Shell.util_set_hidden_from_pick(this._overlay, true); - global.stage.add_child(this._overlay); + Shell.util_set_hidden_from_pick(this._virtual_displays_overlay, true); + global.stage.add_child(this._virtual_displays_overlay); - const cursor_manager_monitor_objs = this._overlay_content.monitor_actors.map(monitor => { + const cursor_manager_monitor_objs = this._virtual_displays_actor.monitor_actors.map(monitor => { return { monitor: monitor.monitorDetails, actor: monitor.containerActor @@ -294,41 +271,42 @@ export default class BreezyDesktopExtension extends Extension { this._update_follow_threshold(this.settings); - // this gets triggered before _effect_enable if in fast-sbs-mode-switching mode - // if (!this.settings.get_boolean('fast-sbs-mode-switching')) - // this._update_widescreen_mode_from_settings(this.settings); - - // this._widescreen_mode_effect_state_connection = this._xr_effect.connect('notify::widescreen-mode-state', this._update_widescreen_mode_from_state.bind(this)); - - this._show_banner_binding = Globals.data_stream.bind_property('show-banner', this._overlay_content, 'show-banner', Gio.SettingsBindFlags.DEFAULT); + this._data_stream_bindings = [ + 'show-banner', + 'custom-banner-enabled' + ].map(data_stream_key => + Globals.data_stream.bind_property(data_stream_key, this._virtual_displays_actor, data_stream_key, Gio.SettingsBindFlags.DEFAULT) + ); + this._show_banner_connection = Globals.data_stream.connect('notify::show-banner', this._handle_show_banner_update.bind(this)); this._was_show_banner = Globals.data_stream.show_banner; if (!this._was_show_banner) this._recenter_display(); - this._custom_banner_enabled_binding = Globals.data_stream.bind_property('custom-banner-enabled', this._overlay_content, 'custom-banner-enabled', Gio.SettingsBindFlags.DEFAULT); + this._effect_settings_bindings = [ + 'monitor-wrapping-scheme', + 'viewport-offset-x', + 'viewport-offset-y', + 'monitor-spacing', + 'display-distance', + 'toggle-display-distance-start', + 'toggle-display-distance-end', + 'display-size', + 'framerate-cap', + 'look-ahead-override', + 'disable-anti-aliasing' + ] + this._effect_settings_bindings.forEach(settings_key => + this.settings.bind(settings_key, this._virtual_displays_actor, settings_key, Gio.SettingsBindFlags.DEFAULT) + ); - this._monitor_wrapping_scheme_binding = this.settings.bind('monitor-wrapping-scheme', this._overlay_content, 'monitor-wrapping-scheme', Gio.SettingsBindFlags.DEFAULT); - this._viewport_offset_x_binding = this.settings.bind('viewport-offset-x', this._overlay_content, 'viewport-offset-x', Gio.SettingsBindFlags.DEFAULT); - this._viewport_offset_y_binding = this.settings.bind('viewport-offset-y', this._overlay_content, 'viewport-offset-y', Gio.SettingsBindFlags.DEFAULT); - this._monitor_spacing_binding = this.settings.bind('monitor-spacing', this._overlay_content, 'monitor-spacing', Gio.SettingsBindFlags.DEFAULT); - this._distance_binding = this.settings.bind('display-distance', this._overlay_content, 'display-distance', Gio.SettingsBindFlags.DEFAULT); this._distance_connection = this.settings.connect('changed::display-distance', this._update_display_distance.bind(this)); this._follow_threshold_connection = this.settings.connect('changed::follow-threshold', this._update_follow_threshold.bind(this)); - - // this._widescreen_mode_settings_connection = this.settings.connect('changed::widescreen-mode', this._update_widescreen_mode_from_settings.bind(this)) - this._start_binding = this.settings.bind('toggle-display-distance-start', this._overlay_content, 'toggle-display-distance-start', Gio.SettingsBindFlags.DEFAULT) - this._end_binding = this.settings.bind('toggle-display-distance-end', this._overlay_content, 'toggle-display-distance-end', Gio.SettingsBindFlags.DEFAULT); - this._display_size_binding = this.settings.bind('display-size', this._overlay_content, 'display-size', Gio.SettingsBindFlags.DEFAULT); - this._framerate_cap_binding = this.settings.bind('framerate-cap', this._overlay_content, 'framerate-cap', Gio.SettingsBindFlags.DEFAULT); - // this._curved_display_binding = this.settings.bind('curved-display', this._xr_effect, 'curved-display', Gio.SettingsBindFlags.DEFAULT) - this._look_ahead_override_binding = this.settings.bind('look-ahead-override', this._overlay_content, 'look-ahead-override', Gio.SettingsBindFlags.DEFAULT); - this._disable_anti_aliasing_binding = this.settings.bind('disable-anti-aliasing', this._overlay_content, 'disable-anti-aliasing', Gio.SettingsBindFlags.DEFAULT); Meta.disable_unredirect_for_display(global.display); this._add_settings_keybinding('toggle-xr-effect-shortcut', this._toggle_xr_effect.bind(this)); this._add_settings_keybinding('recenter-display-shortcut', this._recenter_display.bind(this)); - this._add_settings_keybinding('toggle-display-distance-shortcut', this._overlay_content._change_distance.bind(this._overlay_content)); + this._add_settings_keybinding('toggle-display-distance-shortcut', this._virtual_displays_actor._change_distance.bind(this._virtual_displays_actor)); this._add_settings_keybinding('toggle-follow-shortcut', this._toggle_follow_mode.bind(this)); } catch (e) { Globals.logger.log(`[ERROR] BreezyDesktopExtension _effect_enable ${e.message}\n${e.stack}`); @@ -557,100 +535,35 @@ export default class BreezyDesktopExtension extends Extension { Main.wm.removeKeybinding('toggle-follow-shortcut'); Meta.enable_unredirect_for_display(global.display); - if (this._actor_added_connection) { - global.stage.disconnect(this._actor_added_connection); - this._actor_added_connection = null; - } - if (this._actor_removed_connection) { - global.stage.disconnect(this._actor_removed_connection); - this._actor_removed_connection = null; - } - if (this._distance_binding) { - this.settings.unbind(this._distance_binding); - this._distance_binding = null; - } - if (this._monitor_spacing_binding) { - this.settings.unbind(this._monitor_spacing_binding); - this._monitor_spacing_binding = null; - } - if (this._viewport_offset_x_binding) { - this.settings.unbind(this._viewport_offset_x_binding); - this._viewport_offset_x_binding = null; - } - if (this._viewport_offset_y_binding) { - this.settings.unbind(this._viewport_offset_y_binding); - this._viewport_offset_y_binding = null; - } - if (this._monitor_wrapping_scheme_binding) { - this.settings.unbind(this._monitor_wrapping_scheme_binding); - this._monitor_wrapping_scheme_binding = null; - } - if (this._show_banner_binding) { - this._show_banner_binding.unbind(); - this._show_banner_binding = null; + for (let settings_key of this._effect_settings_bindings) { + Gio.Settings.unbind(this.settings, settings_key); } + this._effect_settings_bindings = []; + this._data_stream_bindings.forEach(binding => binding.unbind()); + this._data_stream_bindings = []; + if (this._show_banner_connection) { Globals.data_stream.disconnect(this._show_banner_connection); this._show_banner_connection = null; } - if (this._custom_banner_enabled_binding) { - this._custom_banner_enabled_binding.unbind(); - this._custom_banner_enabled_binding = null; - } if (this._distance_connection) { this.settings.disconnect(this._distance_connection); this._distance_connection = null; } - if (this._data_stream_connection) { - this._data_stream_connection.unbind(); - this._data_stream_connection = null; - } if (this._follow_threshold_connection) { this.settings.disconnect(this._follow_threshold_connection); this._follow_threshold_connection = null; } - if (this._widescreen_mode_settings_connection) { - this.settings.disconnect(this._widescreen_mode_settings_connection); - this._widescreen_mode_settings_connection = null; - } - if (this._start_binding) { - this.settings.unbind(this._start_binding); - this._start_binding = null; - } - if (this._end_binding) { - this.settings.unbind(this._end_binding); - this._end_binding = null; - } - if (this._curved_display_binding) { - this.settings.unbind(this._curved_display_binding); - this._curved_display_binding = null; - } - if (this._display_size_binding) { - this.settings.unbind(this._display_size_binding); - this._display_size_binding = null; - } - if (this._look_ahead_override_binding) { - this.settings.unbind(this._look_ahead_override_binding); - this._look_ahead_override_binding = null; - } - if (this._disable_anti_aliasing_binding) { - this.settings.unbind(this._disable_anti_aliasing_binding); - this._disable_anti_aliasing_binding = null; - } - if (this._framerate_cap_binding) { - this.settings.unbind(this._framerate_cap_binding); - this._framerate_cap_binding = null; - } - if (this._overlay) { - if (this._overlay_content) { - this._overlay.remove_child(this._overlay_content); - this._overlay_content.destroy(); - this._overlay_content = null; + if (this._virtual_displays_overlay) { + if (this._virtual_displays_actor) { + this._virtual_displays_overlay.remove_child(this._virtual_displays_actor); + this._virtual_displays_actor.destroy(); + this._virtual_displays_actor = null; } - global.stage.remove_child(this._overlay); - this._overlay.destroy(); - this._overlay = null; + global.stage.remove_child(this._virtual_displays_overlay); + this._virtual_displays_overlay.destroy(); + this._virtual_displays_overlay = null; } if (this._cursor_manager) { this._cursor_manager.disable(); @@ -680,21 +593,12 @@ export default class BreezyDesktopExtension extends Extension { Globals.data_stream.disconnect(this._breezy_desktop_running_connection); this._breezy_desktop_running_connection = null; } - if (this._debug_no_device_binding) { - this.settings.unbind(this._debug_no_device_binding); - this._debug_no_device_binding = null; - } + Gio.Settings.unbind(this.settings, 'debug'); + Gio.Settings.unbind(this.settings, 'use-optimal-monitor-config'); + Gio.Settings.unbind(this.settings, 'headset-as-primary'); + Gio.Settings.unbind(this.settings, 'debug-no-device'); if (this._monitor_manager) { - if (this._optimal_monitor_config_binding) { - this.settings.unbind(this._optimal_monitor_config_binding); - this._optimal_monitor_config_binding = null - } - if (this._headset_as_primary_binding) { - this.settings.unbind(this._headset_as_primary_binding); - this._headset_as_primary_binding = null; - } - this._monitor_manager.disable(); this._monitor_manager = null; } diff --git a/gnome/src/systembackground.js b/gnome/src/systembackground.js deleted file mode 100644 index cf7a3bb..0000000 --- a/gnome/src/systembackground.js +++ /dev/null @@ -1,32 +0,0 @@ -import Clutter from 'gi://Clutter'; -import Cogl from 'gi://Cogl'; -import GLib from 'gi://GLib'; -import GObject from 'gi://GObject'; -import Meta from 'gi://Meta'; - -const DEFAULT_BACKGROUND_COLOR = Clutter.Color?.from_pixel(0x2e3436ff) || new Cogl.Color({red: 40, green: 40, blue: 40, alpha: 255}); - -let _systemBackground; - -export const SystemBackground = GObject.registerClass({ - Signals: {'loaded': {}}, -}, class SystemBackground extends Meta.BackgroundActor { - _init() { - if (_systemBackground == null) { - _systemBackground = new Meta.Background({meta_display: global.display}); - _systemBackground.set_color(DEFAULT_BACKGROUND_COLOR); - } - - super._init({ - meta_display: global.display, - monitor: 0, - }); - this.content.background = _systemBackground; - - let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { - this.emit('loaded'); - return GLib.SOURCE_REMOVE; - }); - GLib.Source.set_name_by_id(id, '[gnome-shell] SystemBackground.loaded'); - } -}); diff --git a/gnome/src/virtualdisplayeffect.js b/gnome/src/virtualdisplayeffect.js new file mode 100644 index 0000000..898012d --- /dev/null +++ b/gnome/src/virtualdisplayeffect.js @@ -0,0 +1,423 @@ +import Clutter from 'gi://Clutter' +import Cogl from 'gi://Cogl'; +import GObject from 'gi://GObject'; +import Shell from 'gi://Shell'; + +import Globals from './globals.js'; + +// how far to look ahead is how old the IMU data is plus a constant that is either the default for this device or an override +function lookAheadMS(imuDateMs, lookAheadCfg, override) { + // how stale the imu data is + const dataAge = Date.now() - imuDateMs; + + return (override === -1 ? lookAheadCfg[0] : override) + dataAge; +} + +export const VirtualDisplayEffect = GObject.registerClass({ + Properties: { + 'monitor-index': GObject.ParamSpec.int( + 'monitor-index', + 'Monitor Index', + 'Index of the monitor that this effect is applied to', + GObject.ParamFlags.READWRITE, + 0, 100, 0 + ), + 'monitor-placements': GObject.ParamSpec.jsobject( + 'monitor-placements', + 'Monitor Placements', + 'Target and virtual monitor placement details, as relevant to rendering', + GObject.ParamFlags.READWRITE + ), + 'target-monitor': GObject.ParamSpec.jsobject( + 'target-monitor', + 'Target Monitor', + 'Details about the monitor being used as a viewport', + GObject.ParamFlags.READWRITE + ), + 'imu-snapshots': GObject.ParamSpec.jsobject( + 'imu-snapshots', + 'IMU Snapshots', + 'Latest IMU quaternion snapshots and epoch timestamp for when it was collected', + GObject.ParamFlags.READWRITE + ), + 'width': GObject.ParamSpec.int( + 'width', + 'Width', + 'Width of the viewport', + GObject.ParamFlags.READWRITE, + 1, 10000, 1920 + ), + 'height': GObject.ParamSpec.int( + 'height', + 'Height', + 'Height of the viewport', + GObject.ParamFlags.READWRITE, + 1, 10000, 1080 + ), + 'focused-monitor-index': GObject.ParamSpec.int( + 'focused-monitor-index', + 'Focused Monitor Index', + 'Index of the monitor that is currently focused', + GObject.ParamFlags.READWRITE, + -1, 100, -1 + ), + 'display-zoom-on-focus': GObject.ParamSpec.boolean( + 'display-zoom-on-focus', + 'Display zoom on focus', + 'Automatically move a display closer when it becomes focused.', + GObject.ParamFlags.READWRITE, + true + ), + 'display-distance': GObject.ParamSpec.double( + 'display-distance', + 'Display Distance', + 'Distance of the display from the camera', + GObject.ParamFlags.READWRITE, + 0.0, + 2.5, + 1.0 + ), + 'display-position': GObject.ParamSpec.jsobject( + 'display-position', + 'Display Position', + 'Position of the display in COGL (ESU) coordinates', + GObject.ParamFlags.READWRITE + ), + 'display-distance-default': GObject.ParamSpec.double( + 'display-distance-default', + 'Display distance default', + 'Distance to use when not explicitly set, or when reset', + GObject.ParamFlags.READWRITE, + 0.2, + 2.5, + 1.0 + ), + 'show-banner': GObject.ParamSpec.boolean( + 'show-banner', + 'Show banner', + 'Whether the banner should be displayed', + GObject.ParamFlags.READWRITE, + false + ), + 'lens-vector': GObject.ParamSpec.jsobject( + 'lens-vector', + 'Lens Vector', + 'Vector representing the offset of the lens from the pivot point', + GObject.ParamFlags.READWRITE + ), + 'actor-to-display-ratios': GObject.ParamSpec.jsobject( + 'actor-to-display-ratios', + 'Actor to Display Ratios', + 'Ratios to convert actor coordinates to display coordinates', + GObject.ParamFlags.READWRITE + ), + 'actor-to-display-offsets': GObject.ParamSpec.jsobject( + 'actor-to-display-offsets', + 'Actor to Display Offsets', + 'Offsets to convert actor coordinates to display coordinates', + GObject.ParamFlags.READWRITE + ), + 'is-closest': GObject.ParamSpec.boolean( + 'is-closest', + 'Is Closest', + 'Whether this monitor is the closest to the camera', + GObject.ParamFlags.READWRITE, + false + ), + 'disable-anti-aliasing': GObject.ParamSpec.boolean( + 'disable-anti-aliasing', + 'Disable anti-aliasing', + 'Disable anti-aliasing for the effect', + GObject.ParamFlags.READWRITE, + false + ), + 'look-ahead-override': GObject.ParamSpec.int( + 'look-ahead-override', + 'Look ahead override', + 'Override the look ahead value', + GObject.ParamFlags.READWRITE, + -1, + 45, + -1 + ), + } +}, class VirtualDisplayEffect extends Shell.GLSLEffect { + constructor(params = {}) { + super(params); + + this._current_display_distance = this._is_focused() ? this.display_distance : this.display_distance_default; + this.no_distance_ease = false; + + this.connect('notify::display-distance', this._update_display_distance.bind(this)); + this.connect('notify::focused-monitor-index', this._update_display_distance.bind(this)); + this.connect('notify::monitor-placements', this._update_display_position_uniforms.bind(this)); + this.connect('notify::monitor-wrapping-scheme', this._update_display_position_uniforms.bind(this)); + this.connect('notify::show-banner', this._handle_banner_update.bind(this)); + } + + _is_focused() { + return this.focused_monitor_index === this.monitor_index; + } + + _update_display_distance() { + const desired_distance = this._is_focused() ? this.display_distance : this.display_distance_default; + if (this._distance_ease_timeline?.is_playing()) { + // we're already easing towards the desired distance, do nothing + if (this._distance_ease_target === desired_distance) return; + + this._distance_ease_timeline.stop(); + } + + if (this.no_distance_ease) { + this._current_display_distance = desired_distance; + this._update_display_position_uniforms(); + this.no_distance_ease = false; + return; + } + + // if we're the focused display, we'll double the timeline and wait for the first half to let other + // displays ease out first + this._distance_ease_focus = this._is_focused(); + const ease_out_timeline_ms = 150; + const pause_ms = 50; + const ease_in_timeline_ms = 500; // includes ease out and pause + const ease_in_begin_pct = (ease_out_timeline_ms + pause_ms) / ease_in_timeline_ms; + const timeline_ms = this._distance_ease_focus ? + ease_in_timeline_ms : + ease_out_timeline_ms; + + this._distance_ease_start = this._current_display_distance; + this._distance_ease_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), timeline_ms); + + this._distance_ease_target = desired_distance; + this._distance_ease_timeline.connect('new-frame', (() => { + let progress = this._distance_ease_timeline.get_progress(); + if (this._distance_ease_focus) { + // if we're the focused display, wait for the first half of the timeline to pass + if (progress < ease_in_begin_pct) return; + + // treat the second half of the timeline as its own full progression + progress = (progress - ease_in_begin_pct) / (1 - ease_in_begin_pct); + + // put this display in front as it starts to easy in + this.is_closest = true; + } else { + this.is_closest = false; + } + + this._current_display_distance = this._distance_ease_start + + (1 - Math.cos(progress * Math.PI)) / 2 * (this._distance_ease_target - this._distance_ease_start); + this._update_display_position_uniforms(); + }).bind(this)); + + this._distance_ease_timeline.start(); + } + + _update_display_position_uniforms() { + // this is in NWU coordinates + const monitorPlacement = this.monitor_placements[this.monitor_index]; + // Globals.logger.log_debug(`\t\t\tMonitor ${this.monitor_index} vectors: ${JSON.stringify(monitorPlacement)}`); + + // use the center vector with the distance applied to determine how much to move each coordinate, so they all move uniformly + const inverseAppliedDistance = 1.0 - this._current_display_distance / this.display_distance_default; + const distanceDelta = monitorPlacement.centerNoRotate.map(coord => coord * inverseAppliedDistance); + const noRotationVector = monitorPlacement.topLeftNoRotate.map((coord, index) => coord - distanceDelta[index]); + + // convert to CoGL's east-down-south coordinates and apply display distance + this.set_uniform_float(this.get_uniform_location("u_display_position"), 3, + [-noRotationVector[1], -noRotationVector[2], -noRotationVector[0]]); + + const rotation_radians = this.monitor_placements[this.monitor_index].rotationAngleRadians; + this.set_uniform_float(this.get_uniform_location("u_rotation_x_radians"), 1, [rotation_radians.x]); + this.set_uniform_float(this.get_uniform_location("u_rotation_y_radians"), 1, [rotation_radians.y]); + } + + _handle_banner_update() { + this.set_uniform_float(this.get_uniform_location("u_show_banner"), 1, [this.show_banner ? 1.0 : 0.0]); + } + + perspective(fovVerticalRadians, aspect, near, far) { + const fovHorizontalRadians = fovVerticalRadians * aspect; + const f = 1.0 / Math.tan(fovHorizontalRadians / 2.0); + const range = far - near; + + return [ + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, - (far + near) / range, -1, + 0, 0, - (2.0 * near * far) / range, 0 + ]; + } + + vfunc_build_pipeline() { + const declarations = ` + uniform bool u_show_banner; + uniform mat4 u_imu_data; + uniform float u_look_ahead_ms; + uniform vec4 u_look_ahead_cfg; + uniform mat4 u_projection_matrix; + uniform float u_fov_vertical_radians; + uniform vec3 u_display_position; + uniform float u_rotation_x_radians; + uniform float u_rotation_y_radians; + uniform vec2 u_display_resolution; + uniform vec3 u_lens_vector; + + // vector positions are relative to the width and height of the entire stage + uniform vec2 u_actor_to_display_ratios; + uniform vec2 u_actor_to_display_offsets; + + // discovered through trial and error, no idea the significance + float cogl_position_mystery_factor = 29.09 * 2; + + float look_ahead_ms_cap = 45.0; + + vec4 quatConjugate(vec4 q) { + return vec4(-q.xyz, q.w); + } + + vec3 applyQuaternionToVector(vec3 v, vec4 q) { + vec3 t = 2.0 * cross(q.xyz, v); + return v + q.w * t + cross(q.xyz, t); + } + + vec4 applyXRotationToVector(vec4 v, float angle) { + float c = cos(angle); + float s = sin(angle); + return vec4(v.x, v.y * c - v.z * s, v.y * s + v.z * c, v.w); + } + + vec4 applyYRotationToVector(vec4 v, float angle) { + float c = cos(angle); + float s = sin(angle); + return vec4(v.x * c + v.z * s, v.y, v.z * c - v.x * s, v.w); + } + + vec4 nwuToESU(vec4 v) { + return vec4(-v.y, v.z, -v.x, v.w); + } + + // returns the rate of change between the two vectors, in same time units as delta_time + // e.g. if delta_time is in ms, then the rate of change is "per ms" + vec3 rateOfChange(vec3 v1, vec3 v2, float delta_time) { + return (v1-v2) / delta_time; + } + + // attempt to figure out where the current position should be based on previous position and velocity. + // velocity and time values should use the same time units (secs, ms, etc...) + vec3 applyLookAhead(vec3 position, vec3 velocity, float look_ahead_ms) { + return position + velocity * look_ahead_ms; + } + + // project the vector onto a flat surface, return it's vertical position relative to the vertical fov, where 0.0 is + // the top and 1.0 is the bottom. vectors that project outside the vertical range of the display will have values + // outside this range, but capped + float vectorToScanline(float fovVerticalRadians, vec3 v) { + return clamp(1.0 - (-v.y / (tan(fovVerticalRadians / 2.0) * v.z) + 1.0) / 2.0, -1.5, 2.5); + } + `; + + const main = ` + vec4 world_pos = cogl_position_in; + + if (!u_show_banner) { + float aspect_ratio = u_display_resolution.x / u_display_resolution.y; + + float cogl_position_width = cogl_position_mystery_factor * aspect_ratio / u_actor_to_display_ratios.y; + float cogl_position_height = cogl_position_width / aspect_ratio; + + vec3 pos_factors = vec3( + cogl_position_width / u_display_resolution.x, + cogl_position_height / u_display_resolution.y, + cogl_position_mystery_factor / u_display_resolution.x + ); + world_pos.x -= u_display_position.x * pos_factors.x; + world_pos.y -= u_display_position.y * pos_factors.y; + world_pos.z = u_display_position.z * pos_factors.z; + + // if the perspective includes more than just our viewport actor, move vertices towards the center of the perspective so they'll be properly rotated + world_pos.x += u_actor_to_display_offsets.x * cogl_position_width / 2; + world_pos.y -= u_actor_to_display_offsets.y * cogl_position_height / 2; + + world_pos.z *= aspect_ratio / u_actor_to_display_ratios.y; + world_pos = applyXRotationToVector(world_pos, u_rotation_x_radians); + world_pos = applyYRotationToVector(world_pos, u_rotation_y_radians); + + vec4 quat_t0 = nwuToESU(quatConjugate(u_imu_data[0])); + vec3 adjusted_lens_vector = u_lens_vector * pos_factors; + vec3 complete_vector = world_pos.xyz + adjusted_lens_vector; + vec3 rotated_vector_t0 = applyQuaternionToVector(complete_vector, quat_t0); + vec3 rotated_vector_t1 = applyQuaternionToVector(complete_vector, nwuToESU(quatConjugate(u_imu_data[1]))); + float delta_time_t0 = u_imu_data[3][0] - u_imu_data[3][1]; + vec3 velocity_t0 = rateOfChange(rotated_vector_t0, rotated_vector_t1, delta_time_t0); + + // compute the capped look ahead with scanline adjustments + float look_ahead_scanline_ms = vectorToScanline(u_fov_vertical_radians, rotated_vector_t0) * u_look_ahead_cfg[2]; + float effective_look_ahead_ms = min(min(u_look_ahead_ms, look_ahead_ms_cap), u_look_ahead_cfg[3]) + look_ahead_scanline_ms; + + vec3 look_ahead_vector = applyLookAhead(rotated_vector_t0, velocity_t0, effective_look_ahead_ms); + + vec3 rotated_lens_vector = applyQuaternionToVector(adjusted_lens_vector, quat_t0); + world_pos = vec4(look_ahead_vector - rotated_lens_vector, world_pos.w); + + world_pos.z /= aspect_ratio / u_actor_to_display_ratios.y; + + world_pos.x *= u_actor_to_display_ratios.y / u_actor_to_display_ratios.x; + + world_pos = u_projection_matrix * world_pos; + + // if the perspective includes more than just our viewport actor, move the vertices back to just the area we can see. + // this needs to be done after the projection matrix multiplication so it will be projected as if centered in our vision + world_pos.x -= (u_actor_to_display_offsets.x / u_actor_to_display_ratios.x) * world_pos.w; + world_pos.y += (u_actor_to_display_offsets.y / u_actor_to_display_ratios.y) * world_pos.w; + } else { + world_pos = cogl_modelview_matrix * world_pos; + world_pos = cogl_projection_matrix * world_pos; + } + + cogl_position_out = world_pos; + cogl_tex_coord_out[0] = cogl_tex_coord_in; + ` + + this.add_glsl_snippet(Shell.SnippetHook.VERTEX, declarations, main, false); + } + + vfunc_paint_target(node, paintContext) { + if (!this._initialized) { + const aspect = this.get_actor().width / this.get_actor().height; + const fovDiagonalRadians = Globals.data_stream.device_data.displayFov * Math.PI / 180.0; + const diagToVertRatio = Math.sqrt(aspect * aspect + 1); + const fovVerticalRadians = fovDiagonalRadians / diagToVertRatio; + const projection_matrix = this.perspective( + fovVerticalRadians, + aspect, + 0.0001, + 1000.0 + ); + this.set_uniform_matrix(this.get_uniform_location("u_projection_matrix"), false, 4, projection_matrix); + this.set_uniform_float(this.get_uniform_location("u_fov_vertical_radians"), 1, [fovVerticalRadians]); + this.set_uniform_float(this.get_uniform_location("u_display_resolution"), 2, [this.target_monitor.width, this.target_monitor.height]); + this.set_uniform_float(this.get_uniform_location("u_look_ahead_cfg"), 4, Globals.data_stream.device_data.lookAheadCfg); + this.set_uniform_float(this.get_uniform_location("u_actor_to_display_ratios"), 2, this.actor_to_display_ratios); + this.set_uniform_float(this.get_uniform_location("u_actor_to_display_offsets"), 2, this.actor_to_display_offsets); + this.set_uniform_float(this.get_uniform_location("u_lens_vector"), 3, this.lens_vector); + this._update_display_position_uniforms(); + this._handle_banner_update(); + this._initialized = true; + } + + this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [lookAheadMS(this.imu_snapshots.timestamp_ms, Globals.data_stream.device_data.lookAheadCfg, this.look_ahead_override)]); + this.set_uniform_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.imu_data); + + if (!this.disable_anti_aliasing) { + // improves sampling quality for smooth text and edges + this.get_pipeline().set_layer_filters( + 0, + Cogl.PipelineFilter.LINEAR_MIPMAP_LINEAR, + Cogl.PipelineFilter.LINEAR + ); + } + + super.vfunc_paint_target(node, paintContext); + } +}); \ No newline at end of file diff --git a/gnome/src/virtualmonitorsactor.js b/gnome/src/virtualdisplaysactor.js similarity index 64% rename from gnome/src/virtualmonitorsactor.js rename to gnome/src/virtualdisplaysactor.js index ccd1ba7..66da820 100644 --- a/gnome/src/virtualmonitorsactor.js +++ b/gnome/src/virtualdisplaysactor.js @@ -6,6 +6,9 @@ import GObject from 'gi://GObject'; import Mtk from 'gi://Mtk'; import Shell from 'gi://Shell'; import St from 'gi://St'; + +import { VirtualDisplayEffect } from './virtualdisplayeffect.js'; + import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import Globals from './globals.js'; @@ -323,443 +326,7 @@ function monitorsToPlacements(fovDetails, monitorDetailsList, monitorWrappingSch return monitorPlacements; } -function monitorVectorToRotationAngle(vector, monitorWrappingScheme) { - if (monitorWrappingScheme === 'horizontal') { - // monitors wrap around us horizontally - return { - angle: Math.atan2(vector[1], vector[0]), - axis: Clutter.RotateAxis.Y_AXIS - }; - } else if (monitorWrappingScheme === 'vertical') { - // monitors wrap around us vertically - return { - angle: Math.atan2(vector[2], vector[0]), - axis: Clutter.RotateAxis.X_AXIS - } - } else { - // no rotation - return undefined; - } -} - -// how far to look ahead is how old the IMU data is plus a constant that is either the default for this device or an override -function lookAheadMS(imuDateMs, lookAheadCfg, override) { - // how stale the imu data is - const dataAge = Date.now() - imuDateMs; - - return (override === -1 ? lookAheadCfg[0] : override) + dataAge; -} - -export const VirtualMonitorEffect = GObject.registerClass({ - Properties: { - 'monitor-index': GObject.ParamSpec.int( - 'monitor-index', - 'Monitor Index', - 'Index of the monitor that this effect is applied to', - GObject.ParamFlags.READWRITE, - 0, 100, 0 - ), - 'monitor-placements': GObject.ParamSpec.jsobject( - 'monitor-placements', - 'Monitor Placements', - 'Target and virtual monitor placement details, as relevant to rendering', - GObject.ParamFlags.READWRITE - ), - 'target-monitor': GObject.ParamSpec.jsobject( - 'target-monitor', - 'Target Monitor', - 'Details about the monitor being used as a viewport', - GObject.ParamFlags.READWRITE - ), - 'imu-snapshots': GObject.ParamSpec.jsobject( - 'imu-snapshots', - 'IMU Snapshots', - 'Latest IMU quaternion snapshots and epoch timestamp for when it was collected', - GObject.ParamFlags.READWRITE - ), - 'width': GObject.ParamSpec.int( - 'width', - 'Width', - 'Width of the viewport', - GObject.ParamFlags.READWRITE, - 1, 10000, 1920 - ), - 'height': GObject.ParamSpec.int( - 'height', - 'Height', - 'Height of the viewport', - GObject.ParamFlags.READWRITE, - 1, 10000, 1080 - ), - 'focused-monitor-index': GObject.ParamSpec.int( - 'focused-monitor-index', - 'Focused Monitor Index', - 'Index of the monitor that is currently focused', - GObject.ParamFlags.READWRITE, - -1, 100, -1 - ), - 'display-zoom-on-focus': GObject.ParamSpec.boolean( - 'display-zoom-on-focus', - 'Display zoom on focus', - 'Automatically move a display closer when it becomes focused.', - GObject.ParamFlags.READWRITE, - true - ), - 'display-distance': GObject.ParamSpec.double( - 'display-distance', - 'Display Distance', - 'Distance of the display from the camera', - GObject.ParamFlags.READWRITE, - 0.0, - 2.5, - 1.0 - ), - 'display-position': GObject.ParamSpec.jsobject( - 'display-position', - 'Display Position', - 'Position of the display in COGL (ESU) coordinates', - GObject.ParamFlags.READWRITE - ), - 'display-distance-default': GObject.ParamSpec.double( - 'display-distance-default', - 'Display distance default', - 'Distance to use when not explicitly set, or when reset', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.0 - ), - 'show-banner': GObject.ParamSpec.boolean( - 'show-banner', - 'Show banner', - 'Whether the banner should be displayed', - GObject.ParamFlags.READWRITE, - false - ), - 'lens-vector': GObject.ParamSpec.jsobject( - 'lens-vector', - 'Lens Vector', - 'Vector representing the offset of the lens from the pivot point', - GObject.ParamFlags.READWRITE - ), - 'actor-to-display-ratios': GObject.ParamSpec.jsobject( - 'actor-to-display-ratios', - 'Actor to Display Ratios', - 'Ratios to convert actor coordinates to display coordinates', - GObject.ParamFlags.READWRITE - ), - 'actor-to-display-offsets': GObject.ParamSpec.jsobject( - 'actor-to-display-offsets', - 'Actor to Display Offsets', - 'Offsets to convert actor coordinates to display coordinates', - GObject.ParamFlags.READWRITE - ), - 'is-closest': GObject.ParamSpec.boolean( - 'is-closest', - 'Is Closest', - 'Whether this monitor is the closest to the camera', - GObject.ParamFlags.READWRITE, - false - ), - 'disable-anti-aliasing': GObject.ParamSpec.boolean( - 'disable-anti-aliasing', - 'Disable anti-aliasing', - 'Disable anti-aliasing for the effect', - GObject.ParamFlags.READWRITE, - false - ), - 'look-ahead-override': GObject.ParamSpec.int( - 'look-ahead-override', - 'Look ahead override', - 'Override the look ahead value', - GObject.ParamFlags.READWRITE, - -1, - 45, - -1 - ), - } -}, class VirtualMonitorEffect extends Shell.GLSLEffect { - constructor(params = {}) { - super(params); - - this._current_display_distance = this._is_focused() ? this.display_distance : this.display_distance_default; - this.no_distance_ease = false; - - this.connect('notify::display-distance', this._update_display_distance.bind(this)); - this.connect('notify::focused-monitor-index', this._update_display_distance.bind(this)); - this.connect('notify::monitor-placements', this._update_display_position_uniforms.bind(this)); - this.connect('notify::monitor-wrapping-scheme', this._update_display_position_uniforms.bind(this)); - this.connect('notify::show-banner', this._handle_banner_update.bind(this)); - } - - _is_focused() { - return this.focused_monitor_index === this.monitor_index; - } - - _update_display_distance() { - const desired_distance = this._is_focused() ? this.display_distance : this.display_distance_default; - if (this._distance_ease_timeline?.is_playing()) { - // we're already easing towards the desired distance, do nothing - if (this._distance_ease_target === desired_distance) return; - - this._distance_ease_timeline.stop(); - } - - if (this.no_distance_ease) { - this._current_display_distance = desired_distance; - this._update_display_position_uniforms(); - this.no_distance_ease = false; - return; - } - - // if we're the focused display, we'll double the timeline and wait for the first half to let other - // displays ease out first - this._distance_ease_focus = this._is_focused(); - const ease_out_timeline_ms = 150; - const pause_ms = 50; - const ease_in_timeline_ms = 500; // includes ease out and pause - const ease_in_begin_pct = (ease_out_timeline_ms + pause_ms) / ease_in_timeline_ms; - const timeline_ms = this._distance_ease_focus ? - ease_in_timeline_ms : - ease_out_timeline_ms; - - this._distance_ease_start = this._current_display_distance; - this._distance_ease_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), timeline_ms); - - this._distance_ease_target = desired_distance; - this._distance_ease_timeline.connect('new-frame', (() => { - let progress = this._distance_ease_timeline.get_progress(); - if (this._distance_ease_focus) { - // if we're the focused display, wait for the first half of the timeline to pass - if (progress < ease_in_begin_pct) return; - - // treat the second half of the timeline as its own full progression - progress = (progress - ease_in_begin_pct) / (1 - ease_in_begin_pct); - - // put this display in front as it starts to easy in - this.is_closest = true; - } else { - this.is_closest = false; - } - - this._current_display_distance = this._distance_ease_start + - (1 - Math.cos(progress * Math.PI)) / 2 * (this._distance_ease_target - this._distance_ease_start); - this._update_display_position_uniforms(); - }).bind(this)); - - this._distance_ease_timeline.start(); - } - - _update_display_position_uniforms() { - // this is in NWU coordinates - const monitorPlacement = this.monitor_placements[this.monitor_index]; - // Globals.logger.log_debug(`\t\t\tMonitor ${this.monitor_index} vectors: ${JSON.stringify(monitorPlacement)}`); - - // use the center vector with the distance applied to determine how much to move each coordinate, so they all move uniformly - const inverseAppliedDistance = 1.0 - this._current_display_distance / this.display_distance_default; - const distanceDelta = monitorPlacement.centerNoRotate.map(coord => coord * inverseAppliedDistance); - const noRotationVector = monitorPlacement.topLeftNoRotate.map((coord, index) => coord - distanceDelta[index]); - - // convert to CoGL's east-down-south coordinates and apply display distance - this.set_uniform_float(this.get_uniform_location("u_display_position"), 3, - [-noRotationVector[1], -noRotationVector[2], -noRotationVector[0]]); - - const rotation_radians = this.monitor_placements[this.monitor_index].rotationAngleRadians; - this.set_uniform_float(this.get_uniform_location("u_rotation_x_radians"), 1, [rotation_radians.x]); - this.set_uniform_float(this.get_uniform_location("u_rotation_y_radians"), 1, [rotation_radians.y]); - } - - _handle_banner_update() { - this.set_uniform_float(this.get_uniform_location("u_show_banner"), 1, [this.show_banner ? 1.0 : 0.0]); - } - - perspective(fovVerticalRadians, aspect, near, far) { - const fovHorizontalRadians = fovVerticalRadians * aspect; - const f = 1.0 / Math.tan(fovHorizontalRadians / 2.0); - const range = far - near; - - return [ - f / aspect, 0, 0, 0, - 0, f, 0, 0, - 0, 0, - (far + near) / range, -1, - 0, 0, - (2.0 * near * far) / range, 0 - ]; - } - - vfunc_build_pipeline() { - const declarations = ` - uniform bool u_show_banner; - uniform mat4 u_imu_data; - uniform float u_look_ahead_ms; - uniform vec4 u_look_ahead_cfg; - uniform mat4 u_projection_matrix; - uniform float u_fov_vertical_radians; - uniform vec3 u_display_position; - uniform float u_rotation_x_radians; - uniform float u_rotation_y_radians; - uniform vec2 u_display_resolution; - uniform vec3 u_lens_vector; - - // vector positions are relative to the width and height of the entire stage - uniform vec2 u_actor_to_display_ratios; - uniform vec2 u_actor_to_display_offsets; - - // discovered through trial and error, no idea the significance - float cogl_position_mystery_factor = 29.09 * 2; - - float look_ahead_ms_cap = 45.0; - - vec4 quatConjugate(vec4 q) { - return vec4(-q.xyz, q.w); - } - - vec3 applyQuaternionToVector(vec3 v, vec4 q) { - vec3 t = 2.0 * cross(q.xyz, v); - return v + q.w * t + cross(q.xyz, t); - } - - vec4 applyXRotationToVector(vec4 v, float angle) { - float c = cos(angle); - float s = sin(angle); - return vec4(v.x, v.y * c - v.z * s, v.y * s + v.z * c, v.w); - } - - vec4 applyYRotationToVector(vec4 v, float angle) { - float c = cos(angle); - float s = sin(angle); - return vec4(v.x * c + v.z * s, v.y, v.z * c - v.x * s, v.w); - } - - vec4 nwuToESU(vec4 v) { - return vec4(-v.y, v.z, -v.x, v.w); - } - - // returns the rate of change between the two vectors, in same time units as delta_time - // e.g. if delta_time is in ms, then the rate of change is "per ms" - vec3 rateOfChange(vec3 v1, vec3 v2, float delta_time) { - return (v1-v2) / delta_time; - } - - // attempt to figure out where the current position should be based on previous position and velocity. - // velocity and time values should use the same time units (secs, ms, etc...) - vec3 applyLookAhead(vec3 position, vec3 velocity, float look_ahead_ms) { - return position + velocity * look_ahead_ms; - } - - // project the vector onto a flat surface, return it's vertical position relative to the vertical fov, where 0.0 is - // the top and 1.0 is the bottom. vectors that project outside the vertical range of the display will have values - // outside this range, but capped - float vectorToScanline(float fovVerticalRadians, vec3 v) { - return clamp(1.0 - (-v.y / (tan(fovVerticalRadians / 2.0) * v.z) + 1.0) / 2.0, -1.5, 2.5); - } - `; - - const main = ` - vec4 world_pos = cogl_position_in; - - if (!u_show_banner) { - float aspect_ratio = u_display_resolution.x / u_display_resolution.y; - - float cogl_position_width = cogl_position_mystery_factor * aspect_ratio / u_actor_to_display_ratios.y; - float cogl_position_height = cogl_position_width / aspect_ratio; - - vec3 pos_factors = vec3( - cogl_position_width / u_display_resolution.x, - cogl_position_height / u_display_resolution.y, - cogl_position_mystery_factor / u_display_resolution.x - ); - world_pos.x -= u_display_position.x * pos_factors.x; - world_pos.y -= u_display_position.y * pos_factors.y; - world_pos.z = u_display_position.z * pos_factors.z; - - // if the perspective includes more than just our viewport actor, move vertices towards the center of the perspective so they'll be properly rotated - world_pos.x += u_actor_to_display_offsets.x * cogl_position_width / 2; - world_pos.y -= u_actor_to_display_offsets.y * cogl_position_height / 2; - - world_pos.z *= aspect_ratio / u_actor_to_display_ratios.y; - world_pos = applyXRotationToVector(world_pos, u_rotation_x_radians); - world_pos = applyYRotationToVector(world_pos, u_rotation_y_radians); - - vec4 quat_t0 = nwuToESU(quatConjugate(u_imu_data[0])); - vec3 adjusted_lens_vector = u_lens_vector * pos_factors; - vec3 complete_vector = world_pos.xyz + adjusted_lens_vector; - vec3 rotated_vector_t0 = applyQuaternionToVector(complete_vector, quat_t0); - vec3 rotated_vector_t1 = applyQuaternionToVector(complete_vector, nwuToESU(quatConjugate(u_imu_data[1]))); - float delta_time_t0 = u_imu_data[3][0] - u_imu_data[3][1]; - vec3 velocity_t0 = rateOfChange(rotated_vector_t0, rotated_vector_t1, delta_time_t0); - - // compute the capped look ahead with scanline adjustments - float look_ahead_scanline_ms = vectorToScanline(u_fov_vertical_radians, rotated_vector_t0) * u_look_ahead_cfg[2]; - float effective_look_ahead_ms = min(min(u_look_ahead_ms, look_ahead_ms_cap), u_look_ahead_cfg[3]) + look_ahead_scanline_ms; - - vec3 look_ahead_vector = applyLookAhead(rotated_vector_t0, velocity_t0, effective_look_ahead_ms); - - vec3 rotated_lens_vector = applyQuaternionToVector(adjusted_lens_vector, quat_t0); - world_pos = vec4(look_ahead_vector - rotated_lens_vector, world_pos.w); - - world_pos.z /= aspect_ratio / u_actor_to_display_ratios.y; - - world_pos.x *= u_actor_to_display_ratios.y / u_actor_to_display_ratios.x; - - world_pos = u_projection_matrix * world_pos; - - // if the perspective includes more than just our viewport actor, move the vertices back to just the area we can see. - // this needs to be done after the projection matrix multiplication so it will be projected as if centered in our vision - world_pos.x -= (u_actor_to_display_offsets.x / u_actor_to_display_ratios.x) * world_pos.w; - world_pos.y += (u_actor_to_display_offsets.y / u_actor_to_display_ratios.y) * world_pos.w; - } else { - world_pos = cogl_modelview_matrix * world_pos; - world_pos = cogl_projection_matrix * world_pos; - } - - cogl_position_out = world_pos; - cogl_tex_coord_out[0] = cogl_tex_coord_in; - ` - - this.add_glsl_snippet(Shell.SnippetHook.VERTEX, declarations, main, false); - } - - vfunc_paint_target(node, paintContext) { - if (!this._initialized) { - const aspect = this.get_actor().width / this.get_actor().height; - const fovDiagonalRadians = Globals.data_stream.device_data.displayFov * Math.PI / 180.0; - const diagToVertRatio = Math.sqrt(aspect * aspect + 1); - const fovVerticalRadians = fovDiagonalRadians / diagToVertRatio; - const projection_matrix = this.perspective( - fovVerticalRadians, - aspect, - 0.0001, - 1000.0 - ); - this.set_uniform_matrix(this.get_uniform_location("u_projection_matrix"), false, 4, projection_matrix); - this.set_uniform_float(this.get_uniform_location("u_fov_vertical_radians"), 1, [fovVerticalRadians]); - this.set_uniform_float(this.get_uniform_location("u_display_resolution"), 2, [this.target_monitor.width, this.target_monitor.height]); - this.set_uniform_float(this.get_uniform_location("u_look_ahead_cfg"), 4, Globals.data_stream.device_data.lookAheadCfg); - this.set_uniform_float(this.get_uniform_location("u_actor_to_display_ratios"), 2, this.actor_to_display_ratios); - this.set_uniform_float(this.get_uniform_location("u_actor_to_display_offsets"), 2, this.actor_to_display_offsets); - this.set_uniform_float(this.get_uniform_location("u_lens_vector"), 3, this.lens_vector); - this._update_display_position_uniforms(); - this._handle_banner_update(); - this._initialized = true; - } - - this.set_uniform_float(this.get_uniform_location('u_look_ahead_ms'), 1, [lookAheadMS(this.imu_snapshots.timestamp_ms, Globals.data_stream.device_data.lookAheadCfg, this.look_ahead_override)]); - this.set_uniform_matrix(this.get_uniform_location("u_imu_data"), false, 4, this.imu_snapshots.imu_data); - - if (!this.disable_anti_aliasing) { - // improves sampling quality for smooth text and edges - this.get_pipeline().set_layer_filters( - 0, - Cogl.PipelineFilter.LINEAR_MIPMAP_LINEAR, - Cogl.PipelineFilter.LINEAR - ); - } - - super.vfunc_paint_target(node, paintContext); - } -}); - -export const VirtualMonitorsActor = GObject.registerClass({ +export const VirtualDisplaysActor = GObject.registerClass({ Properties: { 'target-monitor': GObject.ParamSpec.jsobject( 'target-monitor', @@ -913,7 +480,7 @@ export const VirtualMonitorsActor = GObject.registerClass({ false ) } -}, class VirtualMonitorsActor extends Clutter.Actor { +}, class VirtualDisplaysActor extends Clutter.Actor { constructor(params = {}) { super(params); @@ -1017,7 +584,7 @@ export const VirtualMonitorsActor = GObject.registerClass({ // Add the monitor actor to the scene containerActor.add_child(monitorClone); - const effect = new VirtualMonitorEffect({ + const effect = new VirtualDisplayEffect({ focused_monitor_index: this.focused_monitor_index, imu_snapshots: this.imu_snapshots, monitor_index: index, diff --git a/gnome/src/xrEffect.js b/gnome/src/xrEffect.js deleted file mode 100644 index 18cc5fa..0000000 --- a/gnome/src/xrEffect.js +++ /dev/null @@ -1,473 +0,0 @@ -import Clutter from 'gi://Clutter'; -import Cogl from 'gi://Cogl'; -import GdkPixbuf from 'gi://GdkPixbuf'; -import GLib from 'gi://GLib'; -import GObject from 'gi://GObject'; -import Shell from 'gi://Shell'; - -import Globals from './globals.js'; - -import { - dataViewEnd, - dataViewUint8, - dataViewBigUint, - dataViewUint32Array, - dataViewUint8Array, - dataViewFloat, - dataViewFloatArray, - BOOL_SIZE, - DATA_VIEW_INFO_COUNT_INDEX, - DATA_VIEW_INFO_OFFSET_INDEX, - FLOAT_SIZE, - UINT_SIZE, - UINT8_SIZE -} from "./ipc.js"; -import { degreeToRadian } from "./math.js"; -import { getShaderSource } from "./shader.js"; -import { isValidKeepAlive, toSec } from "./time.js"; - -export const IPC_FILE_PATH = "/dev/shm/breezy_desktop_imu"; - -// the driver should be using the same data layout version -const DATA_LAYOUT_VERSION = 3; - -// DataView info: [offset, size, count] -const VERSION = [0, UINT8_SIZE, 1]; -const ENABLED = [dataViewEnd(VERSION), BOOL_SIZE, 1]; -const LOOK_AHEAD_CFG = [dataViewEnd(ENABLED), FLOAT_SIZE, 4]; -const DISPLAY_RES = [dataViewEnd(LOOK_AHEAD_CFG), UINT_SIZE, 2]; -const DISPLAY_FOV = [dataViewEnd(DISPLAY_RES), FLOAT_SIZE, 1]; -const LENS_DISTANCE_RATIO = [dataViewEnd(DISPLAY_FOV), FLOAT_SIZE, 1]; -const SBS_ENABLED = [dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1]; -const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1]; -const EPOCH_MS = [dataViewEnd(CUSTOM_BANNER_ENABLED), UINT_SIZE, 2]; -const IMU_QUAT_DATA = [dataViewEnd(EPOCH_MS), FLOAT_SIZE, 16]; -const IMU_PARITY_BYTE = [dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1]; -const DATA_VIEW_LENGTH = dataViewEnd(IMU_PARITY_BYTE); - -// cached after first retrieval -const shaderUniformLocations = { - 'virtual_display_enabled': null, - 'show_banner': null, - 'imu_quat_data': null, - 'look_ahead_cfg': null, - 'look_ahead_ms': null, - 'trim_percent': null, - 'display_size': null, - 'display_north_offset': null, - 'lens_vector': null, - 'lens_vector_r': null, // only used if sbs_enabled is true - 'texcoord_x_limits': null, // index 0: min; index 1: max - 'texcoord_x_limits_r': null, // only used if sbs_enabled is true - 'sbs_enabled': null, - 'custom_banner_enabled': null, - 'half_fov_z_rads': null, - 'half_fov_y_rads': null, - 'fov_half_widths': null, - 'fov_widths': null, - 'display_resolution': null, - 'source_to_display_ratio': null, - 'texcoord_visible_area': null, - 'curved_display': null, - - // only used by the reshade integration, but needs to be set to a default value by this effect - 'frametime': null, - 'sideview_enabled': null, - 'sideview_position': null, - 'sideview_display_size': null -}; - -function setUniformFloat(effect, locationName, dataViewInfo, value) { - effect.set_uniform_float(shaderUniformLocations[locationName], dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX], value); -} - -function transferUniformFloat(effect, locationName, dataView, dataViewInfo) { - setUniformFloat(effect, locationName, dataViewInfo, dataViewFloatArray(dataView, dataViewInfo)); -} - -function setSingleFloat(effect, locationName, value) { - effect.set_uniform_float(shaderUniformLocations[locationName], 1, [value]); -} - -function setUniformMatrix(effect, locationName, components, dataView, dataViewInfo) { - const numValues = dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX]; - if (numValues / components !== components) { - throw new Error('Invalid matrix size'); - } - - const floatArray = [].fill(0, 0, numValues); - let offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX]; - for (let i = 0; i < numValues; i++) { - // GLSL uses column-major order, so we need to transpose the matrix - const row = i % components; - const column = Math.floor(i / components); - - floatArray[row * components + column] = dataView.getFloat32(offset, true); - offset += FLOAT_SIZE; - } - effect.set_uniform_matrix(shaderUniformLocations[locationName], true, components, floatArray); -} - -function lookAheadMS(dataView) { - const lookAheadCfg = dataViewFloatArray(dataView, LOOK_AHEAD_CFG); - const imuDateMS = dataViewBigUint(dataView, EPOCH_MS); - - // how stale the imu data is - const dataAge = Date.now() - imuDateMS; - - return lookAheadCfg[0] + dataAge; -} - -// most uniforms don't change frequently, this function should be called periodically -function setIntermittentUniformVariables() { - try { - const dataView = this._dataView; - - if (dataView.byteLength === DATA_VIEW_LENGTH) { - const version = dataViewUint8(dataView, VERSION); - const imuDateMs = dataViewBigUint(dataView, EPOCH_MS); - const validKeepalive = isValidKeepAlive(toSec(imuDateMs)); - const imuData = dataViewFloatArray(dataView, IMU_QUAT_DATA); - const imuResetState = validKeepalive && imuData[0] === 0.0 && imuData[1] === 0.0 && imuData[2] === 0.0 && imuData[3] === 1.0; - const enabled = dataViewUint8(dataView, ENABLED) !== 0 && version === DATA_LAYOUT_VERSION && validKeepalive; - const displayRes = dataViewUint32Array(dataView, DISPLAY_RES); - const sbsEnabled = dataViewUint8(dataView, SBS_ENABLED) !== 0; - - const texture_actor = this.get_actor(); - if (enabled) { - const displayFov = dataViewFloat(dataView, DISPLAY_FOV); - - // TODO - drive these values from settings - const sbsContent = false; - const sbsModeStretched = true; - - // compute these values once, they only change when the XR device changes - const displayAspectRatio = displayRes[0] / displayRes[1]; - const diagToVertRatio = Math.sqrt(Math.pow(displayAspectRatio, 2) + 1); - const halfFovZRads = degreeToRadian(displayFov / diagToVertRatio) / 2; - const halfFovYRads = halfFovZRads * displayAspectRatio; - const fovHalfWidths = [Math.tan(halfFovYRads), Math.tan(halfFovZRads)]; - const fovWidths = [fovHalfWidths[0] * 2, fovHalfWidths[1] * 2]; - const lensDistanceRatio = dataViewFloat(dataView, LENS_DISTANCE_RATIO); - let lensFromCenter = 0.0; - let texcoordXLimits = [0.0, 1.0]; - let texcoordXLimitsRight = [0.0, 1.0]; - if (sbsEnabled) { - lensFromCenter = lensDistanceRatio / 3.0; - if (sbsContent) { - texcoordXLimits[1] = 0.5; - texcoordXLimitsRight[0] = 0.5; - if (!sbsModeStretched) { - texcoordXLimits[0] = 0.25; - texcoordXLimitsRight[1] = 0.75; - } - } else if (!sbsModeStretched) { - texcoordXLimits[0] = 0.25; - texcoordXLimits[1] = 0.75; - texcoordXLimitsRight[0] = 0.25; - texcoordXLimitsRight[1] = 0.75; - } - } - - Globals.logger.log(`texture_actor: ${texture_actor.x}, ${texture_actor.y}, ${texture_actor.width}, ${texture_actor.height}`); - - const texcoordVisibleArea = [ - this.texture_monitor_position.x / texture_actor.width, - this.texture_monitor_position.y / texture_actor.height, - (this.texture_monitor_position.x + this.target_monitor.width) / texture_actor.width, - (this.texture_monitor_position.y + this.target_monitor.height) / texture_actor.height - ] - - const lensVector = [lensDistanceRatio, lensFromCenter, 0.0]; - const lensVectorRight = [lensDistanceRatio, -lensFromCenter, 0.0]; - - // our overlay doesn't quite cover the full screen texture, which allows us to see some of the real desktop - // underneath, so we trim three pixels around the entire edge of the texture - const trimWidthPercent = 3.0 / texture_actor.width; - const trimHeightPercent = 3.0 / texture_actor.height; - - // all these values are transferred directly, unmodified from the driver - transferUniformFloat(this, 'look_ahead_cfg', dataView, LOOK_AHEAD_CFG); - transferUniformFloat(this, 'lens_distance_ratio', dataView, LENS_DISTANCE_RATIO); - - // computed values with no dataViewInfo, so we set these manually - this.set_uniform_float(shaderUniformLocations['trim_percent'], 2, [trimWidthPercent, trimHeightPercent]); - setSingleFloat(this, 'half_fov_z_rads', halfFovZRads); - setSingleFloat(this, 'half_fov_y_rads', halfFovYRads); - this.set_uniform_float(shaderUniformLocations['fov_half_widths'], 2, fovHalfWidths); - this.set_uniform_float(shaderUniformLocations['fov_widths'], 2, fovWidths); - this.set_uniform_float(shaderUniformLocations['texcoord_x_limits'], 2, texcoordXLimits); - this.set_uniform_float(shaderUniformLocations['texcoord_x_limits_r'], 2, texcoordXLimitsRight); - this.set_uniform_float(shaderUniformLocations['texcoord_visible_area'], 4, texcoordVisibleArea); - setSingleFloat(this, 'curved_display', this.curved_display ? 1.0 : 0.0); - this.set_uniform_float(shaderUniformLocations['lens_vector'], 3, lensVector); - this.set_uniform_float(shaderUniformLocations['lens_vector_r'], 3, lensVectorRight); - } - - // update the supported device detected property if the state changes, trigger "notify::" events - if (this.supported_device_detected !== validKeepalive) this.supported_device_detected = validKeepalive; - - // update the widescreen property if the state changes while still enabled, trigger "notify::" events - if (enabled && this.widescreen_mode_state !== sbsEnabled) this.widescreen_mode_state = sbsEnabled; - - // these variables are always in play, even if enabled is false - setSingleFloat(this, 'virtual_display_enabled', enabled ? 1.0 : 0.0); - setSingleFloat(this, 'show_banner', imuResetState ? 1.0 : 0.0); - setSingleFloat(this, 'sbs_enabled', sbsEnabled ? 1.0 : 0.0); - setSingleFloat(this, 'custom_banner_enabled', dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0 ? 1.0 : 0.0); - setSingleFloat(this, 'frametime', 0.0); - - setSingleFloat(this, 'sideview_enabled', 0.0); - setSingleFloat(this, 'sideview_position', 0.0); - setSingleFloat(this, 'sideview_display_size', 1.0); - - this.set_uniform_float(shaderUniformLocations['display_resolution'], 2, displayRes); - Globals.logger.log_debug(`Source resolution ${texture_actor.width}x${texture_actor.height}`); - this.set_uniform_float(shaderUniformLocations['source_to_display_ratio'], 2, [texture_actor.width/displayRes[0], texture_actor.height/displayRes[1]]); - } else if (dataView.byteLength !== 0) { - throw new Error(`Invalid dataView.byteLength: ${dataView.byteLength} !== ${DATA_VIEW_LENGTH}`); - } - } catch (e) { - Globals.logger.log(`[ERROR] xrEffect.js setIntermittentUniformVariables ${e.message}\n${e.stack}`); - } -} - -function checkParityByte(dataView) { - const parityByte = dataViewUint8(dataView, IMU_PARITY_BYTE); - let parity = 0; - const epochUint8 = dataViewUint8Array(dataView, EPOCH_MS); - const imuDataUint8 = dataViewUint8Array(dataView, IMU_QUAT_DATA); - for (let i = 0; i < epochUint8.length; i++) { - parity ^= epochUint8[i]; - } - for (let i = 0; i < imuDataUint8.length; i++) { - parity ^= imuDataUint8[i]; - } - return parityByte === parity; -} - -export const XREffect = GObject.registerClass({ - Properties: { - 'supported-device-detected': GObject.ParamSpec.boolean( - 'supported-device-detected', - 'Supported device detected', - 'Whether a supported device is connected', - GObject.ParamFlags.READWRITE, - false - ), - 'target-monitor': GObject.ParamSpec.jsobject( - 'target-monitor', - 'Target Monitor', - 'Geometry of the target monitor for this effect', - GObject.ParamFlags.READWRITE - ), - 'target-framerate': GObject.ParamSpec.uint( - 'target-framerate', - 'Target Framerate', - 'Target framerate for this effect', - GObject.ParamFlags.READWRITE, 30, 240, 60 - ), - 'texture-monitor-position': GObject.ParamSpec.jsobject( - 'texture-monitor-position', - 'Texture Monitor Position', - 'Coordinates of the monitor relative to the target actor texture', - GObject.ParamFlags.READWRITE - ), - 'display-distance': GObject.ParamSpec.double( - 'display-distance', - 'Display Distance', - 'How far away the display appears', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.05 - ), - 'display-size': GObject.ParamSpec.double( - 'display-size', - 'Display size', - 'Size of the display', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.0 - ), - 'toggle-display-distance-start': GObject.ParamSpec.double( - 'toggle-display-distance-start', - 'Display distance start', - 'Start distance when using the "change distance" shortcut.', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.05 - ), - 'toggle-display-distance-end': GObject.ParamSpec.double( - 'toggle-display-distance-end', - 'Display distance end', - 'End distance when using the "change distance" shortcut.', - GObject.ParamFlags.READWRITE, - 0.2, - 2.5, - 1.05 - ), - 'curved-display': GObject.ParamSpec.boolean( - 'curved-display', - 'Curved Display', - 'Whether the display is curved', - GObject.ParamFlags.READWRITE, - false - ), - 'widescreen-mode-state': GObject.ParamSpec.boolean( - 'widescreen-mode-state', - 'Widescreen mode state', - 'The state of widescreen mode from the perspective of the driver', - GObject.ParamFlags.READWRITE, - false - ), - 'look-ahead-override': GObject.ParamSpec.int( - 'look-ahead-override', - 'Look ahead override', - 'Override the look ahead value', - GObject.ParamFlags.READWRITE, - -1, - 45, - -1 - ), - 'disable-anti-aliasing': GObject.ParamSpec.boolean( - 'disable-anti-aliasing', - 'Disable anti-aliasing', - 'Disable anti-aliasing for the effect', - GObject.ParamFlags.READWRITE, - false - ) - } -}, class XREffect extends Shell.GLSLEffect { - constructor(params = {}) { - super(params); - - this._distance_ease_timeline = null; - this.connect('notify::toggle-display-distance-start', this._handle_display_distance_properties_change.bind(this)); - this.connect('notify::toggle-display-distance-end', this._handle_display_distance_properties_change.bind(this)); - this.connect('notify::display-distance', this._handle_display_distance_properties_change.bind(this)); - this._handle_display_distance_properties_change(); - - const calibrating = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/calibrating.png`); - this.calibratingImage = new Clutter.Image(); - this.calibratingImage.set_data(calibrating.get_pixels(), Cogl.PixelFormat.RGB_888, - calibrating.width, calibrating.height, calibrating.rowstride); - - const customBanner = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/custom_banner.png`); - this.customBannerImage = new Clutter.Image(); - this.customBannerImage.set_data(customBanner.get_pixels(), Cogl.PixelFormat.RGB_888, - customBanner.width, customBanner.height, customBanner.rowstride); - } - - _handle_display_distance_properties_change() { - const distance_from_end = Math.abs(this.display_distance - this.toggle_display_distance_end); - const distance_from_start = Math.abs(this.display_distance - this.toggle_display_distance_start); - this._is_display_distance_at_end = distance_from_end < distance_from_start; - } - - _change_distance() { - if (this._distance_ease_timeline?.is_playing()) this._distance_ease_timeline.stop(); - - this._distance_ease_start = this.display_distance; - this._distance_ease_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), 250); - - const toggle_display_distance_target = this._is_display_distance_at_end ? - this.toggle_display_distance_start : this.toggle_display_distance_end; - this._distance_ease_timeline.connect('new-frame', () => { - this.display_distance = this._distance_ease_start + - this._distance_ease_timeline.get_progress() * - (toggle_display_distance_target - this._distance_ease_start); - }); - - this._distance_ease_timeline.start(); - } - - vfunc_build_pipeline() { - const code = getShaderSource(`${Globals.extension_dir}/Sombrero.frag`); - const main = 'PS_Sombrero(virtual_display_enabled, false, source_to_display_ratio, show_banner, cogl_tex_coord_in[0].xy, cogl_color_out);'; - this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, code, main, false); - } - - vfunc_paint_target(node, paintContext) { - var calibratingImage = this.calibratingImage; - var customBannerImage = this.customBannerImage; - let data = Globals.ipc_file.load_contents(null); - if (data[0]) { - let buffer = new Uint8Array(data[1]).buffer; - this._dataView = new DataView(buffer); - if (!this._initialized) { - this.set_uniform_float(this.get_uniform_location('screenTexture'), 1, [0]); - - this.get_pipeline().set_layer_texture(1, calibratingImage.get_texture()); - this.get_pipeline().set_layer_texture(2, customBannerImage.get_texture()); - this.get_pipeline().set_uniform_1i(this.get_uniform_location('calibratingTexture'), 1); - this.get_pipeline().set_uniform_1i(this.get_uniform_location('customBannerTexture'), 2); - - for (let key in shaderUniformLocations) { - shaderUniformLocations[key] = this.get_uniform_location(key); - } - this.setIntermittentUniformVariables = setIntermittentUniformVariables.bind(this); - this.setIntermittentUniformVariables(); - - this._redraw_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), 1000); - this._redraw_timeline.connect('new-frame', (() => { - this.queue_repaint(); - }).bind(this)); - this._redraw_timeline.set_repeat_count(-1); - this._redraw_timeline.start(); - - this._uniforms_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, (() => { - this.setIntermittentUniformVariables(); - return GLib.SOURCE_CONTINUE; - }).bind(this)); - - this._initialized = true; - } - - let success = false; - let attempts = 0; - while (!success && attempts < 2) { - if (this._dataView.byteLength === DATA_VIEW_LENGTH) { - if (checkParityByte(this._dataView)) { - setSingleFloat(this, 'display_north_offset', this.display_distance); - setSingleFloat(this, 'look_ahead_ms', - this.look_ahead_override === -1 ? lookAheadMS(this._dataView) : this.look_ahead_override); - setUniformMatrix(this, 'imu_quat_data', 4, this._dataView, IMU_QUAT_DATA); - setSingleFloat(this, 'display_size', this.display_size); - success = true; - } - } else if (this._dataView.byteLength !== 0) { - Globals.logger.log(`[ERROR] Invalid dataView.byteLength: ${this._dataView.byteLength} !== ${DATA_VIEW_LENGTH}`) - } - - if (!success && ++attempts < 3) { - data = Globals.ipc_file.load_contents(null); - if (data[0]) { - buffer = new Uint8Array(data[1]).buffer; - this._dataView = new DataView(buffer); - } - } - } - - if (!this.disable_anti_aliasing) { - // improves sampling quality for smooth text and edges - this.get_pipeline().set_layer_filters( - 0, - Cogl.PipelineFilter.LINEAR_MIPMAP_LINEAR, - Cogl.PipelineFilter.LINEAR - ); - } - } - super.vfunc_paint_target(node, paintContext); - } - - cleanup() { - if (this._redraw_timeline) { - this._redraw_timeline.stop(); - this._redraw_timeline = null; - } - if (this._uniforms_timeout_id) GLib.source_remove(this._uniforms_timeout_id); - } -}); \ No newline at end of file