repo clean up
This commit is contained in:
parent
2c3df28c48
commit
1569d20679
7 changed files with 235 additions and 330 deletions
|
|
@ -1,94 +0,0 @@
|
||||||
# Migration Status
|
|
||||||
|
|
||||||
## Implemented
|
|
||||||
|
|
||||||
- Replaced the default Tauri + Leptos starter UI with a desktop-oriented Razer app shell.
|
|
||||||
- Added native Linux hidraw discovery for supported devices:
|
|
||||||
- Razer Basilisk V3 `1532:0099`
|
|
||||||
- Razer Basilisk V3 Pro wired `1532:00aa`
|
|
||||||
- Razer Basilisk V3 Pro wireless `1532:00ab`
|
|
||||||
- Ported the Python report framing used by `qdrazer.protocol.Report` into Rust:
|
|
||||||
- 90-byte report payload plus report id
|
|
||||||
- Basilisk transaction id `0x1f`
|
|
||||||
- command class/id layout
|
|
||||||
- XOR CRC over bytes 2 through 87
|
|
||||||
- feature report send/receive through `HIDIOCSFEATURE` and `HIDIOCGFEATURE`
|
|
||||||
- Implemented a native connect and snapshot flow:
|
|
||||||
- serial number
|
|
||||||
- firmware version
|
|
||||||
- available onboard profiles
|
|
||||||
- basic profile settings
|
|
||||||
- Implemented native writes for the first vertical slice:
|
|
||||||
- scroll wheel mode
|
|
||||||
- scroll acceleration
|
|
||||||
- smart reel
|
|
||||||
- polling delay
|
|
||||||
- current DPI X/Y
|
|
||||||
- DPI stages get/set, stage count, and active stage selection
|
|
||||||
- Basic tab parity improvements from the Vue app:
|
|
||||||
- editable DPI stage list
|
|
||||||
- stage count control
|
|
||||||
- active stage control
|
|
||||||
- `Y = X` helper for DPI stages
|
|
||||||
- Profile tab parity improvements from the Vue app:
|
|
||||||
- create profile
|
|
||||||
- delete profile
|
|
||||||
- direct + white/red/green/blue/cyan slot management UI
|
|
||||||
- YAML export/import for the selected profile's basic, button, and LED bundle
|
|
||||||
- backend profile info chunked read/write support (`0x0588` / `0x0508`) carried in exported/imported bundles
|
|
||||||
- Connect flow parity improvements from the Vue app:
|
|
||||||
- scan + select device
|
|
||||||
- auto-connect first supported device after scan
|
|
||||||
- connect status and error handling
|
|
||||||
- LED tab parity improvements from the Vue app:
|
|
||||||
- region selection for wheel/logo/strip
|
|
||||||
- off/static/spectrum/wave effects
|
|
||||||
- per-region speed and brightness controls
|
|
||||||
- apply-to-all regions helper
|
|
||||||
- Button tab parity improvements from the Vue app:
|
|
||||||
- per-button assignment editing for normal and hypershift layers
|
|
||||||
- categories for mouse, keyboard, macro, DPI switch, profile switch, system, consumer, hypershift toggle, scroll mode toggle, and custom payloads
|
|
||||||
- native get/set button mapping commands through the Rust backend
|
|
||||||
- Macro tab parity improvements from the Vue app:
|
|
||||||
- macro list loading
|
|
||||||
- load/edit/save/delete by macro ID
|
|
||||||
- YAML export/import for all macro operation lists
|
|
||||||
- flash reset action
|
|
||||||
- native macro op encode/decode for keyboard, system, consumer, mouse button, mouse wheel, and delay operations
|
|
||||||
- preserve loaded `macro_info_hex` when round-tripping an existing macro through the editor
|
|
||||||
- Sensor tab parity improvements from the Vue app:
|
|
||||||
- lift mode selection for smart, smart asym, config, and self calibration modes
|
|
||||||
- calibration start/stop controls
|
|
||||||
- retrieved calibration data editor
|
|
||||||
- parameter calculator for symmetric and asymmetric lift settings
|
|
||||||
- set-params write path for config A / config B blobs
|
|
||||||
- Debug tooling improvements:
|
|
||||||
- raw sent/received report logging from the Tauri hidraw backend
|
|
||||||
- log viewer and clear action in the Info tab
|
|
||||||
- warning/error entries for exclusive-access and command failures
|
|
||||||
|
|
||||||
## Follow-Up Fixes Applied During Parity Review
|
|
||||||
|
|
||||||
- Removed the user-facing `Use Without Mouse` entry point.
|
|
||||||
- Added auto-connect after device scan.
|
|
||||||
- Replaced the polling delay number input with the source-style fixed Hz choices.
|
|
||||||
- Made short or partially decoded button mappings fall back to `custom/raw` instead of failing the tab.
|
|
||||||
- Fixed the Sensor tab reactive loop that could freeze the app.
|
|
||||||
- Restored the stylesheet after the broken import split and re-split it into trunk-linked CSS files under the file-size cap.
|
|
||||||
- Fixed the button-mapping save payload shape for the Tauri `set_button_mapping` command.
|
|
||||||
- Fixed LED option/layout regressions caused by over-aggressive wrapping rules.
|
|
||||||
- Serialized "Apply to all regions" through a single backend command instead of racing multiple hidraw writes from the frontend.
|
|
||||||
|
|
||||||
## Source Features Still To Port
|
|
||||||
|
|
||||||
- Completion audit and parity fixes against the Vue reference still remain.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
This target no longer embeds Pyodide or uses WebHID. The backend is Tauri-native Rust and currently targets Linux hidraw devices. Running the real device workflow requires read/write permission for the matching `/dev/hidraw*` node, typically handled with a udev rule.
|
|
||||||
|
|
||||||
The active bundle target is AppImage. On modern rolling Linux distributions, linuxdeploy's bundled `strip` binary can fail on `.relr.dyn` ELF sections, so build AppImage artifacts with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
NO_STRIP=true cargo tauri build
|
|
||||||
```
|
|
||||||
127
PROMPT.md
127
PROMPT.md
|
|
@ -1,127 +0,0 @@
|
||||||
Goal: migrate the existing `razerqdhid` app into `razer-linux-desktop` as a native Linux desktop application using Tauri 2 for the shell and Leptos for the UI.
|
|
||||||
|
|
||||||
Context:
|
|
||||||
- Source app: `./razerqdhid`
|
|
||||||
- Target app: `./razer-linux-desktop`
|
|
||||||
- Current target state: the target is still the default Tauri + Leptos template (`src/app.rs`, `src-tauri/src/lib.rs`).
|
|
||||||
- Current source stack: Vue 3 + Vite + WebHID + Pyodide/Python.
|
|
||||||
- Source device/protocol logic lives mainly under `razerqdhid/public/py/`.
|
|
||||||
- This project is Linux-only. The app configures supported Razer mice, currently centered on Basilisk V3 / V3 Pro behavior already implemented in the source project.
|
|
||||||
|
|
||||||
Local test hardware (dev machine):
|
|
||||||
- Connected device: Razer Basilisk V3 Pro (USB)
|
|
||||||
- `lsusb`: `1532:00ab` (Razer USA, Ltd)
|
|
||||||
- USB sysfs node: `/sys/bus/usb/devices/5-2.2.1`
|
|
||||||
- Exposed HID raw nodes (same VID/PID): `/dev/hidraw3`, `/dev/hidraw5`, `/dev/hidraw6`
|
|
||||||
- `ID_USB_INTERFACE_NUM`: `00` -> `/dev/hidraw3` (mouse interface)
|
|
||||||
- `ID_USB_INTERFACE_NUM`: `01` -> `/dev/hidraw5`
|
|
||||||
- `ID_USB_INTERFACE_NUM`: `02` -> `/dev/hidraw6`
|
|
||||||
- Device serial string currently reports as `000000000000` (do not rely on it for unique identification).
|
|
||||||
|
|
||||||
What exists in the source app:
|
|
||||||
- A connect flow in `razerqdhid/src/components/ConnectDevice.vue`
|
|
||||||
- A main device screen in `razerqdhid/src/components/DeviceMain.vue`
|
|
||||||
- Configuration sections for:
|
|
||||||
- basic settings
|
|
||||||
- button mapping
|
|
||||||
- LED
|
|
||||||
- profiles
|
|
||||||
- macros
|
|
||||||
- sensor settings
|
|
||||||
- device info
|
|
||||||
- The source app currently relies on browser APIs and Pyodide. The desktop app should replace that with native Tauri/Rust integration.
|
|
||||||
|
|
||||||
What I want:
|
|
||||||
- Port the app into `./razer-linux-desktop`.
|
|
||||||
- Keep the migrated app grounded in the existing source behavior and feature structure instead of inventing a new product.
|
|
||||||
- Replace WebHID/Pyodide with a native backend that talks to the device on Linux.
|
|
||||||
- Reuse or carefully reimplement the existing protocol logic from the Python code in Rust where appropriate.
|
|
||||||
- Keep the app runnable after each meaningful step. Do not leave the target in a half-broken state.
|
|
||||||
|
|
||||||
Execution requirements:
|
|
||||||
1. Inspect the source app and identify the real feature surface before changing architecture.
|
|
||||||
2. Inspect the Python protocol/device code under `razerqdhid/public/py/` and use it as the behavioral reference.
|
|
||||||
3. Build the migration incrementally, starting with a working vertical slice instead of attempting every feature at once.
|
|
||||||
4. Preserve the current single-app workflow:
|
|
||||||
- connect to device
|
|
||||||
- show main configuration UI
|
|
||||||
- expose the existing sections progressively
|
|
||||||
5. Prefer Rust/Tauri-native device access over embedding Python in the final architecture unless there is a concrete blocker.
|
|
||||||
6. Keep the UI practical and desktop-oriented. Do not ship the default template UI.
|
|
||||||
7. Do not perform unrelated refactors in the source project unless required for the migration.
|
|
||||||
|
|
||||||
Suggested migration order:
|
|
||||||
1. Replace the default Tauri + Leptos starter UI with a real app shell.
|
|
||||||
2. Implement backend device discovery/connection for supported Razer devices on Linux.
|
|
||||||
3. Port the connect screen and basic device info flow.
|
|
||||||
4. Port the basic settings/profile selection path end to end.
|
|
||||||
5. Port the remaining sections in descending order of user value: LED, button mapping, profiles, macros, sensor, info/debug tooling.
|
|
||||||
6. Remove template/demo code once replaced.
|
|
||||||
|
|
||||||
Definition of done:
|
|
||||||
- `./razer-linux-desktop` is the active app, not a starter template.
|
|
||||||
- The app can launch locally as a Tauri desktop app.
|
|
||||||
- At least one real end-to-end device workflow is implemented natively in the target app.
|
|
||||||
- The migration status is clear: completed pieces, remaining gaps, and next highest-value steps.
|
|
||||||
- Any missing source features are called out explicitly instead of being silently dropped.
|
|
||||||
|
|
||||||
Faithful Port Checklist (source parity targets):
|
|
||||||
- App workflow parity:
|
|
||||||
- Connect screen: scan/select device, connect, error handling, optional "no hardware" mode for UI-only exploration.
|
|
||||||
- Main screen: profile selector + tabs matching the Vue app: Basic, Button, LED, Profile, Macro, Sensor, Info, plus log/debug tooling.
|
|
||||||
- Backend protocol parity (map to Python `razerqdhid/public/py/qdrazer/device.py` + `qdrazer/protocol.py`):
|
|
||||||
- Device info:
|
|
||||||
- manufacturer/product strings (USB indexed strings)
|
|
||||||
- serial number (`0x0082`)
|
|
||||||
- firmware version (`0x0081`)
|
|
||||||
- device mode get/set (`0x0084` / `0x0004`) as required for sensor calibration flows
|
|
||||||
- Basic settings (already partially ported):
|
|
||||||
- scroll mode get/set (`0x0294` / `0x0214`)
|
|
||||||
- scroll acceleration get/set (`0x0296` / `0x0216`)
|
|
||||||
- smart reel get/set (`0x0297` / `0x0217`)
|
|
||||||
- polling delay get/set (`0x008e` / `0x000e`)
|
|
||||||
- current DPI X/Y get/set (`0x0485` / `0x0405`)
|
|
||||||
- DPI stages get/set (`0x0486` / `0x0406`) and active stage selection
|
|
||||||
- Profile management:
|
|
||||||
- available profile count/list (`0x0580` / `0x0581`)
|
|
||||||
- create/delete profile (`0x0502` / `0x0503`)
|
|
||||||
- profile info read/write chunking (`0x0588` / `0x0508`) for YAML import/export parity (source exports/imports Basic/Button/LED configs)
|
|
||||||
- flash usage/readiness helpers (`0x068e`, `0x0086`) as needed for safe writes
|
|
||||||
- LED:
|
|
||||||
- per-region effect get/set (`0x0f82` / `0x0f02`) including mode/speed/colors
|
|
||||||
- per-region brightness get/set (`0x0f84` / `0x0f04`)
|
|
||||||
- optional static LED write path (`0x0f03`) if needed for full parity
|
|
||||||
- Button mapping + Hypershift:
|
|
||||||
- button function get/set (`0x028c` / `0x020c`)
|
|
||||||
- full `ButtonFunction` encoding/decoding parity with `qdrazer.protocol.ButtonFunction`
|
|
||||||
- categories used by UI: disabled, mouse, keyboard, macro, dpi_switch, profile_switch, system, consumer, hypershift_toggle, scroll_mode_toggle, plus "custom/raw"
|
|
||||||
- support both Hypershift OFF and ON mappings
|
|
||||||
- Macros:
|
|
||||||
- macro list/count (`get_macro_list`/`get_macro_count` paths in Python) and any required query commands
|
|
||||||
- macro info chunked get/set (`0x068c` / `0x060c`)
|
|
||||||
- macro size get/set (`0x0688` / `0x0608`)
|
|
||||||
- macro function chunked get/set (`0x0689` / `0x0609`)
|
|
||||||
- delete macro (`0x0603`)
|
|
||||||
- flash reset tooling (`0x060a` + poll `0x068a`) for the source "reset flash" workflow
|
|
||||||
- YAML import/export for macro functions (source uses YAML to represent macro op lists)
|
|
||||||
- Sensor / lift-off calibration:
|
|
||||||
- sensor lift config get/set (`0x0b8b` / `0x0b0b`)
|
|
||||||
- lift config blobs get/set (`0x0b85` / `0x0b05`, `0x0b8c` / `0x0b0c`, `0x0b8d` / `0x0b0d`)
|
|
||||||
- sensor state + calibration toggles (`0x0b83` / `0x0b03`, `0x0b09`)
|
|
||||||
- device mode transitions needed by the calibration workflow (`DeviceMode.DRIVER`/`NORMAL`)
|
|
||||||
- Logs/debug:
|
|
||||||
- source-equivalent log console: show sent/received report frames, status, and raw bytes
|
|
||||||
- safe exclusive-access messaging when device replies with "different command" (indicates competing software)
|
|
||||||
- UI parity notes from Vue components:
|
|
||||||
- Basic: includes DPI stages editing (count 1..5), active stage, and a "Y = X" helper.
|
|
||||||
- LED: regions wheel/logo/strip; effects off/static/spectrum/wave variants; per-region speed + brightness; apply-to-all.
|
|
||||||
- Button: per-button assignments + per-button Hypershift assignments; category editor; custom/raw inspector.
|
|
||||||
- Profile: create/delete; YAML export/import for selected profile configs (basic/button/led only in source).
|
|
||||||
- Macro: list macros; load/edit/save/delete; YAML export/import all.
|
|
||||||
- Sensor: lift config modes; calibration start/stop; parameter calculator + set params.
|
|
||||||
|
|
||||||
While working:
|
|
||||||
- Make concrete code changes, not just a plan.
|
|
||||||
- Explain architecture decisions briefly when they matter.
|
|
||||||
- Validate builds/tests when possible.
|
|
||||||
- If the full migration is too large for one pass, complete the highest-value vertical slice and leave the repo in a clean, runnable state with clear next steps.
|
|
||||||
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
A Tauri + Leptos desktop app for configuring supported Razer devices on Linux.
|
A Tauri + Leptos desktop app for configuring supported Razer devices on Linux.
|
||||||
|
|
||||||
|
Supported are:
|
||||||
|
- Razer Basilisk V3
|
||||||
|
- Razer Basilisk V3 Pro
|
||||||
|
|
||||||
|
This desktop app is a port of the good work from [geezmolycos](https://github.com/geezmolycos/razerqdhid). The port and maintenance of this app is entirely done with Codex GPT 5.5 xhigh.
|
||||||
|
|
||||||
## CachyOS / Arch Setup
|
## CachyOS / Arch Setup
|
||||||
|
|
||||||
Install the Tauri Linux build dependencies:
|
Install the Tauri Linux build dependencies:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
<title>Razer Basilisk V3 Onboard Memory Tools</title>
|
<title>Razer Basilisk V3</title>
|
||||||
<link data-trunk rel="css" href="styles/structure.css" />
|
<link data-trunk rel="css" href="styles/structure.css" />
|
||||||
<link data-trunk rel="css" href="styles/panels.css" />
|
<link data-trunk rel="css" href="styles/panels.css" />
|
||||||
<link data-trunk rel="css" href="styles/details.css" />
|
<link data-trunk rel="css" href="styles/details.css" />
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,19 @@
|
||||||
|
const MOUSE_BUTTONS: [&str; 13] = [
|
||||||
|
"left",
|
||||||
|
"right",
|
||||||
|
"middle",
|
||||||
|
"wheel_left",
|
||||||
|
"wheel_right",
|
||||||
|
"wheel_up",
|
||||||
|
"wheel_down",
|
||||||
|
"middle_forward",
|
||||||
|
"middle_backward",
|
||||||
|
"forward",
|
||||||
|
"backward",
|
||||||
|
"aim",
|
||||||
|
"bottom",
|
||||||
|
];
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ButtonPanel(
|
fn ButtonPanel(
|
||||||
snapshot: DeviceState,
|
snapshot: DeviceState,
|
||||||
|
|
@ -5,21 +21,6 @@ fn ButtonPanel(
|
||||||
set_status: WriteSignal<String>,
|
set_status: WriteSignal<String>,
|
||||||
set_busy: WriteSignal<bool>,
|
set_busy: WriteSignal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
const BUTTONS: [&str; 13] = [
|
|
||||||
"aim",
|
|
||||||
"left",
|
|
||||||
"middle",
|
|
||||||
"right",
|
|
||||||
"forward",
|
|
||||||
"wheel_up",
|
|
||||||
"middle_forward",
|
|
||||||
"wheel_left",
|
|
||||||
"backward",
|
|
||||||
"wheel_down",
|
|
||||||
"middle_backward",
|
|
||||||
"wheel_right",
|
|
||||||
"bottom",
|
|
||||||
];
|
|
||||||
const CATEGORIES: [&str; 11] = [
|
const CATEGORIES: [&str; 11] = [
|
||||||
"disabled",
|
"disabled",
|
||||||
"mouse",
|
"mouse",
|
||||||
|
|
@ -93,7 +94,7 @@ fn ButtonPanel(
|
||||||
}
|
}
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let mut categories = std::collections::BTreeMap::new();
|
let mut categories = std::collections::BTreeMap::new();
|
||||||
for button in BUTTONS {
|
for button in MOUSE_BUTTONS {
|
||||||
if let Ok(mapping) = invoke::<ButtonMappingState, _>(
|
if let Ok(mapping) = invoke::<ButtonMappingState, _>(
|
||||||
"get_button_mapping",
|
"get_button_mapping",
|
||||||
&ButtonMappingQueryArgs {
|
&ButtonMappingQueryArgs {
|
||||||
|
|
@ -158,23 +159,22 @@ fn ButtonPanel(
|
||||||
<section class="panel-grid">
|
<section class="panel-grid">
|
||||||
<article class="panel wide">
|
<article class="panel wide">
|
||||||
<h3>"Button Mapping"</h3>
|
<h3>"Button Mapping"</h3>
|
||||||
<div class="button-mapper-grid">
|
|
||||||
{BUTTONS.into_iter().map(|button| {
|
<div class="mouse-assignment-layout">
|
||||||
let button_name = button.to_string();
|
<div class="mouse-button-picker">
|
||||||
let button_active = button_name.clone();
|
<div class="mouse-selection-summary" aria-live="polite">
|
||||||
let button_click = button_name.clone();
|
<span>"Selected"</span>
|
||||||
view! {
|
<strong>{move || button_label(&selected_button.get()).to_string()}</strong>
|
||||||
<button
|
<em>
|
||||||
type="button"
|
{move || {
|
||||||
class="button-tile"
|
let button = selected_button.get();
|
||||||
class:active=move || selected_button.get() == button_active
|
button_categories
|
||||||
on:click=move |_| selected_button.set(button_click.clone())
|
.get()
|
||||||
>
|
.get(&button)
|
||||||
<strong>{button_label(button)}</strong>
|
.map(|category| button_category_label(category))
|
||||||
<span>{move || button_categories.get().get(button).cloned().unwrap_or_else(|| button.to_string())}</span>
|
.unwrap_or_else(|| "Loading".to_string())
|
||||||
</button>
|
}}
|
||||||
}
|
</em>
|
||||||
}).collect_view()}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="check-row button-hypershift-toggle">
|
<label class="check-row button-hypershift-toggle">
|
||||||
|
|
@ -185,12 +185,44 @@ fn ButtonPanel(
|
||||||
/>
|
/>
|
||||||
<span>"When Hypershift on"</span>
|
<span>"When Hypershift on"</span>
|
||||||
</label>
|
</label>
|
||||||
<p class="subtle">
|
|
||||||
"Each button can be assigned a function when Hypershift is off, and another function when Hypershift is on."
|
<div class="mouse-button-list" role="group" aria-label="Assignable mouse buttons">
|
||||||
</p>
|
{MOUSE_BUTTONS.into_iter().map(|button| {
|
||||||
<p class="subtle">
|
let button_name = button.to_string();
|
||||||
"Assign a button to hypershift_toggle to let it switch Hypershift status."
|
let button_active = button_name.clone();
|
||||||
</p>
|
let button_click = button_name.clone();
|
||||||
|
let button_category = button_name.clone();
|
||||||
|
let label = button_label(button).to_string();
|
||||||
|
let category_fallback = button_category.clone();
|
||||||
|
view! {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mouse-button-row"
|
||||||
|
class:active=move || selected_button.get() == button_active
|
||||||
|
on:click=move |_| selected_button.set(button_click.clone())
|
||||||
|
>
|
||||||
|
<span>{label}</span>
|
||||||
|
<strong>
|
||||||
|
{move || {
|
||||||
|
button_categories
|
||||||
|
.get()
|
||||||
|
.get(&button_category)
|
||||||
|
.map(|category| button_category_label(category))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
if category_fallback == selected_button.get() {
|
||||||
|
"Loading".to_string()
|
||||||
|
} else {
|
||||||
|
"Pending".to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</strong>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}).collect_view()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{move || {
|
{move || {
|
||||||
let Some(mapping) = mapping_state.get() else {
|
let Some(mapping) = mapping_state.get() else {
|
||||||
|
|
@ -223,7 +255,7 @@ fn ButtonPanel(
|
||||||
<p class="subtle">
|
<p class="subtle">
|
||||||
{format!(
|
{format!(
|
||||||
"Editing {} on profile {}{}.",
|
"Editing {} on profile {}{}.",
|
||||||
mapping.button,
|
button_label(&mapping.button),
|
||||||
mapping.profile,
|
mapping.profile,
|
||||||
if mapping.hypershift { " with hypershift enabled" } else { "" }
|
if mapping.hypershift { " with hypershift enabled" } else { "" }
|
||||||
)}
|
)}
|
||||||
|
|
@ -238,7 +270,7 @@ fn ButtonPanel(
|
||||||
class:active=move || mapping_state.get().map(|item| item.category == category_name).unwrap_or(false)
|
class:active=move || mapping_state.get().map(|item| item.category == category_name).unwrap_or(false)
|
||||||
on:click=move |_| reset_category(category)
|
on:click=move |_| reset_category(category)
|
||||||
>
|
>
|
||||||
{category}
|
{button_category_label(category)}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
}).collect_view()}
|
}).collect_view()}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,40 @@
|
||||||
fn button_label(button: &str) -> &'static str {
|
fn button_label(button: &str) -> &'static str {
|
||||||
match button {
|
match button {
|
||||||
"aim" => "Aim",
|
"aim" => "Aim Trigger",
|
||||||
"left" => "Left",
|
"left" => "Left",
|
||||||
"middle" => "Middle",
|
"middle" => "Middle",
|
||||||
"right" => "Right",
|
"right" => "Right",
|
||||||
"forward" => "Forward",
|
"forward" => "Forward",
|
||||||
"wheel_up" => "Wheel Up",
|
"wheel_up" => "Wheel Up",
|
||||||
"middle_forward" => "Tilt Forward",
|
"middle_forward" => "Scroll Mode",
|
||||||
"wheel_left" => "Wheel Left",
|
"wheel_left" => "Wheel Left",
|
||||||
"backward" => "Backward",
|
"backward" => "Backward",
|
||||||
"wheel_down" => "Wheel Down",
|
"wheel_down" => "Wheel Down",
|
||||||
"middle_backward" => "Tilt Back",
|
"middle_backward" => "DPI Cycle",
|
||||||
"wheel_right" => "Wheel Right",
|
"wheel_right" => "Wheel Right",
|
||||||
"bottom" => "Bottom",
|
"bottom" => "Profile Button",
|
||||||
_ => "Button",
|
_ => "Button",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn button_category_label(category: &str) -> String {
|
||||||
|
match category {
|
||||||
|
"disabled" => "Disabled",
|
||||||
|
"mouse" => "Mouse",
|
||||||
|
"keyboard" => "Keyboard",
|
||||||
|
"macro" => "Macro",
|
||||||
|
"dpi_switch" => "DPI Switch",
|
||||||
|
"profile_switch" => "Profile Switch",
|
||||||
|
"system" => "System",
|
||||||
|
"consumer" => "Consumer",
|
||||||
|
"hypershift_toggle" => "Hypershift",
|
||||||
|
"scroll_mode_toggle" => "Scroll Mode",
|
||||||
|
"custom" => "Custom",
|
||||||
|
_ => category,
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn default_payload_for_category(category: &str) -> Value {
|
fn default_payload_for_category(category: &str) -> Value {
|
||||||
match category {
|
match category {
|
||||||
"disabled" => json!({}),
|
"disabled" => json!({}),
|
||||||
|
|
@ -190,4 +208,3 @@ fn calculate_sensor_params(
|
||||||
Some((param_a, Vec::new()))
|
Some((param_a, Vec::new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -214,59 +214,120 @@
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-slot-fixed {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-mapper-grid,
|
|
||||||
.category-grid,
|
|
||||||
.modifier-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-mapper-grid {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-tile,
|
|
||||||
.category-grid button {
|
|
||||||
min-height: 40px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-tile {
|
|
||||||
display: grid;
|
|
||||||
gap: 4px;
|
|
||||||
align-content: start;
|
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid var(--color-border-subtle);
|
|
||||||
text-align: left;
|
|
||||||
background: var(--color-panel-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-tile span {
|
|
||||||
color: var(--color-small-text);
|
|
||||||
font-size: 0.78rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-tile strong,
|
|
||||||
.button-tile span,
|
|
||||||
.profile-admin-list strong,
|
.profile-admin-list strong,
|
||||||
.led-region-tabs button,
|
.led-region-tabs button,
|
||||||
.flash-summary span {
|
.flash-summary span {
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-tile.active {
|
.profile-slot-fixed {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-grid,
|
||||||
|
.modifier-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-assignment-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(260px, 420px);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-button-picker {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-selection-summary {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
border-bottom: 1px solid var(--color-border-subtle);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-selection-summary span,
|
||||||
|
.mouse-button-row strong {
|
||||||
|
color: var(--color-small-text);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-selection-summary strong {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-selection-summary em {
|
||||||
|
color: var(--color-metric);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-button-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-button-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(76px, max-content);
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 34px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
color: var(--color-secondary-text);
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-button-row:hover,
|
||||||
|
.mouse-button-row:focus-visible {
|
||||||
|
border-color: var(--color-active);
|
||||||
|
background: var(--color-panel-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-button-row.active {
|
||||||
border-color: var(--color-active);
|
border-color: var(--color-active);
|
||||||
background: var(--color-active-surface);
|
background: var(--color-active-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mouse-button-row span,
|
||||||
|
.mouse-button-row strong {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-button-row span {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-button-row strong {
|
||||||
|
justify-self: end;
|
||||||
|
max-width: 132px;
|
||||||
|
}
|
||||||
|
|
||||||
.button-hypershift-toggle {
|
.button-hypershift-toggle {
|
||||||
margin-bottom: 18px;
|
margin: 0;
|
||||||
|
border-bottom: 1px solid var(--color-border-subtle);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-grid button {
|
||||||
|
min-height: 40px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-grid {
|
.category-grid {
|
||||||
|
|
@ -497,6 +558,12 @@
|
||||||
max-width: 680px;
|
max-width: 680px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.mouse-assignment-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.app-shell,
|
.app-shell,
|
||||||
.panel-grid,
|
.panel-grid,
|
||||||
|
|
@ -514,4 +581,8 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mouse-button-list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue