Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
- Never reach inside a poisoned lock. Use `expect` when the critical section cannot panic, or a fallback that leaves the data alone. Tests that serialize on process-wide state use `test_env::lock()` or a `test_env::Lease`.
- `catch_unwind` is reserved for the reviewed worker-isolation sites that carry an `#[expect]` reason. Tests observe panics through a joined scoped thread.
- Getters are `foo()`/`foo_mut()`, a fallible constructor is `try_new`, and type lifetimes have descriptive names. Every `unsafe` block has a `// SAFETY:` comment that says why it is sound.
- Convert numbers losslessly with `From` (`f64::from(width)`) wherever it can be called; `const` initializers keep `as`. Take the middle of two values with `midpoint` (`f64::midpoint(a, b)`), which cannot overflow.
- Inline named format arguments: `format!("{name}: {err}")`.
- Use `map_or`, `map_or_else`, `is_some_and`, or `is_ok_and` instead of `map(..).unwrap_or(..)`. Use `let ... else` for a `match` that only unwraps or leaves, and `if let ... else` for a `match` with one pattern and a fallback.
- In doc comments, put identifiers and protocol names in backticks (`wl_output`). Product names that docs write plainly go in `doc-valid-idents` in `clippy.toml`.
- Take raw pointers with `&raw const`/`&raw mut`, change pointer constness with `cast_const`/`cast_mut`, and write the anonymous lifetime in paths (`Formatter<'_>`). Give every `#[ignore]` a reason: `#[ignore = "needs a running Wayland compositor"]`.
- Clippy pedantic lints are enabled one at a time in `Cargo.toml`, never as the whole group, so a toolchain update does not turn on new ones.

## Coupled Changes
- Config changes often require `config.example.toml`, `docs/CONFIG.md`, configurator models/views, schema behavior, and tests.
Expand Down
31 changes: 31 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,37 @@ undocumented_unsafe_blocks = "warn"
unnecessary_safety_comment = "warn"
unwrap_used = "warn"
wildcard_imports = "warn"
# Pedantic lints, adopted one at a time. Most of them have machine-applicable
# fixes, so `cargo clippy --fix` can apply them.
borrow_as_ptr = "warn"
cast_lossless = "warn"
comparison_chain = "warn"
doc_markdown = "warn"
duration_suboptimal_units = "warn"
elidable_lifetime_names = "warn"
explicit_iter_loop = "warn"
if_not_else = "warn"
ignore_without_reason = "warn"
ignored_unit_patterns = "warn"
implicit_clone = "warn"
manual_is_variant_and = "warn"
manual_let_else = "warn"
manual_midpoint = "warn"
manual_string_new = "warn"
map_unwrap_or = "warn"
needless_raw_string_hashes = "warn"
option_as_ref_cloned = "warn"
ptr_cast_constness = "warn"
redundant_else = "warn"
semicolon_if_nothing_returned = "warn"
single_match_else = "warn"
uninlined_format_args = "warn"
unnecessary_debug_formatting = "warn"
unnested_or_patterns = "warn"

[workspace.lints.rust]
# Write the anonymous lifetime in paths: `Formatter<'_>`, not `Formatter`.
elided_lifetimes_in_paths = "warn"

[lints]
workspace = true
Expand Down
4 changes: 4 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ disallowed-methods = [
{ path = "std::sync::Mutex::clear_poison", reason = "a poisoned lock may hold broken invariants" },
{ path = "std::sync::RwLock::clear_poison", reason = "a poisoned lock may hold broken invariants" },
]

# Product and library names that docs may write without backticks. The `..`
# keeps Clippy's default list.
doc-valid-idents = ["GLib", "HarfBuzz", "HiDPI", "JetBrains", "MSBuild", "StatusNotifierItem", "VSync", ".."]
2 changes: 1 addition & 1 deletion configurator/src/app/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn load_app_stylesheet() {
}
}

