Working Randr plugin for Hyprland

This commit is contained in:
Kirottu
2023-04-24 08:20:30 +03:00
parent 0874e09c31
commit eafb676585
8 changed files with 546 additions and 0 deletions

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"

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

@@ -0,0 +1,149 @@
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) => {
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::RNone,
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::RNone,
// 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::RNone,
id: ROption::RSome((&Configure::Zero).into()),
});
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,68 @@
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!(),
}
}
}
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);
}