Working Randr plugin for Hyprland
This commit is contained in:
15
plugins/randr/Cargo.toml
Normal file
15
plugins/randr/Cargo.toml
Normal 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
149
plugins/randr/src/lib.rs
Normal 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);
|
11
plugins/randr/src/randr/dummy.rs
Normal file
11
plugins/randr/src/randr/dummy.rs
Normal 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) {}
|
||||
}
|
141
plugins/randr/src/randr/hyprland.rs
Normal file
141
plugins/randr/src/randr/hyprland.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
68
plugins/randr/src/randr/mod.rs
Normal file
68
plugins/randr/src/randr/mod.rs
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user