From 2126d2b3ef0adc435d878ca4db5782fba5675940 Mon Sep 17 00:00:00 2001 From: Ivan Vasilev Date: Fri, 11 Sep 2026 02:54:04 +0300 Subject: [PATCH] =?UTF-8?q?Windows:=20=D0=BE=D0=B4=D0=BD=D0=B0=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=83=D1=81=D0=BA=D0=B0=20=D0=B8=20=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D1=8F=200.6.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit На Windows в настройках было две похожие кнопки: автозапуск службы обхода и автозапуск самого приложения (задача планировщика). Теперь это одна кнопка «Запускать вместе с Windows»: при загрузке поднимается служба, а SenBoost открывается свёрнутым в трей, без окна. Задача планировщика больше не переключается отдельно — followZapretAutoStart ставит и снимает её вслед за автозапуском службы (без лишнего UAC: приложение и так работает с правами администратора). На macOS и Linux ничего не меняется. --- package-lock.json | 4 +- package.json | 2 +- src/main/app-autostart.ts | 43 ++++++++++++++++ src/main/app-autostart.win32.ts | 3 ++ src/main/index.ts | 3 +- src/renderer/src/components/SettingsView.tsx | 53 ++++++++++++-------- src/shared/ipc-contract.ts | 10 +++- 7 files changed, 90 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f54bf8..7582370 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "senboost", - "version": "0.6.1", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "senboost", - "version": "0.6.1", + "version": "0.6.2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1d17704..dcbfc84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "senboost", - "version": "0.6.1", + "version": "0.6.2", "description": "Кроссплатформенное десктопное приложение на Electron + React + TypeScript", "main": "./out/main/index.js", "desktopName": "senboost.desktop", diff --git a/src/main/app-autostart.ts b/src/main/app-autostart.ts index 528f1c8..bf3f47f 100644 --- a/src/main/app-autostart.ts +++ b/src/main/app-autostart.ts @@ -1,6 +1,9 @@ import { app } from 'electron' +import type { ZapretStatus } from '../shared/ipc-contract' import * as linux from './app-autostart.linux' import * as win32 from './app-autostart.win32' +import { isTrayActive } from './tray' +import { onZapretStatusChanged, zapretStatus } from './zapret' /** * Автозапуск САМОГО приложения вместе с системой — не путать с автозапуском обхода @@ -8,6 +11,9 @@ import * as win32 from './app-autostart.win32' * приложения, а этот открывает приложение свёрнутым в трей, чтобы обход можно было включить * и выключить одним кликом. * + * На Windows отдельного переключателя у него нет — он следует за автозапуском службы + * (см. `followZapretAutoStart`), в настройках одна кнопка «Запускать вместе с Windows». + * * Источник истины — сама система (реестр/планировщик, SMAppService, каталог автозапуска), * в settings.json ничего не дублируем: после переустановки системы или приложения * сохранённое значение всё равно разошлось бы с реальностью. @@ -43,6 +49,43 @@ export async function setAppAutoStart(enabled: boolean): Promise { } } +/** + * Windows: служба обхода стартует при загрузке — значит, и приложение должно подняться + * при входе, свёрнутым в трей, чтобы обход было чем выключить. Две кнопки для этого путали: + * обе назывались «запускать вместе с Windows», а по отдельности смысла почти не имели. + * + * Задача планировщика существует ровно тогда, когда служба установлена и стоит на `Auto`. + * Приложение и так работает с правами администратора, так что лишнего UAC это не добавляет — + * пользователь видит один запрос, на смену типа запуска службы. Подписка на статус, а не + * вызов из IPC: обход включают и из меню трея, и первое включение ставит службу уже с `Auto`. + * + * Вызывать после `recoverZapret()` — до него статус ещё не знает о службе. + */ +export function followZapretAutoStart(): void { + if (process.platform !== 'win32') return + + let applied: boolean | null = null + let queue = Promise.resolve() + + const sync = (status: ZapretStatus): void => { + const wanted = status.serviceInstalled && status.autoStart && isTrayActive() + if (wanted === applied) return + applied = wanted + queue = queue + .then(async () => { + if ((await win32.isAppAutoStartEnabled()) !== wanted) await win32.setAppAutoStart(wanted) + }) + .catch((error) => { + // Главное — служба, она уже настроена. Без задачи обход всё равно поднимется при + // загрузке, просто без значка в трее; в dev-режиме сюда попадаем всегда (нет прав). + console.warn('[autostart] не удалось синхронизировать задачу планировщика:', error) + }) + } + + onZapretStatusChanged(sync) + sync(zapretStatus()) +} + /** * Запуск пришёл из автозапуска — окно показывать не нужно, приложение садится в трей. * diff --git a/src/main/app-autostart.win32.ts b/src/main/app-autostart.win32.ts index f6ed4c2..c83823a 100644 --- a/src/main/app-autostart.win32.ts +++ b/src/main/app-autostart.win32.ts @@ -13,6 +13,9 @@ const TASK_NAME = 'SenBoost Autostart' * (`app.setLoginItemSettings`): exe собран с `requestedExecutionLevel: requireAdministrator` * (см. electron-builder.yml), а UAC при входе в систему не спрашивают — приложение из ключа * реестра просто не запустилось бы. Задача с `/RL HIGHEST` поднимает его с правами сразу. + * + * Сама по себе из интерфейса не переключается: её ставит и снимает `followZapretAutoStart` + * (`app-autostart.ts`) вслед за автозапуском службы обхода. */ function taskCommand(): string { // В dev первым аргументом electron.exe нужен каталог приложения, в сборке — нет. diff --git a/src/main/index.ts b/src/main/index.ts index f179d40..fa7a0c3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,6 +1,6 @@ import { app } from 'electron' import { electronApp, optimizer } from '@electron-toolkit/utils' -import { shouldStartHidden } from './app-autostart' +import { followZapretAutoStart, shouldStartHidden } from './app-autostart' import { beginQuit, createWindow, showMainWindow } from './window' import { showFirstRunSupportDialog } from './first-run' import { registerIpcHandlers } from './ipc' @@ -61,6 +61,7 @@ if (elevatedTask) { // На macOS чинит прокси, оставшийся от аварийно завершённого прошлого запуска, // до того как окно вообще откроется. void recoverZapret().then(() => { + followZapretAutoStart() const window = createWindow({ show: !startHidden }) // Ждём первого кадра: диалог должен появиться поверх готового окна, а не пустого. // Свёрнутому в трей приложению показывать модальное окно некуда — пользователь его diff --git a/src/renderer/src/components/SettingsView.tsx b/src/renderer/src/components/SettingsView.tsx index 5e51395..3297904 100644 --- a/src/renderer/src/components/SettingsView.tsx +++ b/src/renderer/src/components/SettingsView.tsx @@ -8,7 +8,8 @@ import { LIST_TITLES } from '../lib/zapretListMeta' import ListEditor from './ListEditor' /** - * Настройки: списки сайтов + автозапуск (macOS/Windows). Выбор стратегии и проверка + * Настройки: списки сайтов + автозапуск (macOS/Windows; на Windows одна кнопка на службу + * и приложение в трее). Выбор стратегии и проверка * соединения переехали на главный экран (ZapretCard.tsx) — рядом с кнопкой * включения, где ими и пользуются. Отдельный экран за шестерёнкой в шапке. */ @@ -72,33 +73,40 @@ export default function SettingsView({ {background.settings.closeToTray ? 'Включено' : 'Выключено'} - + Запускать приложение вместе с системой + + {background.settings.launchAtLogin ? 'Включён' : 'Выключен'} + + + )} {background.error &&

{background.error}

}

- Приложение открывается свёрнутым в трей и обход можно включить одним кликом по значку. - Это не то же самое, что автозапуск обхода ниже: тот работает вообще без запущенного - приложения. + {isWindows + ? 'Закрытое окно прячется в трей, обход продолжает работать. Включить и ' + + 'выключить его можно одним кликом по значку.' + : 'Приложение открывается свёрнутым в трей и обход можно включить одним кликом ' + + 'по значку. Это не то же самое, что автозапуск обхода ниже: тот работает ' + + 'вообще без запущенного приложения.'}

)} {supportsAutoStart && (
- Автозапуск обхода + {isWindows ? 'Автозапуск' : 'Автозапуск обхода'}