2021-12-03 19:04:05 +00:00
|
|
|
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;
|
2021-12-13 19:21:01 +00:00
|
|
|
this._label_text = label_text;
|
2021-12-03 19:04:05 +00:00
|
|
|
|
|
|
|
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) {
|
2021-12-13 19:21:01 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-12-03 19:04:05 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|