new view, gnome extension

This commit is contained in:
buenosairesam
2025-12-19 21:08:39 -03:00
parent 38f3c7a711
commit 0dde9f1f54
6 changed files with 220 additions and 39 deletions

View File

@@ -20,8 +20,11 @@ def parse_line(line: str) -> tuple[Optional[str], Optional[str]]:
parts = line.split("|") parts = line.split("|")
if len(parts) > 1: if len(parts) > 1:
task_name = parts[0].strip() task_name = parts[0].strip()
task_id = parts[1].split()[0].strip() id_parts = parts[1].split()
if id_parts:
task_id = id_parts[0].strip()
return task_name, task_id return task_name, task_id
return task_name, None
return parts[0].strip(), None return parts[0].strip(), None
@@ -32,7 +35,8 @@ def file_to_db(filepath: str):
filepath = task_file filepath = task_file
current_path = [] current_path = []
tasks.delete_many({}) # Only delete non-historic tasks
tasks.delete_many({"historic": {"$ne": True}})
seen_paths = set() seen_paths = set()
with open(filepath, "r") as f: with open(filepath, "r") as f:
@@ -54,8 +58,8 @@ def file_to_db(filepath: str):
if task_id: if task_id:
tasks.update_one( tasks.update_one(
{"_id": task_id}, {"task_id": task_id},
{"$set": {"path": full_path, "task_id": task_id}}, {"$set": {"path": full_path, "task_id": task_id, "historic": False}},
upsert=True, upsert=True,
) )
elif full_path not in seen_paths: elif full_path not in seen_paths:

View File

