Add calibrating banner

This commit is contained in:
wheaney 2025-02-17 09:58:12 -08:00
parent f544fa484f
commit ed2b77b42c
3 changed files with 164 additions and 41 deletions

View File

@ -95,6 +95,20 @@ export const DeviceDataStream = GObject.registerClass({
'Latest IMU quaternion snapshots and epoch timestamp for when it was collected',
GObject.ParamFlags.READWRITE
),
'show-banner': GObject.ParamSpec.boolean(
'show-banner',
'Show banner',
'Whether the banner should be displayed',
GObject.ParamFlags.READWRITE,
false
),
'custom-banner-enabled': GObject.ParamSpec.boolean(
'custom-banner-enabled',
'Custom banner enabled',
'Whether the custom banner should be displayed',
GObject.ParamFlags.READWRITE,
false
),
'debug-no-device': GObject.ParamSpec.boolean(
'debug-no-device',
'Debug without device',
@ -162,12 +176,15 @@ export const DeviceDataStream = GObject.registerClass({
const validKeepalive = isValidKeepAlive(toSec(imuDateMs));
let 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 customBannerEnabled = dataViewUint8(dataView, CUSTOM_BANNER_ENABLED) !== 0;
const version = dataViewUint8(dataView, VERSION);
const enabled = dataViewUint8(dataView, ENABLED) !== 0 && version === DATA_LAYOUT_VERSION && validKeepalive;
const sbsEnabled = dataViewUint8(dataView, SBS_ENABLED) !== 0;
// update the widescreen property if the state changes while still enabled, trigger "notify::" events
// trigger "notify::" events for properties we want to check on every cycle
if (enabled && this.widescreen_mode_state !== sbsEnabled) this.widescreen_mode_state = sbsEnabled;
if (this.show_banner !== imuResetState) this.show_banner = imuResetState;
if (this.custom_banner_enabled !== customBannerEnabled) this.custom_banner_enabled = customBannerEnabled;
if (!this.device_data) {
this.device_data = {

View File

@ -48,6 +48,8 @@ export default class BreezyDesktopExtension extends Extension {
this._target_monitor = null;
this._is_effect_running = false;
this._distance_binding = null;
this._show_banner_binding = 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;
@ -268,7 +270,9 @@ export default class BreezyDesktopExtension extends Extension {
toggle_display_distance_start: this.settings.get_double('toggle-display-distance-start'),
toggle_display_distance_end: this.settings.get_double('toggle-display-distance-end'),
framerate_cap: this.settings.get_double('framerate-cap'),
imu_snapshots: Globals.data_stream.imu_snapshots
imu_snapshots: Globals.data_stream.imu_snapshots,
show_banner: Globals.data_stream.show_banner,
custom_banner_enabled: Globals.data_stream.custom_banner_enabled
});
this._overlay.set_child(this._overlay_content);
@ -296,6 +300,9 @@ export default class BreezyDesktopExtension extends Extension {
// this._widescreen_mode_effect_state_connection = this._xr_effect.connect('notify::widescreen-mode-state', this._update_widescreen_mode_from_state.bind(this));
this._overlay_content.renderMonitors();
this._show_banner_binding = Globals.data_stream.bind_property('show-banner', this._overlay_content, 'show-banner', Gio.SettingsBindFlags.DEFAULT);
this._custom_banner_enabled_binding = Globals.data_stream.bind_property('custom-banner-enabled', this._overlay_content, 'custom-banner-enabled', 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);
@ -567,6 +574,14 @@ export default class BreezyDesktopExtension extends Extension {
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;
}
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;

View File

@ -1,7 +1,9 @@
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 Mtk from 'gi://Mtk';
import Shell from 'gi://Shell';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
@ -369,6 +371,13 @@ export const VirtualMonitorEffect = GObject.registerClass({
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',
@ -421,6 +430,7 @@ export const VirtualMonitorEffect = GObject.registerClass({
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() {
@ -487,6 +497,10 @@ export const VirtualMonitorEffect = GObject.registerClass({
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);
@ -502,6 +516,7 @@ export const VirtualMonitorEffect = GObject.registerClass({
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;
@ -569,53 +584,59 @@ export const VirtualMonitorEffect = GObject.registerClass({
const main = `
vec4 world_pos = cogl_position_in;
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;
if (!u_show_banner) {
float aspect_ratio = u_display_resolution.x / u_display_resolution.y;
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;
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;
// 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;
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;
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);
// 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;
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);
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);
// 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;
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);
vec3 look_ahead_vector = applyLookAhead(rotated_vector_t0, velocity_t0, effective_look_ahead_ms);
// 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 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;
vec3 look_ahead_vector = applyLookAhead(rotated_vector_t0, velocity_t0, effective_look_ahead_ms);
world_pos.x *= u_actor_to_display_ratios.y / u_actor_to_display_ratios.x;
vec3 rotated_lens_vector = applyQuaternionToVector(adjusted_lens_vector, quat_t0);
world_pos = vec4(look_ahead_vector - rotated_lens_vector, world_pos.w);
world_pos = u_projection_matrix * world_pos;
world_pos.z /= aspect_ratio / u_actor_to_display_ratios.y;
// 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;
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;
`
@ -642,13 +663,13 @@ export const VirtualMonitorEffect = GObject.registerClass({
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(
@ -716,6 +737,20 @@ export const VirtualMonitorsActor = GObject.registerClass({
'Latest IMU quaternion snapshots and epoch timestamp for when it was collected',
GObject.ParamFlags.READWRITE
),
'show-banner': GObject.ParamSpec.boolean(
'show-banner',
'Show banner',
'Whether the banner should be displayed',
GObject.ParamFlags.READWRITE,
false
),
'custom-banner-enabled': GObject.ParamSpec.boolean(
'custom-banner-enabled',
'Custom banner enabled',
'Whether the custom banner should be displayed',
GObject.ParamFlags.READWRITE,
false
),
'focused-monitor-index': GObject.ParamSpec.int(
'focused-monitor-index',
'Focused Monitor Index',
@ -804,6 +839,34 @@ export const VirtualMonitorsActor = GObject.registerClass({
this.target_monitor,
...this.monitors
]
const bannerTextureClippingRect = new Mtk.Rectangle({
x: 0,
y: 0,
width: 800,
height: 200
});
const calibratingBanner = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/calibrating.png`);
const calibratingImage = new Clutter.Image();
calibratingImage.set_data(calibratingBanner.get_pixels(), Cogl.PixelFormat.RGB_888,
calibratingBanner.width, calibratingBanner.height, calibratingBanner.rowstride);
this.bannerContent = Clutter.TextureContent.new_from_texture(calibratingImage.get_texture(), bannerTextureClippingRect);
const customBanner = GdkPixbuf.Pixbuf.new_from_file(`${Globals.extension_dir}/textures/custom_banner.png`);
const customBannerImage = new Clutter.Image();
customBannerImage.set_data(customBanner.get_pixels(), Cogl.PixelFormat.RGB_888,
customBanner.width, customBanner.height, customBanner.rowstride);
this.customBannerContent = Clutter.TextureContent.new_from_texture(customBannerImage.get_texture(), bannerTextureClippingRect);
this.bannerActor = new Clutter.Actor({
width: calibratingBanner.width,
height: calibratingBanner.height,
reactive: false
});
this.bannerActor.set_position((this.width - this.bannerActor.width) / 2, this.height * 0.75 - this.bannerActor.height / 2);
this.bannerActor.set_content(this.custom_banner_enabled ? this.customBannerContent : this.bannerContent);
this.bannerActor.hide();
}
renderMonitors() {
@ -845,6 +908,7 @@ export const VirtualMonitorsActor = GObject.registerClass({
// Add the monitor actor to the scene
containerActor.add_child(monitorClone);
const effect = new VirtualMonitorEffect({
focused_monitor_index: 0,
imu_snapshots: this.imu_snapshots,
monitor_index: index,
monitor_placements: this.monitor_placements,
@ -852,11 +916,15 @@ export const VirtualMonitorsActor = GObject.registerClass({
display_distance_default: this._display_distance_default(),
actor_to_display_ratios: actorToDisplayRatios,
actor_to_display_offsets: actorToDisplayOffsets,
lens_vector: this.lens_vector
lens_vector: this.lens_vector,
show_banner: this.show_banner
});
containerActor.add_effect_with_name('viewport-effect', effect);
this.add_child(containerActor);
// do this so the primary monitor is always on top at first, before the focused monitor logic comes into play
this.set_child_below_sibling(containerActor, null);
// TODO - track actors and clean this up
this.bind_property('monitor-placements', effect, 'monitor-placements', GObject.BindingFlags.DEFAULT);
this.bind_property('imu-snapshots', effect, 'imu-snapshots', GObject.BindingFlags.DEFAULT);
@ -865,6 +933,7 @@ export const VirtualMonitorsActor = GObject.registerClass({
this.bind_property('lens-vector', effect, 'lens-vector', GObject.BindingFlags.DEFAULT);
this.bind_property('look-ahead-override', effect, 'look-ahead-override', GObject.BindingFlags.DEFAULT);
this.bind_property('disable-anti-aliasing', effect, 'disable-anti-aliasing', GObject.BindingFlags.DEFAULT);
this.bind_property('show-banner', effect, 'show-banner', GObject.BindingFlags.DEFAULT);
const updateEffectDistanceDefault = (() => {
effect.display_distance_default = this._display_distance_default();
@ -874,19 +943,30 @@ export const VirtualMonitorsActor = GObject.registerClass({
// in addition to rendering distance properly in the shader, the parent actor determines overlap based on child ordering
effect.connect('notify::is-closest', ((actor, _pspec) => {
if (actor.is_closest) this.set_child_above_sibling(containerActor, null);
if (actor.is_closest) {
this.set_child_above_sibling(containerActor, null);
if (this.show_banner) this.set_child_above_sibling(this.bannerActor, null);
}
}).bind(this));
}).bind(this));
this.add_child(this.bannerActor);
if (this.show_banner) {
this.set_child_above_sibling(this.bannerActor, null);
this.bannerActor.show();
}
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, (() => {
if (this.imu_snapshots) {
if (this.show_banner) {
this.focused_monitor_index = 0;
} else if (this.imu_snapshots) {
const closestMonitorIndex = findClosestVector(
this.imu_snapshots.imu_data.splice(0, 4),
this._monitorsAsNormalizedVectors, this.closestMonitorIndex
);
if (closestMonitorIndex !== -1 && (this.focused_monitor_index === undefined || this.focused_monitor_index !== closestMonitorIndex)) {
Globals.logger.log(`Switching to monitor ${closestMonitorIndex}`);
Globals.logger.log_debug(`Switching to monitor ${closestMonitorIndex}`);
this.focused_monitor_index = closestMonitorIndex;
}
}
@ -915,6 +995,8 @@ export const VirtualMonitorsActor = GObject.registerClass({
this.connect('notify::monitor-spacing', this._update_monitor_placements.bind(this));
this.connect('notify::viewport-offset-x', this._update_monitor_placements.bind(this));
this.connect('notify::viewport-offset-y', this._update_monitor_placements.bind(this));
this.connect('notify::show-banner', this._handle_banner_update.bind(this));
this.connect('notify::custom-banner-enabled', this._handle_banner_update.bind(this));
this._handle_display_distance_properties_change();
}
@ -985,6 +1067,15 @@ export const VirtualMonitorsActor = GObject.registerClass({
this._update_monitor_placements();
}
_handle_banner_update() {
if (this.show_banner) {
this.bannerActor.set_content(this.custom_banner_enabled ? this.customBannerContent : this.bannerContent);
this.bannerActor.show();
} else {
this.bannerActor.hide();
}
}
_change_distance() {
this.display_distance = this._is_display_distance_at_end ?
this.toggle_display_distance_start : this.toggle_display_distance_end;