diff --git a/dmapp/dmcore/task.py b/dmapp/dmcore/task.py
index 37c4807..6fe1ac4 100644
--- a/dmapp/dmcore/task.py
+++ b/dmapp/dmcore/task.py
@@ -20,8 +20,11 @@ def parse_line(line: str) -> tuple[Optional[str], Optional[str]]:
parts = line.split("|")
if len(parts) > 1:
task_name = parts[0].strip()
- task_id = parts[1].split()[0].strip()
- return task_name, task_id
+ id_parts = parts[1].split()
+ if id_parts:
+ task_id = id_parts[0].strip()
+ return task_name, task_id
+ return task_name, None
return parts[0].strip(), None
@@ -32,7 +35,8 @@ def file_to_db(filepath: str):
filepath = task_file
current_path = []
- tasks.delete_many({})
+ # Only delete non-historic tasks
+ tasks.delete_many({"historic": {"$ne": True}})
seen_paths = set()
with open(filepath, "r") as f:
@@ -54,8 +58,8 @@ def file_to_db(filepath: str):
if task_id:
tasks.update_one(
- {"_id": task_id},
- {"$set": {"path": full_path, "task_id": task_id}},
+ {"task_id": task_id},
+ {"$set": {"path": full_path, "task_id": task_id, "historic": False}},
upsert=True,
)
elif full_path not in seen_paths:
diff --git a/dmapp/dmweb/get_period_times.py b/dmapp/dmweb/get_period_times.py
index a331655..9b166bb 100644
--- a/dmapp/dmweb/get_period_times.py
+++ b/dmapp/dmweb/get_period_times.py
@@ -1,5 +1,6 @@
from collections import Counter, defaultdict
from datetime import datetime, timedelta
+from pathlib import Path
from pymongo import MongoClient
from zoneinfo import ZoneInfo
@@ -11,9 +12,99 @@ client = MongoClient()
db = client.deskmeter
switches = db.switch
tasks = db.task
+task_history = db.task_history
task_file = "/home/mariano/LETRAS/adm/task/main"
+task_dir = Path(task_file).parent
+
+
+def parse_task_line(line):
+ """Parse a task line to extract task name and ID."""
+ line = line.strip()
+ if not line:
+ return None, None
+
+ parts = line.split("|")
+ if len(parts) > 1:
+ task_name = parts[0].strip()
+ id_parts = parts[1].split()
+ if id_parts:
+ task_id = id_parts[0].strip()
+ return task_name, task_id
+ return task_name, None
+
+ return parts[0].strip(), None
+
+
+def load_task_from_files(task_id):
+ """Search task directory files for a task ID and load it into task_history."""
+ for task_filepath in task_dir.glob("*"):
+ if not task_filepath.is_file():
+ continue
+
+ current_path = []
+ try:
+ with open(task_filepath, "r") as f:
+ for line in f:
+ if not line.strip():
+ continue
+
+ indent = len(line) - len(line.lstrip())
+ level = indent // 4
+
+ task_name, found_id = parse_task_line(line)
+ if task_name is None:
+ continue
+
+ current_path = current_path[:level]
+ current_path.append(task_name)
+ full_path = "/".join(current_path)
+
+ if found_id == task_id:
+ # Found it! Insert into task_history
+ task_history.update_one(
+ {"task_id": task_id},
+ {"$set": {
+ "path": full_path,
+ "task_id": task_id,
+ "source_file": task_filepath.name
+ }},
+ upsert=True
+ )
+ return full_path
+ except:
+ # Skip files that can't be read
+ continue
+
+ return None
+
+
+def get_task_path(task_id):
+ """
+ Get task path from tasks collection, falling back to task_history.
+ If not found, searches task directory files and populates task_history on-demand.
+ """
+ if not task_id:
+ return None
+
+ # Try current tasks first
+ task_doc = tasks.find_one({"task_id": task_id})
+ if task_doc and "path" in task_doc:
+ return task_doc["path"]
+
+ # Try task history cache
+ task_doc = task_history.find_one({"task_id": task_id})
+ if task_doc and "path" in task_doc:
+ return task_doc["path"]
+
+ # Not in cache, search files and populate history
+ task_path = load_task_from_files(task_id)
+ if task_path:
+ return task_path
+
+ # Still not found, return ID as fallback
+ return task_id
def get_current_task_info():
@@ -134,9 +225,8 @@ def get_work_period_totals(start, end):
total_seconds = result["total_seconds"]
if total_seconds > 0:
- # Get task path from tasks collection
- task_doc = tasks.find_one({"task_id": task_id})
- task_path = task_doc["path"] if task_doc and "path" in task_doc else task_id
+ # Get task path with history fallback
+ task_path = get_task_path(task_id)
combined_rows.append({
"ws": task_path,
@@ -194,15 +284,12 @@ def get_task_blocks_calendar(start, end, task=None, min_block_seconds=300):
if current_block is not None:
blocks.append(current_block)
- # Get task path
- task_path = None
- if task_id:
- task_doc = tasks.find_one({"task_id": task_id})
- task_path = task_doc["path"] if task_doc and "path" in task_doc else task_id
+ # Get task path with history fallback
+ task_path = get_task_path(task_id) or "No Task"
current_block = {
"task_id": task_id,
- "task_path": task_path or "No Task",
+ "task_path": task_path,
"start": switch_start,
"end": switch_end,
"duration": switch_duration,
@@ -256,15 +343,13 @@ def get_raw_switches(start, end, task=None):
result = []
for switch in raw_switches:
task_id = switch.get("task")
- task_path = None
- if task_id:
- task_doc = tasks.find_one({"task_id": task_id})
- task_path = task_doc["path"] if task_doc and "path" in task_doc else task_id
+ # Get task path with history fallback
+ task_path = get_task_path(task_id) or "No Task"
result.append({
"workspace": switch["workspace"],
"task_id": task_id,
- "task_path": task_path or "No Task",
+ "task_path": task_path,
"date": switch["date"].replace(tzinfo=utctz).astimezone(timezone),
"delta": switch["delta"]
})
diff --git a/dmapp/dmweb/templates/switches_view.html b/dmapp/dmweb/templates/switches_view.html
index ace4d25..b1a7ee2 100644
--- a/dmapp/dmweb/templates/switches_view.html
+++ b/dmapp/dmweb/templates/switches_view.html
@@ -56,7 +56,6 @@
display: flex;
align-items: center;
padding: 8px;
- background: #f9f9f9;
border-left: 4px solid;
font-size: 11pt;
}
@@ -83,12 +82,9 @@
color: #666;
}
- /* Workspace colors */
- .ws-Work { border-color: #2563eb; background: #eff6ff; }
- .ws-Think { border-color: #7c3aed; background: #f5f3ff; }
- .ws-Plan { border-color: #059669; background: #ecfdf5; }
- .ws-Other { border-color: #64748b; background: #f8fafc; }
- .ws-Away { border-color: #94a3b8; background: #f1f5f9; }
+ /* Active vs idle workspace indicator */
+ .ws-active { font-weight: bold; }
+ .ws-idle { opacity: 0.6; }
.stats {
margin: 20px 0;
@@ -161,7 +157,13 @@
{% for switch in switches %}
-
+ {% set is_active = switch.workspace in ['Plan', 'Think', 'Work'] %}
+ {% set task_hash = switch.task_path|hash if switch.task_path else 0 %}
+ {% set border_hue = task_hash % 360 %}
+ {% set border_color = 'hsl(%d, 70%%, 50%%)'|format(border_hue) %}
+ {% set bg_color = 'hsl(%d, 70%%, 95%%)'|format(border_hue) %}
+
{{ switch.date.strftime('%m/%d %H:%M:%S') }}
{{ switch.workspace }}
{{ switch.task_path }}
diff --git a/gnome-extension/README.md b/gnome-extension/README.md
index 535f95c..e867983 100644
--- a/gnome-extension/README.md
+++ b/gnome-extension/README.md
@@ -13,8 +13,7 @@ A GNOME Shell extension that displays your current deskmeter task in the top pan
1. Copy the extension to your GNOME extensions directory:
```bash
-cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local \
- ~/.local/share/gnome-shell/extensions/
+cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local ~/.local/share/gnome-shell/extensions/
```
2. Restart GNOME Shell:
@@ -31,10 +30,12 @@ Or use GNOME Extensions app (install with `sudo apt install gnome-shell-extensio
## Configuration
-The extension polls the API every 3 seconds. You can adjust this in `extension.js`:
+The extension updates automatically when you switch workspaces. It waits 2.2 seconds after a workspace switch to allow dmcore (which polls every 2 seconds) to detect the change and update MongoDB.
+
+You can adjust the delay in `extension.js`:
```javascript
-const UPDATE_INTERVAL = 3000; // milliseconds
+const DEBOUNCE_DELAY = 2200; // milliseconds
```
The API URL is set to:
@@ -52,6 +53,28 @@ rm -rf ~/.local/share/gnome-shell/extensions/deskmeter-indicator@local
Then restart GNOME Shell.
+## Updating the Extension
+
+After making changes to the extension code:
+
+```bash
+# Use the update script
+cd /home/mariano/wdir/dm/gnome-extension
+./update.sh
+
+# Then restart GNOME Shell (X11 only)
+Alt+F2, type: r, press Enter
+
+# On Wayland: log out and back in
+```
+
+Or manually:
+```bash
+cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local \
+ ~/.local/share/gnome-shell/extensions/
+# Then restart GNOME Shell
+```
+
## Troubleshooting
### Extension not showing
@@ -66,6 +89,12 @@ Then restart GNOME Shell.
journalctl -f -o cat /usr/bin/gnome-shell
```
+3. Try disabling and re-enabling:
+ ```bash
+ gnome-extensions disable deskmeter-indicator@local
+ gnome-extensions enable deskmeter-indicator@local
+ ```
+
### Shows "offline" or "error"
- Ensure dmweb Flask server is running on port 10000
@@ -75,6 +104,16 @@ Then restart GNOME Shell.
```
Should return JSON like: `{"task_id":"12345678","task_path":"work/default"}`
+### Changes not appearing
+
+- Make sure you copied files after editing
+- GNOME Shell must be restarted (no way around this)
+- Check logs for JavaScript errors: `journalctl -b -o cat /usr/bin/gnome-shell | grep deskmeter`
+
+### Debug with Looking Glass
+
+Press `Alt+F2`, type `lg`, press Enter. Go to Extensions tab to see if the extension loaded and check for errors.
+
### Task path too long
The extension automatically truncates paths longer than 40 characters, showing only the last two segments with a `.../ ` prefix.
diff --git a/gnome-extension/deskmeter-indicator@local/extension.js b/gnome-extension/deskmeter-indicator@local/extension.js
index 73b4d4c..edaa702 100644
--- a/gnome-extension/deskmeter-indicator@local/extension.js
+++ b/gnome-extension/deskmeter-indicator@local/extension.js
@@ -8,7 +8,7 @@ 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 UPDATE_INTERVAL = 3000; // 3 seconds
+const DEBOUNCE_DELAY = 2200; // Wait 2.2s after workspace switch (dmcore polls every 2s)
const TaskIndicator = GObject.registerClass(
class TaskIndicator extends PanelMenu.Button {
@@ -24,11 +24,36 @@ class TaskIndicator extends PanelMenu.Button {
this.add_child(this._label);
- // Start periodic updates
- this._updateTask();
- this._timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, UPDATE_INTERVAL, () => {
+ 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();
- return GLib.SOURCE_CONTINUE;
+ this._debounceTimeout = null;
+ return GLib.SOURCE_REMOVE;
});
}
@@ -70,10 +95,16 @@ class TaskIndicator extends PanelMenu.Button {
}
destroy() {
- if (this._timeout) {
- GLib.source_remove(this._timeout);
- this._timeout = null;
+ if (this._debounceTimeout) {
+ GLib.source_remove(this._debounceTimeout);
+ this._debounceTimeout = null;
}
+
+ if (this._workspaceSwitchedId) {
+ this._workspaceManager.disconnect(this._workspaceSwitchedId);
+ this._workspaceSwitchedId = null;
+ }
+
super.destroy();
}
});
diff --git a/gnome-extension/update.sh b/gnome-extension/update.sh
new file mode 100644
index 0000000..11f2fea
--- /dev/null
+++ b/gnome-extension/update.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Quick update script for GNOME extension development
+
+EXTENSION_DIR="$HOME/.local/share/gnome-shell/extensions"
+EXTENSION_NAME="deskmeter-indicator@local"
+SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)/$EXTENSION_NAME"
+
+echo "Updating Deskmeter Task Indicator..."
+
+# Copy extension files
+cp -r "$SOURCE_DIR" "$EXTENSION_DIR/"
+
+echo "Files copied."
+echo ""
+echo "Next: Restart GNOME Shell"
+echo " X11: Alt+F2, type 'r', press Enter"
+echo " Wayland: Log out and back in"
+echo ""
+echo "Check logs: journalctl -f -o cat /usr/bin/gnome-shell"