chore: add license file, add icons, images flow, update readme

This commit is contained in:
2026-08-26 18:07:44 +05:00
parent ea09f47938
commit 4c33bba276
29 changed files with 294 additions and 33 deletions
Generated
+11
View File
@@ -614,6 +614,7 @@ dependencies = [
"toml 1.1.4+spec-1.1.0",
"winit",
"winreg",
"winresource",
]
[[package]]
@@ -5784,6 +5785,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winresource"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
dependencies = [
"toml 1.1.4+spec-1.1.0",
"version_check",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
+1
View File
@@ -15,6 +15,7 @@ winreg = "0.56.0"
[build-dependencies]
slint-build = "1.16.1"
winresource = "0.1.31"
[profile.release]
lto = true
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Ku6epXBOCTuK
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+70 -23
View File
@@ -1,37 +1,84 @@
## 🚀 Rust Browser Picker (Working Title)
<div align="center">
Легкий и мгновенный переключатель браузеров на Rust + Slint. Замена тяжеловесным аналогам, которые долго загружаются.
<img src="assets/icon.png" width="96" alt="bropicker">
## 🎯 Цель
# bropicker
Создать утилиту, которая перехватывает открытие ссылок и предлагает выбрать браузер в красивом UI. Главный приоритет — скорость запуска (Cold Start < 100ms).
Лёгкий переключатель браузеров: кликаешь ссылку — bropicker спрашивает, каким браузером её открыть.
## 🛠 Технологический стек
**Rust + Slint, нативный рендер без WebView, cold start ~110 ms.**
- Язык: [Rust](https://www.rust-lang.org/)
- GUI: [Slint](https://slint.dev/) (Native-speed, Declarative UI)
- Запуск процессов: std::process::Command
<a href="https://slint.dev"><img src="https://raw.githubusercontent.com/slint-ui/slint/master/logo/MadeWithSlint-logo-whitebg.png" alt="Made with Slint" height="28"></a>
## 📊 Текущий статус
<img src="docs/img/screenshot-dark.png" width="244" alt="Тёмная тема">&nbsp;<img src="docs/img/screenshot-light.png" width="244" alt="Светлая тема">
- Frameless-окно с прозрачным фоном: карточка парит над рабочим столом со скруглёнными углами
- Тёмная и светлая темы (переключение кнопкой, env `BP_THEME=light`)
- Перетаскивание окна за хедер, Esc — закрыть, центрирование при старте
- Дизайн-макеты: [refs/dark.html](refs/dark.html) / [refs/light.html](refs/light.html)
</div>
## 🔧 Разработка
## Возможности
План работ и цикл визуальной разработки с ИИ-агентом — [docs/dev-workflow.md](docs/dev-workflow.md).
- Frameless-окно с прозрачностью, перетаскивание за хедер, Esc — закрыть
- Тёмная и светлая темы
- Автодетект установленных браузеров из реестра (Firefox, Chrome, Edge, Zen, Opera, Brave, Яндекс…)
- Запоминание выбора по домену: повторные ссылки открываются сразу, без вопроса
- Галочка «Всегда спрашивать» — пикер вызывается даже для запомненных доменов
- Несколько профилей одного браузера как отдельные записи (флаги + эмодзи-иконка)
- Конфиг в TOML, открывается кнопкой шестерёнки
## Сборка
```powershell
cargo run # запуск
tools/render-refs.ps1 # PNG-референсы из макетов
tools/run-and-shot.ps1 # собрать, снять скриншот окна
tools/run-and-shot.ps1 -Light # то же в светлой теме
cargo build --release
```
## 🗺 Дорожная карта
## Установка обработчиком ссылок
1. Список браузеров с логотипами, запуск выбранного браузера с URL (MVP)
2. Регистрация в системе как браузер по умолчанию
3. config.toml + страница настроек
```powershell
tools\register.ps1 # скопирует сборку в %LOCALAPPDATA%\bropicker и откроет настройки Windows
```
В открывшемся окне выбери **bropicker** для HTTP и HTTPS. Откат: `tools\register.ps1 -Uninstall`.
## Конфигурация
`%APPDATA%\bropicker\config.toml` (шестерёнка в пикере открывает его в редакторе).
Пример с профилями и эмодзи: [docs/config.example.toml](docs/config.example.toml).
```toml
[[browsers]]
name = "Chrome — Work"
path = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
flags = '--profile-directory="Default"'
emoji = "💼"
[remembered]
"github.com" = "Chrome — Work"
[settings]
remember_choice = true
always_ask = true
```
## Разработка
```powershell
just check # fmt + clippy -D warnings + тесты
just screenshots # перегенерация скриншотов README (тёмная + светлая)
just icons # перегенерация иконок из assets/icon.svg
just release # release-сборка
just register # установка/обновление обработчика ссылок
```
То же напрямую: `tools\check.ps1`, `tools\screenshots.ps1`, `tools\register.ps1`.
Планы и процесс: [docs/dev-workflow.md](docs/dev-workflow.md).
## Лицензия
Код bropicker — [MIT](LICENSE).
Интерфейс построен на [Slint](https://slint.dev), который используется по
[Slint Royalty-free License 2.0](licenses/Slint-Royalty-free-2.0.md)
Текст лицензии также входит
в состав установки (`licenses/`).
Логотипы браузеров — [alrrr/browser-logos](https://github.com/alrrr/browser-logos), MIT
([LICENSE](logos/LICENSE-browser-logos.txt)).
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+20
View File
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3b82f6"/>
<stop offset="1" stop-color="#06b6d4"/>
</linearGradient>
</defs>
<rect width="256" height="256" rx="58" fill="url(#bg)"/>
<g stroke="#ffffff" stroke-width="13" fill="none" stroke-linecap="round">
<circle cx="128" cy="122" r="62"/>
<ellipse cx="128" cy="122" rx="27" ry="62"/>
<line x1="66" y1="122" x2="190" y2="122"/>
</g>
<circle cx="128" cy="212" r="11" fill="#ffffff"/>
<circle cx="88" cy="212" r="11" fill="#ffffff" opacity="0.45"/>
<circle cx="168" cy="212" r="11" fill="#ffffff" opacity="0.45"/>
</svg>

After

Width:  |  Height:  |  Size: 750 B

+6 -3
View File
@@ -1,6 +1,9 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
fn main() {
slint_build::compile("ui/main.slint").unwrap();
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
let mut res = winresource::WindowsResource::new();
res.set_icon("assets/app.ico");
res.compile().unwrap();
}
}
+12 -4
View File
@@ -169,10 +169,18 @@ HTML-макеты → PNG: **headless Edge** (`--headless --screenshot`), он
Цель <100 ms чуть не достигнута; варианты оптимизации, если захочется:
renderer-femtovg вместо skia (быстрее init GPU), отложенная загрузка
конфига. Пока считаем приемлемым
- [ ] Иконка exe (.ico через build.rs winres)
- [ ] Выбрать лицензию проекта: GPLv3 (опенсорс) или Royalty-free Slint (закрытый десктоп,
атрибуция AboutSlint) — зафиксировать в README и LICENSE
- [ ] Скриншоты в README из tools/out
- [x] Иконка exe: `assets/icon.svg` (градиент + глобус + точки «выбора»)
`magick``assets/icon.png` + `assets/app.ico` (16-256) → `winresource`
в build.rs; проверено ExtractAssociatedIcon
- [x] Лицензия MIT (`LICENSE`); Slint — по Royalty-free License 2.0 (маршрут (b):
бейдж Made-with-Slint в README, текст лицензии в `licenses/` и в
составе установки `%LOCALAPPDATA%\bropicker\licenses\`).
ВАЖНО: маршрут (b) требует публичной страницы с бейджем; если репо
станет приватным — добавить виджет AboutSlint в окно настроек
- [x] Скриншоты в README: `docs/img/screenshot-{dark,light}.png`;
перегенерация — `just screenshots` (tools/screenshots.ps1 → shot.ps1 -Card:
DPI-aware обрезка до карточки, клип на 2px внутрь срезает AA-бахрому
скруглений, углы прозрачные — чужих пикселей нет)
- [ ] Ручной сценарий приёмки: register → клик ссылки в другом приложении →
пикер → выбор → вкладка открылась; повторный клик того же домена при
always_ask=off → сразу браузер
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+26
View File
@@ -0,0 +1,26 @@
default:
@just --list
# fmt + clippy -D warnings + тесты
check:
powershell -ExecutionPolicy Bypass -File tools/check.ps1
# release-сборка
release:
cargo build --release
# перегенерация скриншотов для README (тёмная + светлая, только карточка)
screenshots:
powershell -ExecutionPolicy Bypass -File tools/screenshots.ps1
# перегенерация иконок из assets/icon.svg (icon.png, app.ico, PNG-лестница)
icons:
powershell -ExecutionPolicy Bypass -File tools/icons.ps1
# установка/обновление обработчика ссылок (+ открытие настроек Windows)
register:
powershell -ExecutionPolicy Bypass -File tools/register.ps1
# снять регистрацию и удалить %LOCALAPPDATA%\bropicker
unregister:
powershell -ExecutionPolicy Bypass -File tools/register.ps1 -Uninstall
+43
View File
@@ -0,0 +1,43 @@
# Slint Royalty-free Desktop, Mobile, and Web Applications License
Version 2.0
## Preamble
Slint is a toolkit that can be used to build user interfaces for applications. Slint (hereafter referred to as **Software**) is made available under different licenses by SixtyFPS GmbH incorporated at Oranienburger Str. 44, 16540 Hohen Neuendorf, Germany (**SixtyFPS**). The **Slint Royalty-free Desktop, Mobile, and Web Applications License** is suitable for those who develop desktop, mobile, or web applications and do not want to use open source components under copyleft licenses.
## 1. Grant of Rights
SixtyFPS hereby grants You a world-wide, royalty-free, non-exclusive license to use, reproduce, make available, modify, display, perform, distribute the Software as part of a Desktop, Mobile, or Web Application.
A **Desktop Application** is a computer program that is designed to run on a general-purpose computer (PC or notebook), typically installed and executed locally on the computer's operating system.
A **Mobile Application** is a computer program that is designed to run on a general-purpose mobile computer (mobile phone or tablet), typically installed and executed locally on the computer's operating system.
A **Web Application** is a computer program that is designed to run in the sandbox environment provided by a web browser.
Desktop Application, Mobile Application, and Web Application are hereafter referred to as **Application**.
## 2. License Conditions - Attribution
You may distribute the Software as part of an Application, modified or unmodified, provided that You do either of the following:
(a) Display the [`AboutSlint`](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/misc/aboutslint/) widget in an "About" screen or dialog that is accessible from the top level menu of the Application. In the absence of such a screen or dialog, display the widget in the "Splash Screen" of the Application.
(b) Display the [Slint attribution badge](https://github.com/slint-ui/slint/tree/master/logo/MadeWithSlint-logo-whitebg.png) on a public webpage, preferably where the binaries of your Application can be downloaded from, in such a way that it can be easily found by any visitor to that page.
## 3. Limitations
The License does not permit to distribute or make the Software publicly available alone and without integration into an Application. For this purpose you may use the Software under the GNU General Public License, version 3.
The License does not permit the use of the Software within Embedded Systems. An **Embedded System** is a computer system designed to perform a specific task within a larger mechanical or electrical system.
The License does not permit the distribution of Application that exposes the APIs, in part or in total, of the Software.
You may not remove or alter any license notices (including copyright notices, disclaimers of warranty, or limitations of liability) contained within the source code form of the Software.
## 4. Warranty and Liability
SixtyFPS is only liable for conflicting rights of third parties if SixtyFPS was aware of these rights without informing you. Unless required by applicable law or agreed to in writing, SixtyFPS provides the Software on an "as is" basis, without warranties or conditions of any kind, either express or implied, including, without limitation, any warranties or conditions of merchantability, or fitness for a particular purpose.
Unless required by law, SixtyFPS won't be liable for any direct, indirect, incidental, or consequential damages arising in any way out of the use of the Software.
+12
View File
@@ -0,0 +1,12 @@
$ErrorActionPreference = "Stop"
$svg = "assets/icon.svg"
magick -background none $svg -resize 256x256 assets/icon.png
magick -background none $svg -define icon:auto-resize=256,128,64,48,32,24,16 assets/app.ico
foreach ($size in @(16, 24, 32, 44, 48, 50, 64, 88, 128, 150, 256, 512)) {
magick -background none $svg -resize "${size}x${size}" ("assets/icon-{0}.png" -f $size)
}
Write-Host "icons regenerated: icon.png, app.ico, icon-{16..512}.png"
+5
View File
@@ -29,6 +29,11 @@ Copy-Item $src (Join-Path $installDir "bropicker.exe") -Force
Copy-Item (Join-Path $PSScriptRoot "..\logos") (Join-Path $installDir "logos") -Recurse -Force
Copy-Item (Join-Path $PSScriptRoot "..\icons") (Join-Path $installDir "icons") -Recurse -Force
$licensesDir = Join-Path $installDir "licenses"
New-Item -ItemType Directory -Force -Path $licensesDir | Out-Null
Copy-Item (Join-Path $PSScriptRoot "..\licenses\Slint-Royalty-free-2.0.md") $licensesDir -Force
Copy-Item (Join-Path $PSScriptRoot "..\logos\LICENSE-browser-logos.txt") $licensesDir -Force
$exe = Join-Path $installDir "bropicker.exe"
Write-Host "Installed to: $exe"
+23
View File
@@ -0,0 +1,23 @@
$ErrorActionPreference = "Stop"
cargo build
if ($LASTEXITCODE -ne 0) { exit 1 }
New-Item -ItemType Directory -Force -Path docs\img | Out-Null
$exe = Join-Path $PWD "target\debug\bropicker.exe"
$proc = Start-Process -FilePath $exe -WorkingDirectory $PWD -PassThru
Start-Sleep -Milliseconds 1800
powershell -ExecutionPolicy Bypass -File "$PSScriptRoot\shot.ps1" -ProcId $proc.Id -Out docs\img\screenshot-dark.png -Card
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
if ($LASTEXITCODE -ne 0) { exit 1 }
$env:BP_THEME = "light"
$proc = Start-Process -FilePath $exe -WorkingDirectory $PWD -PassThru
Start-Sleep -Milliseconds 1800
powershell -ExecutionPolicy Bypass -File "$PSScriptRoot\shot.ps1" -ProcId $proc.Id -Out docs\img\screenshot-light.png -Card
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
Remove-Item Env:\BP_THEME -ErrorAction SilentlyContinue
if ($LASTEXITCODE -ne 0) { exit 1 }
Write-Host "screenshots updated: docs\img\screenshot-{dark,light}.png"
+44 -3
View File
@@ -1,7 +1,8 @@
param(
[string]$ProcName = "bropicker",
[int]$ProcId = 0,
[string]$Out = "tools/out/app.png"
[string]$Out = "tools/out/app.png",
[switch]$Card
)
$ErrorActionPreference = "Stop"
@@ -18,6 +19,7 @@ public class BpWin {
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr after, int x, int y, int cx, int cy, uint flags);
[DllImport("user32.dll")] public static extern uint GetDpiForWindow(IntPtr hWnd);
[StructLayout(LayoutKind.Sequential)]
public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
}
@@ -64,11 +66,50 @@ if ($w -le 0 -or $h -le 0) {
}
Add-Type -AssemblyName System.Drawing
$bmp = New-Object System.Drawing.Bitmap($w, $h)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$full = New-Object System.Drawing.Bitmap($w, $h)
$g = [System.Drawing.Graphics]::FromImage($full)
$g.CopyFromScreen($rect.Left, $rect.Top, 0, 0, (New-Object System.Drawing.Size($w, $h)))
$g.Dispose()
if ($Card) {
$dpi = [BpWin]::GetDpiForWindow($found)
$scale = if ($dpi -gt 0) { $dpi / 96.0 } else { 1.0 }
$inset = [int][Math]::Round(20 * $scale)
$radius = [int][Math]::Round(16 * $scale)
$clipInset = 2
$cw = $w - 2 * $inset - 2 * $clipInset
$ch = $h - 2 * $inset - 2 * $clipInset
$srcX = $inset + $clipInset
$srcY = $inset + $clipInset
$radius = [Math]::Max(1, $radius - $clipInset)
$bmp = New-Object System.Drawing.Bitmap($cw, $ch)
$g2 = [System.Drawing.Graphics]::FromImage($bmp)
$g2.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$g2.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$cardPath = New-Object System.Drawing.Drawing2D.GraphicsPath
$cardPath.AddArc(0, 0, $radius, $radius, 180, 90)
$cardPath.AddArc($cw - $radius, 0, $radius, $radius, 270, 90)
$cardPath.AddArc($cw - $radius, $ch - $radius, $radius, $radius, 0, 90)
$cardPath.AddArc(0, $ch - $radius, $radius, $radius, 90, 90)
$cardPath.CloseFigure()
$g2.SetClip($cardPath)
$srcRect = New-Object System.Drawing.Rectangle($srcX, $srcY, $cw, $ch)
$dstRect = New-Object System.Drawing.Rectangle(0, 0, $cw, $ch)
$g2.DrawImage($full, $dstRect, $srcRect, [System.Drawing.GraphicsUnit]::Pixel)
$g2.Dispose()
$cardPath.Dispose()
$full.Dispose()
$w = $cw
$h = $ch
} else {
$bmp = $full
}
$outDir = Split-Path -Parent (Join-Path $PWD $Out)
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
$outAbs = [System.IO.Path]::GetFullPath((Join-Path $PWD $Out))