/// GApplication id. A valid dotted id is required by GLib; the window still
/// `GApplication` id. A valid dotted id is required by GLib; the window still
/// advertises this as its Wayland app-id, so compositor rules and the
/// `.desktop` `StartupWMClass` must match it.
const APP_ID: &str = "org.wayscriber.Configurator";
Expand Down
30 changes: 13 additions & 17 deletions configurator/src/app/component/rebuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,23 +185,19 @@ pub(super) fn restore(widgets: &AppWidgets, state: ViewState) {
if let (Some(editable), Some(caret)) =
(widget.downcast_ref::<gtk::Editable>(), caret)
{
match caret.selection {
// `select_region` puts the caret on its second
// argument, and the bounds come back ordered: a
// selection made backwards keeps its active end
// where the caret was.
Some((start, end)) => {
let (anchor, active) = if caret.position == start {
(end, start)
} else {
(start, end)
};
editable.select_region(anchor, active);
}
None => {
editable.select_region(caret.position, caret.position);
editable.set_position(caret.position);
}
// `select_region` puts the caret on its second argument,
// and the bounds come back ordered: a selection made
// backwards keeps its active end where the caret was.
if let Some((start, end)) = caret.selection {
let (anchor, active) = if caret.position == start {
(end, start)
} else {
(start, end)
};
editable.select_region(anchor, active);
} else {
editable.select_region(caret.position, caret.position);
editable.set_position(caret.position);
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions configurator/src/app/daemon_setup/hyprland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ fn read_light_controls_status_from_config_root(config_root: &Path) -> HyprlandLi
let main_config_path = main_config_path(config_root);
let source_line = source_line_for_include(&include_path);
let source_present = fs::read_to_string(&main_config_path)
.map(|content| has_source_line(&content, &source_line))
.unwrap_or(false);
.is_ok_and(|content| has_source_line(&content, &source_line));

HyprlandLightControlsStatus {
include_exists: include_path.exists(),
Expand Down Expand Up @@ -327,9 +326,7 @@ fn strip_matching_quotes(value: &str) -> &str {

fn normalize_source_target(target: &str) -> PathBuf {
let expanded = if let Some(stripped) = target.strip_prefix("~/") {
paths::home_dir()
.map(|home| home.join(stripped))
.unwrap_or_else(|| PathBuf::from(target))
paths::home_dir().map_or_else(|| PathBuf::from(target), |home| home.join(stripped))
} else {
PathBuf::from(target)
};
Expand Down
14 changes: 5 additions & 9 deletions configurator/src/app/daemon_setup/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ pub(super) fn detect_service_unit_path(systemctl_available: bool) -> Option<Path
}

pub(super) fn query_service_enabled() -> bool {
let capture = match run_command("systemctl", &["--user", "is-enabled", SERVICE_NAME]) {
Ok(capture) => capture,
Err(_) => return false,
let Ok(capture) = run_command("systemctl", &["--user", "is-enabled", SERVICE_NAME]) else {
return false;
};
if !capture.success {
return false;
Expand All @@ -66,9 +65,8 @@ pub(super) fn query_service_enabled() -> bool {
}

pub(super) fn query_service_active() -> bool {
let capture = match run_command("systemctl", &["--user", "is-active", SERVICE_NAME]) {
Ok(capture) => capture,
Err(_) => return false,
let Ok(capture) = run_command("systemctl", &["--user", "is-active", SERVICE_NAME]) else {
return false;
};
capture.success && capture.stdout.trim() == "active"
}
Expand Down Expand Up @@ -135,9 +133,7 @@ pub(super) fn install_or_update_user_service() -> Result<PathBuf, String> {
}

pub(super) fn detect_managed_daemon_portal_runtime_supported() -> bool {
managed_daemon_runtime_capabilities()
.map(|capabilities| capabilities.portal)
.unwrap_or(false)
managed_daemon_runtime_capabilities().is_ok_and(|capabilities| capabilities.portal)
}

fn managed_daemon_runtime_capabilities() -> Result<RuntimeCapabilities, String> {
Expand Down
8 changes: 4 additions & 4 deletions configurator/src/app/daemon_workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ impl DaemonFeedback {
trf("Background setup action failed: {}", &[error])
}
Self::Action(ActionFeedback::RefreshFailed { previous, error }) => {
let previous = previous
.as_deref()
.map(DaemonFeedback::text)
.unwrap_or_else(|| tr("Background setup action failed.").to_string());
let previous = previous.as_deref().map_or_else(
|| tr("Background setup action failed.").to_string(),
DaemonFeedback::text,
);
trf("{}\nStatus refresh failed: {}", &[&previous, error])
}
}
Expand Down
6 changes: 3 additions & 3 deletions configurator/src/app/pages/daemon/groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,9 @@ pub(super) fn light_controls_group(
None => set_visible(&path_label, false),
}

let capability = status
.map(|status| status.light_shortcut_apply_capability)
.unwrap_or(LightShortcutApplyCapability::Manual);
let capability = status.map_or(LightShortcutApplyCapability::Manual, |status| {
status.light_shortcut_apply_capability
});
let native = capability == LightShortcutApplyCapability::HyprlandNative;
set_visible(&native_body, native);
set_visible(&manual_label, !native);
Expand Down
31 changes: 14 additions & 17 deletions configurator/src/app/pages/keybindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,21 @@ fn conflict_banner(

let revealer_for_bind = revealer.clone();
bindings.push(Box::new(move |app, _summary| {
match &app.shortcuts.conflict() {
Some(conflict) => {
set_label(&label, &conflict.prompt());
replace.set_label(conflict.replace_label());
set_accessible_label(&replace, conflict.replace_label());
let target = conflict.jump_field();
jump_field.set(target);
set_sensitive(&jump, target.is_some());
if !revealer_for_bind.reveals_child() {
revealer_for_bind.set_reveal_child(true);
}
set_visible(&revealer_for_bind, true);
if let Some(conflict) = &app.shortcuts.conflict() {
set_label(&label, &conflict.prompt());
replace.set_label(conflict.replace_label());
set_accessible_label(&replace, conflict.replace_label());
let target = conflict.jump_field();
jump_field.set(target);
set_sensitive(&jump, target.is_some());
if !revealer_for_bind.reveals_child() {
revealer_for_bind.set_reveal_child(true);
}
None => {
jump_field.set(None);
if revealer_for_bind.reveals_child() {
revealer_for_bind.set_reveal_child(false);
}
set_visible(&revealer_for_bind, true);
} else {
jump_field.set(None);
if revealer_for_bind.reveals_child() {
revealer_for_bind.set_reveal_child(false);
}
}
}));
Expand Down
17 changes: 7 additions & 10 deletions configurator/src/app/pages/keybindings/recorder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,13 @@ fn attach_button_controller(popover: &gtk::Popover, sender: &ComponentSender<Con
let Some(button_event) = event.downcast_ref::<ButtonEvent>() else {
return Propagation::Proceed;
};
let kind = event
.device()
.map(|device| match device.source() {
InputSource::Mouse | InputSource::Touchpad | InputSource::Trackpoint => {
RecorderDeviceKind::Mouse
}
InputSource::Pen => RecorderDeviceKind::Pen,
_ => RecorderDeviceKind::Other,
})
.unwrap_or(RecorderDeviceKind::Other);
let kind = match event.device().map(|device| device.source()) {
Some(InputSource::Mouse | InputSource::Touchpad | InputSource::Trackpoint) => {
RecorderDeviceKind::Mouse
}
Some(InputSource::Pen) => RecorderDeviceKind::Pen,
_ => RecorderDeviceKind::Other,
};
let modifiers = event.modifier_state();
let recorded = KeyboardModifiers {
ctrl: modifiers.contains(ModifierType::CONTROL_MASK),
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/app/pages/keybindings/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ pub(super) fn binding_row(
.shortcuts
.editor()
.filter(|editor| editor.field == field);
let editor_text = editing.map(|editor| editor.text.as_str()).unwrap_or(value);
let editor_text = editing.map_or(value, |editor| editor.text.as_str());
let editor_error = editing.and_then(|editor| editor.parse_error());
text_editor.refresh(editing.is_some(), editor_text, editor_error.as_deref());

Expand Down
15 changes: 6 additions & 9 deletions configurator/src/app/pages/keybindings/text_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,12 @@ impl TextEditorPopover {

pub(super) fn refresh(&self, open: bool, text: &str, parse_error: Option<&str>) {
set_text_blocked(&self.entry, &self.entry_handler, text);
match parse_error {
Some(message) => {
set_label(&self.error, message);
self.error.set_visible(true);
}
None => {
set_label(&self.error, "");
self.error.set_visible(false);
}
if let Some(message) = parse_error {
set_label(&self.error, message);
self.error.set_visible(true);
} else {
set_label(&self.error, "");
self.error.set_visible(false);
}
if open {
if !self.popover.is_visible() {
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/app/pages/keybindings/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub(super) fn connect_clicked(
});
}

/// GtkPopover widgets created with `set_parent` are not ordinary children.
/// `GtkPopover` widgets created with `set_parent` are not ordinary children.
/// If they are still attached when the parent is destroyed, GTK warns on
/// finalize. Unparent in the destroy handler so shutdown stays quiet.
pub(super) fn unparent_on_destroy(parent: &impl IsA<gtk::Widget>, popover: &gtk::Popover) {
Expand Down
6 changes: 3 additions & 3 deletions configurator/src/app/search/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn tab_summary(app: &ConfiguratorApp, tab: TabId) -> Option<TabSearchSummary> {
TabId::Keybindings => keybinding_matches(app, query, &mut summary),
#[cfg(feature = "tablet-input")]
TabId::Tablet => {
add_area_if(query, &mut summary, tab, SearchArea::Tablet, TABLET_TERMS)
add_area_if(query, &mut summary, tab, SearchArea::Tablet, TABLET_TERMS);
}
}
}
Expand Down Expand Up @@ -392,11 +392,11 @@ fn session_matches(app: &ConfiguratorApp, query: &SearchQuery, summary: &mut Tab
);
for item in &app.session_catalog.items {
let text = format!(
"session saved recent catalog {} {} {} {:?}",
"session saved recent catalog {} {} {} {}",
item.display_name,
item.path_label,
item.created_label(),
item.path,
item.path.display(),
);
if query.matches_text(&text) {
summary.add_area(SearchArea::SessionCatalog);
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/app/session_catalog/move_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ fn rollback_moved_artifacts_after_catalog_failure(
reason: &str,
) -> Result<SessionCatalogActionResult, String> {
match session::rollback_named_session_non_lock_artifacts_move(outcome) {
Ok(_) => Err(trf(
Ok(()) => Err(trf(
"Move Session failed because {}; rolled back moved files for {}.",
&[&reason, &item.display_name],
)),
Expand Down
5 changes: 2 additions & 3 deletions configurator/src/app/session_catalog/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,8 @@ fn inactive_session_reservation_refuses_a_running_daemon() {
let _env = EnvGuard::set(&[(XDG_RUNTIME_DIR_ENV, temp.path().as_os_str())]);
let _daemon_lock = acquire_runtime_lock_for_clear(RuntimeLockKind::Daemon).unwrap();

let error = match reserve_inactive_session_operation(SessionCatalogOperation::Move) {
Ok(_) => panic!("a held daemon lock must refuse the operation"),
Err(error) => error,
let Err(error) = reserve_inactive_session_operation(SessionCatalogOperation::Move) else {
panic!("a held daemon lock must refuse the operation");
};

assert!(error.contains("manually started daemon"), "{error}");
Expand Down
10 changes: 5 additions & 5 deletions configurator/src/app/update/color_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ impl ConfiguratorApp {
let values = rgb.map(format_rgb255);
for (component, value) in values.iter().enumerate() {
if let Some(slot) = self.draft.drawing_color.rgb.get_mut(component) {
*slot = value.to_string();
*slot = value.clone();
}
}
}
Expand All @@ -130,7 +130,7 @@ impl ConfiguratorApp {
if let Some(entry) = self.draft.drawing_quick_colors.get_mut(index) {
for (component, value) in values.iter().enumerate() {
if let Some(slot) = entry.color.rgb.get_mut(component) {
*slot = value.to_string();
*slot = value.clone();
}
}
}
Expand All @@ -139,7 +139,7 @@ impl ConfiguratorApp {
if let Some(item) = self.draft.boards.items.get_mut(index) {
for (component, value) in values.iter().enumerate() {
item.background_color
.set_component(component, value.to_string());
.set_component(component, value.clone());
}
}
}
Expand All @@ -148,7 +148,7 @@ impl ConfiguratorApp {
for (component, value) in values.iter().enumerate() {
item.default_pen_color
.color
.set_component(component, value.to_string());
.set_component(component, value.clone());
}
}
}
Expand Down Expand Up @@ -206,7 +206,7 @@ impl ConfiguratorApp {

fn apply_quad_rgb(&mut self, field: QuadField, values: &[String; 3], alpha: Option<f64>) {
for (component, value) in values.iter().enumerate() {
self.draft.set_quad(field, component, value.to_string());
self.draft.set_quad(field, component, value.clone());
}
if let Some(alpha) = alpha {
self.draft.set_quad(field, 3, format_float(alpha));
Expand Down
Loading
Loading