ci: build against bread-ecosystem's shared Arch CI image, add check.yml
All checks were successful
check / check (push) Successful in 26s
All checks were successful
check / check (push) Successful in 26s
Same fix as breadpad: build inside the shared pinned Arch container (bread-ecosystem/ci/, cloned at the sha in ci/bread-ecosystem.rev) instead of building natively against whatever's on the runner host. Adds check.yml (clippy + test on feature/**/fix/**) as a fast-fail gate before anything reaches main. Turning on clippy -D warnings for the first time surfaced 10 pre-existing warnings (int_plus_one, ptr_arg on &mut Vec params, collapsible_if, collapsible_match) across layout.rs, mirror.rs, profile.rs, and the config/mirror TUI views — all fixed exactly per clippy's suggested diffs, verified behavior-preserving (the mirror_view.rs collapse in particular: confirmed the "do nothing" fallthrough when mirror.result is None is unchanged, since that was already the fallthrough behavior of the original nested if with no matching else on the outer condition). Verified locally: build, clippy, and test all pass through the new container path.
This commit is contained in:
parent
c692b597b3
commit
d73eacf40f
12 changed files with 97 additions and 53 deletions
24
.forgejo/workflows/check.yml
Normal file
24
.forgejo/workflows/check.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
name: check
|
||||||
|
|
||||||
|
# Fast-fail lint/test on short-lived work branches, before it ever reaches
|
||||||
|
# main and triggers a dev-track release build.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['feature/**', 'fix/**']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: [self-hosted, hestia]
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
rm -rf src && mkdir src
|
||||||
|
git clone --branch "${GITHUB_REF_NAME}" --depth 1 \
|
||||||
|
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
|
||||||
|
|
||||||
|
- name: clippy
|
||||||
|
run: cd src && bash ci/build.sh cargo clippy --workspace --all-targets --locked -- -D warnings
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
run: cd src && bash ci/build.sh cargo test --workspace --locked
|
||||||
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
|
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
run: cd src && cargo build --release --locked
|
run: cd src && bash ci/build.sh cargo build --release --locked
|
||||||
|
|
||||||
- name: compute dev version
|
- name: compute dev version
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
|
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
run: cd src && cargo build --release --locked
|
run: cd src && bash ci/build.sh cargo build --release --locked
|
||||||
|
|
||||||
- name: prepare artifacts
|
- name: prepare artifacts
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
|
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
run: cd src && cargo build --release --locked
|
run: cd src && bash ci/build.sh cargo build --release --locked
|
||||||
|
|
||||||
- name: prepare artifacts
|
- name: prepare artifacts
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
1
ci/bread-ecosystem.rev
Normal file
1
ci/bread-ecosystem.rev
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
620c5a1317a6b57276eabca961facdb78bf510db
|
||||||
20
ci/build.sh
Executable file
20
ci/build.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Delegates to bread-ecosystem's shared CI build image/script, pinned to
|
||||||
|
# the commit in ci/bread-ecosystem.rev — not `main`. bread-ecosystem's CI
|
||||||
|
# files now affect every product's release pipeline, so bumping the pin
|
||||||
|
# is a deliberate act instead of silent drift.
|
||||||
|
#
|
||||||
|
# Usage: ci/build.sh cargo build --release --locked
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
REV="$(cat "${ROOT}/ci/bread-ecosystem.rev")"
|
||||||
|
|
||||||
|
CACHE_DIR="/tmp/bread-ecosystem-ci-${REV}"
|
||||||
|
if [ ! -d "$CACHE_DIR" ]; then
|
||||||
|
rm -rf /tmp/bread-ecosystem-ci-*
|
||||||
|
git clone https://git.breadway.dev/Breadway/bread-ecosystem.git "$CACHE_DIR"
|
||||||
|
git -C "$CACHE_DIR" checkout --quiet "$REV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
bash "${CACHE_DIR}/ci/build.sh" breadmon "$ROOT" "$@"
|
||||||
|
|
@ -103,7 +103,7 @@ pub fn snap_position(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the selected monitor by (dx, dy) pixels, then snap.
|
/// Move the selected monitor by (dx, dy) pixels, then snap.
|
||||||
pub fn move_selected(state: &LayoutState, monitors: &mut Vec<Monitor>, dx: i32, dy: i32) {
|
pub fn move_selected(state: &LayoutState, monitors: &mut [Monitor], dx: i32, dy: i32) {
|
||||||
let idx = state.selected;
|
let idx = state.selected;
|
||||||
if idx >= monitors.len() {
|
if idx >= monitors.len() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -116,7 +116,7 @@ pub fn move_selected(state: &LayoutState, monitors: &mut Vec<Monitor>, dx: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Place monitors in a left-to-right row with no gaps.
|
/// Place monitors in a left-to-right row with no gaps.
|
||||||
pub fn auto_arrange(monitors: &mut Vec<Monitor>) {
|
pub fn auto_arrange(monitors: &mut [Monitor]) {
|
||||||
let mut cursor = 0i32;
|
let mut cursor = 0i32;
|
||||||
for m in monitors.iter_mut() {
|
for m in monitors.iter_mut() {
|
||||||
m.x = cursor;
|
m.x = cursor;
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,10 @@ pub fn find_mirror_modes(source: &Monitor, target: &Monitor) -> Option<MirrorRes
|
||||||
// Approximate: check all source ARs within 5%
|
// Approximate: check all source ARs within 5%
|
||||||
for &s_ar in src_by_ar.keys() {
|
for &s_ar in src_by_ar.keys() {
|
||||||
let s_ratio = ratio_f64(s_ar);
|
let s_ratio = ratio_f64(s_ar);
|
||||||
if (s_ratio - tgt_ratio).abs() / s_ratio < 0.05 {
|
if (s_ratio - tgt_ratio).abs() / s_ratio < 0.05
|
||||||
if !candidates.iter().any(|c| c.src_ar == s_ar && c.tgt_ar == tgt_ar) {
|
&& !candidates.iter().any(|c| c.src_ar == s_ar && c.tgt_ar == tgt_ar) {
|
||||||
candidates.push(Candidate { src_ar: s_ar, tgt_ar, is_exact: false });
|
candidates.push(Candidate { src_ar: s_ar, tgt_ar, is_exact: false });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ pub fn from_monitors(name: &str, monitors: &[Monitor]) -> Profile {
|
||||||
|
|
||||||
/// Apply a profile's settings onto a list of live monitors (matched by name).
|
/// Apply a profile's settings onto a list of live monitors (matched by name).
|
||||||
/// Monitors not in the profile are left unchanged.
|
/// Monitors not in the profile are left unchanged.
|
||||||
pub fn apply_to_monitors(profile: &Profile, monitors: &mut Vec<Monitor>) {
|
pub fn apply_to_monitors(profile: &Profile, monitors: &mut [Monitor]) {
|
||||||
for pm in &profile.monitors {
|
for pm in &profile.monitors {
|
||||||
if let Some(m) = monitors.iter_mut().find(|m| m.name == pm.name) {
|
if let Some(m) = monitors.iter_mut().find(|m| m.name == pm.name) {
|
||||||
if let Some(mode) = Mode::parse(&format!("{}Hz", pm.mode)) {
|
if let Some(mode) = Mode::parse(&format!("{}Hz", pm.mode)) {
|
||||||
|
|
|
||||||
|
|
@ -242,14 +242,14 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) {
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('l') | KeyCode::Right => {
|
KeyCode::Char('l') | KeyCode::Right
|
||||||
if state.config.res_idx + 1 < state.config.resolutions.len() {
|
if state.config.res_idx + 1 < state.config.resolutions.len() =>
|
||||||
state.config.res_idx += 1;
|
{
|
||||||
let m = &state.monitors[idx];
|
state.config.res_idx += 1;
|
||||||
state.config.update_refreshes(m);
|
let m = &state.monitors[idx];
|
||||||
sync_mode_to_monitor(state, idx);
|
state.config.update_refreshes(m);
|
||||||
state.dirty = true;
|
sync_mode_to_monitor(state, idx);
|
||||||
}
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
@ -261,12 +261,12 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) {
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('l') | KeyCode::Right => {
|
KeyCode::Char('l') | KeyCode::Right
|
||||||
if state.config.refresh_idx + 1 < state.config.refreshes.len() {
|
if state.config.refresh_idx + 1 < state.config.refreshes.len() =>
|
||||||
state.config.refresh_idx += 1;
|
{
|
||||||
sync_mode_to_monitor(state, idx);
|
state.config.refresh_idx += 1;
|
||||||
state.dirty = true;
|
sync_mode_to_monitor(state, idx);
|
||||||
}
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
@ -335,12 +335,12 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) {
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('l') | KeyCode::Right => {
|
KeyCode::Char('l') | KeyCode::Right
|
||||||
if state.config.mirror_idx + 1 < state.config.mirror_options.len() {
|
if state.config.mirror_idx + 1 < state.config.mirror_options.len() =>
|
||||||
state.config.mirror_idx += 1;
|
{
|
||||||
sync_mirror_to_monitor(state, idx);
|
state.config.mirror_idx += 1;
|
||||||
state.dirty = true;
|
sync_mirror_to_monitor(state, idx);
|
||||||
}
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -259,9 +259,9 @@ pub fn canvas_area(terminal_size: (u16, u16)) -> Rect {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn in_canvas(col: u16, row: u16, canvas: Rect) -> bool {
|
fn in_canvas(col: u16, row: u16, canvas: Rect) -> bool {
|
||||||
col >= canvas.x + 1
|
col > canvas.x
|
||||||
&& col < canvas.x + canvas.width.saturating_sub(1)
|
&& col < canvas.x + canvas.width.saturating_sub(1)
|
||||||
&& row >= canvas.y + 1
|
&& row > canvas.y
|
||||||
&& row < canvas.y + canvas.height.saturating_sub(1)
|
&& row < canvas.y + canvas.height.saturating_sub(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,33 +210,33 @@ pub fn handle_mouse(event: MouseEvent, state: &mut AppState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r if r >= 7 => {
|
r if r >= 7
|
||||||
// Result panel: Apply is on the line with buttons.
|
// Result panel: Apply is on the line with buttons.
|
||||||
// Rough column check: col < 20 = Apply, col >= 20 = Cancel
|
// Rough column check: col < 20 = Apply, col >= 20 = Cancel
|
||||||
if state.mirror.result.is_some() {
|
&& state.mirror.result.is_some() =>
|
||||||
let col = event.column;
|
{
|
||||||
if col < 20 {
|
let col = event.column;
|
||||||
// Activate Apply
|
if col < 20 {
|
||||||
state.mirror.focused = FIELDS.iter().position(|&f| f == MirrorField::Apply).unwrap_or(3);
|
// Activate Apply
|
||||||
if let Some(result) = state.mirror.result.clone() {
|
state.mirror.focused = FIELDS.iter().position(|&f| f == MirrorField::Apply).unwrap_or(3);
|
||||||
state.push_undo();
|
if let Some(result) = state.mirror.result.clone() {
|
||||||
let src_name = state.monitors[state.mirror.source_idx].name.clone();
|
state.push_undo();
|
||||||
let tgt_idx = state.mirror.target_idx;
|
let src_name = state.monitors[state.mirror.source_idx].name.clone();
|
||||||
state.monitors[tgt_idx].active_mode = result.mirror_mode.clone();
|
let tgt_idx = state.mirror.target_idx;
|
||||||
state.monitors[tgt_idx].mirror_of = Some(src_name.clone());
|
state.monitors[tgt_idx].active_mode = result.mirror_mode.clone();
|
||||||
state.dirty = true;
|
state.monitors[tgt_idx].mirror_of = Some(src_name.clone());
|
||||||
state.mirror.result = None;
|
state.dirty = true;
|
||||||
state.mirror.focused = 0;
|
|
||||||
state.set_status(
|
|
||||||
format!("Mirror set: {} → {} at {}", src_name, state.monitors[tgt_idx].name, result.mirror_mode),
|
|
||||||
crate::ui::StatusLevel::Success,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Cancel
|
|
||||||
state.mirror.result = None;
|
state.mirror.result = None;
|
||||||
state.mirror.focused = 0;
|
state.mirror.focused = 0;
|
||||||
|
state.set_status(
|
||||||
|
format!("Mirror set: {} → {} at {}", src_name, state.monitors[tgt_idx].name, result.mirror_mode),
|
||||||
|
crate::ui::StatusLevel::Success,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Cancel
|
||||||
|
state.mirror.result = None;
|
||||||
|
state.mirror.focused = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue