Skip to content

Repository files navigation

FocusLock

Блокировщик отвлекающих приложений для Windows. Живёт в трее, по расписанию и дневным лимитам закрывает выбранные программы и не даёт запустить их снова. Внутри же — таймер помодоро, который на время работы закрывает отвлекающее тем же механизмом.

Сделан для себя, выложен как есть: берите, меняйте, предлагайте.

In short. A Windows app blocker that lives in the system tray: it closes chosen programs on a schedule or when a daily time limit runs out, and keeps them closed. Hard scenarios cannot be switched off on impulse — unlocking takes a deliberate delay. Includes a Pomodoro timer that blocks distractions during work intervals. Tauri 2 + Rust engine, React 19 front end, local-only data. Interface and code comments are in Russian.

Что он делает

  • Сценарии — набор правил: что закрывать, в какие дни, в какие часы, сколько минут в сутки. Несколько сценариев работают одновременно, ограничения складываются.
  • Жёсткие сценарии нельзя выключить в момент слабости: выключение принимается сразу, а срабатывает через заданную задержку. Передумать можно одной кнопкой, обойти в один клик — нельзя.
  • Дневные лимиты — «час на игры»: считается календарное время, а не сумма по приложениям.
  • Свои приложения выбираются из списка запущенных процессов по тому имени, которое видно на экране: War Thunder, а не aces.exe.
  • Помодоро с блокировкой отвлекающего на рабочих отрезках.
  • Статистика по дням: сколько потрачено, сколько запусков пресечено, сколько дней подряд без превышений.
  • Всё локально. Ни аккаунтов, ни сети, ни телеметрии — данные лежат в %ProgramData%\FocusLock и никуда не уходят.

Четыре темы (системная, светлая, тёмная, AMOLED), режим высокого контраста и масштаб интерфейса.

Как это выглядит

Демонстрация: запущенный Minecraft закрывается по исчерпании лимита, повторный запуск не проходит

Лимит на игры закончился. Minecraft закрывается сам, а попытка запустить его заново упирается в «Заблокировано» — лаунчер поднимается, игра нет.

Экран «Сценарии»: идёт дневной лимит, ниже карточки сценариев

Главный экран. Вверху — что происходит прямо сейчас, ниже — сценарии; у каждого видно расписание, цели и строгость. Сценарий «no more tundra today !» ограничивает не весь Steam, а конкретную игру.

Экран «Фокус»: таймер помодоро и выбор того, что закрывать

Помодоро. Отдельно настраивается, что именно закрывается на рабочих отрезках, — этим таймер и отличается от любого другого.

Экран «Статистика»: плитки и график по дням

Статистика по дням: серия без превышений, расход, отрезки помодоро. Красным — дни с превышением лимита, пунктир — сам порог.

Жёлтая полоса наверху снимков — не поломка: так приложение честно предупреждает, что работает без прав администратора и потому не сможет закрыть программы, запущенные с повышением. Полоса исчезает, когда включён автозапуск с наивысшими правами.

Установка

Скачайте установщик со страницы Releases и запустите. Можно поставить для себя или для всех пользователей — установщик спросит. Либо соберите сами, см. Разработку: готовый .exe появится в src-tauri/target/release/bundle/nsis/.

Windows будет ругаться. Установщик не подписан сертификатом, а само приложение принудительно завершает процессы и создаёт задания в Планировщике — для SmartScreen и антивирусов это набор признаков вредоносной программы. Пока подписи нет, это неизбежно: сертификат подписи кода стоит денег и выдаётся на юридическое лицо. Единственный честный ответ — исходный код открыт, соберите сами и проверьте, что он делает.

Приложению нужны права администратора: без них не закрыть процессы, запущенные с повышением, и не создать задание автозапуска. Интерфейс честно показывает, если прав нет, — иначе человек думает, что защищён, а блокировка молча не работает.

Через что написано

Слой Чем Почему так
Движок Rust + windows 0.61 завершение дерева процессов через Job Object, перечисление окон, ресурсы версии файлов — всё это WinAPI, и делать это из JS нечем
Оболочка Tauri 2 один исполняемый файл ~10 МБ вместо ~150 МБ у Electron; приложению висеть в фоне сутками, и память тут важнее удобства сборки
Интерфейс React 19 + TypeScript 5.9
Стили Tailwind 4 поверх собственных токенов тема меняет значения в одном файле, компоненты не знают о цветах
Иконки lucide-react
Сборка фронтенда Vite 7
История по дням SQLite через rusqlite (bundled) запись идёт годами, и при выключении питания WAL не оставит от файла обрубок
Уведомления tauri-winrt-notification движок работает без окна, а уведомлять должен именно он

