import GObject from 'gi://GObject'; import St from 'gi://St'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import Clutter from 'gi://Clutter'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; const DESKMETER_API_URL = 'http://localhost:10000/api/current_task'; const DEBOUNCE_DELAY = 2200; // Wait 2.2s after workspace switch (dmcore polls every 2s) const TaskIndicator = GObject.registerClass( class TaskIndicator extends PanelMenu.Button { _init() { super._init(0.0, 'Deskmeter Task Indicator', false); // Create label for task display this._label = new St.Label({ text: 'loading...', y_align: Clutter.ActorAlign.CENTER, style_class: 'deskmeter-task-label' }); this.add_child(this._label); this._debounceTimeout = null; this._workspaceManager = global.workspace_manager; // Connect to workspace switch signal this._workspaceSwitchedId = this._workspaceManager.connect( 'workspace-switched', this._onWorkspaceSwitched.bind(this) ); // Initial update this._scheduleUpdate(); } _onWorkspaceSwitched() { // Debounce updates - dmcore takes ~2 seconds to detect and update // We wait a bit to ensure the task has been updated in MongoDB this._scheduleUpdate(); } _scheduleUpdate() { // Clear any pending update if (this._debounceTimeout) { GLib.source_remove(this._debounceTimeout); } // Schedule new update this._debounceTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, DEBOUNCE_DELAY, () => { this._updateTask(); this._debounceTimeout = null; return GLib.SOURCE_REMOVE; }); } _updateTask() { try { // Create HTTP request let file = Gio.File.new_for_uri(DESKMETER_API_URL); file.load_contents_async(null, (source, result) => { try { let [success, contents] = source.load_contents_finish(result); if (success) { let decoder = new TextDecoder('utf-8'); let data = JSON.parse(decoder.decode(contents)); // Update label with task path let displayText = data.task_path || 'no task'; // Optionally truncate long paths if (displayText.length > 40) { let parts = displayText.split('/'); if (parts.length > 2) { displayText = '.../' + parts.slice(-2).join('/'); } else { displayText = displayText.substring(0, 37) + '...'; } } this._label.set_text(displayText); } } catch (e) { this._label.set_text('error'); logError(e, 'Failed to parse deskmeter response'); } }); } catch (e) { this._label.set_text('offline'); logError(e, 'Failed to fetch deskmeter task'); } } destroy() { if (this._debounceTimeout) { GLib.source_remove(this._debounceTimeout); this._debounceTimeout = null; } if (this._workspaceSwitchedId) { this._workspaceManager.disconnect(this._workspaceSwitchedId); this._workspaceSwitchedId = null; } super.destroy(); } }); export default class Extension { constructor() { this._indicator = null; } enable() { this._indicator = new TaskIndicator(); // Add to panel - position after workspace indicator // Panel boxes: left, center, right // We'll add it to the left panel, after other items Main.panel.addToStatusArea('deskmeter-task-indicator', this._indicator, 1, 'left'); } disable() { if (this._indicator) { this._indicator.destroy(); this._indicator = null; } } }