Merge fix/shared-ecosystem-ci: shared Arch CI image + clippy fixes
All checks were successful
dev release / build (push) Successful in 36s

This commit is contained in:
Breadway 2026-08-05 14:06:14 +08:00
commit 598f85d930
12 changed files with 97 additions and 53 deletions

View 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

View file

@ -19,7 +19,7 @@ jobs:
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
- name: build
run: cd src && cargo build --release --locked
run: cd src && bash ci/build.sh cargo build --release --locked
- name: compute dev version
run: |

View file

@ -21,7 +21,7 @@ jobs:
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
- name: build
run: cd src && cargo build --release --locked
run: cd src && bash ci/build.sh cargo build --release --locked
- name: prepare artifacts
run: |

View file

@ -17,7 +17,7 @@ jobs:
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src
- name: build
run: cd src && cargo build --release --locked
run: cd src && bash ci/build.sh cargo build --release --locked
- name: prepare artifacts
run: |

1
ci/bread-ecosystem.rev Normal file
View file

@ -0,0 +1 @@
620c5a1317a6b57276eabca961facdb78bf510db

20
ci/build.sh Executable file
View 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" "$@"

View file

@ -103,7 +103,7 @@ pub fn snap_position(
}
/// 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;
if idx >= monitors.len() {
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.
pub fn auto_arrange(monitors: &mut Vec<Monitor>) {
pub fn auto_arrange(monitors: &mut [Monitor]) {
let mut cursor = 0i32;
for m in monitors.iter_mut() {
m.x = cursor;

View file

@ -62,13 +62,12 @@ pub fn find_mirror_modes(source: &Monitor, target: &Monitor) -> Option<MirrorRes
// Approximate: check all source ARs within 5%
for &s_ar in src_by_ar.keys() {
let s_ratio = ratio_f64(s_ar);
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) {
if (s_ratio - tgt_ratio).abs() / s_ratio < 0.05
&& !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 });
}
}
}
}
if candidates.is_empty() {
return None;

View file

@ -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).
/// 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 {
if let Some(m) = monitors.iter_mut().find(|m| m.name == pm.name) {
if let Some(mode) = Mode::parse(&format!("{}Hz", pm.mode)) {

View file

@ -242,15 +242,15 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) {
state.dirty = true;
}
}
KeyCode::Char('l') | KeyCode::Right => {
if state.config.res_idx + 1 < state.config.resolutions.len() {
KeyCode::Char('l') | KeyCode::Right
if state.config.res_idx + 1 < state.config.resolutions.len() =>
{
state.config.res_idx += 1;
let m = &state.monitors[idx];
state.config.update_refreshes(m);
sync_mode_to_monitor(state, idx);
state.dirty = true;
}
}
_ => {}
},
ConfigField::Refresh => match event.code {
@ -261,13 +261,13 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) {
state.dirty = true;
}
}
KeyCode::Char('l') | KeyCode::Right => {
if state.config.refresh_idx + 1 < state.config.refreshes.len() {
KeyCode::Char('l') | KeyCode::Right
if state.config.refresh_idx + 1 < state.config.refreshes.len() =>
{
state.config.refresh_idx += 1;
sync_mode_to_monitor(state, idx);
state.dirty = true;
}
}
_ => {}
},
ConfigField::Scale => match event.code {
@ -335,13 +335,13 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) {
state.dirty = true;
}
}
KeyCode::Char('l') | KeyCode::Right => {
if state.config.mirror_idx + 1 < state.config.mirror_options.len() {
KeyCode::Char('l') | KeyCode::Right
if state.config.mirror_idx + 1 < state.config.mirror_options.len() =>
{
state.config.mirror_idx += 1;
sync_mirror_to_monitor(state, idx);
state.dirty = true;
}
}
_ => {}
},
}

View file

@ -259,9 +259,9 @@ pub fn canvas_area(terminal_size: (u16, u16)) -> Rect {
}
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)
&& row >= canvas.y + 1
&& row > canvas.y
&& row < canvas.y + canvas.height.saturating_sub(1)
}

View file

@ -210,10 +210,11 @@ 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.
// 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 {
// Activate Apply
@ -238,7 +239,6 @@ pub fn handle_mouse(event: MouseEvent, state: &mut AppState) {
state.mirror.focused = 0;
}
}
}
_ => {}
}
}