Внешних сервисов нет ни одного. Сеть приложение не открывает вообще.

Про то, как это писалось. Значительная часть кода написана с помощью Claude — от движка на WinAPI до вёрстки. Говорю прямо, потому что это меняет то, как код стоит читать: архитектурные решения и комментарии «почему так» обсуждались и проверялись, но лишней уверенности к ним это не добавляет. Если увидите место, где объяснение красивее реальности, — это как раз повод завести issue.

Состояние

Версия 0.1.2, начато 29 августа 2026 года. Работает у автора каждый день, но это ранняя версия: острые углы наверняка есть. Что уже сделано и что менялось — в CHANGELOG.md.

Если вы ставили 0.1.0 — обновитесь: в ней автозапуск не включался, если приложение стоит в папке с пробелом в имени (то есть в Program Files), и молчал об этом.

Как это устроено

Один исполняемый файл работает в двух режимах:

focuslock.exe              → интерфейс + иконка в трее
focuslock.exe --watchdog   → фоновый движок без окна

Разделение на два бинарника потребовало бы упаковывать движок как sidecar и следить за совпадением версий; флаг исключает рассинхрон.

Общаются они через файлы в %ProgramData%\FocusLock\:

Файл Кто пишет Что внутри
config.json интерфейс сценарии, лимиты, настройки
state.json движок что заблокировано прямо сейчас
focus.json оба текущий отрезок помодоро и счётчик за сутки
stats.db движок история по дням (SQLite, WAL)
watchdog.log движок что происходило, пока окна не было
guard\usage.json движок израсходованное за текущие сутки
guard\hard-lock.json движок снимок правил действующих жёстких сценариев

Файлы вместо сокета выбраны намеренно: любой из процессов может умереть и перезапуститься, не ломая второй, а состояние переживает перезагрузку.

Почему две папки. В guard лежит то, удаление чего снимает ограничения: теневая копия жёстких сценариев и счётчик израсходованного за сутки. У этой подпапки урезанные права — писать может движок с повышением, остальные только читают. Без неё жёсткий сценарий снимался бы двумя действиями в Проводнике: удалить hard-lock.json, вписать в config.json "enabled": false. Всё остальное остаётся доступным на запись, потому что интерфейс работает без прав администратора и должен уметь сохранять сценарии и открывать базу статистики.

Права ставит включение автозапуска — там уже есть повышение, и второе окно UAC человек читал бы как «приложение что-то мутит». Пока автозапуск не включён, данные лежат по-старому в %APPDATA%\FocusLock и защиты нет: без прав администратора приложение тоже должно работать, иначе после установки оно выглядит рабочим и ничего не блокирует. При включении автозапуска файлы переносятся, при выключении — права снимаются обратно.

focus.json — единственный файл, который пишут обе стороны: интерфейс по нажатию кнопки, движок — когда отрезок закончился. Столкнуться они практически не могут (одно событие в двадцать пять минут против одного клика), а запись атомарна, поэтому худшее возможное — потерянное нажатие, которое человек повторит.

Окно и движок — не одно и то же. Оба задания Планировщика запускают --watchdog, то есть движок без окна. Интерфейс не стартует сам никогда: его открывает человек — из меню «Пуск» или кликом по иконке в трее. Поэтому настройки «запускаться свёрнутым» нет: прятать в трей то, что только что открыли руками, не от чего, а снаружи это выглядит как «приложение не запускается». В трей окно уходит по закрытию.

Кто ведёт счёт. Одновременно тикать движки не должны, иначе время начислится дважды. Разводит их поле ownerPid в state.json: фоновый watchdog авторитетен и тикает всегда, интерфейс — только если состояние протухло. Поэтому приложение работает сразу после установки, даже если автозапуск ещё не настроен.

Защита от обхода

