From acfa989819acc1ed5d1b23e2be455bb71cdb23bc Mon Sep 17 00:00:00 2001 From: Luo Yi Date: Thu, 13 Oct 2022 16:30:24 +0800 Subject: [PATCH] refactor: Split event handler from rounded-window-corners.ts to new file src/rounded-window-corners.ts is a little heavy, move all signal connect / disconnect part into a new file. --- .gi.ts.rc.json | 2 + shell.nix | 3 +- src/extension.ts | 95 ++- src/manager/effect_manager.ts | 208 ++++++ src/manager/rounded_corners_manager.ts | 877 +++++++++---------------- src/utils/constants.ts | 13 - src/utils/types.ts | 38 +- src/utils/ui.ts | 17 +- 8 files changed, 598 insertions(+), 655 deletions(-) create mode 100644 src/manager/effect_manager.ts diff --git a/.gi.ts.rc.json b/.gi.ts.rc.json index cc2e223..1aea129 100644 --- a/.gi.ts.rc.json +++ b/.gi.ts.rc.json @@ -56,9 +56,11 @@ ], "xdg_data_dirs": { "ext": [ + "/usr/lib64/mutter-11", "/usr/lib64/mutter-10", "/usr/lib64/mutter-9", "/usr/lib64/mutter-8", + "/usr/lib/x86_64-linux-gnu/mutter-11", "/usr/lib/x86_64-linux-gnu/mutter-10", "/usr/lib/x86_64-linux-gnu/mutter-9", "/usr/lib/x86_64-linux-gnu/mutter-8", diff --git a/shell.nix b/shell.nix index 5fe3c09..91fd14b 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,4 @@ -{ pkgs ? import { } }: +{ pkgs ? import (fetchTarball https://nixos.org/channels/nixos-22.05/nixexprs.tar.xz) { } }: # Use to setup development shell for NixOS. @@ -25,6 +25,7 @@ mkShell { export GIR_EXT_PATH=${ builtins.concatStringsSep ":" [ "${pkgs.gnome.gnome-shell}/share/gnome-shell" + "${pkgs.gnome.mutter}/lib/mutter-11" "${pkgs.gnome.mutter}/lib/mutter-10" "${pkgs.gnome.mutter}/lib/mutter-9" "${pkgs.gnome.mutter}/lib/mutter-8" diff --git a/src/extension.ts b/src/extension.ts index 0430d7d..ba1289a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,14 +8,12 @@ import { MonitorManager } from '@gi/Meta' // gnome-shell modules import { WindowPreview } from '@imports/ui/windowPreview' import { WorkspaceGroup } from '@imports/ui/workspaceAnimation' -import { WindowManager } from '@imports/ui/windowManager' import BackgroundMenu from '@imports/ui/backgroundMenu' import { sessionMode, layoutManager } from '@imports/ui/main' import { overview } from '@imports/ui/main' // local modules import { constants } from '@me/utils/constants' -import { RoundedCornersManager } from '@me/manager/rounded_corners_manager' import { stackMsg, _log } from '@me/utils/log' import * as UI from '@me/utils/ui' import { connections } from '@me/utils/connections' @@ -24,11 +22,12 @@ import { Services } from '@me/dbus/services' import { LinearFilterEffect } from '@me/effect/linear_filter_effect' import { RoundedCornersEffect } from '@me/effect/rounded_corners_effect' import { init_translations } from '@me/utils/i18n' +import { WindowActorTracker } from '@me/manager/effect_manager' // types, which will be removed in output -import { WM } from '@gi/Shell' import { RoundedCornersCfg } from '@me/utils/types' -import { Window, WindowActor } from '@gi/Meta' +import { ExtensionsWindowActor } from '@me/utils/types' +import { Window } from '@gi/Meta' import { global } from '@global' import { registerClass } from '@gi/GObject' @@ -39,30 +38,23 @@ export class Extension { private _orig_add_window !: (_: Window) => void private _orig_create_windows !: () => void private _orig_sync_stacking !: () => void - private _orig_size_changed !: (wm: WM, actor: WindowActor) => void private _add_background_menu !: typeof BackgroundMenu.addBackgroundMenu private _services: Services | null = null - private _rounded_corners_manager: RoundedCornersManager | null = null + private _window_actor_tracker: WindowActorTracker | null = null private _fs_timeout_id = 0 - constructor () { - // Show loaded message in debug mode - _log (constants.LOADED_MSG) - } - enable () { // Restore original methods, those methods will be restore when // extensions is disabled this._orig_add_window = WindowPreview.prototype._addWindow this._orig_create_windows = WorkspaceGroup.prototype._createWindows this._orig_sync_stacking = WorkspaceGroup.prototype._syncStacking - this._orig_size_changed = WindowManager.prototype._sizeChangeWindowDone this._add_background_menu = BackgroundMenu.addBackgroundMenu this._services = new Services () - this._rounded_corners_manager = new RoundedCornersManager () + this._window_actor_tracker = new WindowActorTracker () this._services.export () @@ -73,11 +65,11 @@ export class Extension { if (layoutManager._startingUp) { const id = layoutManager.connect ('startup-complete', () => { - this._enable_effect_managers () + this._window_actor_tracker?.enable () layoutManager.disconnect (id) }) } else { - this._enable_effect_managers () + this._window_actor_tracker?.enable () } // Have to toggle fullscreen for all windows when changed scale factor @@ -88,37 +80,43 @@ export class Extension { // solve this problem. const monitor_manager = MonitorManager.get () - type _Window = Window & { __extensions_rounded_window_fs?: 1 } + type _Window = Window & { __rwc_rounded_window_fs?: 1 } connections.get ().connect (monitor_manager, 'monitors-changed', () => { if (sessionMode.isLocked || sessionMode.isGreeter) { return } - for (const win of this._rounded_corners_manager?.windows () ?? []) { - (win as _Window).__extensions_rounded_window_fs = 1 - win.make_fullscreen () + for (const actor of global.get_window_actors ()) { + if (UI.get_rounded_corners_effect (actor)) { + const win = actor.meta_window + ;(win as _Window).__rwc_rounded_window_fs = 1 + win.make_fullscreen () + } } // waiting 3 seconds then restore marked windows. this._fs_timeout_id = GLib.timeout_add_seconds (0, 3, () => { - for (const _win of this._rounded_corners_manager?.windows () ?? []) { - const win = _win as _Window - if (win && win.__extensions_rounded_window_fs == 1) { + for (const actor of global.get_window_actors ()) { + if (!UI.get_rounded_corners_effect (actor)) { + continue + } + const win = actor.meta_window as _Window + if (win && win.__rwc_rounded_window_fs == 1) { win.unmake_fullscreen () - delete win.__extensions_rounded_window_fs + delete win.__rwc_rounded_window_fs } } return false }) }) - // Restore window that have __extensions_rounded_window_fs props when + // Restore window that have __rwc_rounded_window_fs props when // unlocked for (const win_actor of global.get_window_actors ()) { const win = win_actor.meta_window as _Window - if (win.__extensions_rounded_window_fs === 1) { + if (win.__rwc_rounded_window_fs === 1) { win.unmake_fullscreen () - delete win.__extensions_rounded_window_fs + delete win.__rwc_rounded_window_fs } } @@ -154,7 +152,9 @@ export class Extension { // just return let cfg: RoundedCornersCfg | null = null let has_rounded_corners = false - const shadow = self._rounded_corners_manager?.query_shadow (window) + const window_actor: ExtensionsWindowActor = + window.get_compositor_private () + const shadow = window_actor.__rwc_rounded_window_info?.shadow if (shadow) { cfg = UI.ChoiceRoundedCornersCfg ( settings ().global_rounded_corner_settings, @@ -196,11 +196,9 @@ export class Extension { const name = 'Rounded Corners Effect (Overview)' // Disabled rounded corners of window temporarily when enter overview - const window_actor: WindowActor = window.get_compositor_private () - rounded_effect_of_window_actor = - self._rounded_corners_manager?.get_rounded_corners_effect ( - window_actor - ) + rounded_effect_of_window_actor = UI.get_rounded_corners_effect ( + window_actor + ) as TypeRoundedCornersEffect rounded_effect_of_window_actor?.set_enabled (false) // Add rounded corners effect to preview window actor @@ -268,7 +266,8 @@ export class Extension { win.fullscreen const has_rounded_corners = cfg.keep_rounded_corners || !maximized - const shadow = self._rounded_corners_manager?.query_shadow (win) + const shadow = (actor as ExtensionsWindowActor) + .__rwc_rounded_window_info?.shadow if (shadow && has_rounded_corners) { // Only create shadow actor when window should have rounded // corners when switching workspace @@ -311,17 +310,6 @@ export class Extension { } } - // Window Size Changed - WindowManager.prototype._sizeChangeWindowDone = function (shell_wm, actor) { - self._orig_size_changed.apply (this, [shell_wm, actor]) - // Update shadow actor - if (!self._rounded_corners_manager) { - return - } - self._rounded_corners_manager.on_size_changed (actor) - self._rounded_corners_manager._on_focus_changed (actor.meta_window) - } - if (settings ().enable_preferences_entry) { UI.SetupBackgroundMenu () } @@ -333,6 +321,7 @@ export class Extension { } const c = connections.get () + // Gnome-shell will not disable extensions when _logout/shutdown/restart // system, it means that the signal handlers will not be cleaned when // gnome-shell is closing. @@ -343,11 +332,14 @@ export class Extension { this.disable () }) + // Watch changes of GSettings c.connect (settings ().g_settings, 'changed', (_: Settings, k: string) => { - if ((k as SchemasKeys) === 'enable-preferences-entry') { + switch (k as SchemasKeys) { + case 'enable-preferences-entry': settings ().enable_preferences_entry ? UI.SetupBackgroundMenu () : UI.RestoreBackgroundMenu () + break } }) @@ -359,7 +351,6 @@ export class Extension { WindowPreview.prototype._addWindow = this._orig_add_window WorkspaceGroup.prototype._createWindows = this._orig_create_windows WorkspaceGroup.prototype._syncStacking = this._orig_sync_stacking - WindowManager.prototype._sizeChangeWindowDone = this._orig_size_changed BackgroundMenu.addBackgroundMenu = this._add_background_menu // Remove main loop sources @@ -372,26 +363,18 @@ export class Extension { UI.RestoreBackgroundMenu () this._services?.unexport () - this._disable_effect_managers () + this._window_actor_tracker?.disable () // Disconnect all signals in global connections.get() connections.get ().disconnect_all () connections.del () // Set all props to null - this._rounded_corners_manager = null + this._window_actor_tracker = null this._services = null _log ('Disabled') } - - private _enable_effect_managers () { - this._rounded_corners_manager?.enable () - } - - private _disable_effect_managers () { - this._rounded_corners_manager?.disable () - } } export function init () { diff --git a/src/manager/effect_manager.ts b/src/manager/effect_manager.ts new file mode 100644 index 0000000..143113a --- /dev/null +++ b/src/manager/effect_manager.ts @@ -0,0 +1,208 @@ +// imports.gi +import * as Clutter from '@gi/Clutter' +import { Display, WindowClientType } from '@gi/Meta' + +// local modules +import { _log } from '@me/utils/log' +import { settings } from '@me/utils/settings' +import { Connections } from '@me/utils/connections' +import { RoundedCornersManager } from '@me/manager/rounded_corners_manager' + +// types, those import statements will be removed in output javascript files. +import { SchemasKeys } from '@me/utils/settings' +import { EffectManager } from '@me/utils/types' +import { WindowActor, Window } from '@gi/Meta' +import { WM } from '@gi/Shell' +import { global } from '@global' +import * as Gio from '@gi/Gio' + +// --------------------------------------------------------------- [end imports] + +export class WindowActorTracker { + private effect_managers: EffectManager[] = [] + + /** + * connections store connect handles of GObject, so that we can call + * disconnect_all() to disconnect all signals when extension disabled + */ + private connections: Connections | null = null + + // ---------------------------------------------------------- [public methods] + + private run (exec: (m: EffectManager) => void) { + this.effect_managers.filter ((m) => m.enabled).forEach ((m) => exec (m)) + } + + /** Call When enable extension */ + enable () { + this.connections = new Connections () + this.effect_managers = [new RoundedCornersManager ()] + + // watch settings + this.connections.connect ( + settings ().g_settings, + 'changed', + (_: Gio.Settings, key: string) => + this.run ((m) => m.on_settings_changed (key as SchemasKeys)) + ) + + const wm = global.window_manager + + // Add effects to all windows when extensions enabled + const window_actors = global.get_window_actors () + _log (`Windows count when enable: ${window_actors.length}`) + for (const actor of window_actors) { + this._add_effect (actor) + } + + // Add effects when window opened + this.connections.connect ( + global.display, + 'window-created', + (_: Display, win: Window) => { + _log (`${win.title} mapped`) + const actor: WindowActor = win.get_compositor_private () + + // If wm_class_instance of Meta.Window is null, try to add rounded + // corners when wm_class_instance is set + if (win?.get_wm_class_instance () == null) { + const notify_id = win.connect ('notify::wm-class', () => { + this._add_effect (actor) + win.disconnect (notify_id) + }) + } else { + this._add_effect (actor) + } + } + ) + + // Connect 'minimized' signal + this.connections.connect (wm, 'minimize', (_: WM, actor: WindowActor) => { + this.run ((m) => m.on_minimize (actor)) + }) + + // Restore visible of shadow when un-minimized + this.connections.connect (wm, 'unminimize', (_: WM, actor: WindowActor) => { + // Handle visible of shader with Compiz alike magic lamp effect + // After MagicLampUnminimizeEffect completed, then show shadow + // + // https://github.com/hermes83/compiz-alike-magic-lamp-effect + const effect = actor.get_effect ('unminimize-magic-lamp-effect') + if (effect) { + type Effect = Clutter.Effect & { timerId: Clutter.Timeline } + const timer_id = (effect as Effect).timerId + + const id = timer_id.connect ('new-frame', (source) => { + // Effect completed when get_process() touch 1.0 + // Need show shadow here + if (source.get_progress () > 0.98) { + _log ('Handle Unminimize with Magic Lamp Effect') + + this.run ((m) => m.on_unminimize (actor)) + source.disconnect (id) + } + }) + return + } + + this.run ((m) => m.on_unminimize (actor)) + }) + + // Disconnect all signals of window when closed + this.connections.connect (wm, 'destroy', (_: WM, actor: WindowActor) => + this._remove_effect (actor) + ) + + // When windows restacked, change order of shadow actor too + this.connections.connect (global.display, 'restacked', () => { + global.get_window_actors ().forEach ((actor) => { + if (!actor.visible) { + return + } + this.run ((m) => m.on_restacked (actor)) + }) + }) + } + + /** Call when extension is disabled */ + disable () { + // Remove rounded effect and shadow actor for all windows + global.get_window_actors ().forEach ((actor) => this._remove_effect (actor)) + + // Disconnect all signal + this.connections?.disconnect_all () + this.connections = null + } + + // ------------------------------------------------------- [private methods] + /** + * Add rounded corners effect and setup shadow actor for a window actor + * @param actor - window to add effect + */ + private _add_effect (actor: WindowActor) { + const actor_is_ready = () => { + if (!this.connections) { + return + } + + this.connections.connect (actor.get_texture (), 'size-changed', () => { + this.run ((m) => m.on_size_changed (actor)) + }) + + this.connections.connect (actor, 'notify::size', () => { + this.run ((m) => m.on_size_changed (actor)) + }) + + // Update shadow actor when focus of window has changed. + this.connections.connect ( + actor.meta_window, + 'notify::appears-focused', + () => { + this.run ((m) => m.on_focus_changed (actor)) + } + ) + + // When window is switch between different monitor, + // 'workspace-changed' signal emit. + this.connections.connect (actor.meta_window, 'workspace-changed', () => { + this.run ((m) => m.on_focus_changed (actor)) + }) + + // Update shadows and rounded corners bounds + this.run ((m) => { + m.on_add_effect (actor) + m.on_size_changed (actor) + m.on_focus_changed (actor) + }) + } + + const win = actor.meta_window + _log ('Try to add effect to ', win.get_wm_class_instance ()) + if (win.get_client_type () === WindowClientType.X11) { + // Add rounded corners to surface actor for X11 client + if (actor.first_child) { + actor_is_ready () + } else { + // In wayland session, Surface Actor of XWayland client not ready when + // window created, waiting it + const id = actor.connect ('notify::first-child', () => { + // now it's ready + actor_is_ready () + actor.disconnect (id) + }) + } + } else { + // Add rounded corners to WindowActor for Wayland client + actor_is_ready () + } + } + + private _remove_effect (actor: WindowActor) { + if (this.connections) { + this.connections.disconnect_all (actor.get_texture ()) + this.connections.disconnect_all (actor) + this.connections.disconnect_all (actor.meta_window) + } + this.run ((m) => m.on_remove_effect (actor)) + } +} diff --git a/src/manager/rounded_corners_manager.ts b/src/manager/rounded_corners_manager.ts index fd66b07..977dba8 100644 --- a/src/manager/rounded_corners_manager.ts +++ b/src/manager/rounded_corners_manager.ts @@ -3,7 +3,7 @@ import * as Clutter from '@gi/Clutter' import { ShadowMode, WindowType } from '@gi/Meta' import { WindowClientType } from '@gi/Meta' import { Bin } from '@gi/St' -import { Binding, BindingFlags } from '@gi/GObject' +import { BindingFlags } from '@gi/GObject' import { ThemeContext } from '@gi/St' // local modules @@ -13,262 +13,283 @@ import { constants } from '@me/utils/constants' import { ClipShadowEffect } from '@me/effect/clip_shadow_effect' import * as types from '@me/utils/types' import { settings } from '@me/utils/settings' -import { Connections } from '@me/utils/connections' import { RoundedCornersEffect } from '@me/effect/rounded_corners_effect' // types, those import statements will be removed in output javascript files. import { SchemasKeys } from '../utils/settings' import { Window, WindowActor } from '@gi/Meta' -import { WM } from '@gi/Shell' import { global } from '@global' -import * as Gio from '@gi/Gio' +import { EffectManager } from '@me/utils/types' +import { ExtensionsWindowActor } from '@me/utils/types' type RoundedCornersEffectType = InstanceType // --------------------------------------------------------------- [end imports] -export class RoundedCornersManager { - /** Store connect handles of GObject, to disconnect when we needn't */ - private connections: Connections | null = null - - /** - * This map is used to store Meta.Window, and some information about it. - * Cache those information can reduce repeat compute. - * include: - * - Shadow Actor - * - AppType: Libhandy | LibAdwaita | Other - * - Bindings: Store the property Bindings between Window Actor and shadow - * Actor - */ - private rounded_windows: Map< - Window, - { - shadow: Bin - app_type: UI.AppType - bindings: { - [prop: string]: Binding | undefined - } - } - > | null = null +export class RoundedCornersManager implements EffectManager { + enabled = true /** Rounded corners settings */ - private global_rounded_corners: types.RoundedCornersCfg | null = null - private custom_rounded_corners: types.CustomRoundedCornersCfg | null = null + private global_rounded_corners = settings ().global_rounded_corner_settings + private custom_rounded_corners = settings ().custom_rounded_corner_settings - // -------------------------------------------------------- [public methods] + // ---------------------------------------------------------- [public methods] - /** Call When enable extension */ - enable () { - this.connections = new Connections () - this.rounded_windows = new Map () - this.global_rounded_corners = settings ().global_rounded_corner_settings - this.custom_rounded_corners = settings ().custom_rounded_corner_settings + on_add_effect (actor: ExtensionsWindowActor): void { + const win = actor.meta_window - // watch settings - this.connections.connect ( - settings ().g_settings, - 'changed', - (_: Gio.Settings, key: string) => - this._on_settings_changed (key as SchemasKeys) - ) + // If application failed check, then just return. + if (!this._should_enable_effect (win)) { + return + } - const wm = global.window_manager + // Add rounded corners shader to window + this._actor_to_rounded (actor)?.add_effect_with_name ( + constants.ROUNDED_CORNERS_EFFECT, + new RoundedCornersEffect () + ) - // Try to add rounded corners effect to all windows - const window_actors = global.get_window_actors () - _log (`Windows count when enable: ${window_actors.length}`) - for (const actor of window_actors) { - this._add_effect (actor) + // Turn off original shadow for ssd x11 window. + // - For ssd client in X11, shadow is drew by window manager + // - For csd client, shadow is drew by application itself, it has been cut + // out by rounded corners effect + if (actor.shadow_mode !== undefined) { + actor.shadow_mode = ShadowMode.FORCED_OFF } + // So we have to create an shadow actor for rounded corners shadows + const shadow = this._create_shadow (actor) + // Bind properties between shadow and window + const flag = BindingFlags.SYNC_CREATE + for (const prop of [ + 'pivot-point', + 'translation-x', + 'translation-y', + 'scale-x', + 'scale-y', + ]) { + actor.bind_property (prop, shadow, prop, flag) + } + // Store visible binding so that we can control the visible of shadow + // in some time. + const prop = 'visible' + const visible_binding = actor.bind_property (prop, shadow, prop, flag) - // Add effects when window opened - this.connections.connect (wm, 'map', (_: WM, actor: WindowActor) => { - const win = actor.get_meta_window () - - // If wm_class_instance of Meta.Window is null, try to add rounded - // corners when wm_class_instance is set - if (win?.get_wm_class_instance () == null) { - const notify_id = win.connect ('notify::wm-class', () => { - this._add_effect (actor) - win.disconnect (notify_id) - }) - } else { - this._add_effect (actor) - } - }) - - // Connect 'minimized' signal, hide shadow actor when window minimized - this.connections.connect (wm, 'minimize', (_: WM, actor: WindowActor) => { - const win = actor.get_meta_window () - const info = this.rounded_windows?.get (win) - const shadow = info?.shadow - const bindings = info?.bindings - if (shadow && bindings) { - // Disconnect bindings temporary, it will be restored - // when un-minimized - const visible_binding = bindings['visible'] - if (visible_binding) { - visible_binding.unbind () - delete bindings['visible'] - } - shadow.visible = false - } - }) + // Store shadow, app type, visible binding, so that we can query them later + actor.__rwc_rounded_window_info = { shadow, visible_binding } + } - // Restore visible of shadow when un-minimized - this.connections.connect (wm, 'unminimize', (_: WM, actor: WindowActor) => { - const win = actor.get_meta_window () - const info = this.rounded_windows?.get (win) - const shadow = info?.shadow - const bindings = info?.bindings - if (!shadow || !bindings) { - return - } + on_remove_effect (actor: ExtensionsWindowActor): void { + // Remove rounded corners effect + const name = constants.ROUNDED_CORNERS_EFFECT + this._actor_to_rounded (actor)?.remove_effect_by_name (name) - const restore_binding = () => { - if (!bindings['visible']) { - bindings['visible'] = actor.bind_property ( - 'visible', - shadow, - 'visible', - BindingFlags.SYNC_CREATE - ) - } - } + // Restore shadow for x11 windows + if (actor.shadow_mode) { + actor.shadow_mode = ShadowMode.AUTO + } - // Handle visible of shader with Compiz alike magic lamp effect - // After MagicLampUnminimizeEffect completed, then show shadow - // - // https://github.com/hermes83/compiz-alike-magic-lamp-effect - const effect = actor.get_effect ('unminimize-magic-lamp-effect') - if (effect) { - type Effect = Clutter.Effect & { timerId: Clutter.Timeline } - const timer_id = (effect as Effect).timerId - - const id = timer_id.connect ('new-frame', (source) => { - // Effect completed when get_process() touch 1.0 - // Need show shadow here - if (source.get_progress () > 0.98) { - _log ('Handle Unminimize with Magic Lamp Effect') - - restore_binding () - source.disconnect (id) - } - }) - return - } + // Remove shadow actor + const shadow = actor.__rwc_rounded_window_info?.shadow + if (shadow) { + global.window_group.remove_child (shadow) + shadow.destroy () + } + delete actor.__rwc_rounded_window_info + } - restore_binding () - }) + on_minimize (actor: ExtensionsWindowActor): void { + const info = actor.__rwc_rounded_window_info + const binding = info?.visible_binding + const shadow = info?.shadow + if (shadow && binding) { + binding.unbind () + shadow.visible = false + } + } - // Disconnect all signals of window when closed - this.connections.connect (wm, 'destroy', (_: WM, actor: WindowActor) => - this._remove_effect (actor) - ) + on_unminimize (actor: ExtensionsWindowActor): void { + const info = actor.__rwc_rounded_window_info + if (!info) { + return + } + const prop = 'visible' + const flag = BindingFlags.SYNC_CREATE + info.visible_binding = actor.bind_property (prop, info.shadow, prop, flag) + } + on_restacked (actor: ExtensionsWindowActor): void { // When windows restacked, change order of shadow actor too - this.connections.connect (global.display, 'restacked', () => { - global.get_window_actors ().forEach ((actor) => { - if (!actor.visible) { - return - } - const shadow = this.rounded_windows?.get (actor.meta_window)?.shadow - if (shadow) { - global.window_group.set_child_below_sibling (shadow, actor) - } - }) - }) + if (!actor.visible) { + return + } + const shadow = actor.__rwc_rounded_window_info?.shadow + if (shadow) { + global.window_group.set_child_below_sibling (shadow, actor) + } } - /** Call when extension is disabled */ - disable () { - // Remove rounded effect and shadow actor for all windows - global.get_window_actors ().forEach ((actor) => this._remove_effect (actor)) + on_size_changed (actor: ExtensionsWindowActor): void { + const win = actor.meta_window - // Remove all shadows store in map - this.rounded_windows?.clear () + const window_info = actor.__rwc_rounded_window_info + // Get rounded corners effect from window actor + const effect = this._actor_to_rounded (actor)?.get_effect ( + constants.ROUNDED_CORNERS_EFFECT + ) as RoundedCornersEffectType | null + if (!effect || !window_info) { + return + } - // Disconnect all signal - this.connections?.disconnect_all () + // Cache the offset, so that we can calculate this value once + const content_offset_of_win = UI.computeWindowContentsOffset (win) - // Set all props to null - this.rounded_windows = null - this.connections = null - this.global_rounded_corners = null - this.custom_rounded_corners = null - } + // Skip rounded corners when window is fullscreen & maximize + const cfg = this._get_rounded_corners_cfg (win) + const should_rounded = UI.ShouldHasRoundedCorners (win, cfg) - query_shadow (win: Window): Bin | undefined { - return this.rounded_windows?.get (win)?.shadow + if (!should_rounded && effect.enabled) { + _log ('Disable rounded corners effect for maximized window', win.title) + effect.enabled = false + this.on_focus_changed (actor) + return + } + // Restore Rounded effect when un-maximized + if (should_rounded && !effect.enabled) { + _log ('Restore rounded effect for maximized window', win.title) + effect.enabled = true + this.on_focus_changed (actor) + } + + // When size changed. update uniforms for window + effect.update_uniforms ( + UI.WindowScaleFactor (win), + cfg, + this._compute_bounds (actor, content_offset_of_win), + { + width: settings ().border_width, + color: settings ().border_color, + } + ) + + // Update BindConstraint for shadow + const shadow = window_info.shadow + const offsets = this._compute_shadow_actor_offset ( + actor, + content_offset_of_win + ) + const constraints = shadow.get_constraints () + constraints.forEach ((constraint, i) => { + if (constraint instanceof Clutter.BindConstraint) { + constraint.offset = offsets[i] + } + }) } - /** Return all rounded corners window */ - windows (): IterableIterator | undefined { - return this.rounded_windows?.keys () + on_focus_changed (actor: ExtensionsWindowActor): void { + const win = actor.meta_window + const shadow = actor.__rwc_rounded_window_info?.shadow + if (!shadow) { + return + } + + const shadow_settings = win.appears_focused + ? settings ().focused_shadow + : settings ().unfocused_shadow + + const { border_radius, padding } = this._get_rounded_corners_cfg (win) + + this._update_shadow_actor_style ( + win, + shadow, + border_radius, + shadow_settings, + padding + ) } - /** Query rounded corners effect of window actor */ - get_rounded_corners_effect ( - actor: WindowActor - ): RoundedCornersEffectType | null | undefined { - const name = constants.ROUNDED_CORNERS_EFFECT - type Res = RoundedCornersEffectType | null | undefined - return this._get_actor_to_rounded (actor)?.get_effect (name) as Res + on_settings_changed (key: SchemasKeys): void { + switch (key) { + case 'skip-libadwaita-app': + case 'skip-libhandy-app': + case 'black-list': + this._update_all_window_effect_state () + break + case 'focused-shadow': + case 'unfocused-shadow': + this._update_all_shadow_actor_style () + break + case 'global-rounded-corner-settings': + case 'custom-rounded-corner-settings': + case 'border-color': + case 'border-width': + case 'tweak-kitty-terminal': + this._update_all_rounded_corners_settings () + break + default: + } } - // ------------------------------------------------------- [private methods] + // --------------------------------------------------------- [private methods] - /** Compute outer bound of rounded corners for window actor */ - private _compute_bounds ( - actor: WindowActor, - [x, y, width, height]: [number, number, number, number] - ): types.Bounds { - const bounds = { - x1: x + 1, - y1: y + 1, - x2: x + actor.width + width, - y2: y + actor.height + height, + /** + * Check whether a window should be enable rounded corners effect + * @param win Meta.Window to test + */ + private _should_enable_effect ( + win: Window & { __app_type?: UI.AppType } + ): boolean { + // DING (Desktop Icons NG) is a extensions that create a gtk + // application to show desktop grid on background, we need to + // skip it coercively. + // https://extensions.gnome.org/extension/2087/desktop-icons-ng-ding/ + if (win.gtk_application_id === 'com.rastersoft.ding') { + return false } - // Kitty draw it's window decoration by itself, we need recompute the - // outer bounds for kitty. - if (settings ().tweak_kitty_terminal) { - const type = WindowClientType.WAYLAND - if ( - actor.meta_window.get_client_type () == type && - actor.meta_window.get_wm_class_instance () === 'kitty' - ) { - const scale = UI.WindowScaleFactor (actor.meta_window) - bounds.x1 += 11 * scale /* shadow in left of kitty */ - bounds.y1 += 35 * scale /* shadow in top of kitty */ - bounds.x2 -= 11 * scale /* shadow in right of kitty */ - bounds.y2 -= 11 * scale /* shadow in bottom of kitty */ - } + // Skip applications in black list. + const wm_class_instance = win.get_wm_class_instance () + if (wm_class_instance == null) { + _log (`Warning: wm_class_instance of ${win}: ${win.title} is null`) + return false + } + if (settings ().black_list.includes (wm_class_instance)) { + return false } - return bounds - } + // Check type of window, only need to add rounded corners to normal + // window and dialog. + const normal_type = [ + WindowType.NORMAL, + WindowType.DIALOG, + WindowType.MODAL_DIALOG, + ].includes (win.window_type) + if (!normal_type) { + return false + } - /** Bind property between shadow actor and window actor */ - private _bind_shadow_actor_props ( - actor: WindowActor, - shadow: Bin - ): { [prop: string]: Binding | undefined } { - const flag = BindingFlags.SYNC_CREATE - const bindings: { [prop: string]: Binding | undefined } = {} + // Skip libhandy / libadwaita applications according the settings. + const { AppType, getAppType } = UI + const app_type = win.__app_type ?? getAppType (win) + win.__app_type = app_type // cache result + _log ('Check Type of window:' + `${win.title} => ${AppType[app_type]}`) - for (const prop of [ - 'pivot-point', - 'visible', - 'translation-x', - 'translation-y', - 'scale-x', - 'scale-y', - ]) { - bindings[prop] = actor.bind_property (prop, shadow, prop, flag) + if (settings ().skip_libadwaita_app && app_type === AppType.LibAdwaita) { + return false } + if (settings ().skip_libhandy_app && app_type === AppType.LibHandy) { + return false + } + + return true + } - // restore bindings map, key: property, value: GLib.Binding - return bindings + /** + * return Clutter.Actor that should be add rounded corners, + * In Wayland, we will add rounded corners effect to WindowActor + * In XOrg, we will add rounded corners effect to WindowActor.first_child + */ + private _actor_to_rounded (actor: WindowActor): Clutter.Actor | null { + const type = actor.meta_window.get_client_type () + return type == WindowClientType.X11 ? actor.get_first_child () : actor } /** @@ -306,12 +327,50 @@ export class RoundedCornersManager { const parent = actor.get_parent () parent != null && parent.insert_child_below (shadow, actor) + // Bind position and size between window and shadow + for (let i = 0; i < 4; i++) { + const constraint = new Clutter.BindConstraint ({ + source: actor, + coordinate: i, + offset: 0, + }) + shadow.add_constraint (constraint) + } + // Return the shadow we create, it will be store into // this.rounded_windows + return shadow + } - this._bind_shadow_constraint (actor, shadow) + /** Compute outer bound of rounded corners for window actor */ + private _compute_bounds ( + actor: WindowActor, + [x, y, width, height]: [number, number, number, number] + ): types.Bounds { + const bounds = { + x1: x + 1, + y1: y + 1, + x2: x + actor.width + width, + y2: y + actor.height + height, + } - return shadow + // Kitty draw it's window decoration by itself, we need recompute the + // outer bounds for kitty. + if (settings ().tweak_kitty_terminal) { + const type = WindowClientType.WAYLAND + if ( + actor.meta_window.get_client_type () == type && + actor.meta_window.get_wm_class_instance () === 'kitty' + ) { + const scale = UI.WindowScaleFactor (actor.meta_window) + bounds.x1 += 11 * scale /* shadow in left of kitty */ + bounds.y1 += 35 * scale /* shadow in top of kitty */ + bounds.x2 -= 11 * scale /* shadow in right of kitty */ + bounds.y2 -= 11 * scale /* shadow in bottom of kitty */ + } + } + + return bounds } private _compute_shadow_actor_offset ( @@ -338,25 +397,6 @@ export class RoundedCornersManager { ] } - private _bind_shadow_constraint (actor: WindowActor, shadow: Bin) { - const coordinates = [ - Clutter.BindCoordinate.X, - Clutter.BindCoordinate.Y, - Clutter.BindCoordinate.WIDTH, - Clutter.BindCoordinate.HEIGHT, - ] - coordinates - .map ( - (coordinate) => - new Clutter.BindConstraint ({ - source: actor, - coordinate, - offset: 0, - }) - ) - .forEach ((constraint) => shadow.add_constraint (constraint)) - } - /** Update css style of shadow actor */ private _update_shadow_actor_style ( win: Window, @@ -405,240 +445,32 @@ export class RoundedCornersManager { child.style = 'opacity: 0;' } else { child.style = ` - background: white; - border-radius: ${border_radius * scale_of_style}px; - ${types.box_shadow_css (shadow, scale_of_style)}; - margin: ${top * scale_of_style}px - ${right * scale_of_style}px - ${bottom * scale_of_style}px - ${left * scale_of_style}px;` + background: white; + border-radius: ${border_radius * scale_of_style}px; + ${types.box_shadow_css (shadow, scale_of_style)}; + margin: ${top * scale_of_style}px + ${right * scale_of_style}px + ${bottom * scale_of_style}px + ${left * scale_of_style}px;` } child.queue_redraw () } - /** Add Rounded corners when window actor is ready */ - private _setup_rounded_corners_effect (actor: WindowActor) { - const _setup_effect = (actor_to_add_effect: Clutter.Actor) => { - const effect = new RoundedCornersEffect () - const name = constants.ROUNDED_CORNERS_EFFECT - - actor_to_add_effect.add_effect_with_name (name, effect) - - this.connections?.connect (actor.get_texture (), 'size-changed', () => { - this.on_size_changed (actor) - }) - - // Update shadows and rounded corners bounds - this.on_size_changed (actor) - this._on_focus_changed (actor.meta_window) - } - - const win = actor.meta_window - if (win.get_client_type () === WindowClientType.X11) { - // Add rounded corners to surface actor for X11 client - if (actor.first_child) { - _setup_effect (actor.first_child) - } else { - // Surface Actor may not ready in some time, waiting it by - // connect 'notify::first-child' - const id = actor.connect ('notify::first-child', () => { - // now it's ready - _setup_effect (actor.first_child) - actor.disconnect (id) - }) - } - } else { - // Add rounded corners to WindowActor for Wayland client - _setup_effect (actor) - } - } - - /** - * Add rounded corners effect and setup shadow actor for a window actor - * @param actor - window to add effect - */ - private _add_effect (actor: WindowActor & { shadow_mode?: ShadowMode }) { - // If application failed check, just return and don't add rounded - // corners to it. - const [should_rounded, app_type] = this.should_enable_effect ( - actor.meta_window - ) - if (!should_rounded) { - return - } - - const win = actor.meta_window - - // Add rounded corners to window actor when actor_to_setup is ready - if (!this.connections || !this.rounded_windows) { - return - } - - // The shadow of window - const shadow = this._create_shadow (actor) - - const bindings = this._bind_shadow_actor_props (actor, shadow) - - this.rounded_windows.set (win, { shadow, app_type, bindings }) - - // turn off original shadow for x11 window - if (actor.shadow_mode !== undefined) { - actor.shadow_mode = ShadowMode.FORCED_OFF - } - - // Connect signals of window, those signals will be disconnected - // when window is destroyed - - // Update uniform variables when changed window size - const source = actor.meta_window - - this.connections.connect (actor, 'notify::size', () => { - this.on_size_changed (actor) - }) - - // Update shadow actor when focus of window has changed. - this.connections.connect (source, 'notify::appears-focused', () => { - this._on_focus_changed (source) - }) - - // When window is switch between different monitor, - // 'workspace-changed' signal emit. - this.connections.connect (source, 'workspace-changed', () => { - const shadow = this.rounded_windows?.get (source)?.shadow - if (shadow) { - _log ('Recompute style of shadow...') - this._update_shadow_actor_style (actor.meta_window, shadow) - } - }) - - // Add rounded corers to window - this._setup_rounded_corners_effect (actor) - } - - /** Remove rounded corners effect for window actor */ - private _remove_rounded_corners_effect (actor: WindowActor) { - const name = constants.ROUNDED_CORNERS_EFFECT - this._get_actor_to_rounded (actor)?.remove_effect_by_name (name) - } - - /**` - * Remove rounded corners effect and shadow actor for a window actor - * This method will be called when window is open, or change of settings - * need remove rounded corners. - * It will remove all connected signals, and clear all resources. - */ - private _remove_effect (actor: WindowActor & { shadow_mode?: ShadowMode }) { - if (!this.rounded_windows || !this.connections) { - return - } - - const win = actor.meta_window - this._remove_rounded_corners_effect (actor) - - // Restore shadow for x11 windows - if (actor.shadow_mode) { - actor.shadow_mode = ShadowMode.AUTO - } - - // Remove shadow actor - const shadow = this.rounded_windows.get (win)?.shadow - if (shadow) { - global.window_group.remove_child (shadow) - shadow.destroy () - } - this.rounded_windows.delete (win) - - // Remove handle for window, those handle has been added - // in `_add_effect()` - this.connections.disconnect_all (actor.get_texture ()) - this.connections.disconnect_all (win) - this.connections.disconnect_all (actor) - } - - /** - * Check whether a window should be enable rounded corners effect - * @param win WindowActor to test - * @return {[boolean, UI.AppType]} - */ - should_enable_effect (win: Window): [boolean, UI.AppType] { - // DING (Desktop Icons NG) is a extensions that create a gtk - // application to show desktop grid on background, we need to - // skip it coercively. - // https://extensions.gnome.org/extension/2087/desktop-icons-ng-ding/ - if (win.gtk_application_id === 'com.rastersoft.ding') { - return [false, UI.AppType.Other] - } - - // Skip when application in black list. - - const wm_class_instance = win.get_wm_class_instance () - if (wm_class_instance == null) { - _log (`Warning: wm_class_instance of ${win}: ${win.title} is null`) - return [false, UI.AppType.Other] - } - - if (settings ().black_list.includes (wm_class_instance)) { - return [false, UI.AppType.Other] - } - - // Check type of window, only need to add rounded corners to normal - // window and dialog. - - const normal_type = [ - WindowType.NORMAL, - WindowType.DIALOG, - WindowType.MODAL_DIALOG, - ].includes (win.window_type) - if (!normal_type) { - return [false, UI.AppType.Other] - } - - // Skip libhandy / libadwaita applications according the settings. - const { getAppType, AppType } = UI - - // Try cache first - const app_info = this.rounded_windows?.get (win) - const app_type = app_info?.app_type ?? getAppType (win) - _log ('Check Type of window:' + `${win.title} => ${AppType[app_type]}`) - - if (settings ().skip_libadwaita_app) { - if (getAppType (win) === AppType.LibAdwaita) { - return [false, app_type] - } - } - if (settings ().skip_libhandy_app) { - if (getAppType (win) === AppType.LibHandy) { - return [false, app_type] - } - } - - return [true, app_type] - } - - /** - * In Wayland, we will add rounded corners effect to WindowActor - * In XOrg, we will add rounded corners effect to WindowActor.first_child - */ - private _get_actor_to_rounded (actor: WindowActor): Clutter.Actor | null { - const type = actor.meta_window.get_client_type () - - return type == WindowClientType.X11 ? actor.get_first_child () : actor - } - /** Traversal all windows, add or remove rounded corners for them */ private _update_all_window_effect_state () { global.get_window_actors ().forEach ((actor) => { - const [should_have_effect] = this.should_enable_effect (actor.meta_window) - const has_effect = this.get_rounded_corners_effect (actor) != null + const should_have_effect = this._should_enable_effect (actor.meta_window) + const has_effect = UI.get_rounded_corners_effect (actor) != null if (should_have_effect && !has_effect) { - this._add_effect (actor) + this.on_add_effect (actor) + this.on_size_changed (actor) return } if (!should_have_effect && has_effect) { - this._remove_effect (actor) + this.on_remove_effect (actor) return } }) @@ -646,8 +478,13 @@ export class RoundedCornersManager { /** Update style for all shadow actors */ private _update_all_shadow_actor_style () { - this.rounded_windows?.forEach (({ shadow }, win) => { - const actor: WindowActor = win.get_compositor_private () + for (const actor of global.get_window_actors ()) { + const info = (actor as ExtensionsWindowActor).__rwc_rounded_window_info + if (!info) { + continue + } + const { shadow } = info + const win = actor.meta_window const shadow_cfg = actor.meta_window.appears_focused ? settings ().focused_shadow : settings ().unfocused_shadow @@ -662,7 +499,7 @@ export class RoundedCornersManager { shadow_cfg, padding ) - }) + } } private _get_rounded_corners_cfg (win: Window): types.RoundedCornersCfg { @@ -676,136 +513,18 @@ export class RoundedCornersManager { /** * This method will be called when global rounded corners settings changed. */ - update_all_rounded_corners_settings () { + private _update_all_rounded_corners_settings () { this.global_rounded_corners = settings ().global_rounded_corner_settings this.custom_rounded_corners = settings ().custom_rounded_corner_settings - this.rounded_windows?.forEach ((shadow, win) => { - const actor: WindowActor = win.get_compositor_private () - this.on_size_changed (actor) - }) - this._update_all_shadow_actor_style () - } - - // ------------------------------------------------------- [signal handlers] - - /** - * This handler will be called when settings of extensions changed - * @param key Key of settings in schemas have changed - */ - private _on_settings_changed (key: SchemasKeys): void { - switch (key) { - case 'skip-libadwaita-app': - case 'skip-libhandy-app': - case 'black-list': - this._update_all_window_effect_state () - break - case 'focused-shadow': - case 'unfocused-shadow': - this._update_all_shadow_actor_style () - break - case 'global-rounded-corner-settings': - case 'custom-rounded-corner-settings': - case 'border-color': - case 'border-width': - case 'tweak-kitty-terminal': - this.update_all_rounded_corners_settings () - break - default: - } - } - - /** - * This handler of 'size-changed' signal for Meta.Window, used to update - * uniforms variants of shader of rounded corners effect, also used to - * update bind constraint of shadow actor. - * @param actor - Window actor correlate Meta.Window - */ - on_size_changed (actor: WindowActor): void { - const win = actor.meta_window - - const window_info = this.rounded_windows?.get (win) - if (!window_info) { - return - } - - // Cache the offset, so that we can calculate this value once - const content_offset_of_win = UI.computeWindowContentsOffset (win) - - const cfg = this._get_rounded_corners_cfg (win) - - // Skip rounded corners when window is fullscreen & maximize - let effect = this.get_rounded_corners_effect (actor) - const should_rounded = UI.ShouldHasRoundedCorners (win, cfg) - - if (!should_rounded && effect) { - _log ('Remove rounded effect for maximized window', win.title) - this._remove_rounded_corners_effect (actor) - return - } - // Restore Rounded effect when un-maximized - if (should_rounded && !effect) { - const actor_to_add = this._get_actor_to_rounded (actor) - const name = constants.ROUNDED_CORNERS_EFFECT - if (actor_to_add) { - effect = new RoundedCornersEffect () - actor_to_add.add_effect_with_name (name, effect) - _log ('Restore rounded effect for maximized window', win.title) - } - } - - // When size changed. update uniforms for window - effect?.update_uniforms ( - UI.WindowScaleFactor (win), - cfg, - this._compute_bounds (actor, content_offset_of_win), - { - width: settings ().border_width, - color: settings ().border_color, - } - ) - - // Update BindConstraint for shadow - const shadow = window_info.shadow - if (!shadow) { - return - } - const offsets = this._compute_shadow_actor_offset ( - actor, - content_offset_of_win - ) - const constraints = shadow.get_constraints () - constraints.forEach ((constraint, i) => { - if (constraint instanceof Clutter.BindConstraint) { - constraint.offset = offsets[i] + for (const actor of global.get_window_actors ()) { + const info = (actor as ExtensionsWindowActor).__rwc_rounded_window_info + if (!info) { + continue } - }) - } - - /** - * Handler of 'notify::appears-focus' signal of Meta.Window, will be called - * when focus of window has changed. Use to update shadow actor of rounded - * corners window - * @params win - Meta.Window - */ - _on_focus_changed (win: Window) { - const shadow = this.rounded_windows?.get (win)?.shadow - if (!shadow) { - return + this.on_size_changed (actor) } - const shadow_settings = win.appears_focused - ? settings ().focused_shadow - : settings ().unfocused_shadow - - const { border_radius, padding } = this._get_rounded_corners_cfg (win) - - this._update_shadow_actor_style ( - win, - shadow, - border_radius, - shadow_settings, - padding - ) + this._update_all_shadow_actor_style () } } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 173e25f..5daf8ed 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -2,20 +2,7 @@ import { _ } from '@me/utils/i18n' -/** - * Loaded Message will be shown when debug. - * see: https://patorjk.com/software/taag - */ -const LOADED_MSG = ` -╦═╗┌─┐┬ ┬┌┐┌┌┬┐┌─┐┌┬┐╔═╗┌─┐┬─┐┌┬┐┌─┐┬─┐┌─┐╔═╗┌─┐┌─┐┌─┐┌─┐┌┬┐┌─┐ -╠╦╝│ ││ ││││ ││├┤ ││║ │ │├┬┘ ││├┤ ├┬┘└─┐║╣ ├┤ ├┤ ├┤ │ │ └─┐ -╩╚═└─┘└─┘┘└┘─┴┘└─┘─┴┘╚═╝└─┘┴└──┴┘└─┘┴└─└─┘╚═╝└ └ └─┘└─┘ ┴ └─┘ - -[RoundedCordersEffect] Loaded.` - export const constants = { - /** Message to shown when extensions loaded successfully */ - LOADED_MSG, /** Name of shadow actors */ SHADOW_ACTOR_NAME: 'Rounded Window Shadow Actor', /** Name of rounded corners effects */ diff --git a/src/utils/types.ts b/src/utils/types.ts index 143c663..d7a4d49 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,9 @@ +import { Actor } from '@gi/Clutter' +import { Binding } from '@gi/GObject' +import { ShadowMode, WindowActor } from '@gi/Meta' +import { Bin } from '@gi/St' +import { SchemasKeys } from '@me/utils/settings' + /** Bounds of rounded corners */ export class Bounds { x1 = 0 @@ -39,8 +45,32 @@ export interface BoxShadow { export const box_shadow_css = (box_shadow: BoxShadow, scale = 1) => { return `box-shadow: ${box_shadow.horizontal_offset * scale}px - ${box_shadow.vertical_offset * scale}px - ${box_shadow.blur_offset * scale}px - ${box_shadow.spread_radius * scale}px - rgba(0,0,0, ${box_shadow.opacity / 100})` + ${box_shadow.vertical_offset * scale}px + ${box_shadow.blur_offset * scale}px + ${box_shadow.spread_radius * scale}px + rgba(0,0,0, ${box_shadow.opacity / 100})` +} + +export interface EffectManager { + enabled: boolean + on_settings_changed(key: SchemasKeys): void + on_add_effect(actor: ExtensionsWindowActor): void + on_remove_effect(actor: ExtensionsWindowActor): void + on_minimize(actor: ExtensionsWindowActor): void + on_unminimize(actor: ExtensionsWindowActor): void + on_restacked(actor: ExtensionsWindowActor): void + on_size_changed(actor: ExtensionsWindowActor): void + on_focus_changed(actor: ExtensionsWindowActor): void +} + +export type ExtensionsWindowActor = WindowActor & { + __rwc_rounded_window_info?: { + shadow: Bin + visible_binding: Binding + } + __rwc_blurred_window_info?: { + blur_actor: Actor + visible_binding: Binding + } + shadow_mode?: ShadowMode } diff --git a/src/utils/ui.ts b/src/utils/ui.ts index 50780dc..0d41630 100644 --- a/src/utils/ui.ts +++ b/src/utils/ui.ts @@ -15,7 +15,7 @@ import { _ } from '@me/utils/i18n' // types import { global } from '@global' import * as types from '@me/utils/types' -import { Actor } from '@gi/Clutter' +import { Actor, Effect } from '@gi/Clutter' // --------------------------------------------------------------- [end imports] @@ -113,7 +113,7 @@ export const AddBackgroundMenuItem = (menu: BackgroundMenu) => { }) } -/** Find all Background menu, then add a item for open preferences into menu */ +/** Find all Background menu, then add extra item to it */ export const SetupBackgroundMenu = () => { for (const _bg of global.window_group.first_child.get_children ()) { const menu = (_bg as typeof _bg & BackgroundExtra)._backgroundMenu @@ -186,3 +186,16 @@ export function ShouldHasRoundedCorners ( export function shell_version (): number { return Number.parseFloat (PACKAGE_VERSION) } + +/** + * Get Rounded corners effect from a window actor + */ +export function get_rounded_corners_effect ( + actor: Meta.WindowActor +): Effect | null { + const win = actor.meta_window + const name = constants.ROUNDED_CORNERS_EFFECT + return win.get_client_type () === Meta.WindowClientType.X11 + ? actor.first_child.get_effect (name) + : actor.get_effect (name) +}