const { Clutter, GObject, Meta, Shell, St } = imports.gi; const Background = imports.ui.background; const SwitcherPopup = imports.ui.switcherPopup; const Layout = imports.ui.layout; const Main = imports.ui.main; const WINDOW_ANIMATION_TIME = 2000; var APP_ICON_SIZE = 96; var RealmItem = GObject.registerClass( class RealmItem extends St.BoxLayout { _init(realm, workspace_group) { super._init({ vertical: true }); this.realm = realm; this.add_child(workspace_group); this.label = new St.Label({ text: `realm-${this.realm.realm_name}`, x_align: Clutter.ActorAlign.CENTER, }); this.add_child(this.label); } activate() { this.realm.set_current(); } }); function getRealmItems() { const monitor = Main.layoutManager.primaryMonitor; const realms = Shell.Realms.get_default(); let realm_list = realms.get_running_realms(); let items = []; realm_list.forEach(realm => { let ws = realm.get_active_workspace(); if (ws) { let size = 256; // default thumbnail size let scale = Math.min(1.0, size / monitor.width, size / monitor.height); let wsgroup = new WorkspaceGroup(ws, monitor, scale); items.push(new RealmItem(realm, wsgroup)); } }); return items; } var SwitchRealmList = GObject.registerClass( class SwitchRealmList extends SwitcherPopup.SwitcherList { _init(items) { super._init(false); items.forEach(item => { this.addItem(item, item.label); }); } }); var SwitchRealmPopup = GObject.registerClass( class SwitchRealmPopup extends SwitcherPopup.SwitcherPopup { _init(action, actionBackward) { super._init(); this._action = action; this._actionBackward = actionBackward; this._items = getRealmItems(); this._switcherList = new SwitchRealmList(this._items); } _keyPressHandler(keysym, action) { if (action == this._action) this._select(this._next()); else if (action == this._actionBackward) this._select(this._previous()); else if (keysym == Clutter.KEY_Left) this._select(this._previous()); else if (keysym == Clutter.KEY_Right) this._select(this._next()); else return Clutter.EVENT_PROPAGATE; return Clutter.EVENT_STOP; } _finish() { super._finish(); this._items[this._selectedIndex].activate(); } }); const WorkspaceGroup = GObject.registerClass( class WorkspaceGroup extends Clutter.Actor { _init(workspace, monitor, scale = 1.0) { super._init(); this._workspace = workspace; this._monitor = monitor; this._scale = scale; this._windowRecords = []; this.width = monitor.width * scale; this.height = monitor.height * scale; this._background = new Meta.BackgroundGroup({ width: this.width * this._scale, height: this.height * this._scale, }); this.add_actor(this._background); this._bgManager = new Background.BackgroundManager({ container: this._background, monitorIndex: this._monitor.index, controlPosition: false, }); this.clip_to_allocation = true; this._createWindows(); this.connect('destroy', this._onDestroy.bind(this)); this._restackedId = global.display.connect('restacked', this._syncStacking.bind(this)); } get workspace() { return this._workspace; } _shouldShowWindow(window) { if (!window.showing_on_its_workspace()) return false; const geometry = global.display.get_monitor_geometry(this._monitor.index); const [intersects] = window.get_frame_rect().intersect(geometry); if (!intersects) return false; const isSticky = window.is_on_all_workspaces(); return !isSticky && window.located_on_workspace(this._workspace); } _syncStacking() { const windowActors = global.get_window_actors().filter(w => this._shouldShowWindow(w.meta_window)); let lastRecord; for (const windowActor of windowActors) { const record = this._windowRecords.find(r => r.windowActor === windowActor); this.set_child_above_sibling(record.clone, lastRecord ? lastRecord.clone : this._background); lastRecord = record; } } _createWindows() { const windowActors = global.get_window_actors().filter(w => this._shouldShowWindow(w.meta_window)); for (const windowActor of windowActors) { let [width,height] = windowActor.get_size(); const clone = new Clutter.Clone({ source: windowActor, width: width * this._scale, height: height * this._scale, x: (windowActor.x - this._monitor.x) * this._scale, y: (windowActor.y - this._monitor.y) * this._scale, }); this.add_child(clone); const record = {windowActor, clone }; record.windowDestroyId = windowActor.connect('destroy', () => { clone.destroy(); this._windowRecords.splice(this._windowRecords.indexOf(record), 1); }); this._windowRecords.push(record); } } _removeWindows() { for (const record of this._windowRecords) { record.windowActor.disconnect(record.windowDestroyId); record.clone.destroy(); } this._windowRecords = []; } _onDestroy() { global.display.disconnect(this._restackedId); this._removeWindows(); this._bgManager.destroy(); } }); const MonitorGroup = GObject.registerClass({ Properties: { 'progress': GObject.ParamSpec.double( 'progress', 'progress', 'progress', GObject.ParamFlags.READWRITE, -Infinity, Infinity, 0), }, }, class MonitorGroup extends St.Widget { _init(monitor, fromIndex, toIndex) { super._init({ clip_to_allocation: true, style_class: 'workspace-animation', }); this._monitor = monitor; const constraint = new Layout.MonitorConstraint({ index: monitor.index }); this.add_constraint(constraint); this._container = new Clutter.Actor(); this.add_child(this._container); this._progress = 0; this._fadeOut = true; this._workspaceGroups = []; this._blackBackground = new Clutter.Actor(); this._blackBackground.width = monitor.width; this._blackBackground.height = monitor.height; let [_res, color] = Clutter.Color.from_string("#000000ff"); this._blackBackground.background_color = color; this.addWorkspaceByIndex(toIndex, monitor); // add opaque black actor this._container.add_child(this._blackBackground); this.addWorkspaceByIndex(fromIndex, monitor); // tween 'from' WorkspaceGroup opacity from 255 to 0 fading workspace to black background // tween 'block' actor opacity from 255 to 0 revealing 'to' WorkspaceGroup } addWorkspaceByIndex(idx, monitor) { const workspaceManager = global.workspace_manager; const ws = workspaceManager.get_workspace_by_index(idx); if (ws) { const fullscreen = ws.list_windows().some(w => w.get_monitor() === monitor.index && w.is_fullscreen()); const group = new WorkspaceGroup(ws, monitor); this._workspaceGroups.push(group); this._container.add_child(group); } } get progress() { return this._progress; } // Interpolate opacity from 0 (full opaque) to 50 (full transparent) calculateOpacity(progress) { return 255 - (255 * (progress / 50.0)); } set progress(p) { const fromGroup = this._workspaceGroups[this._workspaceGroups.length - 1]; this._progress = p; // 0 - 50 if (p < 50) { this._blackBackground.opacity = 255; fromGroup.opacity = this.calculateOpacity(p); } else if (p < 100) { if (this._fadeOut) { this._fadeOut = false; } fromGroup.opacity = 0; this._blackBackground.opacity = this.calculateOpacity(p - 50); } else { fromGroup.opacity = 0; this._blackBackground.opacity = 0; } } }); var ContextSwitchAnimationController = class { constructor(indicator) { this._switchData = null; this._indicator = indicator; } _prepareContextSwitch(fromIdx, toIdx) { if (this._switchData) { this._switchData.monitors[0].remove_all_transitions(); this._finishContextSwitch(this._switchData); } const switchData = {}; this._switchData = switchData; switchData.monitors = []; switchData.inProgress = false; const monitor = Main.layoutManager.primaryMonitor; const group = new MonitorGroup(monitor, fromIdx, toIdx); Main.uiGroup.insert_child_above(group, global.window_group); switchData.monitors.push(group); Meta.disable_unredirect_for_display(global.display); } _finishContextSwitch(switchData) { Meta.enable_unredirect_for_display(global.display); this._indicator.update(); this._switchData = null; switchData.monitors.forEach(m => m.destroy()); if (switchData.onComplete) { switchData.onComplete(); } } animateSwitch(fromIdx, toIdx, onComplete) { this._prepareContextSwitch(fromIdx, toIdx); this._switchData.inProgress = true; this._switchData.onComplete = onComplete; const params = { duration: WINDOW_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD, }; params.onComplete = () => { this._finishContextSwitch(this._switchData); }; this._indicator.clear(); this._switchData.monitors[0].ease_property('progress', 100, params); } }