Без этого блокировщик бессмысленен, поэтому сделано несколько вещей сразу:

  • Дерево процессов, а не один PID. Все процессы дерева собираются в один Job Object и гасятся одним TerminateJobObject. Между смертью родителя и смертью детей нет окна, в котором наблюдатель лаунчера успел бы поднять игру.
  • Защита от переиспользования PID. Windows быстро переиспользует номера, поэтому «потомок» старше своего родителя потомком не считается — иначе однажды прилетело бы постороннему процессу.
  • Чёрный список системных процессов. lsass.exe, explorer.exe и им подобные не завершаются ни при каких настройках.
  • Жёсткие сценарии заморожены целиком. Пока такой сценарий включён, Rust отказывается менять не только флаг включения, но и расписание, лимит, цели и строгость — и не даёт удалить сценарий. Это закрывает путь через интерфейс, DevTools и прямой вызов команды.
  • Теневая копия правил (guard\hard-lock.json) закрывает путь мимо приложения. config.json лежит в обычной папке и правится блокнотом, поэтому движок каждый такт сверяет его со снимком и откатывает всё, что делает сценарий слабее: выключение, паузу, урезанное расписание, снятый лимит, удаление сценария целиком. Оформление менять можно — оно ни на что не влияет.
  • Права на саму теневую копию. Сверка со снимком бессмысленна, если снимок удаляется тем же Delete, что и всё остальное: пустая копия означает, что защищать нечего. Поэтому она и счётчик израсходованного лежат в подпапке guard, куда пишет только движок с повышением. Администратор своей машины сменит владельца папки и всё равно доберётся — но это уже осознанная операция в свойствах папки, а не нажатие клавиши.
  • Досрочное снятие с задержкой. Запрос принимается сразу, сценарий отключается через 15 минут (настраивается). Время передумать. Отсчёт ведётся по часам движка и хранится в теневой копии: вписать в конфигурацию вчерашнюю дату, чтобы задержка «уже истекла», не выйдет.
  • Автозапуск через Планировщик заданий, а не папку «Автозагрузка»: последняя отключается одним переключателем в Диспетчере задач.
  • Самолечение. Второе задание раз в минуту просто запускает движок ещё раз. Дубликат упирается в именованный мьютекс и выходит — отдельная логика «проверь и подними» не нужна.

Что и как выбирается для блокировки

Каталог отслеживаемых приложений заполняется из списка запущенных процессов. Ключевая часть здесь — назвать процесс так, как его называет человек: по именам файлов выбирать невозможно, War Thunder — это aces.exe, а рядом лежат двести служебных процессов Windows. Имя собирается из трёх источников по убыванию узнаваемости:

  1. заголовок видимого окна (EnumWindows) — то единственное имя, которое человек в этот момент видит на экране;
  2. FileDescription из ресурса версии файла — для процессов без окна и как запасной вариант: заголовок окна меняется, описание файла нет;
  3. имя файла с заглавной буквы.

Процессы с окном идут первыми, остальные скрыты за переключателем: искомое почти всегда оказывается в первой десятке.

Одна запись каталога держит несколько .exe — у игры обычно есть ещё и лаунчер, и закрывать надо оба, иначе лаунчер поднимет игру обратно.

Добавить приложение можно и прямо из редактора сценария: мысль «а вот это тоже надо закрывать» приходит именно там, а не в настройках.

Помодоро

Таймер живёт в движке, а не в окне: окно закрывается в трей, движок перезапускается, компьютер уходит в сон — отрезок должен всё это пережить. Отсюда два следствия:

  • остаток считается по часам (ends_at минус сейчас), а не «минус секунда каждый тик»: тика может не случиться;
  • отрезок, конец которого застал компьютер спящим, не засчитывается. Иначе помодоро, оставленный на ночь, при следующем запуске движка прокрутил бы десяток фаз подряд и записал бы в статистику рабочий день, которого не было.

Смысл помодоро внутри блокировщика — не сам таймер, а то, что на время рабочего отрезка выбранные приложения закрываются тем же механизмом, что и по расписанию. Отдельный таймер без этого был бы секундомером.

Начать, приостановить и остановить отрезок можно из меню трея, не открывая окно.

Оформление

Отклик на курсор описан одной системой в index.css, а не hover-ами внутри компонентов: разнобой читается как «часть элементов неинтерактивна». Три класса — по тому, как элемент устроен визуально: .tap-flat (плоское: строки списков, иконки) получает подложку, .tap-raise (карточки, переключатели) — свечение и подъём на пиксель, .tap-fill (залитые кнопки и фишки) — свечение и прибавку яркости. Само свечение (--glow) — цельный набор теней в каждой теме, а не добавка к --shadow: в AMOLED теней нет, и box-shadow: none, <свечение> не является допустимым CSS.

Иконки без подписи снабжены своим тултипом (data-tip), а не атрибутом title: системный появляется через секунду с лишним, и человек успевает решить, что элемент неинтерактивный.

Тема меняет только значения в src/styles/tokens.css; компоненты не хардкодят цвета. Отсюда четыре темы (системная, светлая, тёмная, AMOLED) и режим высокого контраста поверх любой из них — без единой ветки в коде компонентов.

