Monitor de Foco com Python: Descubra Onde Seu Dia de Dev Está Vazando
Monitor de foco com Python parece nome de ferramenta que alguém venderia com dashboard roxo, mensalidade em dólar e uma landing page dizendo que você vai “recuperar 14 horas por semana”. A versão honesta é menos sexy: um script local, um banco SQLite e algumas regras simples para descobrir onde seu dia está vazando. Não para virar fiscal de si mesmo. Para parar de discutir com a própria memória.
Eu já perdi a conta de quantas vezes terminei o expediente com aquela sensação de ter trabalhado o dia inteiro e, ao mesmo tempo, não ter terminado nada que importava. O cérebro inventa uma narrativa muito convincente: “foi um dia cheio”. Só que “cheio” não é a mesma coisa que produtivo. Às vezes ele só foi picotado por abas, notificações, terminais, mensagens, recompilações e aquela olhadinha inocente no painel que virou vinte minutos de investigação paralela.
Neste artigo, vamos montar um monitor de foco com Python que registra a janela ativa do sistema, agrupa o tempo por aplicativo/projeto e gera um relatório simples no fim do dia. A proposta é resolver um problema real: transformar sensação em evidência sem instalar uma plataforma invasiva nem entregar sua rotina para uma nuvem qualquer.

