Files
deskmeter/dmapp/dmos/gnome-extension/deskmeter-indicator@local/extension.js
2025-12-23 19:06:43 -03:00

205 lines
6.6 KiB
JavaScript

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';
// Try common ports - worktree (10001) first, then default (10000)
const DEFAULT_PORTS = [10001, 10000];
const DEBOUNCE_DELAY = 2200; // Wait 2.2s after workspace switch (dmcore polls every 2s)
let DESKMETER_API_URL = null;
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: 'detecting...',
y_align: Clutter.ActorAlign.CENTER,
style_class: 'deskmeter-task-label'
});
this.add_child(this._label);
this._debounceTimeout = null;
this._workspaceManager = global.workspace_manager;
this._apiUrl = null;
// Connect to workspace switch signal
this._workspaceSwitchedId = this._workspaceManager.connect(
'workspace-switched',
this._onWorkspaceSwitched.bind(this)
);
// Detect API port, then start updates
this._detectApiPort();
}
_detectApiPort() {
// Try each port in sequence
this._tryNextPort(0);
}
_tryNextPort(index) {
if (index >= DEFAULT_PORTS.length) {
// No ports responded, use default and let it show "offline"
this._apiUrl = `http://localhost:${DEFAULT_PORTS[DEFAULT_PORTS.length - 1]}/api/current_task`;
this._scheduleUpdate();
return;
}
const port = DEFAULT_PORTS[index];
const url = `http://localhost:${port}/api/current_task`;
try {
let file = Gio.File.new_for_uri(url);
file.load_contents_async(null, (source, result) => {
try {
let [success, contents] = source.load_contents_finish(result);
if (success) {
// Port responded, use it
this._apiUrl = url;
this._scheduleUpdate();
return;
}
} catch (e) {
// This port failed, try next
this._tryNextPort(index + 1);
}
});
} catch (e) {
// This port failed, try next
this._tryNextPort(index + 1);
}
}
_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() {
if (!this._apiUrl) {
this._label.set_text('detecting...');
return;
}
try {
// Create HTTP request
let file = Gio.File.new_for_uri(this._apiUrl);
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() {
try {
if (this._debounceTimeout) {
GLib.source_remove(this._debounceTimeout);
this._debounceTimeout = null;
}
if (this._workspaceSwitchedId) {
this._workspaceManager.disconnect(this._workspaceSwitchedId);
this._workspaceSwitchedId = null;
}
super.destroy();
} catch (e) {
// Log error but don't crash GNOME Shell
logError(e, 'Failed to destroy TaskIndicator');
}
}
});
export default class Extension {
constructor() {
this._indicator = null;
}
enable() {
try {
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');
} catch (e) {
// Log error but don't crash GNOME Shell
logError(e, 'Failed to enable Deskmeter extension');
// Clean up if partially initialized
if (this._indicator) {
try {
this._indicator.destroy();
} catch (destroyError) {
logError(destroyError, 'Failed to cleanup indicator');
}
this._indicator = null;
}
}
}
disable() {
try {
if (this._indicator) {
this._indicator.destroy();
this._indicator = null;
}
} catch (e) {
// Log error but don't crash GNOME Shell
logError(e, 'Failed to disable Deskmeter extension');
this._indicator = null;
}
}
}