more solid task updating, no need to stop the main loop to avoid race conditions
This commit is contained in:
@@ -1,204 +1,235 @@
|
||||
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 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';
|
||||
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)
|
||||
const PERIODIC_REFRESH_SECONDS = 10; // Periodic refresh as backup to catch stale data
|
||||
let DESKMETER_API_URL = null;
|
||||
|
||||
const TaskIndicator = GObject.registerClass(
|
||||
class TaskIndicator extends PanelMenu.Button {
|
||||
class TaskIndicator extends PanelMenu.Button {
|
||||
_init() {
|
||||
super._init(0.0, 'Deskmeter Task Indicator', false);
|
||||
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'
|
||||
});
|
||||
// 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.add_child(this._label);
|
||||
|
||||
this._debounceTimeout = null;
|
||||
this._workspaceManager = global.workspace_manager;
|
||||
this._apiUrl = null;
|
||||
this._debounceTimeout = null;
|
||||
this._periodicTimeout = 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)
|
||||
);
|
||||
// Connect to workspace switch signal
|
||||
this._workspaceSwitchedId = this._workspaceManager.connect(
|
||||
"workspace-switched",
|
||||
this._onWorkspaceSwitched.bind(this),
|
||||
);
|
||||
|
||||
// Detect API port, then start updates
|
||||
this._detectApiPort();
|
||||
// Detect API port, then start updates
|
||||
this._detectApiPort();
|
||||
}
|
||||
|
||||
_startPeriodicRefresh() {
|
||||
// Periodic refresh as backup to catch stale data
|
||||
this._periodicTimeout = GLib.timeout_add_seconds(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
PERIODIC_REFRESH_SECONDS,
|
||||
() => {
|
||||
this._updateTask();
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_detectApiPort() {
|
||||
// Try each port in sequence
|
||||
this._tryNextPort(0);
|
||||
// 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;
|
||||
}
|
||||
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();
|
||||
this._startPeriodicRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const port = DEFAULT_PORTS[index];
|
||||
const url = `http://localhost:${port}/api/current_task`;
|
||||
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) {
|
||||
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();
|
||||
this._startPeriodicRefresh();
|
||||
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();
|
||||
// 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);
|
||||
}
|
||||
// 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;
|
||||
});
|
||||
// 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;
|
||||
}
|
||||
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));
|
||||
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';
|
||||
// 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');
|
||||
// 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) + "...";
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
this._label.set_text('offline');
|
||||
logError(e, 'Failed to fetch deskmeter task');
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
try {
|
||||
if (this._debounceTimeout) {
|
||||
GLib.source_remove(this._debounceTimeout);
|
||||
this._debounceTimeout = null;
|
||||
}
|
||||
|
||||
if (this._periodicTimeout) {
|
||||
GLib.source_remove(this._periodicTimeout);
|
||||
this._periodicTimeout = 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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user