192 lines
13 KiB
Markdown
192 lines
13 KiB
Markdown
# cursor-lang
|
|
|
|
[English version](README.md)
|
|
|
|
Приложение WPF показывает раскладку клавиатуры у курсора.
|
|
|
|
## Клонирование
|
|
|
|
Двоичные ресурсы — иконка exe и логотипы MSIX — хранятся в Git LFS, поэтому
|
|
перед клонированием нужно установить [git-lfs](https://git-lfs.com):
|
|
|
|
```powershell
|
|
git lfs install
|
|
git clone git@git.alrakis.kz:alrakis/cursor-lang.git
|
|
```
|
|
|
|
Клонирование без него проходит успешно, но вместо изображений остаются
|
|
текстовые файлы-указатели, и сборка затем падает на нечитаемой иконке.
|
|
Существующий клон чинится командой `git lfs install`, а затем `git lfs pull`.
|
|
|
|
## Права администратора
|
|
|
|
Приложение работает от имени обычного пользователя. От повышения прав
|
|
отказались, чтобы приложение можно было опубликовать в Microsoft Store: пакеты
|
|
MSIX всегда выполняются в контексте вошедшего пользователя, а политика Store
|
|
отклоняет приложения, которым права администратора нужны для любой части
|
|
функциональности.
|
|
|
|
Цена этого — необязательная горячая клавиша Caps Lock. Пока фокусом владеет
|
|
окно с более высоким уровнем целостности — Диспетчер задач, Редактор реестра,
|
|
запросы UAC, — Windows не доставляет нажатия низкоуровневому хуку и не принимает
|
|
запрос на смену раскладки, так что горячая клавиша там ничего не делает. Чтение
|
|
раскладки чужого окна не ограничено, поэтому сама подсказка продолжает работать
|
|
везде.
|
|
|
|
## Автозапуск
|
|
|
|
Автозапуск включается из настроек приложения тем способом, который доступен
|
|
сборке. Пакет объявляет его в манифесте как `windows.startupTask`. Сборка,
|
|
распакованная в папку, прописывается так, как это всегда делали программы для
|
|
рабочего стола, — в `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, и прав
|
|
администратора для этого не нужно.
|
|
|
|
И в том, и в другом случае Windows показывает приложение в разделе Параметры —
|
|
Приложения — Автозагрузка; если пользователь выключит его там, приложение уже не
|
|
сможет включить его обратно и скажет об этом вместо молчаливого отказа. Запись в
|
|
реестре при этом остаётся на месте: решение пользователя Windows хранит отдельно
|
|
от неё, в `StartupApproved`, и приложение с ним считается.
|
|
|
|
## Настройки
|
|
|
|
Расположение `settings.json` зависит от способа установки приложения. Отдельная
|
|
установка хранит его в `%APPDATA%\CursorLang`. Пакетная сборка хранит его в
|
|
собственной папке данных пакета, которую Windows удаляет вместе с приложением —
|
|
предполагается, что приложения из Store не оставляют после себя ничего.
|
|
|
|
При первом запуске пакетная сборка подхватывает настройки, оставленные отдельной
|
|
установкой, и копирует их себе. Исходный файл остаётся на месте: обе сборки могут
|
|
быть установлены рядом, и приложение не вправе удалять настройки, которые ему не
|
|
принадлежат.
|
|
|
|
## Обновления
|
|
|
|
Приложение ищет новые версии среди выпусков собственного репозитория. Выпуск
|
|
годится, если его тег — это просто версия (`v1.2.3` или `1.2.3`) и к нему
|
|
приложен пакет MSIX. Тег, в котором есть что-то ещё, — в том числе `v1.2.3-beta`
|
|
— пропускается: предварительную версию берут намеренно, приложение её не
|
|
предлагает.
|
|
|
|
Из приложенных файлов предпочитается `.msixbundle` — он несёт обе архитектуры.
|
|
Если его нет, берётся пакет, в имени которого стоит архитектура этой машины:
|
|
`CursorLang-1.2.3.0-x64.msix`. Такие имена даёт `build-msix.ps1`, так что выпуск
|
|
делается прикладыванием того, что он собрал.
|
|
|
|
Пакет скачивается во временную папку и передаётся установщику приложений
|
|
Windows: тот показывает издателя, спрашивает подтверждение и заменяет
|
|
установленную версию. Подпись проверяет Windows, поэтому приложенный к выпуску
|
|
пакет должен быть подписан — неподписанный установится только на машине в режиме
|
|
разработчика. Работающее приложение до перезапуска продолжает жить на старых
|
|
файлах.
|
|
|
|
У приложения, установленного из Store, раздела обновлений нет вовсе: его
|
|
обновляет Store, а пакет со стороны Windows поверх него всё равно не примет.
|
|
|
|
Само по себе приложение спрашивает о выпусках раз в сутки, при запуске, и хранит
|
|
дату последней удачной проверки в настройках. Там же её можно выключить — тогда
|
|
остаётся кнопка в окне настроек, делающая то же самое по требованию.
|
|
|
|
Где искать выпуски, задаётся в `UpdateOptions`: репозиторий принадлежит тому, кто
|
|
выпускает приложение, а не пользователю, поэтому значения живут в сборке, а не в
|
|
`settings.json`:
|
|
|
|
```csharp
|
|
services.AddSingleton(new UpdateOptions
|
|
{
|
|
ServiceUri = new Uri("https://git.alrakis.kz/"), // сам сервер Gitea
|
|
Project = "alrakis/cursor-lang",
|
|
});
|
|
```
|
|
|
|
Выпуски берутся из Gitea, а её API живёт на самом сервере: адрес — тот же, по
|
|
которому репозиторий открывают в браузере, и под ним приложение спрашивает
|
|
`/api/v1/repos/{владелец}/{репозиторий}/releases`.
|
|
|
|
Закрытому репозиторию нужен токен. Он читается из переменной окружения
|
|
`CURSORLANG_UPDATE_TOKEN`, а не хранится в исходниках: секрет, встроенный в
|
|
сборку, — это секрет, отданный всем, кто эту сборку получил.
|
|
|
|
## Тесты
|
|
|
|
```powershell
|
|
dotnet test
|
|
```
|
|
|
|
Тесты лежат в `CursorLang.Tests` и работают на xUnit. Половина приложения —
|
|
окна, таймеры диспетчера, перехват клавиатуры — живёт только на потоке STA
|
|
с очередью сообщений, поэтому тесты держат один такой поток на весь прогон
|
|
и выполняют на нём всё, что этого требует.
|
|
|
|
Части проверок нужно настоящее окно переднего плана: положение каретки и
|
|
просьбу сменить раскладку видно только там. Право вывести окно вперёд Windows
|
|
даёт не всегда, и такие проверки сообщают о себе как о пропущенных, а не как
|
|
о провалившихся — без окна переднего плана проверять нечего. Сквозные проверки
|
|
запускают собранное приложение отдельным процессом и пропускают себя, если
|
|
приложение уже работает: вмешиваться в чужой запущенный экземпляр они не вправе.
|
|
|
|
Покрытие снимается так:
|
|
|
|
```powershell
|
|
dotnet test --collect:"XPlat Code Coverage" --settings CursorLang.Tests\coverage.runsettings
|
|
```
|
|
|
|
Непокрытым остаётся то, до чего тестовому процессу не дотянуться: пути, которым
|
|
нужен установленный пакет MSIX — `StartupTask` и папка данных пакета, — и
|
|
композиционный корень в `App.xaml.cs`, который вместо этого проверяется сквозным
|
|
запуском приложения.
|
|
|
|
## Непрерывная сборка
|
|
|
|
Пайплайны лежат в `.gitea/workflows` и работают на Gitea Actions. Запрос
|
|
на слияние в `master` собирается и проверяется тестами; по тегу вида `v1.2.3`
|
|
проект собирается, проходит тесты, пакуется в MSIX и выкладывается релизом
|
|
с приложенными пакетами. Номер версии берётся только из тега — тег любого
|
|
другого вида останавливает прогон в самом начале. Версия пакета получается
|
|
`1.2.3.0`: Store принимает четыре числа и последнее оставляет себе, так что тег
|
|
на него не влияет.
|
|
|
|
Обоим пайплайнам нужен runner под Windows с меткой `windows-x64`, на нём —
|
|
.NET 10 SDK и git-lfs. Выгрузка исходников тянет файлы LFS: без них иконка
|
|
остаётся текстовой заглушкой и сборка на ней падает. Работать runner должен
|
|
в интерактивном сеансе рабочего стола — тесты поднимают настоящие окна,
|
|
а службе в нулевом сеансе ждать нечего.
|
|
|
|
Пакет, который несёт релиз, загружается в Partner Center как есть. Identity
|
|
берётся из переменных репозитория, а если те не заданы — из значений по
|
|
умолчанию в скрипте: `MSIX_IDENTITY_NAME`, `MSIX_PUBLISHER`
|
|
и `MSIX_PUBLISHER_DISPLAY_NAME`.
|
|
|
|
## Сборка пакета MSIX
|
|
|
|
Ни Visual Studio, ни Windows SDK не требуются — `makeappx` поставляется из
|
|
пакета NuGet. Порядок публикации целиком — от регистрации разработчика
|
|
до отправки на проверку — описан в
|
|
[Packaging/PUBLISHING.RU.md](Packaging/PUBLISHING.RU.md).
|
|
|
|
```powershell
|
|
# Разово: отрисовать логотипы и иконку exe (уже в репозитории, повторить после правок)
|
|
powershell -File Packaging\New-Assets.ps1
|
|
|
|
# Проверка на своей машине: только своя архитектура
|
|
powershell -File Packaging\build-msix.ps1 -Architectures x64
|
|
|
|
# Для Partner Center — identity та, что зарезервирована там
|
|
powershell -File Packaging\build-msix.ps1 -Version 1.0.1.0 `
|
|
-IdentityName 12345AleksandrNeychev.CursorLang -Publisher "CN=ABCD1234-..."
|
|
```
|
|
|
|
Результат — `artifacts\packages\CursorLang-<версия>.msixbundle`, покрывающий x64
|
|
и arm64; рядом лежат пакеты отдельных архитектур. Bundle загружается в Partner
|
|
Center как есть.
|
|
|
|
Приложение поставляется с собственной копией .NET: Windows не включает .NET 10, а
|
|
MSIX не может установить среду выполнения как зависимость пакета.
|
|
|
|
Чтобы попробовать пакет без установки, зарегистрируйте опубликованный layout —
|
|
для этого нужен режим разработчика и не нужна подпись вовсе:
|
|
|
|
```powershell
|
|
Add-AppxPackage -Register artifacts\layout\x64\AppxManifest.xml
|
|
Remove-AppxPackage (Get-AppxPackage -Name CursorLang).PackageFullName
|
|
```
|