1. O problema: o dia não acaba, ele evapora
Quando alguém fala em produtividade, muita gente pensa em lista de tarefas. Lista ajuda, mas ela tem um defeito cruel: mostra intenção, não execução. Você pode começar o dia com três prioridades cristalinas e terminar com quinze abas abertas, quatro conversas parcialmente respondidas e uma branch chamada fix-final-final-2.
O problema técnico aqui é simples: falta telemetria pessoal. Nós temos logs de servidor, métricas de API, rastreamento de erro, tracing distribuído e alertas para CPU acima de 80%. Mas quando o sistema instável é o nosso próprio fluxo de trabalho, a maioria opera no achismo.
Um monitor local resolve três perguntas:
- Em quais aplicativos eu realmente passei tempo?
- Quanto do meu dia foi trabalho profundo versus troca de contexto?
- Quais janelas aparecem como “só cinco minutos” mas comem blocos inteiros?
O detalhe importante: isso não substitui julgamento. Ele fornece munição contra autoengano. E autoengano, convenhamos, é um microserviço interno altamente disponível.
2. Arquitetura mínima: coletar, normalizar, armazenar, resumir
Vamos evitar arquitetura carnavalesca. Nada de Kafka, nada de observabilidade corporativa, nada de Kubernetes para contar quantos minutos você ficou no navegador. O desenho será:
- Um coletor Python roda em loop a cada poucos segundos.
- Ele identifica a janela ativa.
- Normaliza o título para uma categoria útil.
- Grava eventos em SQLite.
- Um relatório diário agrega os blocos de tempo.
Essa separação parece pequena, mas salva o projeto de virar uma gambiarra opaca. O coletor registra fatos. O classificador interpreta. O relatório decide como mostrar. Misturar tudo em uma função gigante é um convite para o clássico “funciona, mas tenho medo de tocar”.
Estrutura de arquivos
focus-monitor/
├── collect.py
├── report.py
├── rules.json
└── focus.sqlite
O arquivo rules.json vai mapear padrões de janela para categorias. O SQLite guarda eventos. Dois scripts mantêm responsabilidades separadas: coleta e relatório.
3. Dependências: a parte chata que decide se vai funcionar
Identificar janela ativa depende do sistema operacional. Em Linux com X11, dá para usar xdotool. Em macOS, osascript. Em Windows, pygetwindow ou chamadas via ctypes. Para manter o exemplo objetivo, vou mostrar uma implementação Linux/X11 e deixar uma função isolada para adaptar o backend.
sudo apt install xdotool
python3 -m venv .venv
source .venv/bin/activate
pip install python-dateutil
Sim, Wayland pode complicar. Segurança de desktop moderna evita que qualquer processo espie janelas livremente, e isso é bom. Se você usa Wayland, talvez precise adaptar para APIs do compositor, extensões específicas ou aceitar coletar apenas o aplicativo ativo quando disponível. A vida adulta é descobrir que até monitorar a própria janela tem política de permissão.
4. Modelando o banco SQLite sem frescura
O banco precisa guardar eventos pequenos: timestamp, aplicativo, título da janela e categoria normalizada. Também podemos guardar uma chave de sessão para facilitar agrupamentos futuros.
import sqlite3
SCHEMA = """
CREATE TABLE IF NOT EXISTS focus_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
captured_at TEXT NOT NULL,
app TEXT NOT NULL,
title TEXT NOT NULL,
category TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_focus_events_captured_at
ON focus_events(captured_at);
CREATE INDEX IF NOT EXISTS idx_focus_events_category
ON focus_events(category);
"""
def connect(path="focus.sqlite"):
db = sqlite3.connect(path)
db.executescript(SCHEMA)
return db
Não estamos salvando duração diretamente. Cada linha é uma amostra. Se o script roda a cada 10 segundos, cada evento representa aproximadamente 10 segundos naquela janela. Isso simplifica bastante. Não é perfeito, mas é suficientemente bom para detectar padrões. Métrica perfeita que nunca sai do papel vale menos que métrica imperfeita rodando todo dia.
5. Coletando a janela ativa com Python
Agora vem o coletor. No Linux/X11, podemos chamar xdotool getactivewindow getwindowname. Para o nome do aplicativo, uma aproximação é usar xprop e buscar WM_CLASS.
import subprocess
from datetime import datetime, timezone
import json
import re
import time
from db import connect
INTERVAL_SECONDS = 10
def run(command):
return subprocess.check_output(command, text=True).strip()
def active_window_id():
return run(["xdotool", "getactivewindow"])
def active_window_title(window_id):
return run(["xdotool", "getwindowname", window_id])
def active_window_app(window_id):
raw = run(["xprop", "-id", window_id, "WM_CLASS"])
parts = re.findall(r'"([^"]+)"', raw)
return parts[-1] if parts else "unknown"
def load_rules(path="rules.json"):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def classify(app, title, rules):
text = f"{app} {title}".lower()
for rule in rules["rules"]:
if any(pattern.lower() in text for pattern in rule["contains"]):
return rule["category"]
return rules.get("default", "Indefinido")
def insert_event(db, app, title, category):
db.execute(
"INSERT INTO focus_events (captured_at, app, title, category) VALUES (?, ?, ?, ?)",
(datetime.now(timezone.utc).isoformat(), app, title, category),
)
db.commit()
def main():
db = connect()
rules = load_rules()
while True:
try:
wid = active_window_id()
title = active_window_title(wid)
app = active_window_app(wid)
category = classify(app, title, rules)
insert_event(db, app, title, category)
except Exception as exc:
print(f"erro na coleta: {exc}")
time.sleep(INTERVAL_SECONDS)
if __name__ == "__main__":
main()
Repare no tratamento de erro. O script não deve morrer porque uma janela fechou no meio da leitura. Esse é um detalhe pequeno que separa automação útil de brinquedo de domingo.
6. Criando regras que não dependem de telepatia
A classificação é onde o monitor começa a ficar útil. Um relatório com “Google Chrome” por seis horas não diz quase nada. O navegador pode ser documentação, painel de deploy, YouTube, e-mail ou o buraco negro chamado “só vou pesquisar uma coisa”.
Um rules.json simples já resolve muito:
{
"default": "Outros",
"rules": [
{
"category": "Código",
"contains": ["visual studio code", "vscode", "pycharm", "nvim", "github"]
},
{
"category": "Terminal",
"contains": ["terminal", "wezterm", "alacritty", "bash", "zsh"]
},
{
"category": "Documentação",
"contains": ["docs", "developer.mozilla", "python.org", "wordpress.org"]
},
{
"category": "Comunicação",
"contains": ["telegram", "slack", "discord", "gmail", "whatsapp"]
},
{
"category": "Distração",
"contains": ["youtube", "reddit", "x.com", "twitter"]
}
]
}
Não tente criar taxonomia perfeita no primeiro dia. Comece grosseiro. Depois refine. Produtividade pessoal sofre quando a pessoa tenta modelar a ONU inteira antes de registrar o primeiro evento.
7. Gerando relatório diário
Como cada evento representa um intervalo fixo, podemos somar amostras por categoria. Se o coletor roda a cada 10 segundos, 360 eventos equivalem a uma hora.
import sqlite3
from collections import defaultdict
from datetime import datetime, timedelta, timezone
INTERVAL_SECONDS = 10
def daily_report(db_path="focus.sqlite", date_prefix=None):
if date_prefix is None:
date_prefix = datetime.now(timezone.utc).date().isoformat()
db = sqlite3.connect(db_path)
rows = db.execute(
"""
SELECT category, app, COUNT(*) as samples
FROM focus_events
WHERE captured_at LIKE ?
GROUP BY category, app
ORDER BY samples DESC
""",
(f"{date_prefix}%",),
).fetchall()
totals = defaultdict(int)
apps = []
for category, app, samples in rows:
seconds = samples * INTERVAL_SECONDS
totals[category] += seconds
apps.append((category, app, seconds))
print(f"Relatório de foco — {date_prefix}")
print("=" * 40)
for category, seconds in sorted(totals.items(), key=lambda x: x[1], reverse=True):
minutes = seconds / 60
print(f"{category}: {minutes:.1f} min")
print("\nAplicativos principais")
print("-" * 40)
for category, app, seconds in apps[:15]:
print(f"{app} ({category}): {seconds / 60:.1f} min")
if __name__ == "__main__":
daily_report()
Esse relatório não precisa ser bonito. Precisa ser lido. Se você quiser evoluir depois, gere HTML, CSV ou mande para um bot privado. Mas o valor nasce quando você olha e pensa: “por que diabos passei tanto tempo alternando entre terminal e navegador?”. Essa pergunta vale dinheiro.

