const { Clutter, Cogl, GObject, Meta, Shell, St } = imports.gi; var WindowFrameManager = class WindowFrameManager { constructor() { this._realms = Shell.Realms.get_default(); let frames = this._realms.window_frames(); this._frame_effects = []; global.window_manager.connect('map', this._handleWindowMap.bind(this)); global.workspace_manager.connect('context-window-moved', this._onContextWindowMoved.bind(this)); global.workspace_manager.connect('context-removed', this._onContextRemoved.bind(this)); frames.connect('realm-frame-colors-changed', this._onFrameColorsChanged.bind(this)); this.trackWindows(); } _onContextWindowMoved(workspaceManager, window) { let actor = window.get_compositor_private(); if (actor) { this.handleWindow(actor); } return Clutter.EVENT_PROPAGATE; } _handleWindowMap(shellwm, actor) { this.handleWindow(actor); return Clutter.EVENT_PROPAGATE; } _onContextRemoved(workspaceManager, id) { this.trackWindows(); } _onFrameColorsChanged(realms) { this.trackWindows(); } trackWindows() { var actors = global.get_window_actors(); actors.forEach(a => this.handleWindow(a)); } handleWindow(actor) { let win = actor.metaWindow; let win_id = win.get_stable_sequence(); let effect = this._frame_effects[win_id]; let frames = this._realms.window_frames(); if (frames.has_frame(win) && frames.is_frame_enabled(win)) { let color = frames.color_for_window(win); if (effect) { effect.setColor(color); } else { let label = frames.label_for_window(win); effect = new RealmFrameEffect(actor, color, label); this._frame_effects[win_id] = effect; } } else if (effect) { effect.removeEffect(actor); this._frame_effects[win_id] = null; } } } var RealmFrameEffect = GObject.registerClass( class RealmFrameEffect extends Clutter.Effect { _init(actor, color, label_text) { super._init(); this._frame_width = 2; this._pipeline = null; this._color = color; this._label_on_top = true; this._label = null; this._label_text = label_text; if (label_text) { this._updateLabel(actor.metaWindow); } this._sizeChangedId = actor.metaWindow.connect('size-changed', window => { this._updateLabel(window); }); actor.add_effect(this); } removeEffect(actor) { if (this._label) { actor.remove_child(this._label); this._label = null; } if (this._sizeChangedId) { let win = actor.metaWindow; win.disconnect(this._sizeChangedId); this._sizeChangedId = 0; } actor.remove_effect(this); } _createLabel(actor, label_text) { let label = new St.Label({ style_class: 'realm-frame-label', z_position: 1.0, }); label.set_text(' '+label_text+' '); actor.add_child(label); return label; } _updateLabel(window) { if (!this._label_text) { return; } if (window.is_fullscreen()) { if (this._label) { let actor = window.get_compositor_private(); actor.remove_child(this._label); this._label = null; } } else if (!this._label) { let actor = window.get_compositor_private(); this._label = this._createLabel(actor, this._label_text); } if (this._label) { this._updateLabelPosition(window); this._updateLabelColor(); } } _updateLabelPosition(window) { if (!this._label_height) { // If we scale the text, the reported size of the label will not be the value we need so // save the initial value. this._label_height = this._label.get_height(); } let maximized = window.is_fullscreen() === true || // Fullscreen [Meta.MaximizeFlags.BOTH, Meta.MaximizeFlags.VERTICAL].includes(window.get_maximized()); // Maximized this._label_on_top = !maximized; let frame_rect = window.get_frame_rect(); let buffer_rect = window.get_buffer_rect(); let offsetX = frame_rect.x - buffer_rect.x; let offsetY = frame_rect.y - buffer_rect.y; if (window.get_client_type() === Meta.WindowClientType.WAYLAND) { let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; if (scaleFactor !== 1) { offsetX = offsetX / scaleFactor; this._label.set_style(`font-size: ${12 / scaleFactor}pt;`); } offsetX -= 1; offsetY -= 4; } // If label is on top and there is enough space above title bar move position up by label height if (this._label_on_top && this._label_height <= offsetY) { offsetY -= this._label_height; } else if (maximized) { offsetX = 0; offsetY = 0; } this._label.set_position(offsetX, offsetY); } _updateLabelColor() { let fg = new Clutter.Color({ red: 0, green: 0, blue: 0, alpha: 96, }); let bg = this._color.copy(); if (this._label_on_top) { bg.alpha = 100; } else { bg.alpha = 200; } let clutter_text = this._label.get_clutter_text(); clutter_text.set_color(fg); clutter_text.set_background_color(bg); } setColor(color) { if (this._color && this._color.equal(color)) { return; } this._color = color; this.setPipelineColor(); if (this._label) { this._updateLabelColor(); } } setPipelineColor() { if (!this._color || !this._pipeline) { return; } let s = this._color.to_string(); let cogl_color = new Cogl.Color(); cogl_color.init_from_4ub(this._color.red, this._color.green, this._color.blue, 0xc4); this._pipeline.set_color(cogl_color); } _calculate_frame_box(window, allocation) { let frame_rect = window.get_frame_rect(); let buffer_rect = window.get_buffer_rect(); let offsetX = frame_rect.x - buffer_rect.x; let offsetY = frame_rect.y - buffer_rect.y; let top = offsetY - 3; let bottom = offsetY - 1; let left = offsetX - 3; let right = offsetX - 1; let wayland = window.get_client_type() == Meta.WindowClientType.WAYLAND; if (wayland) { bottom += 4; } let fw = this._frame_width; switch (window.get_maximized()) { case Meta.MaximizeFlags.BOTH: top += fw; right += fw; bottom += fw - (wayland ? 5 : 0); left += fw; break; case Meta.MaximizeFlags.HORIZONTAL: right += fw; left += fw; break; case Meta.MaximizeFlags.VERTICAL: top += fw; bottom += fw; break; } if (window.is_fullscreen()) { top += 3; right += 2; bottom -= (wayland ? 3 : 0); left += 3; } if (!wayland && !window.decorated && !window.is_fullscreen() && (window.get_maximized() !== Meta.MaximizeFlags.BOTH)) { bottom += 4; } let x = left; let y = top + fw; let w = allocation.get_width() - (right + left); let h = allocation.get_height() - (bottom + top + fw); return [x, y, w, h]; } draw_rect(node, x, y, width, height) { const box = new Clutter.ActorBox(); box.set_origin(x, y); box.set_size(width, height); node.add_rectangle(box); } draw_hline(node, x, y, width, width_factor = 1) { this.draw_rect(node, x, y, width, this._frame_width * width_factor); } draw_vline(node, x, y, height, width_factor = 1) { this.draw_rect(node, x, y, this._frame_width * width_factor, height); } vfunc_paint_node(node, ctx) { let actor = this.get_actor(); const actorNode = new Clutter.ActorNode(actor, -1); node.add_child(actorNode); if (!this._pipeline) { let framebuffer = ctx.get_framebuffer(); let coglContext = framebuffer.get_context(); this._pipeline = new Cogl.Pipeline(coglContext); this.setPipelineColor(); } const pipelineNode = new Clutter.PipelineNode(this._pipeline); pipelineNode.set_name('Realm Frame'); node.add_child(pipelineNode); let [x, y, width, height] = this._calculate_frame_box(actor.metaWindow, actor.get_allocation_box()); // Top this.draw_hline(pipelineNode, x, y, width, 2); // Right this.draw_vline(pipelineNode, x + width, y, height); // Bottom this.draw_hline(pipelineNode, x, y + height, width); // Left this.draw_vline(pipelineNode, x, y, height); } });