Цвета графика — отдельные токены, а не статусные: у визуализации своя полоса светлоты и свои требования к различимости при дальтонизме. Пара проверена валидатором (ΔE 27.8 при протанопии на белом фоне); менять её можно только вместе с повторным прогоном проверки.

Иконки приложения и трея порождаются из кода:

node scripts/make-icons.mjs
npx tauri icon src-tauri/icons/source.png

Разработка

npm install

Разработка (окно + горячая перезагрузка интерфейса):

npm run app

Сборка установщика (.exe, NSIS):

npm run app:build

Готовый установщик появится в src-tauri/target/release/bundle/nsis/.

Что нужно для сборки

Требование Зачем
Node 20+ сборка интерфейса
Rust (stable, x86_64-pc-windows-msvc) движок
MSVC Build Tools (workload «C++ build tools») линкер для Rust
WebView2 Runtime есть в Windows 11 из коробки

Если npm run app падает так:

failed to run 'cargo metadata' command to get workspace directory:
program not found

Rust установлен, но терминал об этом не знает. rustup дописывает %USERPROFILE%\.cargo\bin в PATH только для процессов, запущенных после установки: уже открытый терминал (и редактор, из которого он открыт) держит старое окружение до перезапуска. Проверить и починить:

cargo --version                    # «command not found» → дело в PATH

Закройте редактор целиком и откройте заново — не вкладку терминала, а само приложение: терминал наследует окружение у него. Разово можно обойтись и так:

set PATH=%USERPROFILE%\.cargo\bin;%PATH% && npm run app

Проверки

npx tsc --noEmit                          # типы интерфейса
cargo test --manifest-path src-tauri/Cargo.toml --lib

Тесты движка покрывают то, что легко сломать незаметно: интервалы через полночь, принадлежность дням недели, час сброса суток, обход дерева процессов, идемпотентность записи статистики, все способы ослабить жёсткий сценарий правкой файла, а у помодоро — замирание отсчёта на паузе, момент длинного перерыва и отказ засчитывать брошенный отрезок.

Честная граница защиты. Всё вышеперечисленное поднимает цену обхода, но не делает его невозможным: администратор своей машины может удалить задания Планировщика, сменить владельца защищённой папки или переустановить систему. Смысл защиты в другом — чтобы обход требовал осознанных многошаговых действий, а не одного клика в минуту слабости.

И одно ограничение, которое стоит знать заранее: всё это держится на том, что вы не администратор в момент слабости. Пока автозапуск не включён, защиты файлов нет вовсе. Когда включён — она есть ровно до того момента, как вы решите открыть свойства папки. Приложение помогает не передумать, а не удерживает силой, и обещать второе было бы враньём.

Чего пока нет

Из исходного ТЗ не реализовано:

  • блокировка сайтов (hosts-файл или локальный DNS-фильтр).

Режим белого списка реализован в границах каталога отслеживаемых приложений: блокируется всё, что в каталоге, кроме выбранного. «Гасить всё незнакомое» означало бы завершать системные службы.

Идеи, которые кажутся следующими по отдаче: задержка с вопросом «зачем открываешь?» вместо мгновенного закрытия; поле «записать мысль и вернуться позже» на экране фокуса; деление приложений на продуктивные и отвлекающие с баллом за день; недельный лимит вместо дневного.

Как помочь

Проект открыт в том числе ради этого — скажите, что не так.

  • Нашли ошибку — заведите issue: что делали, что ожидали, что случилось. Полезно приложить %ProgramData%\FocusLock\watchdog.log, но сначала посмотрите, нет ли в нём имён ваших программ, которые вы не хотите показывать.
  • Есть идея — тоже issue. Особенно интересны способы обхода: их лучше знать, чем не знать.
  • Хотите поправить код — присылайте pull request. Перед этим прогоняйте npx tsc --noEmit и cargo test.

Комментарии в коде на русском и объясняют не «что делает строка», а почему сделано именно так. Держитесь этого же: причина решения ценнее его описания.

Лицензия

MIT. Берите, меняйте, встраивайте куда угодно — хоть в закрытый продукт. Единственное условие: сохраните упоминание авторства.

About

Блокировщик отвлекающих приложений для Windows: сценарии по расписанию и дневным лимитам, жёсткие сценарии со снятием через задержку, помодоро с блокировкой отвлекающего на рабочих отрезках, статистика по дням. Всё локально, без аккаунтов и сети.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages