From c423b7d3ce05661c64459c4bec55615d616f991f Mon Sep 17 00:00:00 2001 From: anaximeno Date: Mon, 20 May 2024 04:03:36 -0100 Subject: [PATCH] ws: Add option to display window icons --- .../workspace-switcher@cinnamon.org/applet.js | 416 ++++++++++++------ .../settings-schema.json | 17 + 2 files changed, 310 insertions(+), 123 deletions(-) diff --git a/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/applet.js index e8f8bf80a2..8b80deac1a 100644 --- a/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/applet.js @@ -11,6 +11,7 @@ const Tooltips = imports.ui.tooltips; const Settings = imports.ui.settings; const ModalDialog = imports.ui.modalDialog; const Pango = imports.gi.Pango; +const Cinnamon = imports.gi.Cinnamon; const MIN_SWITCH_INTERVAL_MS = 220; @@ -77,7 +78,7 @@ class WorkspaceButton { } } - update() { + update(options = {}) { // defined in subclass } @@ -92,6 +93,223 @@ class WorkspaceButton { } } + +class SimpleButton extends WorkspaceButton { + constructor(index, applet) { + super(index, applet); + + this.actor = new St.Button({ name: 'workspaceButton', + style_class: 'workspace-button', + reactive: applet._draggable.inhibit }); + + if (applet.orientation == St.Side.TOP || applet.orientation == St.Side.BOTTOM) { + this.actor.set_height(applet._panelHeight); + } else { + this.actor.set_width(applet._panelHeight); + this.actor.add_style_class_name('vertical'); + } + + let label = new St.Label({ text: (index + 1).toString() }); + label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE); + this.actor.set_child(label); + this.update(); + } + + activate(active) { + if (active) { + this.actor.add_style_pseudo_class('outlined'); + } + else { + this.actor.remove_style_pseudo_class('outlined'); + this.update(); + } + } + + shade(used) { + if (!used) { + this.actor.add_style_pseudo_class('shaded'); + } + else { + this.actor.remove_style_pseudo_class('shaded'); + } + } + + update(options = {}) { + let windows = this.workspace.list_windows(); + let used = windows.some(Main.isInteresting); + this.shade(used); + } +} + + +class WindowGraph { + constructor(workspaceGraph, metaWindow, showIcon, iconSize) { + this.workspaceGraph = workspaceGraph; + this.metaWindow = metaWindow; + this.showIcon = showIcon; + this.iconSize = iconSize; + + this.drawingArea = new St.DrawingArea({ + style_class: 'windows', + width: this.workspaceGraph.width, + height: this.workspaceGraph.height, + important: true, + }); + + this.drawingArea.connect('repaint', this.onRepaint.bind(this)); + + this._icon = this.showIcon ? undefined : this.getIcon(); + + if (this.showIcon) { + const [x, y] = this.calcIconPos(); + this.icon.set_x(x); + this.icon.set_y(y); + } + } + + get icon() { + if (!this._icon) { + this._icon = this.getIcon(); + } + + return this._icon; + } + + calcIconPos(rect = undefined) { + if (!rect) { + rect = this.intersectionRect(); + } + + const x = Math.round(rect.x + rect.width / 2 - this.iconSize * global.ui_scale / 2); + const y = Math.round(rect.y + rect.height / 2 - this.iconSize * global.ui_scale / 2); + + return [x, y]; + } + + /** + * Intersection between the scaled window rect area + * and the workspace graph area. + */ + intersectionRect() { + const intersection = new Meta.Rectangle(); + const rect = this.scaledRect(); + + const workspace_rect = this.workspaceGraph.workspace_size; + const scale_factor = this.workspaceGraph.scaleFactor; + + const workspace_x = Math.round(workspace_rect.x / scale_factor); + const workspace_y = Math.round(workspace_rect.y / scale_factor); + + const offsetX = workspace_x - rect.x; + const offsetY = workspace_y - rect.y; + + const heightSurplus = Math.max(0, -offsetY + rect.height - this.workspaceGraph.height); + const widthSurplus = Math.max(0, -offsetX + rect.width - this.workspaceGraph.width); + + intersection.x = Math.max(workspace_x, rect.x); + intersection.y = Math.max(workspace_y, rect.y); + intersection.width = Math.round(rect.width - Math.max(0, offsetX) - widthSurplus); + intersection.height = Math.round(rect.height - Math.max(0, offsetY) - heightSurplus); + + return intersection; + } + + scaledRect() { + const scaled_rect = new Meta.Rectangle(); + const windows_rect = this.metaWindow.get_buffer_rect(); + const workspace_rect = this.workspaceGraph.workspace_size; + const scale_factor = this.workspaceGraph.scaleFactor; + scaled_rect.x = Math.round((windows_rect.x - workspace_rect.x) / scale_factor); + scaled_rect.y = Math.round((windows_rect.y - workspace_rect.y) / scale_factor); + scaled_rect.width = Math.round(windows_rect.width / scale_factor); + scaled_rect.height = Math.round(windows_rect.height / scale_factor); + return scaled_rect; + } + + onRepaint(area) { + const [winBackgroundColor, winBorderColor] = this.getWinThemeColors(); + const rect = this.intersectionRect(); + const cr = area.get_context(); + + cr.setLineWidth(1); + + Clutter.cairo_set_source_color(cr, winBorderColor); + cr.rectangle(rect.x, rect.y, rect.width, rect.height); + + cr.strokePreserve(); + + Clutter.cairo_set_source_color(cr, winBackgroundColor); + + cr.fill(); + cr.$dispose(); + + if (this.showIcon) { + const [x, y] = this.calcIconPos(rect); + this.icon.set_x(x); + this.icon.set_y(y); + } + } + + getWinThemeColors() { + const graphThemeNode = this.workspaceGraph.graphArea.get_theme_node(); + let windowBackgroundColor, windowBorderColor; + + if (this.metaWindow.has_focus()) { + windowBorderColor = graphThemeNode.get_color('-active-window-border'); + windowBackgroundColor = graphThemeNode.get_color('-active-window-background'); + } else { + windowBorderColor = graphThemeNode.get_color('-inactive-window-border'); + windowBackgroundColor = graphThemeNode.get_color('-inactive-window-background'); + } + + return [windowBackgroundColor, windowBorderColor]; + } + + getIcon() { + let iconActor = null; + let tracker = Cinnamon.WindowTracker.get_default(); + let app = tracker.get_window_app(this.metaWindow); + + if (app) { + iconActor = app.create_icon_texture_for_window( + this.iconSize, + this.metaWindow + ); + } + + if (!iconActor) { + iconActor = new St.Icon({ + icon_name: 'applications-other', + icon_type: St.IconType.FULLCOLOR, + icon_size: this.iconSize, + }); + } + + return iconActor; + } + + destroy() { + if (this.showIcon && this._icon) { + this._icon.destroy(); + } + + this.drawingArea.destroy(); + } + + update(options = {}) { + this.drawingArea.queue_repaint(); + } + + show() { + this.workspaceGraph.graphArea.add_child(this.drawingArea); + + if (this.showIcon) { + this.workspaceGraph.graphArea.add_child(this.icon); + } + } +} + + class WorkspaceGraph extends WorkspaceButton { constructor(index, applet) { super(index, applet); @@ -105,14 +323,20 @@ class WorkspaceGraph extends WorkspaceButton { this.graphArea = new St.DrawingArea({ style_class: 'windows', important: true }); this.actor.add_actor(this.graphArea); - this.panelApplet = applet; this.graphArea.set_size(1, 1); - this.graphArea.connect('repaint', Lang.bind(this, this.onRepaint)); + this.graphArea.connect('repaint', this.onRepaint.bind(this)); + + this.focusGraph = undefined; + this.windowsGraphs = []; + + this.height = 1; + this.width = 1; } - getSizeAdjustment (actor, vertical) { - let themeNode = actor.get_theme_node() + getSizeAdjustment(actor, vertical) { + let themeNode = actor.get_theme_node(); + if (vertical) { return themeNode.get_horizontal_padding() + themeNode.get_border_width(St.Side.LEFT) + @@ -125,65 +349,39 @@ class WorkspaceGraph extends WorkspaceButton { } } - setGraphSize () { + setGraphSize() { this.workspace_size = this.workspace.get_work_area_all_monitors(); - let height, width; - if (this.panelApplet.orientation == St.Side.LEFT || - this.panelApplet.orientation == St.Side.RIGHT) { + if (this.applet.orientation == St.Side.LEFT || + this.applet.orientation == St.Side.RIGHT) { - width = this.panelApplet._panelHeight - - this.getSizeAdjustment(this.panelApplet.actor, true) - + this.width = this.applet._panelHeight - + this.getSizeAdjustment(this.applet.actor, true) - this.getSizeAdjustment(this.actor, true); - this.scaleFactor = this.workspace_size.width / width; - height = Math.round(this.workspace_size.height / this.scaleFactor); + this.scaleFactor = this.workspace_size.width / this.width; + this.height = Math.round(this.workspace_size.height / this.scaleFactor); } else { - height = this.panelApplet._panelHeight - - this.getSizeAdjustment(this.panelApplet.actor, false) - + this.height = this.applet._panelHeight - + this.getSizeAdjustment(this.applet.actor, false) - this.getSizeAdjustment(this.actor, false); - this.scaleFactor = this.workspace_size.height / height; - width = Math.round(this.workspace_size.width / this.scaleFactor); + this.scaleFactor = this.workspace_size.height / this.height; + this.width = Math.round(this.workspace_size.width / this.scaleFactor); } - this.graphArea.set_size(width, height); - } - - scale (windows_rect, workspace_rect) { - let scaled_rect = new Meta.Rectangle(); - scaled_rect.x = Math.round((windows_rect.x - workspace_rect.x) / this.scaleFactor); - scaled_rect.y = Math.round((windows_rect.y - workspace_rect.y) / this.scaleFactor); - scaled_rect.width = Math.round(windows_rect.width / this.scaleFactor); - scaled_rect.height = Math.round(windows_rect.height / this.scaleFactor); - return scaled_rect; + this.graphArea.set_size(this.width, this.height); } - sortWindowsByUserTime (win1, win2) { + sortWindowsByUserTime(win1, win2) { let t1 = win1.get_user_time(); let t2 = win2.get_user_time(); return (t2 < t1) ? 1 : -1; } - paintWindow(metaWindow, themeNode, cr) { - let windowBackgroundColor; - let windowBorderColor; - - let scaled_rect = this.scale(metaWindow.get_buffer_rect(), this.workspace_size); - - if (metaWindow.has_focus()) { - windowBorderColor = themeNode.get_color('-active-window-border'); - windowBackgroundColor = themeNode.get_color('-active-window-background'); - } else { - windowBorderColor = themeNode.get_color('-inactive-window-border'); - windowBackgroundColor = themeNode.get_color('-inactive-window-background'); - } - - Clutter.cairo_set_source_color(cr, windowBorderColor); - cr.rectangle(scaled_rect.x, scaled_rect.y, scaled_rect.width, scaled_rect.height); - cr.strokePreserve(); - - Clutter.cairo_set_source_color(cr, windowBackgroundColor); - cr.fill(); + filterWindows(win) { + return Main.isInteresting(win) && + !win.is_skip_taskbar() && + !win.minimized; } onRepaint(area) { @@ -191,44 +389,51 @@ class WorkspaceGraph extends WorkspaceButton { // accurate measurements until everything is added to the stage if (this.scaleFactor === 0) this.setGraphSize(); - let graphThemeNode = this.graphArea.get_theme_node(); - let cr = area.get_context(); - cr.setLineWidth(1); - // construct a list with all windows let windows = this.workspace.list_windows(); - windows = windows.filter( Main.isInteresting ); - windows = windows.filter( - function(w) { - return !w.is_skip_taskbar() && !w.minimized; - }); - + windows = windows.filter(this.filterWindows); windows.sort(this.sortWindowsByUserTime); - if (windows.length) { - let focusWindow = null; + this.graphArea.remove_all_children(); - for (let i = 0; i < windows.length; ++i) { - let metaWindow = windows[i]; + if (this.windowsGraphs) { + this.windowsGraphs.forEach(e => e.destroy()); + } - if (metaWindow.has_focus()) { - focusWindow = metaWindow; - continue; - } + const showIcon = this.applet.show_window_icons; + const iconSize = this.applet.window_icon_size; - this.paintWindow(metaWindow, graphThemeNode, cr); - } + this.windowsGraphs = []; + this.focusGraph = undefined; + for (const window of windows) { + const graph = new WindowGraph(this, window, showIcon, iconSize); - if (focusWindow) { - this.paintWindow(focusWindow, graphThemeNode, cr); + this.windowsGraphs.push(graph); + + if (window.has_focus()) { + this.focusGraph = graph; + } else { + graph.show(); + graph.update(); } } - cr.$dispose(); + if (this.focusGraph) { + this.focusGraph.show(); + this.focusGraph.update(); + } } - update() { - this.graphArea.queue_repaint(); + update(options = {}) { + const stateChanged = options.stateChanged; + + if ((stateChanged == "position-changed" || stateChanged == "size-changed") && + this.focusGraph && this.focusGraph.metaWindow.has_focus() + ) { + this.focusGraph.update(options); + } else { + this.graphArea.queue_repaint(); + } } activate(active) { @@ -237,55 +442,14 @@ class WorkspaceGraph extends WorkspaceButton { else this.actor.remove_style_pseudo_class('active'); } -} - -class SimpleButton extends WorkspaceButton { - constructor(index, applet) { - super(index, applet); - - this.actor = new St.Button({ name: 'workspaceButton', - style_class: 'workspace-button', - reactive: applet._draggable.inhibit }); - - if (applet.orientation == St.Side.TOP || applet.orientation == St.Side.BOTTOM) { - this.actor.set_height(applet._panelHeight); - } else { - this.actor.set_width(applet._panelHeight); - this.actor.add_style_class_name('vertical'); - } - - let label = new St.Label({ text: (index + 1).toString() }); - label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE); - this.actor.set_child(label); - this.update(); - } - activate(active) { - if (active) { - this.actor.add_style_pseudo_class('outlined'); - } - else { - this.actor.remove_style_pseudo_class('outlined'); - this.update(); - } - } - - shade(used) { - if (!used) { - this.actor.add_style_pseudo_class('shaded'); - } - else { - this.actor.remove_style_pseudo_class('shaded'); - } - } - - update() { - let windows = this.workspace.list_windows(); - let used = windows.some(Main.isInteresting); - this.shade(used); + destroy() { + this.windowsGraphs.forEach(e => e.destroy()); + super.destroy(); } } + class CinnamonWorkspaceSwitcher extends Applet.Applet { constructor(metadata, orientation, panel_height, instance_id) { super(orientation, panel_height, instance_id); @@ -306,6 +470,8 @@ class CinnamonWorkspaceSwitcher extends Applet.Applet { this.settings = new Settings.AppletSettings(this, metadata.uuid, instance_id); this.settings.bind("display-type", "display_type", this.queueCreateButtons); this.settings.bind("scroll-behavior", "scroll_behavior"); + this.settings.bind("show-window-icons", "show_window_icons", this.updateButtons); + this.settings.bind("window-icon-size", "window_icon_size", this.updateButtons); this.actor.connect('scroll-event', this.hook.bind(this)); @@ -409,6 +575,10 @@ class CinnamonWorkspaceSwitcher extends Applet.Applet { } } + updateButtons() { + this.buttons.forEach(btn => btn.update()); + } + _createButtons() { this.createButtonsQueued = false; for (let i = 0; i < this.buttons.length; ++i) { @@ -453,14 +623,14 @@ class CinnamonWorkspaceSwitcher extends Applet.Applet { return; this._focusWindow = global.display.focus_window; - this.signals.connect(this._focusWindow, "position-changed", Lang.bind(this, this._onPositionChanged), this); - this.signals.connect(this._focusWindow, "size-changed", Lang.bind(this, this._onPositionChanged), this); - this._onPositionChanged(); + this.signals.connect(this._focusWindow, "position-changed", () => this._onWindowsStateChanged("position-changed"), this); + this.signals.connect(this._focusWindow, "size-changed", () => this._onWindowsStateChanged("size-changed"), this); + this._onWindowsStateChanged("focus-changed"); } - _onPositionChanged() { + _onWindowsStateChanged(stateChanged) { let button = this.buttons[global.workspace_manager.get_active_workspace_index()]; - button.update(); + button.update({stateChanged: stateChanged}); } on_applet_removed_from_panel() { diff --git a/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/settings-schema.json b/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/settings-schema.json index aedfec819e..b474c042e3 100644 --- a/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/settings-schema.json +++ b/files/usr/share/cinnamon/applets/workspace-switcher@cinnamon.org/settings-schema.json @@ -9,6 +9,23 @@ "Simple buttons": "buttons" } }, + "show-window-icons": { + "type": "switch", + "dependency": "display-type=visual", + "description": "Display window icons", + "default": false + }, + "window-icon-size": { + "type": "spinbutton", + "default": 12, + "min": 8, + "max": 32, + "step": 2, + "units": "px", + "description": "Window icon size", + "dependency": "display-type=visual", + "tooltip": "Depending on the size, the icons might look more or less blurry due to the rescaling." + }, "scroll-behavior": { "type": "combobox", "default": "normal",