diff --git a/.gitignore b/.gitignore index acd6668..62ee715 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ __pycache__ nohup.out dm.out dm.err -sample_task_file \ No newline at end of file +sample_task_file +dmfnt diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..866adbd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,100 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Deskmeter is a productivity tool that measures time spent across desktop workspaces. It consists of three main components: + +- **dmcore**: Core tracking daemon (`dmapp/dmcore/main.py`) that monitors active workspace and task changes +- **dmweb**: Flask web application (`dmapp/dmweb/`) for viewing productivity data +- **dmfnt**: Angular frontend (`dmapp/dmfnt/`) for enhanced UI (in development) + +## Architecture + +### Core Components + +**Task Management System**: +- Task definitions stored in files, tracked via `dmapp/dmcore/task.py` +- File modification time monitoring for automatic task switching +- Hierarchical task organization under workspace paths (e.g., `work/default`, `work/dlt`) + +**Workspace Tracking**: +- Uses `wmctrl` to detect active X11 workspace (requires XORG, not Wayland) +- Maps workspace indices to labels in `dmapp/dmcore/main.py:desktops` +- Special handling for "work" desktops with automatic task enforcement + +**Data Storage**: +- MongoDB backend storing workspace switches with timestamps and durations +- State persistence in `dmapp/dmcore/state.py` for current workspace/task tracking + +### Web Interface Structure + +**Flask Routes** (`dmapp/dmweb/dm.py`): +- `/` - Today's productivity summary +- `/day//` - Single day view +- `/calendar` - Monthly calendar view via `dmapp/dmweb/dmcal.py` +- `/totals` - All-time statistics + +## Development Commands + +### Python Backend + +```bash +# Install dependencies +pip install -r requirements.txt + +# Start MongoDB service +sudo systemctl start mongod.service + +# Run core tracking daemon +cd dmapp/dmcore +python3 main.py + +# Run Flask web server +cd dmapp/dmweb +python3 run.py + +# Run tests +cd dmapp/tests +python3 test_dmapp.py +``` + +### Angular Frontend + +```bash +cd dmapp/dmfnt + +# Install dependencies +npm install + +# Development server +npm run start + +# Build for production +npm run build + +# Run tests +npm run test +``` + +### System Setup + +The application requires: +- MongoDB running locally +- wmctrl installed (`apt install wmctrl`) +- X11 desktop environment (not Wayland) +- Python virtual environment recommended + +## Key Configuration + +**Workspace Labels**: Edit `desktops` tuple in `dmapp/dmcore/main.py:12` +**Work Desktop Mapping**: Configure `work_desktops` dict in `dmapp/dmcore/main.py:13` +**Timezone**: Set in `dmapp/dmcore/main.py:19` using zoneinfo + +## Development Notes + +- The system enforces task constraints within work desktops - switching to a non-work task automatically reverts to the designated work task +- Task files are monitored for changes to enable automatic context switching +- Web interface runs on port 10000 by default +- Core daemon sleeps for 2 seconds between workspace checks \ No newline at end of file diff --git a/dmapp/.env b/dmapp/.env new file mode 100644 index 0000000..5d75777 --- /dev/null +++ b/dmapp/.env @@ -0,0 +1,5 @@ +// dmcore +task_file = "/home/mariano/LETRAS/adm/task/main" +desktops = ("Plan", "Think", "Work", "Other", "Away", "Work", "Work", "Work") +work_desktops = {2: "default", 5: "dlt", 6: "vhs", 7: "cal"} +unlabeled = "Away" diff --git a/dmapp/__init__.py b/dmapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dmapp/dm.sh b/dmapp/dm.sh index 67f3d2c..521b502 100644 --- a/dmapp/dm.sh +++ b/dmapp/dm.sh @@ -1,9 +1,9 @@ sudo systemctl start mongod.service . /home/mariano/wdir/venv/dm/bin/activate -cd /home/mariano/wdir/def/deskmeter -nohup python3 dmmain.py >dm.out 2>dm.err & +cd /home/mariano/wdir/def/deskmeter/dmapp/dmcore +nohup python3 main.py >dm.out 2>dm.err & -cd /home/mariano/wdir/def/deskmeter/dmapp +cd /home/mariano/wdir/def/deskmeter/dmapp/dmweb nohup python3 run.py >dm.out 2>dm.err & cd ~ diff --git a/dmapp/dmcore/main.py b/dmapp/dmcore/main.py index 3cc3de7..de8e888 100644 --- a/dmapp/dmcore/main.py +++ b/dmapp/dmcore/main.py @@ -9,8 +9,8 @@ import task from config import logger, switches from zoneinfo import ZoneInfo -desktops = ("Plan", "Think", "Work", "Other", "Away", "Work", "Work") -work_desktops = {2: "default", 5: "dlt", 6: "vhs"} +desktops = ("Plan", "Think", "Work", "Other", "Away", "Work", "Work", "Work") +work_desktops = {2: "default", 5: "dlt", 6: "vhs", 7: "cal"} unlabeled = "Away" @@ -60,7 +60,6 @@ while True: current_mtime = state.retrieve("current").get("filetime") file_mtime = task.get_file_mtime(None) - logger.debug(f"current_mtime: {current_mtime}, file_mtime:{file_mtime}") # First handle file changes if current_mtime != file_mtime: task_id = task.read_and_extract(None) @@ -86,9 +85,6 @@ while True: f"work/{work_desktops[current_workspace]}" ) work_task_ids = {t["task_id"] for t in workspace_tasks if "task_id" in t} - logger.debug( - f"work_task_ids:{work_task_ids}, current_work_task: {current_work_task},current_task: {current_task}" - ) # if current_task in work_task_ids and current_task != current_work_task: if current_task not in work_task_ids: @@ -97,6 +93,7 @@ while True: current_task = current_work_task state.save("current", task=current_task) task.db_to_file_as_is(None) + elif current_task != current_work_task: # Update work state when switching to a different valid task state.update_work_state(work_desktops[current_workspace], current_task) diff --git a/dmapp/dmcore/task.py b/dmapp/dmcore/task.py index db4d8ab..37c4807 100644 --- a/dmapp/dmcore/task.py +++ b/dmapp/dmcore/task.py @@ -7,7 +7,7 @@ import state from bson import ObjectId from config import logger, tasks -task_file = "/home/mariano/wdir/def/deskmeter/dmapp/dmcore/sample_task_file" +task_file = "/home/mariano/LETRAS/adm/task/main" def parse_line(line: str) -> tuple[Optional[str], Optional[str]]: diff --git a/dmapp/dmweb/__init__.py b/dmapp/dmweb/__init__.py index 3e49386..de6735f 100644 --- a/dmapp/dmweb/__init__.py +++ b/dmapp/dmweb/__init__.py @@ -1,13 +1,12 @@ from flask import Flask -from dmweb import dm, dmcal + +from . import dm, dmcal + def create_app(): - - app = Flask("deskmeter") + app = Flask("deskmeter") app.debug = True app.register_blueprint(dm.dmbp) return app - - diff --git a/dmapp/dmweb/dm.py b/dmapp/dmweb/dm.py index 0340ba3..a041307 100644 --- a/dmapp/dmweb/dm.py +++ b/dmapp/dmweb/dm.py @@ -24,6 +24,7 @@ def index(task=None): end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) rows = get_period_totals(start, end, task) + print(rows) return render_template("main.html", rows=rows) diff --git a/dmapp/dmweb/dmcal.py b/dmapp/dmweb/dmcal.py index 600007a..45f1b27 100644 --- a/dmapp/dmweb/dmcal.py +++ b/dmapp/dmweb/dmcal.py @@ -2,10 +2,10 @@ import calendar from datetime import datetime from pprint import pprint -# import pytz -from dmweb.dm import dmbp from flask import Blueprint, render_template +# import pytz +from .dm import dmbp from .get_period_times import ( get_period_totals, read_and_extract, @@ -77,6 +77,8 @@ class DMHTMLCalendar(calendar.HTMLCalendar): rows = get_period_totals(start, end, self.task) + print(rows) + returnstr = "" for row in rows: returnstr += "".format( diff --git a/dmapp/dmweb/get_period_times.py b/dmapp/dmweb/get_period_times.py index 350056b..39bb3e2 100644 --- a/dmapp/dmweb/get_period_times.py +++ b/dmapp/dmweb/get_period_times.py @@ -178,8 +178,6 @@ def get_period_totals(start, end, task=None): aux_results = list(switches.aggregate(pipeline_before_after)) - print(aux_results) - bfirst = aux_results[0]["before_first"] if bfirst: diff --git a/dmapp/run.py b/dmapp/dmweb/run.py similarity index 82% rename from dmapp/run.py rename to dmapp/dmweb/run.py index 1648d44..607ba28 100644 --- a/dmapp/run.py +++ b/dmapp/dmweb/run.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from dmweb import create_app app = create_app()
{}{}