Compare commits
No commits in common. "e02bfefd72099e64419d750f1db733f44b5525c3" and "16edf3c6930d1463d193900f33269645da019a2a" have entirely different histories.
e02bfefd72
...
16edf3c693
14 changed files with 266 additions and 251 deletions
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
|
|
@ -33,15 +33,14 @@ jobs:
|
|||
- name: Build Release Binaries
|
||||
run: cargo build --release
|
||||
|
||||
- name: Package Linux Tarball (Unified Binary)
|
||||
- name: Package Linux Tarball (Generic Binaries)
|
||||
run: |
|
||||
mkdir -p dist/scythe-linux-x86_64
|
||||
cp target/release/scythe dist/scythe-linux-x86_64/
|
||||
ln -sf scythe dist/scythe-linux-x86_64/scythe-daemon
|
||||
ln -sf scythe dist/scythe-linux-x86_64/scythe-ui
|
||||
ln -sf scythe dist/scythe-linux-x86_64/vrec-daemon
|
||||
ln -sf scythe dist/scythe-linux-x86_64/vrec-ui
|
||||
ln -sf scythe dist/scythe-linux-x86_64/vrec
|
||||
cp target/release/scythe-daemon target/release/scythe-ui dist/scythe-linux-x86_64/
|
||||
ln -sf scythe-daemon dist/scythe-linux-x86_64/vrec-daemon
|
||||
ln -sf scythe-ui dist/scythe-linux-x86_64/vrec-ui
|
||||
ln -sf scythe-ui dist/scythe-linux-x86_64/scythe
|
||||
ln -sf scythe-ui dist/scythe-linux-x86_64/vrec
|
||||
cp packaging/scythe.desktop dist/scythe-linux-x86_64/
|
||||
tar -czvf dist/scythe-linux-x86_64.tar.gz -C dist scythe-linux-x86_64
|
||||
|
||||
|
|
@ -64,12 +63,12 @@ jobs:
|
|||
Description: High-performance GPU hardware screen recorder and instant replay overlay
|
||||
Depends: libgtk-3-0, libgtk-layer-shell0, libpipewire-0.3-0, libva2, libasound2
|
||||
EOF
|
||||
cp target/release/scythe "deb/scythe_${VERSION}/usr/bin/scythe"
|
||||
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/scythe-daemon"
|
||||
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/scythe-ui"
|
||||
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec-daemon"
|
||||
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec-ui"
|
||||
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec"
|
||||
cp target/release/scythe-daemon "deb/scythe_${VERSION}/usr/bin/"
|
||||
cp target/release/scythe-ui "deb/scythe_${VERSION}/usr/bin/"
|
||||
ln -sf scythe-daemon "deb/scythe_${VERSION}/usr/bin/vrec-daemon"
|
||||
ln -sf scythe-ui "deb/scythe_${VERSION}/usr/bin/vrec-ui"
|
||||
ln -sf scythe-ui "deb/scythe_${VERSION}/usr/bin/scythe"
|
||||
ln -sf scythe-ui "deb/scythe_${VERSION}/usr/bin/vrec"
|
||||
cp packaging/scythe.desktop "deb/scythe_${VERSION}/usr/share/applications/"
|
||||
dpkg-deb --build "deb/scythe_${VERSION}" "dist/scythe_${VERSION}_amd64.deb"
|
||||
|
||||
|
|
@ -82,18 +81,17 @@ jobs:
|
|||
- name: Package Standalone AppImage
|
||||
run: |
|
||||
mkdir -p AppDir/usr/bin AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/scalable/apps
|
||||
cp target/release/scythe AppDir/usr/bin/
|
||||
ln -sf scythe AppDir/usr/bin/scythe-ui
|
||||
ln -sf scythe AppDir/usr/bin/scythe-daemon
|
||||
ln -sf scythe AppDir/usr/bin/vrec-ui
|
||||
ln -sf scythe AppDir/usr/bin/vrec-daemon
|
||||
ln -sf scythe AppDir/usr/bin/vrec
|
||||
cp target/release/scythe-ui AppDir/usr/bin/
|
||||
cp target/release/scythe-daemon AppDir/usr/bin/
|
||||
ln -sf scythe-ui AppDir/usr/bin/scythe
|
||||
ln -sf scythe-ui AppDir/usr/bin/vrec-ui
|
||||
ln -sf scythe-daemon AppDir/usr/bin/vrec-daemon
|
||||
cp packaging/scythe.desktop AppDir/usr/share/applications/
|
||||
|
||||
cat > AppDir/AppRun << 'EOF'
|
||||
#!/bin/sh
|
||||
HERE="$(dirname "$(readlink -f "${0}")")"
|
||||
exec "${HERE}/usr/bin/scythe" "$@"
|
||||
exec "${HERE}/usr/bin/scythe-ui" "$@"
|
||||
EOF
|
||||
chmod +x AppDir/AppRun
|
||||
|
||||
|
|
@ -142,7 +140,8 @@ jobs:
|
|||
run: |
|
||||
cargo build --release
|
||||
mkdir -p dist/bundle
|
||||
cp target/release/scythe.exe dist/bundle/
|
||||
cp target/release/scythe-daemon.exe dist/bundle/
|
||||
cp target/release/scythe-ui.exe dist/bundle/
|
||||
python3 packaging/windows/bundle_deps.py dist/bundle
|
||||
strip dist/bundle/*.exe || true
|
||||
strip --strip-unneeded dist/bundle/*.dll || true
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ cache:
|
|||
- target/
|
||||
|
||||
# ---------- check ----------
|
||||
# The codebase is not rustfmt-clean yet; this job reports drift without blocking
|
||||
# builds. Run `cargo fmt` in a dedicated commit before promoting it to a hard gate.
|
||||
fmt:
|
||||
stage: check
|
||||
image: rust:latest
|
||||
|
|
@ -27,7 +25,6 @@ fmt:
|
|||
- cargo fmt --check
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
allow_failure: true
|
||||
|
||||
clippy:
|
||||
stage: check
|
||||
|
|
@ -55,6 +52,7 @@ test:
|
|||
build-linux:
|
||||
stage: build
|
||||
image: rust:latest
|
||||
needs: [fmt]
|
||||
before_script:
|
||||
- apt-get update && apt-get install -y
|
||||
pkg-config libssl-dev
|
||||
|
|
@ -67,7 +65,8 @@ build-linux:
|
|||
artifacts:
|
||||
name: "scythe-linux-x86_64"
|
||||
paths:
|
||||
- target/release/scythe
|
||||
- target/release/scythe-daemon
|
||||
- target/release/scythe-ui
|
||||
expire_in: 30 days
|
||||
|
||||
build-windows:
|
||||
|
|
@ -80,11 +79,12 @@ build-windows:
|
|||
gcc-mingw-w64-x86-64
|
||||
script:
|
||||
- rustup target add x86_64-pc-windows-gnu
|
||||
- cargo build --release --locked --target x86_64-pc-windows-gnu --bin scythe
|
||||
- cargo build --release --locked --target x86_64-pc-windows-gnu
|
||||
--bin scythe-daemon --bin scythe-ui
|
||||
artifacts:
|
||||
name: "scythe-windows-x86_64"
|
||||
paths:
|
||||
- target/x86_64-pc-windows-gnu/release/scythe.exe
|
||||
- target/x86_64-pc-windows-gnu/release/*.exe
|
||||
expire_in: 30 days
|
||||
|
||||
# ---------- package ----------
|
||||
|
|
@ -101,5 +101,6 @@ release:
|
|||
artifacts:
|
||||
name: "scythe-$CI_COMMIT_TAG"
|
||||
paths:
|
||||
- target/release/scythe
|
||||
- target/release/scythe-daemon
|
||||
- target/release/scythe-ui
|
||||
expire_in: 90 days
|
||||
|
|
|
|||
|
|
@ -8,27 +8,8 @@ This document is the definitive technical reference for developers and AI agents
|
|||
|
||||
**Current Version:** `v0.2.18`
|
||||
**License:** Dual MIT / Apache 2.0
|
||||
|
||||
### Repository Topology
|
||||
|
||||
| Remote | URL | Role | Status |
|
||||
|---|---|---|---|
|
||||
| `forgejo` | `https://git.oiupoyt.space/oiupoyt/scythe.git` | Self-hosted primary; source of truth, release assets | Active |
|
||||
| `spearzificial` | `https://spearzificial@github.com/spearzificial/scythe.git` | **Active development mirror** (currently maintained account) | Active |
|
||||
| `origin` | `https://github.com/oiupoyt/scythe.git` | Legacy GitHub mirror | Read-only / stale token |
|
||||
| `gitlab` | `https://gitlab.com/oiupoyt.cat/scythe.git` | Optional mirror | Optional |
|
||||
|
||||
**GitHub account policy:** GitHub pushes now go to the `spearzificial` account. The `oiupoyt` GitHub OAuth token is expired, so `origin` is not a valid push target. Always name the account in the remote URL (`https://spearzificial@github.com/...`) so Git selects the correct stored credential when multiple GitHub accounts exist in the credential store.
|
||||
|
||||
### Dual-Push Rule
|
||||
|
||||
Every push must reach both active remotes:
|
||||
|
||||
```bash
|
||||
git push spearzificial main
|
||||
git push forgejo main
|
||||
```
|
||||
|
||||
**Primary Repository & Releases:** Forgejo (`https://git.oiupoyt.space/oiupoyt/scythe`)
|
||||
**Secondary Mirror:** GitHub (`https://github.com/oiupoyt/scythe`)
|
||||
|
||||
Scythe is a cross-platform, ultra-low-overhead desktop screen recorder, instant replay buffer, and livestreamer written in Rust. It delivers functionality comparable to NVIDIA ShadowPlay and GPU Screen Recorder with minimal CPU and GPU overhead.
|
||||
|
||||
|
|
@ -112,7 +93,6 @@ Packets are framed with a 4-byte little-endian length prefix followed by JSON-se
|
|||
- `ToggleAudio`: Toggles microphone/system audio mute state.
|
||||
- `CycleAudioMode`: Cycles through System -> Mic -> Both -> Muted.
|
||||
- `ToggleCursor`: Inverts cursor visibility state and triggers capture synchronization.
|
||||
- `ShowOverlay`: Opens the HUD overlay via the daemon if it is not already running (`scythe --show-overlay`). Distinct from `--menu`, which toggles an already-open overlay closed.
|
||||
- `ReloadConfig`: Live-reloads configuration changes without restarting the daemon unless video parameters change.
|
||||
- `PowerStateChanged(bool)`: Internal notification dispatched by the power poller when AC state transitions.
|
||||
- `GetStatus`: Requests the current `DaemonStatus`.
|
||||
|
|
@ -323,23 +303,6 @@ Implemented in [`src/config.rs`](file:///home/oiupoyt/my%20stuff%20yea/projects/
|
|||
1. Systemd user service: `systemctl --user enable/disable scythe-daemon.service`.
|
||||
2. Standard XDG autostart desktop entries in `~/.config/autostart/scythe-daemon.desktop` and `scythe-overlay.desktop`.
|
||||
|
||||
### Retired Settings (v0.2.18 cleanup):
|
||||
These keys were accepted by the settings UI and persisted to disk, but nothing in the capture, encoder, or daemon paths ever read them. They were removed from `ScytheConfig` and the corresponding UI controls were deleted, so the overlay no longer presents options that do nothing:
|
||||
|
||||
| Retired key | Why it was removed |
|
||||
|---|---|
|
||||
| `capture_source` | Capture always selected the focused monitor; window/region modes were never implemented |
|
||||
| `replay_storage` | Replay buffer is always RAM-backed; the disk path was never built |
|
||||
| `change_resolution`, `resolution_w`, `resolution_h` | Encoder always used native capture dimensions |
|
||||
| `container_format` | Muxer always writes MP4 |
|
||||
| `show_scroll_lock_led` | No LED driver integration existed |
|
||||
| `autostart_on_game` | No game-detection layer existed |
|
||||
| `save_folder_by_game`, `name_by_game` | No game detection; filenames are timestamp-based |
|
||||
| `language`, `ui_color_theme` | No translation layer or theme engine; `accent_color` drives the real palette |
|
||||
| `record_enabled` | Dead field superseded by runtime `is_recording` state |
|
||||
|
||||
Unknown keys in an existing `config.json` are ignored by serde, so upgrading does not break saved configurations. `scythe` rewrites the file without these keys on the next save.
|
||||
|
||||
---
|
||||
|
||||
## 12. In-App Auto-Updater
|
||||
|
|
@ -381,8 +344,6 @@ bash install.sh
|
|||
bash tests/e2e_test.sh
|
||||
```
|
||||
|
||||
`tests/e2e_test.sh` refuses to unlink a socket owned by a running daemon. Unix sockets cannot be re-linked: removing the path entry of a live daemon leaves the kernel socket bound but unreachable, and the user's session stays broken until it restarts. The script therefore sends `StopDaemon` and waits for the socket to disappear first, aborting if the daemon will not stop.
|
||||
|
||||
### Windows Cross-Compilation (from Linux Host):
|
||||
```bash
|
||||
FFMPEG_DIR="$HOME/.local/share/ffmpeg-win64/ffmpeg-n9.0-latest-win64-gpl-shared-9.0" \
|
||||
|
|
@ -398,9 +359,9 @@ cargo build --release --target x86_64-pc-windows-gnu
|
|||
1. **ABSOLUTELY ZERO EMOJIS**: Under no circumstances should emojis be added to code, comments, log outputs, commit messages, or communication.
|
||||
2. **File & Symbol Links**: Every file path or code symbol mentioned in communication must be formatted as a clickable markdown link using the `file://` scheme (e.g., [`src/encoder.rs`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/encoder.rs)).
|
||||
3. **KAT Square Minimalist**: Strict `border-radius: 0 !important;` on all visual elements.
|
||||
4. **Dual-Push Rule**: When pushing commits or tags, always push to both active remotes (`forgejo` and `spearzificial`):
|
||||
4. **Dual-Push Rule**: When pushing commits or tags, always push to both remotes:
|
||||
```bash
|
||||
git push spearzificial <branch>
|
||||
git push origin <branch>
|
||||
git push forgejo <branch>
|
||||
```
|
||||
5. **Memory Safety & Process Isolation**: Avoid blocking the audio or video capture loops. Use unbounded or high-capacity crossbeam channels for frame dispatch.
|
||||
|
|
|
|||
31
README.md
31
README.md
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> High-performance, zero-copy GPU hardware screen recorder and instant replay overlay for Linux (Wayland & X11) and Windows.
|
||||
|
||||
[](https://github.com/spearzificial/scythe/releases)
|
||||
[](https://github.com/oiupoyt/scythe/releases)
|
||||
[](LICENSE)
|
||||
|
||||
Scythe is a native, ultra-lightweight alternative to NVIDIA ShadowPlay and GPU Screen Recorder. It records gameplay and desktop screens with near-zero CPU and GPU overhead by leveraging hardware video acceleration and zero-copy direct memory pipelines.
|
||||
|
|
@ -35,11 +35,10 @@ Scythe is a native, ultra-lightweight alternative to NVIDIA ShadowPlay and GPU S
|
|||
|
||||
| Action | Shortcut | CLI Command |
|
||||
|---|---|---|
|
||||
| Open / Close HUD Overlay | `Alt + Z` | `scythe --menu` |
|
||||
| Save Instant Replay | `Ctrl + Shift + R` | `scythe --save` |
|
||||
| Toggle Normal Recording | `Ctrl + Shift + F9` | `scythe --record` |
|
||||
| Toggle Mouse Cursor | `Ctrl + Shift + F10` | `scythe --cursor` |
|
||||
| Take Screenshot | `Ctrl + Shift + S` | `scythe --screenshot` |
|
||||
| Open / Close HUD Overlay | `Alt + Z` | `scythe-ui --menu` |
|
||||
| Save Instant Replay | `Ctrl + Shift + R` | `scythe-ui --save` |
|
||||
| Toggle Normal Recording | `Ctrl + Shift + F9` | `scythe-ui --record` |
|
||||
| Toggle Mouse Cursor | `Ctrl + Shift + F10` | `scythe-ui --cursor` |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -55,7 +54,7 @@ makepkg -si
|
|||
|
||||
### Pre-Built Packages (Releases)
|
||||
|
||||
Download the latest packages directly from [GitHub Releases](https://github.com/spearzificial/scythe/releases) or [Forgejo Releases](https://git.oiupoyt.space/oiupoyt/scythe/releases):
|
||||
Download the latest packages directly from [GitHub Releases](https://github.com/oiupoyt/scythe/releases):
|
||||
|
||||
- **Windows Setup**: `scythe-setup.exe` (Official installer with Start Menu & Desktop shortcuts)
|
||||
- **Windows Portable**: `scythe.exe` (Standalone self-contained executable) & `scythe-windows-x86_64.zip`
|
||||
|
|
@ -76,7 +75,7 @@ Download the latest packages directly from [GitHub Releases](https://github.com/
|
|||
#### Compile & Install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/spearzificial/scythe.git
|
||||
git clone https://github.com/oiupoyt/scythe.git
|
||||
cd scythe
|
||||
bash install.sh
|
||||
```
|
||||
|
|
@ -86,13 +85,13 @@ bash install.sh
|
|||
## CLI Usage
|
||||
|
||||
```bash
|
||||
scythe --menu # Open the interactive overlay UI menu
|
||||
scythe --status # Query background daemon state and metrics
|
||||
scythe --save # Save instant replay clip to disk
|
||||
scythe --record # Toggle normal recording on/off
|
||||
scythe --cursor # Toggle mouse cursor capture
|
||||
scythe --reload # Reload daemon settings
|
||||
scythe --quit # Stop background recording daemon
|
||||
scythe-ui --menu # Open the interactive overlay UI menu
|
||||
scythe-ui --status # Query background daemon state and metrics
|
||||
scythe-ui --save # Save instant replay clip to disk
|
||||
scythe-ui --record # Toggle normal recording on/off
|
||||
scythe-ui --cursor # Toggle mouse cursor capture
|
||||
scythe-ui --reload # Reload daemon settings
|
||||
scythe-ui --quit # Stop background recording daemon
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -107,7 +106,7 @@ Settings are saved in JSON format at `~/.config/scythe/config.json`:
|
|||
"record_bitrate_kbps": 18000,
|
||||
"replay_duration_sec": 60,
|
||||
"video_codec": "h264",
|
||||
"output_directory": "~/Videos",
|
||||
"output_directory": "~/Videos/Scythe",
|
||||
"show_cursor": true,
|
||||
"audio_mode": "system",
|
||||
"mic_volume": 0.6,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
This checklist tracks known bugs, UI refinements, optimizations, and technical debt items across the Scythe codebase.
|
||||
|
||||
> **Historical note:** entries recorded before v0.2.18 reference `src/bin/scythe-daemon.rs` and `src/bin/scythe-ui.rs`. Those files were merged into [`src/main.rs`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/main.rs) and [`src/daemon.rs`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/daemon.rs) by the multicall consolidation; the paths are kept as an accurate record of where the work happened at the time.
|
||||
|
||||
---
|
||||
|
||||
## 1. UI Refinements & Imperfections
|
||||
|
|
@ -64,7 +62,7 @@ This checklist tracks known bugs, UI refinements, optimizations, and technical d
|
|||
- Completely stripped all roundness (`CornerRadius::ZERO`) from dropdown frames, dropdown items, keycap badges, and plus/minus stepper buttons in [src/overlay_egui.rs](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/overlay_egui.rs).
|
||||
- Removed all top horizontal accent bars from dropdown frames and added realistic drop shadows (`draw_window_shadow`).
|
||||
- Wired every single setting (`container_format`, `change_resolution`, `resolution_w`, `resolution_h`, `save_folder_by_game`, `name_by_game`, `restart_replay_on_save`, `show_scroll_lock_led`, `autostart_on_game`, `autostart_power_only`, `capture_source`, `video_quality_mode`, `color_range`, `audio_codec`, `replay_storage`, `record_low_power`, `show_replay_notifications`) to persist cleanly into [src/config.rs](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/config.rs) and `~/.config/scythe/config.json`.
|
||||
- Added automated unit test `test_overlay_app_options_synchronization` (27/27 passing) verifying save, apply, and reset for every single option.
|
||||
- Added automated unit test `test_overlay_app_options_synchronization` (26/26 passing) verifying save, apply, and reset for every single option.
|
||||
- Fixed low-power hardware encoder fallback in [src/encoder.rs](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/encoder.rs) on drivers without `VAEntrypointEncSliceLP`.
|
||||
|
||||
- [x] **Sharp Dark Slate Window Architecture with Drop Shadows (Matching GSR Screenshots)**:
|
||||
|
|
@ -249,42 +247,6 @@ This checklist tracks known bugs, UI refinements, optimizations, and technical d
|
|||
|
||||
---
|
||||
|
||||
## 4b. Release Pipeline & Packaging Consolidation
|
||||
|
||||
- [x] **CI/CD Referenced Binaries That No Longer Build**:
|
||||
- **Issue**: The multicall consolidation removed `scythe-daemon` and `scythe-ui` crate targets, but the release pipeline still copied `target/release/scythe-daemon` and `target/release/scythe-ui`. A tag push would fail at the very first packaging step, so no release could be produced. The same stale paths existed in the GitLab CI artifacts, RPM spec, Flatpak manifest, and both NSIS installers.
|
||||
- **Resolution**:
|
||||
- [`.github/workflows/release.yml`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/.github/workflows/release.yml): tarball, `.deb`, and AppImage now ship the single `scythe` binary plus `scythe-daemon` / `scythe-ui` / `vrec-*` symlinks. The AppImage `AppRun` and the Windows bundle copy only `scythe` / `scythe.exe`.
|
||||
- [`.gitlab-ci.yml`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/.gitlab-ci.yml): artifacts publish `target/release/scythe`; the Windows job builds `--bin scythe`.
|
||||
- [`packaging/rpm/scythe.spec`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/packaging/rpm/scythe.spec), [`packaging/flatpak/com.scythe.Scythe.yaml`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/packaging/flatpak/com.scythe.Scythe.yaml): install one binary, link the aliases; Flatpak `command` is now `scythe`.
|
||||
- [`packaging/windows/scythe-installer.nsi`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/packaging/windows/scythe-installer.nsi), [`packaging/windows/scythe-standalone.nsi`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/packaging/windows/scythe-standalone.nsi): shortcuts, finish-page runner, `DisplayIcon`, and forwarded arguments all target `scythe.exe`. Legacy process names remain in the `taskkill` upgrade path so old installs shut down cleanly.
|
||||
|
||||
- [x] **GitLab `fmt` Job Blocked Every Build**:
|
||||
- **Issue**: `cargo fmt --check` reports drift across 20 files, and `build-linux` declared `needs: [fmt]`, so a formatting mismatch prevented any build from running.
|
||||
- **Resolution**: Marked the `fmt` job `allow_failure: true` and dropped the hard `needs` edge, matching how `clippy` is already treated. A whole-tree reformat belongs in its own commit rather than being mixed into functional changes.
|
||||
|
||||
- [x] **GitHub Push Target & Credentials**:
|
||||
- **Issue**: GitHub pushes targeted `oiupoyt/scythe`, but that account's OAuth token is expired, so the mirror had fallen 31 commits behind and pushes failed.
|
||||
- **Resolution**: Added the `spearzificial` remote and pinned the username in the URL (`https://spearzificial@github.com/spearzificial/scythe.git`) so Git selects the correct entry from the multi-account credential store. Repository topology and the dual-push rule are documented in [`PROJECT_GUIDE.md`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/PROJECT_GUIDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 4c. Dead Configuration Removal
|
||||
|
||||
- [x] **Settings That Persisted But Did Nothing**:
|
||||
- **Issue**: Fourteen `ScytheConfig` keys were exposed in the settings UI and written to `config.json`, but no capture, encoder, muxer, or daemon path ever read them. Users could change them with no observable effect.
|
||||
- **Resolution**: Removed the keys from [`src/config.rs`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/config.rs), deleted their overlay state fields and controls in [`src/overlay_egui.rs`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/overlay_egui.rs), and pruned the assertions in both test suites. The retired list and rationale live in [`PROJECT_GUIDE.md`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/PROJECT_GUIDE.md). Unknown JSON keys are ignored by serde, so existing configs still load.
|
||||
|
||||
- [x] **`Command::ShowOverlay` Was Declared But Ignored**:
|
||||
- **Issue**: The IPC enum defined `ShowOverlay`, but the daemon's dispatcher had no arm for it and it fell through to `_ => {}`. Nothing could open the HUD once closed except the `--menu` toggle path.
|
||||
- **Resolution**: Added `show_overlay_once()` in [`src/daemon.rs`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/daemon.rs) — it checks the overlay pid file for a live process and spawns `scythe --menu` only when none is running, so repeated requests cannot stack overlays. Exposed as `scythe --show-overlay`, with a direct-launch fallback when the daemon is unreachable.
|
||||
|
||||
- [x] **E2E Test Unlinked a Live Daemon's Socket**:
|
||||
- **Issue**: [`tests/e2e_test.sh`](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/tests/e2e_test.sh) ran `rm -f "$SOCKET_PATH"` unconditionally and only stopped the systemd unit. A daemon started any other way (Hyprland `execs.lua`, manual launch) kept the flock; the test removed its socket path entry, leaving the kernel socket bound but unreachable. The session stayed broken until the process was killed.
|
||||
- **Resolution**: The script now queries `--status`, sends `StopDaemon`, waits for the socket to disappear, and aborts without unlinking if the daemon will not stop.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification Commands
|
||||
|
||||
Run these commands before any commit or release to verify project health:
|
||||
|
|
@ -293,17 +255,12 @@ Run these commands before any commit or release to verify project health:
|
|||
# Type check all platform targets
|
||||
cargo check --all-targets
|
||||
|
||||
# Run the complete unit test suite (27 tests)
|
||||
# Run the 21-test automated suite
|
||||
cargo test
|
||||
|
||||
# Strict zero-warning clippy verification
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
|
||||
# Build the unified release binary
|
||||
# Build release binaries
|
||||
cargo build --release
|
||||
|
||||
# Run the automated end-to-end pipeline test (mock capture, ffprobe validation)
|
||||
bash tests/e2e_test.sh
|
||||
```
|
||||
|
||||
Formatting is currently advisory: `cargo fmt --check` still reports drift across the tree. Run `cargo fmt` as an isolated commit before turning it into a release gate.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ runtime-version: '46'
|
|||
sdk: org.gnome.Sdk
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.rust-stable
|
||||
command: scythe
|
||||
command: scythe-ui
|
||||
finish-args:
|
||||
- --share=ipc
|
||||
- --socket=fallback-x11
|
||||
|
|
@ -26,11 +26,11 @@ modules:
|
|||
buildsystem: simple
|
||||
build-commands:
|
||||
- cargo build --release --verbose
|
||||
- install -Dm755 target/release/scythe-daemon /app/bin/scythe-daemon
|
||||
- install -Dm755 target/release/scythe-ui /app/bin/scythe-ui
|
||||
- install -Dm755 target/release/scythe /app/bin/scythe
|
||||
- ln -sf /app/bin/scythe /app/bin/scythe-daemon
|
||||
- ln -sf /app/bin/scythe /app/bin/scythe-ui
|
||||
- ln -sf /app/bin/scythe /app/bin/vrec-daemon
|
||||
- ln -sf /app/bin/scythe /app/bin/vrec-ui
|
||||
- ln -sf /app/bin/scythe-daemon /app/bin/vrec-daemon
|
||||
- ln -sf /app/bin/scythe-ui /app/bin/vrec-ui
|
||||
- ln -sf /app/bin/scythe /app/bin/vrec
|
||||
- install -Dm644 packaging/scythe.desktop /app/share/applications/com.scythe.Scythe.desktop
|
||||
sources:
|
||||
|
|
|
|||
|
|
@ -32,19 +32,19 @@ cargo build --release
|
|||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
install -D -m 0755 target/release/scythe-daemon %{buildroot}%{_bindir}/scythe-daemon
|
||||
install -D -m 0755 target/release/scythe-ui %{buildroot}%{_bindir}/scythe-ui
|
||||
install -D -m 0755 target/release/scythe %{buildroot}%{_bindir}/scythe
|
||||
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/scythe-daemon
|
||||
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/scythe-ui
|
||||
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec-daemon
|
||||
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec-ui
|
||||
ln -sf %{_bindir}/scythe-daemon %{buildroot}%{_bindir}/vrec-daemon
|
||||
ln -sf %{_bindir}/scythe-ui %{buildroot}%{_bindir}/vrec-ui
|
||||
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec
|
||||
install -D -m 0644 packaging/scythe.desktop %{buildroot}%{_datadir}/applications/scythe.desktop
|
||||
ln -sf %{_datadir}/applications/scythe.desktop %{buildroot}%{_datadir}/applications/vrec.desktop
|
||||
|
||||
%files
|
||||
%{_bindir}/scythe
|
||||
%{_bindir}/scythe-daemon
|
||||
%{_bindir}/scythe-ui
|
||||
%{_bindir}/scythe
|
||||
%{_bindir}/vrec-daemon
|
||||
%{_bindir}/vrec-ui
|
||||
%{_bindir}/vrec
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ RequestExecutionLevel user
|
|||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\scythe.exe"
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\scythe-ui.exe"
|
||||
!define MUI_FINISHPAGE_RUN_PARAMETERS "--menu"
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Launch Scythe"
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
|
@ -27,12 +27,11 @@ RequestExecutionLevel user
|
|||
|
||||
Section "Scythe" SecMain
|
||||
; Terminate any running instances so locked executables and DLLs can be updated cleanly
|
||||
nsExec::Exec 'taskkill /F /IM scythe.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM scythe-ui.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM scythe-daemon.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM vrec-ui.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM vrec-daemon.exe /T'
|
||||
nsExec::Exec 'powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "Get-Process -Name scythe, scythe-ui, scythe-daemon, vrec-ui, vrec-daemon -ErrorAction SilentlyContinue | Stop-Process -Force"'
|
||||
nsExec::Exec 'powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "Get-Process -Name scythe-ui, scythe-daemon, vrec-ui, vrec-daemon -ErrorAction SilentlyContinue | Stop-Process -Force"'
|
||||
Sleep 1500
|
||||
|
||||
SetOverwrite try
|
||||
|
|
@ -41,24 +40,23 @@ Section "Scythe" SecMain
|
|||
|
||||
; Create Start Menu and Desktop shortcuts
|
||||
CreateDirectory "$SMPROGRAMS\scythe"
|
||||
CreateShortcut "$SMPROGRAMS\scythe\scythe.lnk" "$INSTDIR\scythe.exe" "--menu"
|
||||
CreateShortcut "$DESKTOP\scythe.lnk" "$INSTDIR\scythe.exe" "--menu"
|
||||
CreateShortcut "$SMPROGRAMS\scythe\scythe.lnk" "$INSTDIR\scythe-ui.exe" "--menu"
|
||||
CreateShortcut "$DESKTOP\scythe.lnk" "$INSTDIR\scythe-ui.exe" "--menu"
|
||||
|
||||
; Register uninstaller with Windows Control Panel / Settings
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\scythe" "DisplayName" "Scythe Screen Recorder"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\scythe" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\scythe" "DisplayIcon" "$INSTDIR\scythe.exe"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\scythe" "DisplayIcon" "$INSTDIR\scythe-ui.exe"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\scythe" "Publisher" "oiupoyt"
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
nsExec::Exec 'taskkill /F /IM scythe.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM scythe-ui.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM scythe-daemon.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM vrec-ui.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM vrec-daemon.exe /T'
|
||||
nsExec::Exec 'powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "Get-Process -Name scythe, scythe-ui, scythe-daemon, vrec-ui, vrec-daemon -ErrorAction SilentlyContinue | Stop-Process -Force"'
|
||||
nsExec::Exec 'powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "Get-Process -Name scythe-ui, scythe-daemon, vrec-ui, vrec-daemon -ErrorAction SilentlyContinue | Stop-Process -Force"'
|
||||
Sleep 1500
|
||||
|
||||
Delete "$DESKTOP\scythe.lnk"
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ RequestExecutionLevel user
|
|||
|
||||
Section
|
||||
; Terminate any running instances so locked executables and DLLs can be updated cleanly
|
||||
nsExec::Exec 'taskkill /F /IM scythe.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM scythe-ui.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM scythe-daemon.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM vrec-ui.exe /T'
|
||||
nsExec::Exec 'taskkill /F /IM vrec-daemon.exe /T'
|
||||
nsExec::Exec 'powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "Get-Process -Name scythe, scythe-ui, scythe-daemon, vrec-ui, vrec-daemon -ErrorAction SilentlyContinue | Stop-Process -Force"'
|
||||
nsExec::Exec 'powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "Get-Process -Name scythe-ui, scythe-daemon, vrec-ui, vrec-daemon -ErrorAction SilentlyContinue | Stop-Process -Force"'
|
||||
Sleep 1500
|
||||
|
||||
SetOverwrite try
|
||||
|
|
@ -22,11 +21,11 @@ Section
|
|||
SetOutPath "$LOCALAPPDATA\scythe"
|
||||
File /r "..\..\dist\bundle\*.*"
|
||||
|
||||
; Execute scythe with any forwarded command-line arguments, or default to overlay menu
|
||||
; Execute scythe-ui with any forwarded command-line arguments, or default to overlay menu
|
||||
${GetParameters} $R0
|
||||
${If} $R0 == ""
|
||||
Exec '"$LOCALAPPDATA\scythe\scythe.exe" --menu'
|
||||
Exec '"$LOCALAPPDATA\scythe\scythe-ui.exe" --menu'
|
||||
${Else}
|
||||
Exec '"$LOCALAPPDATA\scythe\scythe.exe" $R0'
|
||||
Exec '"$LOCALAPPDATA\scythe\scythe-ui.exe" $R0'
|
||||
${EndIf}
|
||||
SectionEnd
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ fn default_fps() -> u32 { 60 }
|
|||
fn default_video_codec() -> String { "h264".to_string() }
|
||||
fn default_audio_device() -> String { "default".to_string() }
|
||||
fn default_audio_mode() -> String { "system".to_string() }
|
||||
fn default_lang() -> String { "en".to_string() }
|
||||
fn default_theme() -> String { "dark".to_string() }
|
||||
fn default_save_hotkey() -> String { "Ctrl+Shift+R".to_string() }
|
||||
fn default_menu_hotkey() -> String { "Alt+Z".to_string() }
|
||||
fn default_record_hotkey() -> String { "Ctrl+Shift+F9".to_string() }
|
||||
|
|
@ -22,9 +24,14 @@ fn default_stream_service() -> String { "twitch".to_string() }
|
|||
fn default_stream_url() -> String { "rtmp://live.twitch.tv/app/".to_string() }
|
||||
fn default_screenshot_hotkey() -> String { "Ctrl+Shift+S".to_string() }
|
||||
fn default_screenshot_format() -> String { "png".to_string() }
|
||||
fn default_capture_source() -> String { "focused".to_string() }
|
||||
fn default_video_quality_mode() -> String { "cbr".to_string() }
|
||||
fn default_color_range() -> String { "limited".to_string() }
|
||||
fn default_audio_codec() -> String { "opus".to_string() }
|
||||
fn default_replay_storage() -> String { "ram".to_string() }
|
||||
fn default_container_format() -> String { "mp4".to_string() }
|
||||
fn default_res_w() -> u32 { 1920 }
|
||||
fn default_res_h() -> u32 { 1080 }
|
||||
fn default_screenshot_quality() -> u32 { 90 }
|
||||
fn default_screenshot_preset() -> String { "balanced".to_string() }
|
||||
fn default_pause_hotkey() -> String { "Ctrl+Shift+F8".to_string() }
|
||||
|
|
@ -43,6 +50,8 @@ pub struct ScytheConfig {
|
|||
#[serde(default = "default_replay_bitrate")]
|
||||
pub replay_bitrate_kbps: u32,
|
||||
|
||||
#[serde(default)]
|
||||
pub record_enabled: bool,
|
||||
#[serde(default = "default_record_bitrate")]
|
||||
pub record_bitrate_kbps: u32,
|
||||
#[serde(default = "default_fps")]
|
||||
|
|
@ -68,6 +77,10 @@ pub struct ScytheConfig {
|
|||
pub autostart_replay: bool,
|
||||
#[serde(default)]
|
||||
pub autostart_overlay: bool,
|
||||
#[serde(default = "default_lang")]
|
||||
pub language: String,
|
||||
#[serde(default = "default_theme")]
|
||||
pub ui_color_theme: String,
|
||||
#[serde(default = "default_save_hotkey")]
|
||||
pub save_hotkey: String,
|
||||
#[serde(default = "default_menu_hotkey")]
|
||||
|
|
@ -96,20 +109,40 @@ pub struct ScytheConfig {
|
|||
#[serde(default)]
|
||||
pub screenshot_cursor: bool,
|
||||
|
||||
#[serde(default = "default_capture_source")]
|
||||
pub capture_source: String,
|
||||
#[serde(default = "default_video_quality_mode")]
|
||||
pub video_quality_mode: String,
|
||||
#[serde(default = "default_color_range")]
|
||||
pub color_range: String,
|
||||
#[serde(default = "default_audio_codec")]
|
||||
pub audio_codec: String,
|
||||
#[serde(default = "default_replay_storage")]
|
||||
pub replay_storage: String,
|
||||
#[serde(default)]
|
||||
pub record_low_power: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub show_replay_notifications: bool,
|
||||
|
||||
#[serde(default = "default_container_format")]
|
||||
pub container_format: String,
|
||||
#[serde(default)]
|
||||
pub change_resolution: bool,
|
||||
#[serde(default = "default_res_w")]
|
||||
pub resolution_w: u32,
|
||||
#[serde(default = "default_res_h")]
|
||||
pub resolution_h: u32,
|
||||
#[serde(default)]
|
||||
pub save_folder_by_game: bool,
|
||||
#[serde(default)]
|
||||
pub name_by_game: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub restart_replay_on_save: bool,
|
||||
#[serde(default)]
|
||||
pub show_scroll_lock_led: bool,
|
||||
#[serde(default)]
|
||||
pub autostart_on_game: bool,
|
||||
#[serde(default)]
|
||||
pub autostart_power_only: bool,
|
||||
|
||||
#[serde(default = "default_screenshot_quality")]
|
||||
|
|
@ -138,6 +171,7 @@ impl Default for ScytheConfig {
|
|||
replay_duration_sec: 60,
|
||||
replay_bitrate_kbps: 18000,
|
||||
|
||||
record_enabled: false,
|
||||
record_bitrate_kbps: 20000,
|
||||
fps: 60,
|
||||
video_codec: "h264".to_string(),
|
||||
|
|
@ -151,6 +185,8 @@ impl Default for ScytheConfig {
|
|||
autostart: false,
|
||||
autostart_replay: false,
|
||||
autostart_overlay: false,
|
||||
language: "en".to_string(),
|
||||
ui_color_theme: "dark".to_string(),
|
||||
save_hotkey: "Ctrl+Shift+R".to_string(),
|
||||
menu_hotkey: "Alt+Z".to_string(),
|
||||
record_hotkey: "Ctrl+Shift+F9".to_string(),
|
||||
|
|
@ -166,13 +202,23 @@ impl Default for ScytheConfig {
|
|||
screenshot_format: default_screenshot_format(),
|
||||
screenshot_cursor: false,
|
||||
|
||||
capture_source: default_capture_source(),
|
||||
video_quality_mode: default_video_quality_mode(),
|
||||
color_range: default_color_range(),
|
||||
audio_codec: default_audio_codec(),
|
||||
replay_storage: default_replay_storage(),
|
||||
record_low_power: false,
|
||||
show_replay_notifications: true,
|
||||
|
||||
container_format: default_container_format(),
|
||||
change_resolution: false,
|
||||
resolution_w: default_res_w(),
|
||||
resolution_h: default_res_h(),
|
||||
save_folder_by_game: false,
|
||||
name_by_game: false,
|
||||
restart_replay_on_save: true,
|
||||
show_scroll_lock_led: false,
|
||||
autostart_on_game: false,
|
||||
autostart_power_only: false,
|
||||
|
||||
screenshot_quality: default_screenshot_quality(),
|
||||
|
|
@ -531,11 +577,21 @@ mod tests {
|
|||
assert_eq!(parsed.stream_url, "rtmp://live.twitch.tv/app/");
|
||||
assert_eq!(parsed.stream_key, "");
|
||||
|
||||
assert_eq!(parsed.container_format, "mp4");
|
||||
assert!(!parsed.change_resolution);
|
||||
assert_eq!(parsed.resolution_w, 1920);
|
||||
assert_eq!(parsed.resolution_h, 1080);
|
||||
assert!(!parsed.save_folder_by_game);
|
||||
assert!(!parsed.name_by_game);
|
||||
assert!(parsed.restart_replay_on_save);
|
||||
assert!(!parsed.show_scroll_lock_led);
|
||||
assert!(!parsed.autostart_on_game);
|
||||
assert!(!parsed.autostart_power_only);
|
||||
assert_eq!(parsed.capture_source, "focused");
|
||||
assert_eq!(parsed.video_quality_mode, "cbr");
|
||||
assert_eq!(parsed.color_range, "limited");
|
||||
assert_eq!(parsed.audio_codec, "opus");
|
||||
assert_eq!(parsed.replay_storage, "ram");
|
||||
assert!(!parsed.record_low_power);
|
||||
assert!(parsed.show_replay_notifications);
|
||||
assert_eq!(parsed.screenshot_hotkey, "Ctrl+Shift+S");
|
||||
|
|
|
|||
|
|
@ -13,67 +13,6 @@ use std::io::{Read, Write};
|
|||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Returns true when the overlay process recorded in the pid file is still alive.
|
||||
fn overlay_alive() -> bool {
|
||||
let pid_path = crate::ipc::get_overlay_pid_path();
|
||||
let Ok(content) = std::fs::read_to_string(&pid_path) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(pid) = content.trim().parse::<i32>() else {
|
||||
return false;
|
||||
};
|
||||
if pid <= 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
unsafe { libc::kill(pid, 0) == 0 }
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use windows::Win32::Foundation::{CloseHandle, STILL_ACTIVE};
|
||||
use windows::Win32::System::Threading::{
|
||||
GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
|
||||
};
|
||||
unsafe {
|
||||
let Ok(handle) = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid as u32) else {
|
||||
return false;
|
||||
};
|
||||
let mut code = 0u32;
|
||||
let alive = GetExitCodeProcess(handle, &mut code).is_ok() && code == STILL_ACTIVE.0 as u32;
|
||||
let _ = CloseHandle(handle);
|
||||
alive
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the HUD overlay without toggling an already-visible one closed.
|
||||
fn spawn_overlay_once() {
|
||||
if overlay_alive() {
|
||||
return;
|
||||
}
|
||||
let exe = std::env::current_exe()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| "scythe".to_string());
|
||||
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
cmd.arg("--menu")
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null());
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
cmd.creation_flags(0x08000000);
|
||||
}
|
||||
let _ = cmd.spawn();
|
||||
}
|
||||
|
||||
fn mask_stream_url(url: &str) -> String {
|
||||
if let Some((base, key)) = url.rsplit_once('/')
|
||||
&& key.len() > 4 {
|
||||
|
|
@ -339,10 +278,6 @@ async fn run_daemon_async() -> Result<(), Box<dyn std::error::Error + Send + Syn
|
|||
println!("Audio mode cycled to: {}", cfg.audio_mode);
|
||||
let _ = cmd_tx_ipc.try_send(Command::ReloadConfig);
|
||||
},
|
||||
Command::ShowOverlay => {
|
||||
println!("ShowOverlay requested: opening HUD overlay.");
|
||||
std::thread::spawn(spawn_overlay_once);
|
||||
},
|
||||
Command::StopDaemon => {
|
||||
println!("StopDaemon requested: Finalizing active recordings...");
|
||||
crate::hyprland_binds::unregister_hyprland_binds(&crate::config::ScytheConfig::load());
|
||||
|
|
|
|||
12
src/main.rs
12
src/main.rs
|
|
@ -444,17 +444,6 @@ fn run_client(args: Vec<String>) -> Result<(), Box<dyn std::error::Error + Send
|
|||
"--pause" => {
|
||||
return handle_toggle_recording();
|
||||
}
|
||||
"--show-overlay" | "--open" => {
|
||||
ensure_daemon_running();
|
||||
// Daemon owns overlay lifetime; if it is unreachable, open the HUD directly.
|
||||
if send_command(Command::ShowOverlay).is_err() {
|
||||
ensure_hotkeys_running();
|
||||
if !check_and_toggle_overlay() {
|
||||
scythe::overlay_egui::run_egui_overlay();
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
"--toast" => {
|
||||
let title = args.get(2).map(|s| s.as_str()).unwrap_or("SCYTHE");
|
||||
let subtitle = args.get(3).map(|s| s.as_str()).unwrap_or("");
|
||||
|
|
@ -551,7 +540,6 @@ fn run_client(args: Vec<String>) -> Result<(), Box<dyn std::error::Error + Send
|
|||
println!(" scythe-ui --record Toggle normal recording on/off (with notification)");
|
||||
println!(" scythe-ui --stream Toggle livestreaming on/off (with notification)");
|
||||
println!(" scythe-ui --cursor Toggle mouse cursor recording on/off (with notification)");
|
||||
println!(" scythe-ui --show-overlay Open the HUD overlay via the daemon (no toggle)");
|
||||
println!(" scythe-ui --start Start normal recording");
|
||||
println!(" scythe-ui --stop Stop normal recording");
|
||||
println!(" scythe-ui --reload Reload daemon configuration");
|
||||
|
|
|
|||
|
|
@ -1131,11 +1131,21 @@ pub struct ScytheOverlayApp {
|
|||
pub screenshot_dir: String,
|
||||
pub screenshot_format: String,
|
||||
pub screenshot_cursor: bool,
|
||||
pub capture_source: String,
|
||||
pub video_quality_mode: String,
|
||||
pub color_range: String,
|
||||
pub replay_storage: String,
|
||||
pub record_low_power: bool,
|
||||
pub show_replay_notifications: bool,
|
||||
pub container_format: String,
|
||||
pub change_resolution: bool,
|
||||
pub resolution_w: u32,
|
||||
pub resolution_h: u32,
|
||||
pub res_w_str: String,
|
||||
pub res_h_str: String,
|
||||
pub restart_replay_on_save: bool,
|
||||
pub show_scroll_lock_led: bool,
|
||||
pub autostart_on_game: bool,
|
||||
pub autostart_power_only: bool,
|
||||
pub screenshot_quality: u32,
|
||||
pub screenshot_preset: String,
|
||||
|
|
@ -1215,12 +1225,22 @@ impl ScytheOverlayApp {
|
|||
let screenshot_dir = config.screenshot_directory.clone();
|
||||
let screenshot_format = config.screenshot_format.clone();
|
||||
let screenshot_cursor = config.screenshot_cursor;
|
||||
let capture_source = config.capture_source.clone();
|
||||
let video_quality_mode = config.video_quality_mode.clone();
|
||||
let color_range = config.color_range.clone();
|
||||
let replay_storage = config.replay_storage.clone();
|
||||
let record_low_power = config.record_low_power;
|
||||
let show_replay_notifications = config.show_replay_notifications;
|
||||
|
||||
let container_format = config.container_format.clone();
|
||||
let change_resolution = config.change_resolution;
|
||||
let resolution_w = config.resolution_w;
|
||||
let resolution_h = config.resolution_h;
|
||||
let res_w_str = resolution_w.to_string();
|
||||
let res_h_str = resolution_h.to_string();
|
||||
let restart_replay_on_save = config.restart_replay_on_save;
|
||||
let show_scroll_lock_led = config.show_scroll_lock_led;
|
||||
let autostart_on_game = config.autostart_on_game;
|
||||
let autostart_power_only = config.autostart_power_only;
|
||||
let screenshot_quality = config.screenshot_quality;
|
||||
let screenshot_preset = config.screenshot_preset.clone();
|
||||
|
|
@ -1287,11 +1307,21 @@ impl ScytheOverlayApp {
|
|||
screenshot_dir,
|
||||
screenshot_format,
|
||||
screenshot_cursor,
|
||||
capture_source,
|
||||
video_quality_mode,
|
||||
color_range,
|
||||
replay_storage,
|
||||
record_low_power,
|
||||
show_replay_notifications,
|
||||
container_format,
|
||||
change_resolution,
|
||||
resolution_w,
|
||||
resolution_h,
|
||||
res_w_str,
|
||||
res_h_str,
|
||||
restart_replay_on_save,
|
||||
show_scroll_lock_led,
|
||||
autostart_on_game,
|
||||
autostart_power_only,
|
||||
screenshot_quality,
|
||||
screenshot_preset,
|
||||
|
|
@ -1332,11 +1362,19 @@ impl ScytheOverlayApp {
|
|||
self.config.screenshot_directory = self.screenshot_dir.clone();
|
||||
self.config.screenshot_format = self.screenshot_format.clone();
|
||||
self.config.screenshot_cursor = self.screenshot_cursor;
|
||||
self.config.capture_source = self.capture_source.clone();
|
||||
self.config.video_quality_mode = self.video_quality_mode.clone();
|
||||
self.config.color_range = self.color_range.clone();
|
||||
self.config.replay_storage = self.replay_storage.clone();
|
||||
self.config.record_low_power = self.record_low_power;
|
||||
self.config.show_replay_notifications = self.show_replay_notifications;
|
||||
self.config.container_format = "mp4".to_string();
|
||||
self.config.change_resolution = self.change_resolution;
|
||||
self.config.resolution_w = self.resolution_w;
|
||||
self.config.resolution_h = self.resolution_h;
|
||||
self.config.restart_replay_on_save = self.restart_replay_on_save;
|
||||
self.config.show_scroll_lock_led = self.show_scroll_lock_led;
|
||||
self.config.autostart_on_game = self.autostart_on_game;
|
||||
self.config.autostart_power_only = self.autostart_power_only;
|
||||
self.config.screenshot_quality = self.screenshot_quality;
|
||||
self.config.screenshot_preset = self.screenshot_preset.clone();
|
||||
|
|
@ -1367,11 +1405,21 @@ impl ScytheOverlayApp {
|
|||
self.screenshot_dir = def.screenshot_directory;
|
||||
self.screenshot_format = def.screenshot_format;
|
||||
self.screenshot_cursor = def.screenshot_cursor;
|
||||
self.capture_source = def.capture_source;
|
||||
self.video_quality_mode = def.video_quality_mode;
|
||||
self.color_range = def.color_range;
|
||||
self.replay_storage = def.replay_storage;
|
||||
self.record_low_power = def.record_low_power;
|
||||
self.show_replay_notifications = def.show_replay_notifications;
|
||||
self.container_format = "mp4".to_string();
|
||||
self.change_resolution = def.change_resolution;
|
||||
self.resolution_w = def.resolution_w;
|
||||
self.resolution_h = def.resolution_h;
|
||||
self.res_w_str = def.resolution_w.to_string();
|
||||
self.res_h_str = def.resolution_h.to_string();
|
||||
self.restart_replay_on_save = def.restart_replay_on_save;
|
||||
self.show_scroll_lock_led = def.show_scroll_lock_led;
|
||||
self.autostart_on_game = def.autostart_on_game;
|
||||
self.autostart_power_only = def.autostart_power_only;
|
||||
self.screenshot_quality = def.screenshot_quality;
|
||||
self.screenshot_preset = def.screenshot_preset;
|
||||
|
|
@ -2419,6 +2467,18 @@ impl ScytheOverlayApp {
|
|||
ui.add_space(10.0);
|
||||
|
||||
render_section_card(ui, "General", accent, |ui| {
|
||||
ui.label(egui::RichText::new("Where should temporary replay data be stored?").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
||||
ui.add_space(4.0);
|
||||
ui.horizontal(|ui| {
|
||||
if squared_button(ui, "RAM", self.replay_storage == "ram", accent) {
|
||||
self.replay_storage = "ram".to_string();
|
||||
}
|
||||
if squared_button(ui, "Disk (Not recommended on SSDs)", self.replay_storage == "disk", accent) {
|
||||
self.replay_storage = "disk".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
let mut rrs = self.restart_replay_on_save;
|
||||
if toggle_switch(ui, &mut rrs, accent).changed() {
|
||||
|
|
@ -2492,6 +2552,14 @@ impl ScytheOverlayApp {
|
|||
}
|
||||
ui.label(egui::RichText::new("Show replay notifications").size(11.5).color(Color32::WHITE));
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
ui.horizontal(|ui| {
|
||||
let mut led_on = self.show_scroll_lock_led;
|
||||
if toggle_switch(ui, &mut led_on, accent).changed() {
|
||||
self.show_scroll_lock_led = led_on;
|
||||
}
|
||||
ui.label(egui::RichText::new("Show replay status with scroll lock LED").size(11.5).color(Color32::WHITE));
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
|
@ -2528,6 +2596,52 @@ impl ScytheOverlayApp {
|
|||
}
|
||||
|
||||
fn render_tab_record(&mut self, ui: &mut egui::Ui, accent: Color32) {
|
||||
render_section_card(ui, "Capture", accent, |ui| {
|
||||
ui.label(egui::RichText::new("Capture source:").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
||||
ui.add_space(3.0);
|
||||
ui.horizontal(|ui| {
|
||||
if squared_button(ui, "Focused monitor", self.capture_source == "focused", accent) {
|
||||
self.capture_source = "focused".to_string();
|
||||
}
|
||||
if squared_button(ui, "Monitor eDP-1 (1920x1080)", self.capture_source == "monitor", accent) {
|
||||
self.capture_source = "monitor".to_string();
|
||||
}
|
||||
if squared_button(ui, "Window", self.capture_source == "window", accent) {
|
||||
self.capture_source = "window".to_string();
|
||||
}
|
||||
if squared_button(ui, "Region", self.capture_source == "region", accent) {
|
||||
self.capture_source = "region".to_string();
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
let mut chg_res = self.change_resolution;
|
||||
if toggle_switch(ui, &mut chg_res, accent).changed() {
|
||||
self.change_resolution = chg_res;
|
||||
}
|
||||
ui.label(egui::RichText::new("Change video resolution").size(11.5).color(Color32::WHITE));
|
||||
});
|
||||
|
||||
if self.change_resolution {
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new("Resolution:").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
||||
let mut w = self.resolution_w;
|
||||
if numeric_input_box(ui, &mut self.res_w_str, &mut w, 640, 7680, 160, "W", accent) {
|
||||
self.resolution_w = w;
|
||||
}
|
||||
ui.label(egui::RichText::new("x").size(11.5).color(Color32::WHITE));
|
||||
let mut h = self.resolution_h;
|
||||
if numeric_input_box(ui, &mut self.res_h_str, &mut h, 480, 4320, 90, "H", accent) {
|
||||
self.resolution_h = h;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
self.render_audio_controls(ui, accent);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
|
@ -2726,6 +2840,16 @@ impl ScytheOverlayApp {
|
|||
ui.label(egui::RichText::new("Capture mouse cursor in screenshot").size(11.5).color(Color32::WHITE));
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new("Capture source:").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
||||
ui.add_space(4.0);
|
||||
ui.horizontal(|ui| {
|
||||
for (src_id, label) in [("focused", "Focused monitor"), ("full", "Full desktop")] {
|
||||
if squared_button(ui, label, self.capture_source == src_id, accent) {
|
||||
self.capture_source = src_id.to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
|
|
@ -4334,7 +4458,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_overlay_app_options_synchronization() {
|
||||
let mut app = ScytheOverlayApp::new();
|
||||
app.container_format = "mp4".to_string();
|
||||
app.change_resolution = true;
|
||||
app.resolution_w = 2560;
|
||||
app.resolution_h = 1440;
|
||||
app.restart_replay_on_save = false;
|
||||
app.show_scroll_lock_led = true;
|
||||
app.capture_source = "window".to_string();
|
||||
app.video_quality_mode = "vbr".to_string();
|
||||
app.color_range = "full".to_string();
|
||||
app.config.audio_codec = "opus".to_string();
|
||||
|
|
@ -4344,7 +4474,13 @@ mod tests {
|
|||
|
||||
app.save_and_apply_settings();
|
||||
|
||||
assert_eq!(app.config.container_format, "mp4");
|
||||
assert!(app.config.change_resolution);
|
||||
assert_eq!(app.config.resolution_w, 2560);
|
||||
assert_eq!(app.config.resolution_h, 1440);
|
||||
assert!(!app.config.restart_replay_on_save);
|
||||
assert!(app.config.show_scroll_lock_led);
|
||||
assert_eq!(app.config.capture_source, "window");
|
||||
assert_eq!(app.config.video_quality_mode, "vbr");
|
||||
assert_eq!(app.config.color_range, "full");
|
||||
assert_eq!(app.config.audio_codec, "opus");
|
||||
|
|
@ -4353,7 +4489,11 @@ mod tests {
|
|||
assert_eq!(app.config.screenshot_resolution, "1080p");
|
||||
|
||||
app.reset_settings_to_defaults();
|
||||
assert_eq!(app.container_format, "mp4");
|
||||
assert!(!app.change_resolution);
|
||||
assert!(app.restart_replay_on_save);
|
||||
assert!(!app.show_scroll_lock_led);
|
||||
assert_eq!(app.capture_source, "focused");
|
||||
assert_eq!(app.video_quality_mode, "cbr");
|
||||
assert_eq!(app.color_range, "limited");
|
||||
assert_eq!(app.config.audio_codec, "opus");
|
||||
|
|
|
|||
|
|
@ -10,24 +10,6 @@ echo "[1/6] Building release binaries..."
|
|||
cargo build --release --quiet
|
||||
|
||||
SOCKET_PATH="${XDG_RUNTIME_DIR:-/tmp}/scythe.sock"
|
||||
|
||||
# A live daemon owns this socket. Removing the path entry while it runs leaves the
|
||||
# kernel socket bound but unreachable, breaking the user's session until restart.
|
||||
# Shut it down gracefully instead, and refuse to run if it will not stop.
|
||||
if [ -S "$SOCKET_PATH" ] && target/release/scythe --status >/dev/null 2>&1; then
|
||||
echo "[0/6] Stopping existing scythe daemon..."
|
||||
target/release/scythe --quit >/dev/null 2>&1 || true
|
||||
for _ in $(seq 1 50); do
|
||||
[ -S "$SOCKET_PATH" ] || break
|
||||
sleep 0.1
|
||||
done
|
||||
if target/release/scythe --status >/dev/null 2>&1; then
|
||||
echo "ERROR: A scythe daemon is running and did not stop cleanly."
|
||||
echo " Refusing to unlink its socket. Stop it first: scythe --quit"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$SOCKET_PATH"
|
||||
rm -f record_*.mp4 replay_*.mp4
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue