Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add always_switch optional config for Acer monitors that don't correctly report they are on the usb-c input #141

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2020 Haim Gelfenbeyn
Copyright (c) 2024 Luke Nuttall

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ Configuration file settings:
```ini
usb_device = "1050:0407"
on_usb_connect = "Hdmi1"
on_usb_disconnect = "Hdmi2"
on_usb_disconnect = "Hdmi2"
always_switch = true
```

`usb_device` is which USB device to watch (vendor id / device id in hex), and `on_usb_connect` is which monitor input
Expand All @@ -42,6 +43,10 @@ The optional `on_usb_disconnect` settings allows to switch in the other directio
Note that the preferred way is to have this app installed on both computers. Switching "away" is problematic: if the
other computer has put the monitors to sleep, they will switch immediately back to the original input.

The optional `always_switch` setting can be used to attempt to switch even if the monitor reports that it currently
on the input we want to switch to. This is useful for Acer monitors like the XR343CRK, which pretend they are still
connected to `DisplayPort1` or `0xF`, when you switch to USB-C using `DisplayPort2` or `0x10`.

### Different inputs on different monitors
`display-switch` supports per-monitor configuration: add one or more monitor-specific configuration sections to set
monitor-specific inputs. For example:
Expand Down
51 changes: 51 additions & 0 deletions src/configuration.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright © 2020 Haim Gelfenbeyn
// Copyright © 2020 Luke Nuttall
// This code is licensed under MIT license (see LICENSE.txt for details)
//

Expand Down Expand Up @@ -35,6 +36,9 @@ struct PerMonitorConfiguration {
pub struct Configuration {
#[serde(deserialize_with = "Configuration::deserialize_usb_device")]
pub usb_device: String,
#[serde(default)]
#[serde(deserialize_with = "Configuration::deserialize_always_switch")]
pub always_switch: bool,
#[serde(flatten)]
pub default_input_sources: InputSources,
monitor1: Option<PerMonitorConfiguration>,
Expand Down Expand Up @@ -108,6 +112,14 @@ impl Configuration {
Ok(s.to_lowercase())
}

fn deserialize_always_switch<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
let b: bool = Deserialize::deserialize(deserializer)?;
Ok(b)
}

pub fn config_file_name() -> Result<std::path::PathBuf> {
let config_dir = if cfg!(target_os = "macos") {
dirs::preference_dir().ok_or_else(|| anyhow!("Config directory not found"))?
Expand Down Expand Up @@ -198,6 +210,45 @@ mod tests {
assert_eq!(config.usb_device, "dead:beef")
}

#[test]
fn test_always_switch_is_true_deserialization() {
let config = load_test_config(
r#"
usb_device = "dead:BEEF"
always_switch = true
on_usb_connect = "DisplayPort2"
"#,
)
.unwrap();
assert_eq!(config.always_switch, true)
}

#[test]
fn test_always_switch_is_false_deserialization() {
let config = load_test_config(
r#"
usb_device = "dead:BEEF"
always_switch = false
on_usb_connect = "DisplayPort2"
"#,
)
.unwrap();
assert_eq!(config.always_switch, false)
}

#[test]
fn test_always_switch_defaults_to_false_deserialization() {
let config = load_test_config(
r#"
usb_device = "dead:BEEF"
on_usb_connect = "DisplayPort2"
"#,
)
.unwrap();
assert_eq!(config.always_switch, false)
}


#[test]
fn test_symbolic_input_deserialization() {
let config = load_test_config(
Expand Down
11 changes: 8 additions & 3 deletions src/display_control.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright © 2020 Haim Gelfenbeyn
// Copyright © 2024 Luke Nuttall
// This code is licensed under MIT license (see LICENSE.txt for details)
//
use crate::configuration::{Configuration, SwitchDirection};
Expand Down Expand Up @@ -47,12 +48,16 @@ fn are_display_names_unique(displays: &[Display]) -> bool {
displays.iter().all(|display| hash.insert(display_name(display, None)))
}

fn try_switch_display(handle: &mut Handle, display_name: &str, input: InputSource) {
fn try_switch_display(handle: &mut Handle, display_name: &str, input: InputSource, always_switch: bool) {
match handle.get_vcp_feature(INPUT_SELECT) {
Ok(raw_source) => {
if raw_source.value() & 0xff == input.value() {
info!("Display {} is already set to {}", display_name, input);
return;
if always_switch {
info!("Let's try anyway!");
} else {
return;
}
}
}
Err(err) => {
Expand Down Expand Up @@ -121,7 +126,7 @@ pub fn switch(config: &Configuration, switch_direction: SwitchDirection) {
let input_sources = config.configuration_for_monitor(&display_name);
debug!("Input sources found for display {}: {:?}", display_name, input_sources);
if let Some(input) = input_sources.source(switch_direction) {
try_switch_display(&mut display.handle, &display_name, input);
try_switch_display(&mut display.handle, &display_name, input, config.always_switch);
} else {
info!(
"Display {} is not configured to switch on USB {}",
Expand Down