Merge branch 'randr'

This commit is contained in:
Kirottu
2023-04-25 21:45:23 +03:00
8 changed files with 571 additions and 0 deletions

158
Cargo.lock generated
View File

@@ -50,6 +50,15 @@ dependencies = [
"core_extensions",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -118,6 +127,17 @@ dependencies = [
"syn 1.0.107",
]
[[package]]
name = "async-trait"
version = "0.1.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
]
[[package]]
name = "atk"
version = "0.16.0"
@@ -286,6 +306,12 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58baae561b85ca19b3122a9ddd35c8ec40c3bcd14fe89921824eae73f7baffbf"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
@@ -381,6 +407,25 @@ dependencies = [
"syn 1.0.107",
]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version 0.4.0",
"syn 1.0.107",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "downcast-rs"
version = "1.2.0"
@@ -816,6 +861,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "0.2.8"
@@ -887,6 +938,39 @@ dependencies = [
"tokio-rustls",
]
[[package]]
name = "hyprland"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2166a21b9f0018d522bfd25debccdb16af8d2100aee63f842f9da37256129571"
dependencies = [
"async-trait",
"derive_more",
"doc-comment",
"futures",
"hex",
"hyprland-macros",
"lazy_static",
"num-traits",
"paste",
"regex",
"serde",
"serde_json",
"serde_repr",
"strum",
"tokio",
]
[[package]]
name = "hyprland-macros"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2de65550b4ec230167654f367b6ec02795acf2cfd9692f05bedb10ff09e46a6e"
dependencies = [
"quote",
"syn 2.0.13",
]
[[package]]
name = "iana-time-zone"
version = "0.1.53"
@@ -1386,6 +1470,16 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "randr"
version = "0.1.0"
dependencies = [
"abi_stable",
"anyrun-plugin",
"fuzzy-matcher",
"hyprland",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -1401,6 +1495,8 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
@@ -1557,6 +1653,12 @@ dependencies = [
"base64 0.21.0",
]
[[package]]
name = "rustversion"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "ryu"
version = "1.0.12"
@@ -1640,6 +1742,17 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -1662,6 +1775,15 @@ dependencies = [
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.7"
@@ -1705,6 +1827,28 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.107",
]
[[package]]
name = "sublime_fuzzy"
version = "0.7.0"
@@ -1847,11 +1991,25 @@ dependencies = [
"memchr",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.107",
]
[[package]]
name = "tokio-rustls"
version = "0.23.4"

View File

@@ -9,4 +9,5 @@ members = [
"plugins/shell",
"plugins/kidex",
"plugins/translate",
"plugins/randr",
]

View File

@@ -136,6 +136,9 @@ Anyrun requires plugins to function, as they provide the results for input. The
- Run shell commands
- [Kidex](plugins/kidex)
- File search provided by [Kidex](https://github.com/Kirottu/kidex)
- [Randr](plugins/randr)
- Rotate and resize; quickly change monitor configurations on the fly.
- TODO: Only supports Hyprland, needs support for other compositors.
## Configuration

15
plugins/randr/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "randr"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fuzzy-matcher = "0.3.7"
anyrun-plugin = { path = "../../anyrun-plugin" }
abi_stable = "0.11.1"
hyprland = "0.3"

163
plugins/randr/src/lib.rs Normal file
View File

@@ -0,0 +1,163 @@
use std::env;
use abi_stable::std_types::{ROption, RString, RVec};
use anyrun_plugin::{anyrun_interface::HandleResult, plugin, Match, PluginInfo};
use fuzzy_matcher::FuzzyMatcher;
use randr::{dummy::Dummy, hyprland::Hyprland, Configure, Monitor, Randr};
mod randr;
enum InnerState {
None,
Position(Monitor),
}
pub struct State {
randr: Box<dyn Randr + Send + Sync>,
inner: InnerState,
}
pub fn init(_config_dir: RString) -> State {
// Determine which Randr implementation should be used
let randr: Box<dyn Randr + Send + Sync> = if env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
Box::new(Hyprland::new())
} else {
Box::new(Dummy)
};
State {
randr,
inner: InnerState::None,
}
}
pub fn info() -> PluginInfo {
PluginInfo {
name: "Randr".into(),
icon: "video-display".into(),
}
}
pub fn handler(_match: Match, state: &mut State) -> HandleResult {
match &state.inner {
InnerState::None => {
state.inner = InnerState::Position(
state
.randr
.get_monitors()
.into_iter()
.find(|mon| mon.id == _match.id.unwrap())
.unwrap(),
);
HandleResult::Refresh(true)
}
InnerState::Position(mon) => {
if _match.id.unwrap() == u64::MAX {
state.inner = InnerState::None;
return HandleResult::Refresh(false);
}
let rel_id = (_match.id.unwrap() >> 32) as u32;
let action = _match.id.unwrap() as u32;
let rel_mon = state
.randr
.get_monitors()
.into_iter()
.find(|mon| mon.id == rel_id as u64)
.unwrap();
state
.randr
.configure(mon, Configure::from_id(action, &rel_mon));
HandleResult::Close
}
}
}
pub fn get_matches(input: RString, state: &mut State) -> RVec<Match> {
let matcher = fuzzy_matcher::skim::SkimMatcherV2::default().smart_case();
let mut vec = match &state.inner {
InnerState::None => state
.randr
.get_monitors()
.into_iter()
.map(|mon| Match {
title: format!("Change position of {}", mon.name).into(),
description: ROption::RSome(
format!("{}x{} at {}x{}", mon.width, mon.height, mon.x, mon.y).into(),
),
use_pango: false,
icon: ROption::RSome("object-flip-horizontal".into()),
id: ROption::RSome(mon.id),
})
.collect::<RVec<_>>(),
InnerState::Position(mon) => {
let mut vec = state
.randr
.get_monitors()
.into_iter()
.filter_map(|_mon| {
if _mon == *mon {
None
} else {
Some(
[
Configure::Mirror(&_mon),
Configure::LeftOf(&_mon),
Configure::RightOf(&_mon),
Configure::Below(&_mon),
Configure::Above(&_mon),
]
.iter()
.map(|configure| Match {
title: format!("{} {}", configure.to_string(), _mon.name).into(),
description: ROption::RNone,
use_pango: false,
icon: ROption::RSome(configure.icon().into()),
// Store 2 32 bit IDs in the single 64 bit integer, a bit of a hack
id: ROption::RSome(_mon.id << 32 | Into::<u64>::into(configure)),
})
.collect::<Vec<_>>(),
)
}
})
.flatten()
.collect::<RVec<_>>();
vec.push(Match {
title: "Reset position".into(),
description: ROption::RNone,
use_pango: false,
icon: ROption::RSome(Configure::Zero.icon().into()),
id: ROption::RSome((&Configure::Zero).into()),
});
vec.push(Match {
title: "Back".into(),
description: ROption::RSome("Return to the previous menu".into()),
use_pango: false,
icon: ROption::RSome("edit-undo".into()),
id: ROption::RSome(u64::MAX),
});
vec
}
}
.into_iter()
.filter_map(|_match| {
matcher
.fuzzy_match(&_match.title, &input)
.map(|score| (_match, score))
})
.collect::<Vec<_>>();
vec.sort_by(|a, b| b.1.cmp(&a.1));
vec.truncate(5);
vec.into_iter().map(|(_match, _)| _match).collect()
}
plugin!(init, info, get_matches, handler, State);

View File

@@ -0,0 +1,11 @@
use super::Randr;
pub struct Dummy;
impl Randr for Dummy {
fn get_monitors(&self) -> Vec<super::Monitor> {
Vec::new()
}
fn configure(&self, _mon: &super::Monitor, _config: super::Configure) {}
}

View File

@@ -0,0 +1,141 @@
use hyprland::{
data,
keyword::Keyword,
shared::{HyprData, HyprDataVec},
};
use super::{Configure, Monitor, Randr};
pub struct Hyprland {
monitors: Vec<data::Monitor>,
}
impl Hyprland {
pub fn new() -> Self {
Self {
monitors: data::Monitors::get().unwrap().to_vec(),
}
}
}
impl Randr for Hyprland {
fn get_monitors(&self) -> Vec<Monitor> {
self.monitors
.iter()
.cloned()
.map(|mon| Monitor {
x: mon.x,
y: mon.y,
width: mon.width as u32,
height: mon.height as u32,
refresh_rate: mon.refresh_rate,
scale: mon.scale,
name: mon.name,
id: mon.id as u64,
})
.collect()
}
fn configure(&self, mon: &Monitor, config: Configure) {
match config {
Configure::Mirror(rel) => Keyword::set(
"monitor",
format!("{},preferred,auto,1,mirror,{}", mon.name, rel.name),
)
.expect("Failed to configure monitor"),
Configure::LeftOf(rel) => {
let mut x = rel.x - mon.width as i32;
if x < 0 {
Keyword::set(
"monitor",
format!(
"{},{}x{}@{},{}x{},{}",
rel.name,
rel.width,
rel.height,
rel.refresh_rate,
rel.x - x,
rel.y,
rel.scale
),
)
.expect("Failed to configure monitor");
x = 0;
}
Keyword::set(
"monitor",
format!(
"{},{}x{}@{},{}x{},{}",
mon.name, mon.width, mon.height, mon.refresh_rate, x, rel.y, mon.scale
),
)
.expect("Failed to configure monitor");
}
Configure::RightOf(rel) => Keyword::set(
"monitor",
format!(
"{},{}x{}@{},{}x{},1",
mon.name,
mon.width,
mon.height,
mon.refresh_rate,
rel.x + rel.width as i32,
rel.y
),
)
.expect("Failed to configure monitor"),
Configure::Below(rel) => Keyword::set(
"monitor",
format!(
"{},{}x{}@{},{}x{},{}",
mon.name,
mon.width,
mon.height,
mon.refresh_rate,
rel.x,
rel.y + rel.height as i32,
mon.scale
),
)
.expect("Failed to configure monitor"),
Configure::Above(rel) => {
let mut y = rel.y - mon.height as i32;
if y < 0 {
Keyword::set(
"monitor",
format!(
"{},{}x{}@{},{}x{},{}",
rel.name,
rel.width,
rel.height,
rel.refresh_rate,
rel.x,
rel.y - y,
rel.scale
),
)
.expect("Failed to configure monitor");
y = 0;
}
Keyword::set(
"monitor",
format!(
"{},{}x{}@{},{}x{},{}",
mon.name, mon.width, mon.height, mon.refresh_rate, rel.x, y, mon.scale
),
)
.expect("Failed to configure monitor");
}
Configure::Zero => Keyword::set(
"monitor",
format!(
"{},{}x{}@{},0x0,{}",
mon.name, mon.width, mon.height, mon.refresh_rate, mon.scale
),
)
.expect("Failed to configure monitor"),
}
}
}

View File

@@ -0,0 +1,79 @@
pub mod dummy;
pub mod hyprland;
#[derive(PartialEq)]
pub struct Monitor {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub scale: f32,
pub refresh_rate: f32,
pub name: String,
pub id: u64,
}
pub enum Configure<'a> {
Mirror(&'a Monitor),
LeftOf(&'a Monitor),
RightOf(&'a Monitor),
Below(&'a Monitor),
Above(&'a Monitor),
Zero,
}
impl<'a> Configure<'a> {
pub fn from_id(id: u32, mon: &'a Monitor) -> Self {
match id {
0 => Configure::Mirror(mon),
1 => Configure::LeftOf(mon),
2 => Configure::RightOf(mon),
3 => Configure::Below(mon),
4 => Configure::Above(mon),
5 => Configure::Zero,
_ => unreachable!(),
}
}
pub fn icon(&self) -> &'static str {
match self {
Configure::Mirror(_) => "edit-copy",
Configure::LeftOf(_) => "go-previous",
Configure::RightOf(_) => "go-next",
Configure::Below(_) => "go-down",
Configure::Above(_) => "go-up",
Configure::Zero => "go-home",
}
}
}
impl<'a> ToString for Configure<'a> {
fn to_string(&self) -> String {
match self {
Configure::Mirror(_) => "Mirror".to_string(),
Configure::LeftOf(_) => "Left of".to_string(),
Configure::RightOf(_) => "Right of".to_string(),
Configure::Below(_) => "Below".to_string(),
Configure::Above(_) => "Above".to_string(),
Configure::Zero => "Zero".to_string(),
}
}
}
impl<'a> From<&Configure<'a>> for u64 {
fn from(configure: &Configure) -> u64 {
match configure {
Configure::Mirror(_) => 0,
Configure::LeftOf(_) => 1,
Configure::RightOf(_) => 2,
Configure::Below(_) => 3,
Configure::Above(_) => 4,
Configure::Zero => 5,
}
}
}
pub trait Randr {
fn get_monitors(&self) -> Vec<Monitor>;
fn configure(&self, mon: &Monitor, config: Configure);
}