8. Detectando troca de contexto, o ladrão elegante
Tempo por categoria é útil, mas a métrica mais venenosa é troca de contexto. Uma pessoa pode ter quatro horas em “Código”, mas se alternou 180 vezes entre editor, navegador, chat e terminal, o trabalho profundo virou purê.
Para medir isso, conte mudanças de categoria entre amostras consecutivas:
def context_switches(db_path="focus.sqlite", date_prefix=None):
if date_prefix is None:
date_prefix = datetime.now(timezone.utc).date().isoformat()
db = sqlite3.connect(db_path)
rows = db.execute(
"""
SELECT captured_at, category
FROM focus_events
WHERE captured_at LIKE ?
ORDER BY captured_at ASC
""",
(f"{date_prefix}%",),
).fetchall()
switches = 0
previous = None
for _, category in rows:
if previous is not None and category != previous:
switches += 1
previous = category
print(f"Trocas de contexto: {switches}")
Essa métrica é cruel, mas honesta. Se ela estiver alta, talvez o problema não seja falta de disciplina. Talvez seja falta de blocos protegidos, notificações agressivas, ambiente mal configurado ou tarefas pequenas demais brigando pelo mesmo espaço mental.
9. Privacidade: o ponto que não dá para empurrar com a barriga
Um monitor de foco com Python é poderoso justamente porque fica perto da sua rotina. Isso também o torna perigoso. Títulos de janela podem conter nomes de clientes, assuntos sensíveis, URLs privadas, mensagens e documentos. Não jogue isso em analytics. Não suba para um SaaS improvisado. Não mande para um dashboard público porque ficou bonitinho.
Algumas regras razoáveis:
- Grave categorias sempre que possível, não títulos completos.
- Se precisar guardar título, aplique mascaramento em domínios, nomes e IDs.
- Use permissões restritas no arquivo SQLite.
- Não sincronize o banco em pastas compartilhadas sem pensar.
- Apague dados antigos automaticamente.
find ./data -name "focus.sqlite" -mtime +30 -delete
Sim, dá para fazer retenção direto no SQLite também. O comando acima é só um lembrete filosófico em formato de shell: dado que não existe não vaza.
10. Rodando como serviço sem transformar em religião
No Linux, um serviço systemd resolve. Crie ~/.config/systemd/user/focus-monitor.service:
[Unit]
Description=Monitor local de foco com Python
[Service]
WorkingDirectory=/home/seu-usuario/focus-monitor
ExecStart=/home/seu-usuario/focus-monitor/.venv/bin/python collect.py
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
Depois:
systemctl --user daemon-reload
systemctl --user enable --now focus-monitor.service
systemctl --user status focus-monitor.service
Se o serviço cair, ele volta. Se você reiniciar a máquina, ele volta. Se ele registrar erro, você tem logs. O mínimo adulto.
11. Transformando relatório em decisão
A parte mais importante não é o script. É o ritual. Sem ritual, você só criou mais um banco para apodrecer em silêncio.
Todo fim de dia, rode o relatório e responda três perguntas:
- Qual categoria dominou meu tempo?
- Essa categoria corresponde à prioridade do dia?
- Qual ajuste pequeno reduzia o estrago amanhã?
Exemplos de ajuste:
- Bloquear comunicação durante dois blocos de 45 minutos.
- Abrir documentação em uma janela separada para reduzir alternância.
- Separar tarefas investigativas de tarefas de entrega.
- Fechar painéis que você consulta por ansiedade, não por necessidade.
- Transformar interrupções recorrentes em automações.
Esse último ponto conecta diretamente com outros experimentos do AutoMente, como a central de produtividade com Python e SQLite, a fila de automação com SQLite e a análise de observabilidade de logs em produção. O padrão é o mesmo: pare de confiar no grito do momento e comece a registrar o sistema.
12. Melhorias possíveis sem virar produto enterprise
Depois que a versão simples estiver rodando por alguns dias, dá para melhorar com calma:
- Detecção de ociosidade: ignore períodos sem teclado ou mouse.
- Projetos por diretório: quando o app for editor ou terminal, detecte pasta atual.
- Relatório por bloco: agrupe manhã, tarde e noite.
- Alertas locais: avise quando trocas de contexto passarem de um limite.
- Exportação semanal: gere um resumo Markdown para revisão.
Minha melhoria favorita é o alerta de troca de contexto. Não um popup irritante, porque já temos inimigos suficientes, mas uma notificação discreta quando você alterna demais em poucos minutos. Algo como: “Você trocou de categoria 12 vezes em 10 minutos. Está trabalhando ou praticando tênis de mesa cognitivo?”.
Conclusão: produtividade sem romantizar sofrimento
Um monitor de foco com Python não vai fazer café, pagar boleto nem impedir você de abrir uma aba idiota às 16h37. Mas ele faz algo melhor: mostra o que aconteceu. E quando você enxerga o padrão, fica mais difícil continuar chamando dispersão de “dia corrido”.
O ponto não é vigiar cada segundo. É criar um espelho técnico para o fluxo de trabalho. Um espelho meio grosseiro, talvez feio, mas honesto. E honestidade operacional é uma vantagem competitiva subestimada.
Se você quiser transformar isso em algo mais forte, conecte o relatório à sua fila diária, cruze com tarefas planejadas e meça a distância entre intenção e execução. É aí que automação deixa de ser enfeite e vira alavanca.
Agora me diga: qual automação você quer ver no próximo experimento do AutoMente? Um painel local de foco, um bloqueador inteligente de distrações, ou um robô que transforma relatório diário em plano de amanhã?
