summaryrefslogtreecommitdiff
path: root/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz
RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm')
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/extension.js580
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/en/LC_MESSAGES/unity-window-switcher.mobin0 -> 1732 bytes
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/hu/LC_MESSAGES/unity-window-switcher.mobin0 -> 1839 bytes
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/metadata.json14
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/prefs.js256
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/gschemas.compiledbin0 -> 1060 bytes
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/org.gnome.shell.extensions.unity-window-switcher-reforged.gschema.xml66
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/settings.ui215
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/utils.js346
9 files changed, 1477 insertions, 0 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/extension.js b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/extension.js
new file mode 100644
index 0000000..b188440
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/extension.js
@@ -0,0 +1,580 @@
+import Atk from "gi://Atk";
+import Clutter from "gi://Clutter";
+import Meta from "gi://Meta";
+import St from "gi://St";
+
+import Gio from 'gi://Gio';
+import GObject from 'gi://GObject';
+import GLib from 'gi://GLib';
+import Shell from 'gi://Shell';
+
+import * as Main from 'resource:///org/gnome/shell/ui/main.js';
+import * as AltTab from 'resource:///org/gnome/shell/ui/altTab.js';
+import * as SwitcherPopup from 'resource:///org/gnome/shell/ui/switcherPopup.js';
+
+import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
+
+import * as Utils from './utils.js';
+
+
+const baseIconSizes = [96, 64, 48, 32, 22];
+
+
+let injections = {};
+let extensionInstance = null;
+
+function getWindows(workspace) {
+ let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, workspace);
+ return windows.map(w => {
+ return w.is_attached_dialog() ? w.get_transient_for() : w;
+ }).filter((w, i, a) => w && !w.skip_taskbar && a.indexOf(w) === i);
+}
+
+function _finish(timestamp) {
+ this._currentWindow = this._currentWindow < 0 ? 0 : this._currentWindow;
+ return injections._finish.call(this, timestamp);
+}
+
+function _initialSelection(backward, binding) {
+ if (backward || binding != 'switch-applications'
+ || this._items.length == 0 || this._items[0].cachedWindows.length < 2) {
+ injections._initialSelection.call(this, backward, binding);
+ return;
+ }
+
+ let ws = global.workspace_manager.get_active_workspace();
+ let wt = Shell.WindowTracker.get_default();
+ let tab_list = global.display.get_tab_list(Meta.TabList.NORMAL, ws);
+
+ if (!tab_list || tab_list.length < 2) {
+ injections._initialSelection.call(this, backward, binding);
+ return;
+ }
+
+ let currentApp = wt.get_window_app(tab_list[0]);
+ let secondApp = wt.get_window_app(tab_list[1]);
+
+ if (currentApp == secondApp) {
+ this._select(0, 1);
+ } else {
+ injections._initialSelection.call(this, backward, binding);
+ }
+}
+
+function highlight2(index, justOutline) {
+ if (this._items[this._highlighted]) {
+ this._items[this._highlighted].remove_style_pseudo_class('outlined');
+ this._items[this._highlighted].remove_style_pseudo_class('selected');
+ }
+
+ if (this._items[index]) {
+ if (justOutline)
+ this._items[index].add_style_pseudo_class('outlined');
+ else
+ this._items[index].add_style_pseudo_class('selected');
+ }
+
+ this._highlighted = index;
+
+ // GNOME 45+ compatibility: use hadjustment instead of hscroll.adjustment
+ let adjustment = this._scrollView.hadjustment || (this._scrollView.hscroll ? this._scrollView.hscroll.adjustment : null);
+ if (adjustment) {
+ this._scroll(index);
+ }
+}
+
+function _scroll(index) {
+ // GNOME 45+ compatibility: use hadjustment instead of hscroll.adjustment
+ let adjustment = this._scrollView.hadjustment || (this._scrollView.hscroll ? this._scrollView.hscroll.adjustment : null);
+ if (!adjustment) return;
+
+ let { upper, page_size: pageSize } = adjustment;
+
+ let n = this._items.length;
+ let fakeSize = 2;
+
+ this._scrollableRight = index !== n - 1;
+ this._scrollableLeft = index !== 0;
+ if (upper === pageSize)
+ return;
+
+ let item = this._items[index];
+ if (!item) return;
+
+ let sizeItem = (item.allocation.x2 - item.allocation.x1);
+ let value = (upper - pageSize + sizeItem) * (index / n);
+ let maxScrollingAmount = (upper - pageSize);
+ let percentaje = (index - fakeSize) / (n - 1 - 2 * fakeSize);
+ value = percentaje * maxScrollingAmount;
+
+ if (index < fakeSize || percentaje <= 0) {
+ this._scrollableLeft = false;
+ value = 0;
+ } else if (index >= n - fakeSize || percentaje >= 1) {
+ this._scrollableRight = false;
+ value = maxScrollingAmount;
+ }
+
+ adjustment.ease(value, {
+ progress_mode: Clutter.AnimationMode.EASE_OUT_EXPO,
+ duration: 250,
+ onComplete: () => {
+ this.queue_relayout();
+ },
+ });
+}
+
+function addColours(settings) {
+ injections.WINDOW_PREVIEW_SIZE = AltTab.WINDOW_PREVIEW_SIZE;
+
+ if (AltTab.AppSwitcherPopup) {
+ injections._init = AltTab.AppSwitcherPopup.prototype._init;
+ AltTab.AppSwitcherPopup.prototype._init = function (items, mask, action, timeout) {
+ // Call original
+ injections._init.call(this, items, mask, action, timeout);
+
+ // Custom additions
+ this._thumbnails = null;
+ this._thumbnailTimeoutId = 0;
+ this._currentWindow = -1;
+ this.thumbnailsVisible = false;
+
+ let apps = Shell.AppSystem.get_default().get_running();
+
+ // Remove the original switcher list if it exists
+ if (this._switcherList && typeof this._switcherList.destroy === 'function') {
+ try {
+ this._switcherList.destroy();
+ } catch (e) {
+ // Already destroyed or other error
+ }
+ }
+
+ this._switcherList = new AppSwitcher(apps, this, settings);
+ this._items = this._switcherList.icons;
+
+ this.add_child(this._switcherList);
+ };
+ }
+
+ injections.highlight2 = SwitcherPopup.SwitcherList.prototype.highlight;
+ SwitcherPopup.SwitcherList.prototype.highlight = highlight2;
+
+ injections._scroll = SwitcherPopup.SwitcherList.prototype._scroll;
+ SwitcherPopup.SwitcherList.prototype._scroll = _scroll;
+}
+
+function removeColours() {
+ if (AltTab.AppSwitcherPopup && injections._init) {
+ AltTab.AppSwitcherPopup.prototype._init = injections._init;
+ }
+
+ if (injections.highlight2) {
+ SwitcherPopup.SwitcherList.prototype.highlight = injections.highlight2;
+ injections.highlight2 = undefined;
+ }
+
+ if (injections._scroll) {
+ SwitcherPopup.SwitcherList.prototype._scroll = injections._scroll;
+ injections._scroll = undefined;
+ }
+}
+
+function setInitialSelection() {
+ if (!injections._finish && AltTab.AppSwitcherPopup) {
+ injections._finish = AltTab.AppSwitcherPopup.prototype._finish;
+ AltTab.AppSwitcherPopup.prototype._finish = _finish;
+ }
+
+ if (!injections._initialSelection && AltTab.AppSwitcherPopup) {
+ injections._initialSelection = AltTab.AppSwitcherPopup.prototype._initialSelection;
+ AltTab.AppSwitcherPopup.prototype._initialSelection = _initialSelection;
+ }
+}
+
+function resetInitialSelection() {
+ if (injections._finish && AltTab.AppSwitcherPopup) {
+ AltTab.AppSwitcherPopup.prototype._finish = injections._finish;
+ injections._finish = undefined;
+ }
+
+ if (injections._initialSelection && AltTab.AppSwitcherPopup) {
+ AltTab.AppSwitcherPopup.prototype._initialSelection = injections._initialSelection;
+ injections._initialSelection = undefined;
+ }
+}
+
+class MyExtension {
+ constructor(settings) {
+ this._settings = settings;
+ this._connectSettings();
+ this._firstChangeWindowChanged();
+ this._updateHoverSettings();
+ }
+
+ _connectSettings() {
+ this._settingsHandlerFirstSwitch = this._settings.connect(
+ 'changed::first-change-window',
+ this._firstChangeWindowChanged.bind(this)
+ );
+
+ this._settingsHandlers = [];
+ [
+ 'hover-shade-level', 'hover-border-width', 'hover-glow-size',
+ 'hover-opacity', 'icon-spacing',
+ 'container-padding', 'background-color', 'border-size', 'border-color',
+ 'background-border-radius', 'icon-border-radius'
+ ].forEach(key => {
+ this._settingsHandlers.push(
+ this._settings.connect(`changed::${key}`, this._updateHoverSettings.bind(this))
+ );
+ });
+ }
+
+ _updateHoverSettings() {
+ const keys = this._settings.list_keys();
+ this.hoverShadeLevel = keys.includes('hover-shade-level') ? this._settings.get_double('hover-shade-level') : 0.2;
+ this.hoverBorderWidth = keys.includes('hover-border-width') ? this._settings.get_int('hover-border-width') : 2;
+ this.hoverGlowSize = keys.includes('hover-glow-size') ? this._settings.get_int('hover-glow-size') : 15;
+ this.hoverOpacity = keys.includes('hover-opacity') ? this._settings.get_double('hover-opacity') : 0.8;
+
+ this.iconSpacing = keys.includes('icon-spacing') ? this._settings.get_int('icon-spacing') : 10;
+ this.containerPadding = keys.includes('container-padding') ? this._settings.get_int('container-padding') : 24;
+ this.backgroundColor = keys.includes('background-color') ? this._settings.get_string('background-color') : 'rgba(0, 0, 0, 0.5)';
+ this.borderSize = keys.includes('border-size') ? this._settings.get_int('border-size') : 2;
+ this.borderColor = keys.includes('border-color') ? this._settings.get_string('border-color') : 'rgba(255, 255, 255, 0.2)';
+ this.backgroundBorderRadius = keys.includes('background-border-radius') ? this._settings.get_int('background-border-radius') : 24;
+ this.iconBorderRadius = keys.includes('icon-border-radius') ? this._settings.get_int('icon-border-radius') : 16;
+ }
+
+ _firstChangeWindowChanged() {
+ this._firstChangeWindow = this._settings.get_boolean('first-change-window');
+ if (this._firstChangeWindow) {
+ setInitialSelection();
+ } else {
+ resetInitialSelection();
+ }
+ }
+
+ destroy() {
+ this._disconnectSettings();
+ resetInitialSelection();
+ }
+
+ _disconnectSettings() {
+ if (this._settingsHandlerFirstSwitch) {
+ this._settings.disconnect(this._settingsHandlerFirstSwitch);
+ this._settingsHandlerFirstSwitch = 0;
+ }
+ if (this._settingsHandlers) {
+ this._settingsHandlers.forEach(h => this._settings.disconnect(h));
+ this._settingsHandlers = [];
+ }
+ }
+}
+
+export default class UnityLikeAppSwitcherExtension extends Extension {
+ enable() {
+ this.initTranslations("unity-window-switcher");
+ const settings = this.getSettings();
+ extensionInstance = new MyExtension(settings);
+
+ addColours(settings);
+ }
+
+ disable() {
+ removeColours();
+
+ if (extensionInstance) {
+ extensionInstance.destroy();
+ extensionInstance = null;
+ }
+ }
+}
+
+const AppSwitcher = GObject.registerClass(
+ class AppSwitcher extends SwitcherPopup.SwitcherList {
+ _init(apps, altTabPopup, extensionSettings) {
+ super._init(true);
+
+ this.icons = [];
+ this._arrows = [];
+ this._extensionSettings = extensionSettings;
+
+ let windowTracker = Shell.WindowTracker.get_default();
+ let settings = new Gio.Settings({ schema_id: 'org.gnome.shell.app-switcher' });
+
+ let workspace = null;
+ if (settings.get_boolean('current-workspace-only')) {
+ let workspaceManager = global.workspace_manager;
+ workspace = workspaceManager.get_active_workspace();
+ }
+
+ let allWindows = getWindows(workspace);
+
+ for (let i = 0; i < apps.length; i++) {
+ let appIcon = new AltTab.AppIcon(apps[i]);
+ appIcon.cachedWindows = allWindows.filter(
+ w => windowTracker.get_window_app(w) === appIcon.app);
+ if (appIcon.cachedWindows.length > 0)
+ this._addIcon(appIcon);
+ }
+
+ this._altTabPopup = altTabPopup;
+ this._delayedHighlighted = -1;
+ this._mouseTimeOutId = 0;
+
+ // Apply container styles
+ const keys = this._extensionSettings.list_keys();
+ const padding = keys.includes('container-padding') ? this._extensionSettings.get_int('container-padding') : 24;
+ const bgColor = keys.includes('background-color') ? this._extensionSettings.get_string('background-color') : 'rgba(0, 0, 0, 0.5)';
+ const borderWidth = keys.includes('border-size') ? this._extensionSettings.get_int('border-size') : 2;
+ const borderColor = keys.includes('border-color') ? this._extensionSettings.get_string('border-color') : 'rgba(255, 255, 255, 0.2)';
+ const spacing = keys.includes('icon-spacing') ? this._extensionSettings.get_int('icon-spacing') : 10;
+ const borderRadius = keys.includes('background-border-radius') ? this._extensionSettings.get_int('background-border-radius') : 24;
+ this._iconBorderRadius = keys.includes('icon-border-radius') ? this._extensionSettings.get_int('icon-border-radius') : 16;
+
+ this.set_style(`
+ padding: ${padding}px;
+ background-color: ${bgColor};
+ border: ${borderWidth}px solid ${borderColor};
+ border-radius: ${borderRadius}px;
+ `);
+ this._list.set_style(`spacing: ${spacing}px;`);
+
+ this.connect('destroy', this._onDestroy.bind(this));
+ }
+
+ _onDestroy() {
+ if (this._mouseTimeOutId !== 0)
+ GLib.source_remove(this._mouseTimeOutId);
+
+ this.icons.forEach(
+ icon => icon.app.disconnectObject(this));
+ }
+
+ _setIconSize() {
+ let j = 0;
+ while (j < this._items.length && this._items[j].style_class !== 'item-box')
+ j++;
+
+ if (j >= this._items.length) return;
+
+ let themeNode = this._items[j].get_theme_node();
+ this._list.ensure_style();
+
+ let iconPadding = themeNode.get_horizontal_padding();
+ let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
+ let labelNaturalHeight = 0;
+ if (this.icons[j].label) {
+ [, labelNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
+ }
+ let iconSpacing = labelNaturalHeight + iconPadding + iconBorder;
+
+ const keys = this._extensionSettings.list_keys();
+ const spacing = keys.includes('icon-spacing') ? this._extensionSettings.get_int('icon-spacing') : 10;
+ let totalSpacing = spacing * (this._items.length - 1);
+
+ let primary = Main.layoutManager.primaryMonitor;
+ let parent = this.get_parent();
+ let parentPadding = parent ? parent.get_theme_node().get_horizontal_padding() : 0;
+ let availWidth = primary.width - parentPadding - this.get_theme_node().get_horizontal_padding();
+
+ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
+ let iconSizes = baseIconSizes.map(s => s * scaleFactor);
+ let iconSize = baseIconSizes[0];
+
+ if (this._items.length > 1) {
+ for (let i = 0; i < baseIconSizes.length; i++) {
+ iconSize = baseIconSizes[i];
+ let height = iconSizes[i] + iconSpacing;
+ let w = height * this._items.length + totalSpacing;
+ if (w <= availWidth)
+ break;
+ }
+ }
+
+ this._iconSize = iconSize;
+
+ for (let i = 0; i < this.icons.length; i++) {
+ if (this.icons[i] && this.icons[i].icon == null) {
+ this.icons[i].set_size(iconSize);
+ }
+ }
+ }
+
+ vfunc_get_preferred_height(forWidth) {
+ if (!this._iconSize)
+ this._setIconSize();
+
+ return super.vfunc_get_preferred_height(forWidth);
+ }
+
+ vfunc_allocate(box) {
+ super.vfunc_allocate(box);
+
+ let contentBox = this.get_theme_node().get_content_box(box);
+ let arrowHeight = Math.floor(this.get_theme_node().get_padding(St.Side.BOTTOM) / 3);
+ let arrowWidth = arrowHeight * 2;
+
+ let childBox = new Clutter.ActorBox();
+ for (let i = 0; i < this._items.length; i++) {
+ let itemBox = this._items[i].allocation;
+ childBox.x1 = contentBox.x1 + Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2);
+ childBox.x2 = childBox.x1 + arrowWidth;
+ childBox.y1 = contentBox.y1 + itemBox.y2 + arrowHeight;
+ childBox.y2 = childBox.y1 + arrowHeight;
+ if (this._arrows[i])
+ this._arrows[i].allocate(childBox);
+ }
+ }
+
+ _onItemMotion(item) {
+ if (item === this._items[this._highlighted] ||
+ item === this._items[this._delayedHighlighted])
+ return Clutter.EVENT_PROPAGATE;
+
+ const index = this._items.indexOf(item);
+
+ if (this._mouseTimeOutId !== 0) {
+ GLib.source_remove(this._mouseTimeOutId);
+ this._delayedHighlighted = -1;
+ this._mouseTimeOutId = 0;
+ }
+
+ if (this._altTabPopup && this._altTabPopup.thumbnailsVisible) {
+ this._delayedHighlighted = index;
+ this._mouseTimeOutId = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ AltTab.APP_ICON_HOVER_TIMEOUT,
+ () => {
+ this._enterItem(index);
+ this._delayedHighlighted = -1;
+ this._mouseTimeOutId = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+ } else {
+ this._itemEntered(index);
+ }
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ _enterItem(index) {
+ let [x, y] = global.get_pointer();
+ let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
+ if (this._items[index] && this._items[index].contains(pickedActor))
+ this._itemEntered(index);
+ }
+
+ highlight(n, justOutline) {
+ if (this.icons[this._highlighted]) {
+ if (this._arrows[this._highlighted]) {
+ if (this.icons[this._highlighted].cachedWindows.length === 1)
+ this._arrows[this._highlighted].hide();
+ else
+ this._arrows[this._highlighted].remove_style_pseudo_class('highlighted');
+ }
+ }
+
+ let previous = this._items[this._highlighted];
+ if (previous && previous.originalStyle) {
+ previous.set_style(previous.originalStyle);
+ }
+
+ let item = this._items[n];
+ if (item && item.colorPalette && item.colorPalette.baseRgb) {
+ let style = item.originalStyle || '';
+
+ // Read directly from the settings object passed to AppSwitcher
+ const keys = this._extensionSettings.list_keys();
+ let shadeLevel = keys.includes('hover-shade-level') ? this._extensionSettings.get_double('hover-shade-level') : 0.2;
+ let borderWidth = keys.includes('hover-border-width') ? this._extensionSettings.get_int('hover-border-width') : 2;
+ let glowSize = keys.includes('hover-glow-size') ? this._extensionSettings.get_int('hover-glow-size') : 15;
+ let opacity = keys.includes('hover-opacity') ? this._extensionSettings.get_double('hover-opacity') : 0.8;
+ let iconBorderRadius = this._iconBorderRadius;
+
+ let r = Math.round(Math.min(Math.max(item.colorPalette.baseRgb.r * (1 + shadeLevel), 0), 255));
+ let g = Math.round(Math.min(Math.max(item.colorPalette.baseRgb.g * (1 + shadeLevel), 0), 255));
+ let b = Math.round(Math.min(Math.max(item.colorPalette.baseRgb.b * (1 + shadeLevel), 0), 255));
+ let hoverColor = `rgba(${r}, ${g}, ${b}, ${opacity})`;
+
+ item.set_style(style +
+ ' box-shadow: inset 0 0 ' + glowSize + 'px ' + hoverColor + ', 0 0 0 ' + borderWidth + 'px ' + hoverColor + ' !important; ' +
+ ' border: 3px solid transparent !important; ' +
+ ' border-radius: ' + iconBorderRadius + 'px !important;');
+ }
+
+ highlight2.call(this, n, justOutline);
+ this._curApp = n;
+
+ if (this._highlighted !== -1 && this.icons[this._highlighted] && this._arrows[this._highlighted]) {
+ if (justOutline && this.icons[this._highlighted].cachedWindows.length === 1)
+ this._arrows[this._highlighted].show();
+ else
+ this._arrows[this._highlighted].add_style_pseudo_class('highlighted');
+ }
+ }
+
+ _addIcon(appIcon) {
+ this.icons.push(appIcon);
+ let item = this.addItem(appIcon, appIcon.label);
+
+ if (appIcon.label) {
+ appIcon.label.set_style('text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.7); font-weight: bold;');
+ }
+
+ try {
+ item.colorPalette = new Utils.DominantColorExtractor(appIcon.app)._getColorPalette();
+ } catch (e) {
+ item.colorPalette = null;
+ }
+
+ const iconBorderRadius = this._iconBorderRadius;
+
+ if (item.colorPalette == null) {
+ item.colorPalette = {
+ original: '#000000',
+ lighter: '#666666',
+ darker: '#000000',
+ baseRgb: { r: 0, g: 0, b: 0 }
+ }
+ }
+ let hex = item.colorPalette.original;
+ let rgb = Utils.ColorUtils._hexToRgb(hex);
+ // Force background-color and remove background-image (gradients) using !important
+ item.originalStyle = 'background-color: rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ', 0.6) !important; background-image: none !important; border: 3px solid transparent; border-radius: ' + iconBorderRadius + 'px !important;';
+ item.set_style(item.originalStyle);
+
+ appIcon.app.connectObject('notify::state', app => {
+ if (app.state !== Shell.AppState.RUNNING)
+ this._removeIcon(app);
+ }, this);
+
+ let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
+ arrow.connect('repaint', () => SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM));
+ this.add_child(arrow);
+ this._arrows.push(arrow);
+
+ if (appIcon.cachedWindows.length === 1)
+ arrow.hide();
+ else
+ item.add_accessible_state(Atk.StateType.EXPANDABLE);
+ }
+
+ _removeIcon(app) {
+ let index = this.icons.findIndex(icon => {
+ return icon.app === app;
+ });
+ if (index === -1)
+ return;
+
+ if (this._arrows[index]) {
+ this._arrows[index].destroy();
+ this._arrows.splice(index, 1);
+ }
+
+ this.icons.splice(index, 1);
+ this.removeItem(index);
+ }
+ });
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/en/LC_MESSAGES/unity-window-switcher.mo b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/en/LC_MESSAGES/unity-window-switcher.mo
new file mode 100644
index 0000000..5f0d96e
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/en/LC_MESSAGES/unity-window-switcher.mo
Binary files differ
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/hu/LC_MESSAGES/unity-window-switcher.mo b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/hu/LC_MESSAGES/unity-window-switcher.mo
new file mode 100644
index 0000000..9e8b840
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/locale/hu/LC_MESSAGES/unity-window-switcher.mo
Binary files differ
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/metadata.json b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/metadata.json
new file mode 100644
index 0000000..1d1638c
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/metadata.json
@@ -0,0 +1,14 @@
+{
+ "name": "Unity-like App Switcher (Reforged)",
+ "description": "A modern, AI-reforged version of the Unity-like AppSwitcher for GNOME 48+. Bigger, more colorful, and highly customizable.",
+ "original-authors": "gonza",
+ "uuid": "unity-like-appswitcher-reforged@gabeszm",
+ "gettext-domain": "unity-window-switcher",
+ "url": "https://github.com/gonzaarcr/unity-like-switcher-gnome-ext",
+ "settings-schema": "org.gnome.shell.extensions.unity-window-switcher-reforged",
+ "shell-version": [
+ "48",
+ "49",
+ "50"
+ ]
+}
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/prefs.js b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/prefs.js
new file mode 100644
index 0000000..92949e6
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/prefs.js
@@ -0,0 +1,256 @@
+import Adw from 'gi://Adw';
+import GObject from "gi://GObject";
+import Gio from "gi://Gio";
+import Gtk from "gi://Gtk";
+import Gdk from "gi://Gdk";
+
+import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
+
+
+class PrefsWidget {
+
+ constructor(schema) {
+ this._updating = false;
+ this._buildable = new Gtk.Builder();
+ this._buildable.add_from_file(
+ Gio.File.new_for_uri(import.meta.url).get_parent().get_path() + '/settings.ui'
+ );
+
+ let prefsWidget = this._getWidget('prefs_widget');
+ if (!prefsWidget) {
+ console.error('UnityLikeAppSwitcher: Main widget not found');
+ }
+
+ this._settings = schema;
+ this._bindBooleans();
+ this._bindNumbers();
+ this._bindStrings();
+
+ let resetButton = this._getWidget('reset_button');
+ if (resetButton) {
+ resetButton.connect('clicked', () => {
+ this._resetAll();
+ });
+ }
+
+ this._settings.connect(
+ 'changed::first-change-window',
+ this._firstChangeWindowChanged.bind(this)
+ );
+ this._firstChangeWindowChanged();
+ }
+
+ _resetAll() {
+ this._updating = true;
+ try {
+ this._getNumbers().forEach(setting => {
+ this._settings.reset(setting);
+ // Trigger UI update manually for numbers since they use manual binding
+ this._updateNumberWidget(setting);
+ });
+
+ this._getBooleans().forEach(setting => {
+ this._settings.reset(setting);
+ });
+
+ this._getStrings().forEach(setting => {
+ this._settings.reset(setting);
+ this._updateStringWidget(setting);
+ });
+
+ Gio.Settings.sync();
+ } finally {
+ this._updating = false;
+ }
+ }
+
+ _updateNumberWidget(setting) {
+ if (this._updating) return;
+ let widget = this._getWidget(setting);
+ if (!widget || !widget.set_value) {
+ return;
+ }
+ if (!this._settings.list_keys().includes(setting)) {
+ // Fallback values if key is missing
+ let defaultValues = {
+ 'hover-shade-level': 0.2,
+ 'hover-opacity': 0.8,
+ 'hover-border-width': 2,
+ 'hover-glow-size': 15,
+ 'icon-spacing': 10,
+ 'container-padding': 24,
+ 'border-size': 2,
+ 'background-border-radius': 24,
+ 'icon-border-radius': 16
+ };
+ widget.set_value(defaultValues[setting] || 0);
+ return;
+ }
+ let isDouble = (setting === 'hover-shade-level' || setting === 'hover-opacity');
+ if (isDouble) {
+ widget.set_value(this._settings.get_double(setting));
+ } else {
+ widget.set_value(this._settings.get_int(setting));
+ }
+ }
+
+ _updateStringWidget(setting) {
+ if (this._updating) return;
+ let widget = this._getWidget(setting);
+ if (!widget) return;
+ let colorStr = '';
+ if (!this._settings.list_keys().includes(setting)) {
+ let defaultValues = {
+ 'background-color': 'rgba(0, 0, 0, 0.5)',
+ 'border-color': 'rgba(255, 255, 255, 0.2)'
+ };
+ colorStr = defaultValues[setting] || '';
+ } else {
+ colorStr = this._settings.get_string(setting);
+ }
+
+ if (widget instanceof Gtk.ColorDialogButton) {
+ let rgba = new Gdk.RGBA();
+ if (rgba.parse(colorStr)) {
+ this._updating = true;
+ widget.set_rgba(rgba);
+ this._updating = false;
+ }
+ } else if (widget.set_text) {
+ this._updating = true;
+ widget.set_text(colorStr);
+ this._updating = false;
+ }
+ }
+
+ _getWidget(name) {
+ let wname = name.replace(/-/g, '_');
+ return this._buildable.get_object(wname);
+ }
+
+ _getBooleans() {
+ return [
+ 'first-change-window'
+ ];
+ }
+
+ _getNumbers() {
+ return [
+ 'hover-shade-level',
+ 'hover-border-width',
+ 'hover-glow-size',
+ 'hover-opacity',
+ 'icon-spacing',
+ 'container-padding',
+ 'border-size',
+ 'background-border-radius',
+ 'icon-border-radius'
+ ];
+ }
+
+ _getStrings() {
+ return [
+ 'background-color',
+ 'border-color'
+ ];
+ }
+
+ _bindBoolean(setting) {
+ let widget = this._getWidget(setting);
+ if (!widget) return;
+ this._settings.bind(setting, widget, 'active', Gio.SettingsBindFlags.DEFAULT);
+ }
+
+ _bindBooleans() {
+ this._getBooleans().forEach(this._bindBoolean, this);
+ }
+
+ _bindString(setting) {
+ let widget = this._getWidget(setting);
+ if (!widget) return;
+ this._updateStringWidget(setting);
+
+ if (widget instanceof Gtk.ColorDialogButton) {
+ widget.connect('notify::rgba', (w) => {
+ if (this._updating) return;
+ if (!this._settings.list_keys().includes(setting)) return;
+ let rgba = w.get_rgba();
+ let val = rgba.to_string(); // CSS format
+ let current = this._settings.get_string(setting);
+ if (current !== val) {
+ this._settings.set_string(setting, val);
+ }
+ });
+ } else {
+ widget.connect('changed', (w) => {
+ if (this._updating) return;
+ if (!this._settings.list_keys().includes(setting)) return;
+ let val = w.get_text();
+ let current = this._settings.get_string(setting);
+ if (current !== val) {
+ this._settings.set_string(setting, val);
+ }
+ });
+ }
+
+ this._settings.connect(`changed::${setting}`, () => {
+ if (this._updating) return;
+ this._updateStringWidget(setting);
+ });
+ }
+
+ _bindStrings() {
+ this._getStrings().forEach(this._bindString, this);
+ }
+
+ _bindNumber(setting) {
+ let widget = this._getWidget(setting);
+ if (!widget) return;
+ let isDouble = (setting === 'hover-shade-level' || setting === 'hover-opacity');
+
+ // Érték betöltése indításkor
+ this._updateNumberWidget(setting);
+
+ // Automatikus mentés változáskor
+ widget.connect('notify::value', (w) => {
+ if (this._updating) return;
+ if (!this._settings.list_keys().includes(setting)) return;
+ let val = w.get_value();
+ if (isDouble) {
+ // Only update if significantly different to avoid feedback loops
+ let current = this._settings.get_double(setting);
+ if (Math.abs(current - val) > 0.001) {
+ this._settings.set_double(setting, val);
+ }
+ } else {
+ let current = this._settings.get_int(setting);
+ if (current !== Math.round(val)) {
+ this._settings.set_int(setting, Math.round(val));
+ }
+ }
+ });
+
+ // Update widget if setting changes externally (e.g. reset)
+ this._settings.connect(`changed::${setting}`, () => {
+ if (this._updating) return;
+ this._updateNumberWidget(setting);
+ });
+ }
+
+ _bindNumbers() {
+ this._getNumbers().forEach(this._bindNumber, this);
+ }
+
+ _firstChangeWindowChanged() {
+ this._settings.get_boolean('first-change-window');
+ }
+}
+
+export default class UnityLikeAppSwitcherPreferences extends ExtensionPreferences {
+ fillPreferencesWindow (window) {
+ this.initTranslations("unity-window-switcher");
+ window._settings = this.getSettings();
+ const widget = new PrefsWidget(window._settings);
+ window.add(widget._getWidget('prefs_widget'));
+ }
+}
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/gschemas.compiled b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/gschemas.compiled
new file mode 100644
index 0000000..a6544ab
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/gschemas.compiled
Binary files differ
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/org.gnome.shell.extensions.unity-window-switcher-reforged.gschema.xml b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/org.gnome.shell.extensions.unity-window-switcher-reforged.gschema.xml
new file mode 100644
index 0000000..e616c9f
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/schemas/org.gnome.shell.extensions.unity-window-switcher-reforged.gschema.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schemalist gettext-domain="unity-window-switcher">
+ <schema id="org.gnome.shell.extensions.unity-window-switcher-reforged" path="/org/gnome/shell/extensions/unity-window-switcher-reforged/">
+ <key name="first-change-window" type="b">
+ <default>true</default>
+ <summary>Focus the second window when switching</summary>
+ <description>When the switcher is opened, the second window is focused by default, similar to the original Unity behavior.</description>
+ </key>
+ <key name="hover-shade-level" type="d">
+ <default>1.0</default>
+ <summary>Hover Shade Level</summary>
+ <description>The brightness multiplier for the hover glow (e.g., 0.1 means 10% brighter).</description>
+ </key>
+ <key name="hover-border-width" type="i">
+ <default>2</default>
+ <summary>Hover Border Width</summary>
+ <description>The thickness of the border around the hovered item in pixels.</description>
+ </key>
+ <key name="hover-glow-size" type="i">
+ <range min="0" max="30"/>
+ <default>8</default>
+ <summary>Hover Glow Size</summary>
+ <description>The size of the inner glow (box-shadow) for the hovered item.</description>
+ </key>
+ <key name="hover-opacity" type="d">
+ <default>0.9</default>
+ <summary>Hover Opacity</summary>
+ <description>The opacity of the hover highlight (0.0 to 1.0).</description>
+ </key>
+ <key name="icon-spacing" type="i">
+ <default>10</default>
+ <summary>Icon Spacing</summary>
+ <description>The distance between icons in pixels.</description>
+ </key>
+ <key name="container-padding" type="i">
+ <default>10</default>
+ <summary>Container Padding</summary>
+ <description>The padding around the icons inside the switcher.</description>
+ </key>
+ <key name="background-color" type="s">
+ <default>'rgb(24,24,26)'</default>
+ <summary>Background Color</summary>
+ <description>The background color of the switcher (CSS format).</description>
+ </key>
+ <key name="border-size" type="i">
+ <default>0</default>
+ <summary>Border Size</summary>
+ <description>The thickness of the switcher border.</description>
+ </key>
+ <key name="border-color" type="s">
+ <default>'rgba(255, 255, 255, 0.2)'</default>
+ <summary>Border Color</summary>
+ <description>The color of the switcher border (CSS format).</description>
+ </key>
+ <key name="background-border-radius" type="i">
+ <default>24</default>
+ <summary>Background Border Radius</summary>
+ <description>The border radius of the main switcher container in pixels.</description>
+ </key>
+ <key name="icon-border-radius" type="i">
+ <default>16</default>
+ <summary>Icon Border Radius</summary>
+ <description>The border radius of the application icons in pixels.</description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/settings.ui b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/settings.ui
new file mode 100644
index 0000000..ac10116
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/settings.ui
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="unity-window-switcher">
+ <requires lib="gtk" version="4.0"/>
+ <requires lib="adw" version="1.0"/>
+ <object class="AdwPreferencesPage" id="prefs_widget">
+ <property name="width_request">500</property>
+ <property name="visible">True</property>
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Behavior</property>
+ <child>
+ <object class="AdwActionRow">
+ <property name="title" translatable="yes">First tab changes windows (or apps)</property>
+ <child>
+ <object class="GtkSwitch" id="first_change_window">
+ <property name="valign">center</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Panel Appearance</property>
+ <child>
+ <object class="AdwActionRow" id="background_color_row">
+ <property name="title" translatable="yes">Background Color</property>
+ <child type="suffix">
+ <object class="GtkColorDialogButton" id="background_color">
+ <property name="valign">center</property>
+ <property name="dialog">
+ <object class="GtkColorDialog">
+ <property name="with_alpha">True</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSpinRow" id="background_border_radius">
+ <property name="title" translatable="yes">Background Border Radius</property>
+ <property name="subtitle" translatable="yes">The corner radius of the main switcher window</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="value">24</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSpinRow" id="container_padding">
+ <property name="title" translatable="yes">Container Padding</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="value">24</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSpinRow" id="border_size">
+ <property name="title" translatable="yes">Border Size</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">20</property>
+ <property name="step_increment">1</property>
+ <property name="value">2</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwActionRow" id="border_color_row">
+ <property name="title" translatable="yes">Border Color</property>
+ <child type="suffix">
+ <object class="GtkColorDialogButton" id="border_color">
+ <property name="valign">center</property>
+ <property name="dialog">
+ <object class="GtkColorDialog">
+ <property name="with_alpha">True</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Icons</property>
+ <child>
+ <object class="AdwSpinRow" id="icon_spacing">
+ <property name="title" translatable="yes">Icon Spacing</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="value">10</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSpinRow" id="icon_border_radius">
+ <property name="title" translatable="yes">Icon Border Radius</property>
+ <property name="subtitle" translatable="yes">The corner radius of the application icons</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="value">16</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <property name="title" translatable="yes">Hover Effect</property>
+ <child>
+ <object class="AdwSpinRow" id="hover_shade_level">
+ <property name="title" translatable="yes">Brightness level</property>
+ <property name="subtitle" translatable="yes">Darker (&lt; 0) or lighter (&gt; 0) hover effect (-1.0 to 1.0)</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">-1</property>
+ <property name="upper">1</property>
+ <property name="step_increment">0.05</property>
+ <property name="page_increment">0.1</property>
+ <property name="value">0.5</property>
+ </object>
+ </property>
+ <property name="digits">2</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSpinRow" id="hover_opacity">
+ <property name="title" translatable="yes">Opacity</property>
+ <property name="subtitle" translatable="yes">Transparency of the hover effect (0.0 to 1.0)</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">1</property>
+ <property name="step_increment">0.05</property>
+ <property name="page_increment">0.1</property>
+ <property name="value">0.8</property>
+ </object>
+ </property>
+ <property name="digits">2</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSpinRow" id="hover_border_width">
+ <property name="title" translatable="yes">Border width</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">20</property>
+ <property name="step_increment">1</property>
+ <property name="value">3</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSpinRow" id="hover_glow_size">
+ <property name="title" translatable="yes">Glow size</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">0</property>
+ <property name="upper">30</property>
+ <property name="step_increment">1</property>
+ <property name="value">20</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkButton" id="reset_button">
+ <property name="label" translatable="yes">Reset</property>
+ <property name="halign">center</property>
+ <property name="margin_top">20</property>
+ <property name="margin_bottom">20</property>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/utils.js b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/utils.js
new file mode 100644
index 0000000..898b047
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/unity-like-appswitcher-reforged@gabeszm/utils.js
@@ -0,0 +1,346 @@
+import Gdk from "gi://Gdk";
+import GdkPixbuf from "gi://GdkPixbuf";
+import Gio from "gi://Gio";
+import Gtk from "gi://Gtk";
+import St from "gi://St";
+import GLib from "gi://GLib";
+
+let iconCacheMap = new Map();
+const MAX_CACHED_ITEMS = 1000;
+const BATCH_SIZE_TO_DELETE = 50;
+const DOMINANT_COLOR_ICON_SIZE = 64;
+
+function debugLog(msg) {
+ try {
+ let f = Gio.File.new_for_path('/tmp/color_log.txt');
+ let out = f.append_to(Gio.FileCreateFlags.NONE, null);
+ out.write_all(new Date().toISOString() + " - " + msg + "\n", null);
+ out.close(null);
+ } catch (e) { }
+}
+
+function findFallbackIconPath(iconName) {
+ if (!iconName) return null;
+ if (GLib.file_test(iconName, GLib.FileTest.EXISTS)) return iconName;
+
+ const sizes = ['128x128', '256x256', '512x512', '64x64', 'scalable', '48x48'];
+
+ let currentTheme = 'Adwaita';
+ try {
+ const settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
+ if (settings.get_string('icon-theme')) {
+ currentTheme = settings.get_string('icon-theme');
+ }
+ } catch (e) {
+ debugLog("Failed to get active icon theme setting: " + e);
+ }
+
+ // Tegyük a hicolort az első helyre, a jelenleg aktív témát a másodikra, aztán a többi népszerű.
+ // A Set-tel kiszűrjük az esetleges ismétlődéseket.
+ const themes = [...new Set(['hicolor', currentTheme, 'locolor', 'Yaru', 'Pop', 'Adwaita', 'MoreWaita', 'Papirus', 'Tela'])];
+ const bases = [
+ GLib.get_home_dir() + '/.local/share/icons',
+ GLib.get_home_dir() + '/.local/share/flatpak/exports/share/icons',
+ '/var/lib/flatpak/exports/share/icons',
+ '/var/lib/snapd/desktop/icons',
+ '/usr/share/icons',
+ '/usr/local/share/icons'
+ ];
+ const formats = ['.svg', '.png', '.xpm'];
+
+ for (const base of bases) {
+ for (const theme of themes) {
+ for (const size of sizes) {
+ for (const format of formats) {
+ let path = `${base}/${theme}/${size}/apps/${iconName}${format}`;
+ if (GLib.file_test(path, GLib.FileTest.EXISTS)) return path;
+ }
+ }
+ }
+ }
+
+ // Check pixmaps
+ for (const format of formats) {
+ let path = `/usr/share/pixmaps/${iconName}${format}`;
+ if (GLib.file_test(path, GLib.FileTest.EXISTS)) return path;
+ }
+
+ return null;
+}
+
+export class DominantColorExtractor {
+ constructor(app) {
+ this._app = app;
+ }
+
+ _getIconPixBuf() {
+ debugLog("--- START Extracting for app: " + (this._app ? this._app.get_id() : "undefined") + " ---");
+ if (!this._app) {
+ debugLog("Error: _app is null or undefined");
+ return null;
+ }
+
+ const gicon = this._app.get_icon();
+ if (!gicon) {
+ debugLog("Fail: get_icon() returned null");
+ return null;
+ }
+
+ debugLog("Got gicon of type: " + gicon.constructor.name);
+
+ // Try to load via Gio.FileIcon directly
+ if (gicon instanceof Gio.FileIcon) {
+ try {
+ debugLog("gicon is Gio.FileIcon. Getting file path...");
+ const path = gicon.get_file().get_path();
+ debugLog("File path: " + path);
+ if (path) {
+ return GdkPixbuf.Pixbuf.new_from_file(path);
+ }
+ } catch (e) {
+ debugLog("Exception in Gio.FileIcon lookup: " + e);
+ }
+ }
+
+ // Try fallback manual lookup
+ if (gicon instanceof Gio.ThemedIcon) {
+ try {
+ debugLog("Attempting manual fallback search for ThemedIcon...");
+ const names = gicon.get_names();
+ for (const name of names) {
+ let path = findFallbackIconPath(name);
+ if (path) {
+ debugLog("Manual fallback found file: " + path);
+ return GdkPixbuf.Pixbuf.new_from_file(path);
+ }
+ }
+ } catch (e) {
+ debugLog("Exception in manual fallback: " + e);
+ }
+ }
+
+ // Try St.TextureCache as a last resort
+ try {
+ debugLog("Attempting St.TextureCache lookup...");
+ let textureCache = St.TextureCache.get_default();
+ let themeContext = St.ThemeContext.get_for_stage(global.stage);
+ let iconSize = 64 * themeContext.scale_factor;
+
+ let iconTexture = textureCache.load_gicon(null, gicon, iconSize);
+ if (iconTexture) {
+ debugLog("Found texture in cache, but cannot easily convert to Pixbuf in this context. Fallback to default.");
+ }
+ } catch (e) {
+ debugLog("St.TextureCache lookup failed: " + e);
+ }
+
+ debugLog("--- FAIL: Exhausted all methods to get icon pixbuf ---");
+ return null;
+ }
+
+ _getColorPalette() {
+ debugLog("--- START _getColorPalette ---");
+ const appId = this._app ? this._app.get_id() : null;
+ debugLog("App ID: " + appId);
+
+ if (appId && iconCacheMap.has(appId)) {
+ debugLog("Returning cached color for: " + appId);
+ return iconCacheMap.get(appId);
+ }
+
+ debugLog("Cache miss, calling _getIconPixBuf()");
+ const pixBuf = this._getIconPixBuf();
+ if (!pixBuf) {
+ debugLog("FAIL: pixBuf is null, cannot extract color.");
+ return null;
+ }
+
+ try {
+ const width = pixBuf.get_width();
+ const height = pixBuf.get_height();
+ const rowstride = pixBuf.get_rowstride();
+ const nChannels = pixBuf.get_n_channels();
+ const hasAlpha = pixBuf.get_has_alpha();
+
+ debugLog(`PixBuf info: ${width}x${height}, rowstride: ${rowstride}, channels: ${nChannels}, alpha: ${hasAlpha}`);
+
+ const pixels = pixBuf.get_pixels();
+ if (!pixels) {
+ debugLog("FAIL: pixBuf.get_pixels() returned null or undefined");
+ return null;
+ }
+
+ let total = 0, rTotal = 0, gTotal = 0, bTotal = 0;
+ const step = Math.max(1, Math.floor(width / 32));
+ debugLog("Calculated step for sampling: " + step);
+
+ let sampledCount = 0;
+ for (let y = 0; y < height; y += step) {
+ for (let x = 0; x < width; x += step) {
+ const offset = y * rowstride + x * nChannels;
+ const r = pixels[offset];
+ const g = pixels[offset + 1];
+ const b = pixels[offset + 2];
+ const a = hasAlpha ? pixels[offset + 3] : 255;
+
+ if (a < 128) continue;
+
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ const saturation = (max - min) / (max || 1);
+ const relevance = a * (saturation * saturation + 0.1);
+
+ rTotal += r * relevance;
+ gTotal += g * relevance;
+ bTotal += b * relevance;
+ total += relevance;
+ sampledCount++;
+ }
+ }
+
+ debugLog(`Sampling complete. Sampled pixels: ${sampledCount}. Total accumulator: ${total}`);
+
+ if (total === 0) {
+ debugLog("FAIL: Valid pixel total is 0. Returning null.");
+ return null;
+ }
+
+ let r = rTotal / total;
+ let g = gTotal / total;
+ let b = bTotal / total;
+ debugLog(`Averaged RGB: Math.round(${r}), Math.round(${g}), Math.round(${b})`);
+
+ let hsv = ColorUtils.RGBtoHSV(r, g, b);
+ debugLog(`HSV before normalize: h:${hsv.h.toFixed(2)}, s:${hsv.s.toFixed(2)}, v:${hsv.v.toFixed(2)}`);
+
+ if (hsv.s < 0.2) hsv.s = 0.35;
+ hsv.v = Math.max(0.6, Math.min(hsv.v, 0.9));
+
+ debugLog(`HSV after normalize: h:${hsv.h.toFixed(2)}, s:${hsv.s.toFixed(2)}, v:${hsv.v.toFixed(2)}`);
+
+ const rgb = ColorUtils.HSVtoRGB(hsv.h, hsv.s, hsv.v);
+
+ const backgroundColor = {
+ lighter: ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0.5),
+ original: ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0),
+ darker: ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, -0.5),
+ baseRgb: rgb
+ };
+
+ debugLog(`Generated Palette: original=${backgroundColor.original}, lighter=${backgroundColor.lighter}, darker=${backgroundColor.darker}`);
+
+ if (iconCacheMap.size >= MAX_CACHED_ITEMS) {
+ debugLog("Cache full, clearing batch.");
+ const keys = Array.from(iconCacheMap.keys());
+ for (let i = 0; i < BATCH_SIZE_TO_DELETE; i++) {
+ iconCacheMap.delete(keys[i]);
+ }
+ }
+
+ if (appId) {
+ iconCacheMap.set(appId, backgroundColor);
+ debugLog("Added to cache for appId: " + appId);
+ }
+
+ debugLog("--- SUCCESS: Color extraction finished ---");
+ return backgroundColor;
+
+ } catch (e) {
+ debugLog("Exception during pixel processing: " + e);
+ return null;
+ }
+ }
+}
+
+export class ColorUtils {
+ static ColorLuminance(r, g, b, dlum) {
+ let rgbString = '#';
+ rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(r * (1 + dlum), 0), 255)), 2);
+ rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(g * (1 + dlum), 0), 255)), 2);
+ rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(b * (1 + dlum), 0), 255)), 2);
+ return rgbString;
+ }
+
+ static _decimalToHex(d, padding) {
+ let hex = d.toString(16);
+ while (hex.length < padding)
+ hex = '0' + hex;
+ return hex;
+ }
+
+ static _hexToRgb(h) {
+ return {
+ r: parseInt(h.substr(1, 2), 16),
+ g: parseInt(h.substr(3, 2), 16),
+ b: parseInt(h.substr(5, 2), 16)
+ }
+ }
+
+ static HSVtoRGB(h, s, v) {
+ if (arguments.length === 1) {
+ s = h.s;
+ v = h.v;
+ h = h.h;
+ }
+
+ let r, g, b;
+ let c = v * s;
+ let h1 = h * 6;
+ let x = c * (1 - Math.abs(h1 % 2 - 1));
+ let m = v - c;
+
+ if (h1 <= 1)
+ r = c + m, g = x + m, b = m;
+ else if (h1 <= 2)
+ r = x + m, g = c + m, b = m;
+ else if (h1 <= 3)
+ r = m, g = c + m, b = x + m;
+ else if (h1 <= 4)
+ r = m, g = x + m, b = c + m;
+ else if (h1 <= 5)
+ r = x + m, g = m, b = c + m;
+ else
+ r = c + m, g = m, b = x + m;
+
+ return {
+ r: Math.round(r * 255),
+ g: Math.round(g * 255),
+ b: Math.round(b * 255)
+ };
+ }
+
+ static RGBtoHSV(r, g, b) {
+ if (arguments.length === 1) {
+ r = r.r;
+ g = r.g;
+ b = r.b;
+ }
+
+ let h, s, v;
+ let M = Math.max(r, g, b);
+ let m = Math.min(r, g, b);
+ let c = M - m;
+
+ if (c == 0)
+ h = 0;
+ else if (M == r)
+ h = ((g - b) / c) % 6;
+ else if (M == g)
+ h = (b - r) / c + 2;
+ else
+ h = (r - g) / c + 4;
+
+ h = h / 6;
+ v = M / 255;
+ if (M !== 0)
+ s = c / M;
+ else
+ s = 0;
+
+ return {
+ h: h,
+ s: s,
+ v: v
+ };
+ }
+}