Блокировщик отвлекающих приложений для 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 закрывается сам, а попытка запустить его заново упирается в «Заблокировано» — лаунчер поднимается, игра нет.
Главный экран. Вверху — что происходит прямо сейчас, ниже — сценарии; у каждого видно расписание, цели и строгость. Сценарий «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. Имя
собирается из трёх источников по убыванию узнаваемости:
- заголовок видимого окна (
EnumWindows) — то единственное имя, которое человек в этот момент видит на экране; FileDescriptionиз ресурса версии файла — для процессов без окна и как запасной вариант: заголовок окна меняется, описание файла нет;- имя файла с заглавной буквы.
Процессы с окном идут первыми, остальные скрыты за переключателем: искомое почти всегда оказывается в первой десятке.
Одна запись каталога держит несколько .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.pngnpm 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 appnpx 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. Берите, меняйте, встраивайте куда угодно — хоть в закрытый продукт. Единственное условие: сохраните упоминание авторства.