@@ -1,5 +1,6 @@
from collections import Counter, defaultdict from collections import Counter, defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path
from pymongo import MongoClient from pymongo import MongoClient
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
@@ -11,9 +12,99 @@ client = MongoClient()
db = client.deskmeter db = client.deskmeter
switches = db.switch switches = db.switch
tasks = db.task tasks = db.task
task_history = db.task_history
task_file = "/home/mariano/LETRAS/adm/task/main" 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(): def get_current_task_info():
@@ -134,9 +225,8 @@ def get_work_period_totals(start, end):
total_seconds = result["total_seconds"] total_seconds = result["total_seconds"]
if total_seconds > 0: if total_seconds > 0:
# Get task path from tasks collection # Get task path with history fallback
task_doc = tasks.find_one({"task_id": task_id}) task_path = get_task_path(task_id)
task_path = task_doc["path"] if task_doc and "path" in task_doc else task_id
combined_rows.append({ combined_rows.append({
"ws": task_path, "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: if current_block is not None:
blocks.append(current_block) blocks.append(current_block)
# Get task path # Get task path with history fallback
task_path = None task_path = get_task_path(task_id) or "No Task"
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
current_block = { current_block = {
"task_id": task_id, "task_id": task_id,
"task_path": task_path or "No Task", "task_path": task_path,
"start": switch_start, "start": switch_start,
"end": switch_end, "end": switch_end,
"duration": switch_duration, "duration": switch_duration,
@@ -256,15 +343,13 @@ def get_raw_switches(start, end, task=None):
result = [] result = []
for switch in raw_switches: for switch in raw_switches:
task_id = switch.get("task") task_id = switch.get("task")
task_path = None # Get task path with history fallback
if task_id: task_path = get_task_path(task_id) or "No Task"
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
result.append({ result.append({
"workspace": switch["workspace"], "workspace": switch["workspace"],
"task_id": task_id, "task_id": task_id,
"task_path": task_path or "No Task", "task_path": task_path,
"date": switch["date"].replace(tzinfo=utctz).astimezone(timezone), "date": switch["date"].replace(tzinfo=utctz).astimezone(timezone),
"delta": switch["delta"] "delta": switch["delta"]
}) })

View File

@@ -56,7 +56,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
padding: 8px; padding: 8px;
background: #f9f9f9;
border-left: 4px solid; border-left: 4px solid;
font-size: 11pt; font-size: 11pt;
} }
@@ -83,12 +82,9 @@
color: #666; color: #666;
} }
/* Workspace colors */ /* Active vs idle workspace indicator */
.ws-Work { border-color: #2563eb; background: #eff6ff; } .ws-active { font-weight: bold; }
.ws-Think { border-color: #7c3aed; background: #f5f3ff; } .ws-idle { opacity: 0.6; }
.ws-Plan { border-color: #059669; background: #ecfdf5; }
.ws-Other { border-color: #64748b; background: #f8fafc; }
.ws-Away { border-color: #94a3b8; background: #f1f5f9; }
.stats { .stats {
margin: 20px 0; margin: 20px 0;
@@ -161,7 +157,13 @@
<div class="switches-container"> <div class="switches-container">
{% for switch in switches %} {% for switch in switches %}
<div class="switch-row ws-{{ switch.workspace }}"> {% 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) %}
<div class="switch-row {{ 'ws-active' if is_active else 'ws-idle' }}"
style="border-left-color: {{ border_color }}; background-color: {{ bg_color }};">
<div class="switch-time">{{ switch.date.strftime('%m/%d %H:%M:%S') }}</div> <div class="switch-time">{{ switch.date.strftime('%m/%d %H:%M:%S') }}</div>
<div class="switch-workspace">{{ switch.workspace }}</div> <div class="switch-workspace">{{ switch.workspace }}</div>
<div class="switch-task">{{ switch.task_path }}</div> <div class="switch-task">{{ switch.task_path }}</div>

View File

@@ -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: 1. Copy the extension to your GNOME extensions directory:
```bash ```bash
cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local \ cp -r /home/mariano/wdir/dm/gnome-extension/deskmeter-indicator@local ~/.local/share/gnome-shell/extensions/
~/.local/share/gnome-shell/extensions/
``` ```
2. Restart GNOME Shell: 2. Restart GNOME Shell:
@@ -31,10 +30,12 @@ Or use GNOME Extensions app (install with `sudo apt install gnome-shell-extensio
## Configuration ## 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 ```javascript
const UPDATE_INTERVAL = 3000; // milliseconds const DEBOUNCE_DELAY = 2200; // milliseconds
``` ```
The API URL is set to: The API URL is set to:
@@ -52,6 +53,28 @@ rm -rf ~/.local/share/gnome-shell/extensions/deskmeter-indicator@local
Then restart GNOME Shell. 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 ## Troubleshooting
### Extension not showing ### Extension not showing
@@ -66,6 +89,12 @@ Then restart GNOME Shell.
journalctl -f -o cat /usr/bin/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" ### Shows "offline" or "error"
- Ensure dmweb Flask server is running on port 10000 - 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"}` 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 ### Task path too long
The extension automatically truncates paths longer than 40 characters, showing only the last two segments with a `.../ ` prefix. The extension automatically truncates paths longer than 40 characters, showing only the last two segments with a `.../ ` prefix.

View File

@@ -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'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
const DESKMETER_API_URL = 'http://localhost:10000/api/current_task'; 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( const TaskIndicator = GObject.registerClass(
class TaskIndicator extends PanelMenu.Button { class TaskIndicator extends PanelMenu.Button {
@@ -24,11 +24,36 @@ class TaskIndicator extends PanelMenu.Button {
this.add_child(this._label); this.add_child(this._label);
// Start periodic updates 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._updateTask();
this._timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, UPDATE_INTERVAL, () => { this._debounceTimeout = null;
this._updateTask(); return GLib.SOURCE_REMOVE;
return GLib.SOURCE_CONTINUE;
}); });
} }
@@ -70,10 +95,16 @@ class TaskIndicator extends PanelMenu.Button {
} }
destroy() { destroy() {
if (this._timeout) { if (this._debounceTimeout) {
GLib.source_remove(this._timeout); GLib.source_remove(this._debounceTimeout);
this._timeout = null; this._debounceTimeout = null;
} }
if (this._workspaceSwitchedId) {
this._workspaceManager.disconnect(this._workspaceSwitchedId);
this._workspaceSwitchedId = null;
}
super.destroy(); super.destroy();
} }
}); });

20
gnome-extension/update.sh Normal file
View File

@@ -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"