tag rework: tags now can either be standard or other, supports all types better
This commit is contained in:
parent
e4bcc52cbe
commit
f0930ab2eb
480
src/config.rs
480
src/config.rs
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
borrow::BorrowMut,
|
||||
cmp::Ordering,
|
||||
convert::Infallible,
|
||||
collections::HashMap,
|
||||
env,
|
||||
fmt::{Debug, Display},
|
||||
fs::{self},
|
||||
|
@ -13,21 +12,19 @@ use std::{
|
|||
};
|
||||
|
||||
use log::info;
|
||||
use serde::{
|
||||
de::{Expected, Unexpected},
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
environ::{self, ActiveKeybinds, EnvironError},
|
||||
hlwm::{
|
||||
self,
|
||||
attribute::Attribute,
|
||||
color::Color,
|
||||
command::{CommandError, HlwmCommand},
|
||||
hook::Hook,
|
||||
key::{Key, KeyParseError, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction},
|
||||
key::{Key, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction},
|
||||
rule::{Condition, Consequence, FloatPlacement, Rule, Unrule},
|
||||
setting::{FrameLayout, Setting, ShowFrameDecoration, SmartFrameSurroundings},
|
||||
theme::ThemeAttr,
|
||||
|
@ -54,10 +51,10 @@ pub enum ConfigError {
|
|||
CommandError(#[from] CommandError),
|
||||
#[error("non-utf8 string error: {0}")]
|
||||
Utf8StringError(#[from] FromUtf8Error),
|
||||
#[error("failed parsing keybind [{0}]: [{1}]")]
|
||||
KeyParseError(String, KeyParseError),
|
||||
#[error("failed parsing value from string: {0}")]
|
||||
StringParseError(#[from] StringParseError),
|
||||
#[error("getting from environment error: [{0}]")]
|
||||
EnvironError(#[from] EnvironError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
@ -342,23 +339,7 @@ impl Config {
|
|||
info!("loading tag command set");
|
||||
(&self.tags)
|
||||
.into_iter()
|
||||
.map(|tag| (tag, tag.key()))
|
||||
.filter(|(_, key)| key.is_some())
|
||||
.map(|(tag, key)| {
|
||||
let tag_name = tag.to_string();
|
||||
let key = key.unwrap();
|
||||
[
|
||||
HlwmCommand::AddTag(tag_name.clone()),
|
||||
HlwmCommand::Keybind(Keybind::new(
|
||||
[self.mod_key, key],
|
||||
HlwmCommand::UseTag(tag_name.clone()),
|
||||
)),
|
||||
HlwmCommand::Keybind(Keybind::new(
|
||||
[self.mod_key, Key::Shift, key],
|
||||
HlwmCommand::MoveTag(tag_name),
|
||||
)),
|
||||
]
|
||||
})
|
||||
.map(|tag| tag.to_command_set(self.mod_key))
|
||||
.flatten()
|
||||
.chain([HlwmCommand::And {
|
||||
separator: Separator::Comma,
|
||||
|
@ -378,7 +359,9 @@ impl Config {
|
|||
},
|
||||
HlwmCommand::MergeTag {
|
||||
tag: "default".to_string(),
|
||||
target: Some(hlwm::Tag::Name(self.tags.first().unwrap().to_string())),
|
||||
target: Some(hlwm::TagSelect::Name(
|
||||
self.tags.first().cloned().unwrap().name().to_string(),
|
||||
)),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -387,7 +370,7 @@ impl Config {
|
|||
}
|
||||
|
||||
/// Create a config gathered from the herbstluftwm configs,
|
||||
/// using default mouse binds/tags
|
||||
/// using default mouse binds/attributes set
|
||||
pub fn from_herbstluft() -> Self {
|
||||
fn setting<T, E: std::error::Error, F: FnOnce(&str) -> Result<T, E>>(
|
||||
name: &str,
|
||||
|
@ -404,6 +387,7 @@ impl Config {
|
|||
}
|
||||
let client = Client::new();
|
||||
let default = Config::default();
|
||||
let mod_key = setting(Self::MOD_KEY, Key::from_str, default.mod_key);
|
||||
Config {
|
||||
font: client
|
||||
.get_str_attr(Self::FONT.to_string())
|
||||
|
@ -417,7 +401,7 @@ impl Config {
|
|||
font_pango_bold: client
|
||||
.get_str_attr(Self::FONT_PANGO_BOLD.to_string())
|
||||
.unwrap_or_log(default.font_pango_bold),
|
||||
mod_key: setting(Self::MOD_KEY, Key::from_str, default.mod_key),
|
||||
mod_key,
|
||||
services: setting(
|
||||
Self::SERVICES,
|
||||
|v| serde_json::de::from_str(v),
|
||||
|
@ -444,26 +428,9 @@ impl Config {
|
|||
.collect()
|
||||
})(),
|
||||
},
|
||||
keybinds: Self::active_keybinds(true).unwrap_or_log(default.keybinds),
|
||||
tags: (|| -> Result<Vec<Tag>, _> {
|
||||
Result::<_, ConfigError>::Ok({
|
||||
let mut tags = client
|
||||
.tag_status()?
|
||||
.into_iter()
|
||||
.map(|tag| {
|
||||
let tag_result: Result<_, Infallible> = tag.name().parse();
|
||||
tag_result.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
tags.sort_by(|lhs: &Tag, rhs| match lhs.partial_cmp(rhs) {
|
||||
Some(ord) => ord,
|
||||
None => Ordering::Less,
|
||||
});
|
||||
|
||||
tags
|
||||
})
|
||||
})()
|
||||
.unwrap_or_log(default.tags),
|
||||
keybinds: environ::active_keybinds(ActiveKeybinds::OmitNamedTagBinds)
|
||||
.unwrap_or_log(default.keybinds),
|
||||
tags: Self::active_tags(mod_key).unwrap_or_log(default.tags),
|
||||
rules: (|| -> Result<Vec<Rule>, ConfigError> {
|
||||
Ok(
|
||||
String::from_utf8(client.execute(HlwmCommand::ListRules)?.stdout)?
|
||||
|
@ -488,36 +455,75 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
fn active_keybinds(omit_tag_binds: bool) -> Result<Vec<Keybind>, ConfigError> {
|
||||
String::from_utf8(Client::new().execute(HlwmCommand::ListKeybinds)?.stdout)?
|
||||
.split("\n")
|
||||
.filter(|i| {
|
||||
!omit_tag_binds
|
||||
|| match i.split("\t").skip(1).next() {
|
||||
Some(command) => {
|
||||
command
|
||||
!= HlwmCommand::UseIndex {
|
||||
index: Index::Absolute(0),
|
||||
skip_visible: false,
|
||||
}
|
||||
.to_string()
|
||||
&& command
|
||||
!= HlwmCommand::MoveIndex {
|
||||
index: Index::Absolute(0),
|
||||
skip_visible: false,
|
||||
}
|
||||
.to_string()
|
||||
&& command != HlwmCommand::UseTag(String::new()).to_string()
|
||||
&& command != HlwmCommand::MoveTag(String::new()).to_string()
|
||||
}
|
||||
None => false,
|
||||
fn active_tags(mod_key: Key) -> Result<Vec<Tag>, ConfigError> {
|
||||
let mut by_tag: HashMap<String, Vec<Keybind>> = HashMap::new();
|
||||
environ::active_keybinds(ActiveKeybinds::OnlyNamedTagBinds)?
|
||||
.into_iter()
|
||||
.filter(|bind| match bind.command.deref() {
|
||||
HlwmCommand::UseTag(_) | HlwmCommand::MoveTag(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
.for_each(|bind| match bind.command.deref() {
|
||||
HlwmCommand::UseTag(tag) | HlwmCommand::MoveTag(tag) => match by_tag.get_mut(tag) {
|
||||
Some(coll) => coll.push(bind.clone()),
|
||||
None => {
|
||||
by_tag.insert(tag.to_string(), vec![bind]);
|
||||
}
|
||||
},
|
||||
_ => unreachable!(),
|
||||
});
|
||||
Ok(by_tag
|
||||
.into_iter()
|
||||
.map(|(name, binds)| {
|
||||
let standard = binds.len() != 0
|
||||
&& (&binds).into_iter().all(|bind| match bind.command.deref() {
|
||||
HlwmCommand::UseTag(_) => {
|
||||
bind.keys.len() == 2
|
||||
&& bind.keys[0] == mod_key
|
||||
&& bind.keys[1].is_standard()
|
||||
}
|
||||
HlwmCommand::MoveTag(_) => {
|
||||
bind.keys.len() == 3
|
||||
&& bind.keys[0] == mod_key
|
||||
&& bind.keys[1] == Key::Shift
|
||||
&& bind.keys[2].is_standard()
|
||||
}
|
||||
// Filtered out above
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
if standard {
|
||||
let key = *binds.first().unwrap().keys.last().unwrap();
|
||||
let key_same = (&binds)
|
||||
.into_iter()
|
||||
.all(|bind| bind.keys.last().unwrap().eq(&key));
|
||||
if key_same {
|
||||
// Actually standard
|
||||
return Tag::standard(name, key);
|
||||
}
|
||||
}
|
||||
|
||||
let mut use_keys = None;
|
||||
let mut move_keys = None;
|
||||
for bind in binds {
|
||||
match bind.command.deref() {
|
||||
HlwmCommand::UseTag(_) => {
|
||||
if use_keys.is_none() {
|
||||
use_keys = Some(bind.keys);
|
||||
}
|
||||
}
|
||||
HlwmCommand::MoveTag(_) => {
|
||||
if move_keys.is_none() {
|
||||
move_keys = Some(bind.keys);
|
||||
}
|
||||
}
|
||||
// Filtered out above
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Tag::other(name, use_keys, move_keys)
|
||||
})
|
||||
.map(|row: &str| {
|
||||
Keybind::from_str(row)
|
||||
.map_err(|err| ConfigError::KeyParseError(row.to_string(), err))
|
||||
})
|
||||
.collect::<Result<Vec<_>, ConfigError>>()
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,198 +571,6 @@ impl Theme {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Error)]
|
||||
pub enum InclusiveError {
|
||||
#[error("out of range")]
|
||||
OutOfRange,
|
||||
}
|
||||
|
||||
impl Expected for InclusiveError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
InclusiveError::OutOfRange => write!(f, "out of range"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Inclusive<const MAX: u8>(u8);
|
||||
|
||||
impl Inclusive<10> {
|
||||
fn char(&self) -> char {
|
||||
match self.0 {
|
||||
1 => '1',
|
||||
2 => '2',
|
||||
3 => '3',
|
||||
4 => '4',
|
||||
5 => '5',
|
||||
6 => '6',
|
||||
7 => '7',
|
||||
8 => '8',
|
||||
9 => '9',
|
||||
0 => '0',
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn f_key(&self) -> Option<Key> {
|
||||
match self.0 {
|
||||
1 => Some(Key::F1),
|
||||
2 => Some(Key::F2),
|
||||
3 => Some(Key::F3),
|
||||
4 => Some(Key::F4),
|
||||
5 => Some(Key::F5),
|
||||
6 => Some(Key::F6),
|
||||
7 => Some(Key::F7),
|
||||
8 => Some(Key::F8),
|
||||
9 => Some(Key::F9),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX: u8> Display for Inclusive<MAX> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX: u8> Deref for Inclusive<MAX> {
|
||||
type Target = u8;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX: u8> TryFrom<u8> for Inclusive<MAX> {
|
||||
type Error = InclusiveError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
if value > MAX {
|
||||
return Err(InclusiveError::OutOfRange);
|
||||
}
|
||||
Ok(Inclusive(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX: u8> From<Inclusive<MAX>> for u8 {
|
||||
fn from(value: Inclusive<MAX>) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX: u8> Serialize for Inclusive<MAX> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
u8::serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const MAX: u8> Deserialize<'de> for Inclusive<MAX> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let val = u8::deserialize(deserializer)?;
|
||||
if val > MAX {
|
||||
return Err(serde::de::Error::invalid_value(
|
||||
Unexpected::Unsigned(val as u64),
|
||||
&InclusiveError::OutOfRange,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Inclusive(val))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX: u8> Inclusive<MAX> {}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Tag {
|
||||
FrontRow(Inclusive<10>),
|
||||
FunctionRow(Inclusive<10>),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn key(&self) -> Option<Key> {
|
||||
match self {
|
||||
Tag::FrontRow(idx) => Some(Key::Char(idx.char())),
|
||||
Tag::FunctionRow(idx) => Some(match idx.f_key() {
|
||||
Some(f) => f,
|
||||
None => return None,
|
||||
}),
|
||||
Tag::Other(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Tag {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::FrontRow(l0), Self::FrontRow(r0)) => l0 == r0,
|
||||
(Self::FunctionRow(l0), Self::FunctionRow(r0)) => l0 == r0,
|
||||
(Self::Other(l0), Self::Other(r0)) => l0 == r0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Tag {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Tag::FrontRow(lhs), Tag::FrontRow(rhs)) => Some(lhs.0.cmp(&rhs.0)),
|
||||
(Tag::FrontRow(_), Tag::FunctionRow(_)) => Some(Ordering::Greater),
|
||||
(Tag::FrontRow(_), Tag::Other(_)) => Some(Ordering::Greater),
|
||||
(Tag::FunctionRow(_), Tag::FrontRow(_)) => Some(Ordering::Less),
|
||||
(Tag::FunctionRow(lhs), Tag::FunctionRow(rhs)) => Some(lhs.0.cmp(&rhs.0)),
|
||||
(Tag::FunctionRow(_), Tag::Other(_)) => Some(Ordering::Greater),
|
||||
(Tag::Other(_), Tag::FrontRow(_)) | (Tag::Other(_), Tag::FunctionRow(_)) => {
|
||||
Some(Ordering::Less)
|
||||
}
|
||||
(Tag::Other(_), Tag::Other(_)) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Tag {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut chars = s.chars();
|
||||
let indicator = match chars.next() {
|
||||
Some(i) => i,
|
||||
None => return Ok(Self::Other(s.to_string())),
|
||||
};
|
||||
let number = u8::from_str(&chars.next().unwrap_or_default().to_string()).ok();
|
||||
if number.is_none() {
|
||||
return Ok(Self::Other(s.to_string()));
|
||||
}
|
||||
let number = match number.unwrap().try_into() {
|
||||
Ok(number) => number,
|
||||
Err(_) => return Ok(Self::Other(s.to_string())),
|
||||
};
|
||||
match indicator {
|
||||
'.' => Ok(Self::FrontRow(number)),
|
||||
'F' => Ok(Self::FunctionRow(number)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Tag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Tag::FrontRow(tag) => write!(f, ".{tag}"),
|
||||
Tag::FunctionRow(tag) => write!(f, "F{tag}"),
|
||||
Tag::Other(tag) => f.write_str(tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let resize_step = 0.1;
|
||||
|
@ -915,17 +729,19 @@ impl Default for Config {
|
|||
arguments: vec![String::from("panel")],
|
||||
},
|
||||
],
|
||||
tags: (1..=5)
|
||||
.into_iter()
|
||||
.map(|idx| Tag::FrontRow(idx.try_into().unwrap()))
|
||||
.chain(vec![
|
||||
Tag::FunctionRow(1.try_into().unwrap()),
|
||||
Tag::FunctionRow(2.try_into().unwrap()),
|
||||
Tag::FunctionRow(3.try_into().unwrap()),
|
||||
Tag::FunctionRow(4.try_into().unwrap()),
|
||||
Tag::FunctionRow(5.try_into().unwrap()),
|
||||
])
|
||||
.collect(),
|
||||
tags: [
|
||||
Tag::standard(".1", Key::Char('1')),
|
||||
Tag::standard(".2", Key::Char('2')),
|
||||
Tag::standard(".3", Key::Char('3')),
|
||||
Tag::standard(".4", Key::Char('4')),
|
||||
Tag::standard(".5", Key::Char('5')),
|
||||
Tag::standard("F1", Key::F1),
|
||||
Tag::standard("F2", Key::F2),
|
||||
Tag::standard("F3", Key::F3),
|
||||
Tag::standard("F4", Key::F4),
|
||||
Tag::standard("F5", Key::F5),
|
||||
]
|
||||
.into(),
|
||||
mousebinds: vec![
|
||||
Mousebind::new(
|
||||
mod_key,
|
||||
|
@ -1019,6 +835,110 @@ pub struct Service {
|
|||
pub arguments: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Tag {
|
||||
Standard {
|
||||
name: String,
|
||||
key: Key,
|
||||
},
|
||||
Other {
|
||||
name: String,
|
||||
use_keys: Option<Vec<Key>>,
|
||||
move_keys: Option<Vec<Key>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn standard<S: Into<String>>(name: S, key: Key) -> Self {
|
||||
Self::Standard {
|
||||
key,
|
||||
name: name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn other<S: Into<String>, K: IntoIterator<Item = Key>>(
|
||||
name: S,
|
||||
use_keys: Option<K>,
|
||||
move_keys: Option<K>,
|
||||
) -> Self {
|
||||
Self::Other {
|
||||
name: name.into(),
|
||||
use_keys: use_keys.map(|k| k.into_iter().collect()),
|
||||
move_keys: move_keys.map(|k| k.into_iter().collect()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Tag::Standard { name, key: _ } => name.as_str(),
|
||||
Tag::Other {
|
||||
name,
|
||||
use_keys: _,
|
||||
move_keys: _,
|
||||
} => name.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
fn use_keybind(&self, mod_key: Key) -> Option<Keybind> {
|
||||
match self {
|
||||
Tag::Standard { name, key } => Some(Keybind {
|
||||
keys: vec![mod_key, *key],
|
||||
command: Box::new(HlwmCommand::UseTag(name.clone())),
|
||||
}),
|
||||
Tag::Other {
|
||||
name,
|
||||
use_keys,
|
||||
move_keys: _,
|
||||
} => use_keys.clone().map(|use_keys| Keybind {
|
||||
keys: use_keys,
|
||||
command: Box::new(HlwmCommand::UseTag(name.clone())),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn move_keybind(&self, mod_key: Key) -> Option<Keybind> {
|
||||
match self {
|
||||
Tag::Standard { name, key } => Some(Keybind {
|
||||
keys: vec![mod_key, Key::Shift, *key],
|
||||
command: Box::new(HlwmCommand::MoveTag(name.clone())),
|
||||
}),
|
||||
Tag::Other {
|
||||
name,
|
||||
use_keys: _,
|
||||
move_keys,
|
||||
} => move_keys.clone().map(|move_keys| Keybind {
|
||||
keys: move_keys,
|
||||
command: Box::new(HlwmCommand::MoveTag(name.clone())),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_command_set(&self, mod_key: Key) -> Vec<HlwmCommand> {
|
||||
let mut commands = Vec::with_capacity(3);
|
||||
commands.push(HlwmCommand::AddTag(self.name().to_string()));
|
||||
if let Some(keybind) = self.use_keybind(mod_key) {
|
||||
commands.push(HlwmCommand::Keybind(keybind));
|
||||
}
|
||||
if let Some(keybind) = self.move_keybind(mod_key) {
|
||||
commands.push(HlwmCommand::Keybind(keybind));
|
||||
}
|
||||
|
||||
commands
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<(S, Key)> for Tag
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
fn from((name, key): (S, Key)) -> Self {
|
||||
Self::Standard {
|
||||
name: name.into(),
|
||||
key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
use std::{str::FromStr, string::FromUtf8Error};
|
||||
|
||||
use log::debug;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
hlwm::{
|
||||
command::{CommandError, HlwmCommand},
|
||||
key::{KeyParseError, Keybind},
|
||||
Client,
|
||||
},
|
||||
split,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum EnvironError {
|
||||
#[error("keybind parsing error: [{0}]")]
|
||||
KeyParseError(#[from] KeyParseError),
|
||||
#[error("command execution error: [{0}]")]
|
||||
CommandError(#[from] CommandError),
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for EnvironError {
|
||||
fn from(value: FromUtf8Error) -> Self {
|
||||
CommandError::UtfError(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ActiveKeybinds {
|
||||
All,
|
||||
OmitNamedTagBinds,
|
||||
OnlyNamedTagBinds,
|
||||
}
|
||||
|
||||
pub fn active_keybinds(ty: ActiveKeybinds) -> Result<Vec<Keybind>, EnvironError> {
|
||||
let (use_tag, move_tag) = (
|
||||
HlwmCommand::UseTag(String::new()).to_string(),
|
||||
HlwmCommand::MoveTag(String::new()).to_string(),
|
||||
);
|
||||
String::from_utf8(Client::new().execute(HlwmCommand::ListKeybinds)?.stdout)?
|
||||
.split("\n")
|
||||
.map(|l| l.trim())
|
||||
.filter(|l| !l.is_empty())
|
||||
.filter(|i| {
|
||||
let parts = split::tab_or_space(*i);
|
||||
if parts.len() < 2 {
|
||||
debug!(
|
||||
"active_keybinds: parts.len() for [{i}] was [{}]; expected >= 2",
|
||||
parts.len()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
let command = parts[1].as_str();
|
||||
match ty {
|
||||
ActiveKeybinds::All => true,
|
||||
ActiveKeybinds::OmitNamedTagBinds => command != use_tag && command != move_tag,
|
||||
ActiveKeybinds::OnlyNamedTagBinds => command == use_tag || command == move_tag,
|
||||
}
|
||||
})
|
||||
.map(|row: &str| {
|
||||
Ok(Keybind::from_str(row).map_err(|err| {
|
||||
debug!("row: [{row}], error: [{err}]");
|
||||
err
|
||||
})?)
|
||||
})
|
||||
.collect::<Result<Vec<_>, EnvironError>>()
|
||||
}
|
|
@ -4,7 +4,7 @@ use strum::IntoEnumIterator;
|
|||
use serde::{de::Expected, Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{gen_parse, hlwm::Client};
|
||||
use crate::{gen_parse, hlwm::Client, split};
|
||||
|
||||
use super::{
|
||||
attribute::{Attribute, AttributeError, AttributeOption},
|
||||
|
@ -14,9 +14,9 @@ use super::{
|
|||
pad::Pad,
|
||||
rule::{Rule, Unrule},
|
||||
setting::{FrameLayout, Setting, SettingName},
|
||||
split,
|
||||
window::Window,
|
||||
Align, Direction, Index, Monitor, Operator, Separator, StringParseError, Tag, ToCommandString,
|
||||
Align, Direction, Index, Monitor, Operator, Separator, StringParseError, TagSelect,
|
||||
ToCommandString,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)]
|
||||
|
@ -131,7 +131,7 @@ pub enum HlwmCommand {
|
|||
/// If `target` is None, the focused tag will be used
|
||||
MergeTag {
|
||||
tag: String,
|
||||
target: Option<Tag>,
|
||||
target: Option<TagSelect>,
|
||||
},
|
||||
Cycle,
|
||||
Focus(Direction),
|
||||
|
@ -848,7 +848,7 @@ mod test {
|
|||
rule::{Condition, Consequence, Rule, RuleOperator},
|
||||
setting::{Setting, SettingName},
|
||||
window::Window,
|
||||
Align, Direction, Tag, ToCommandString,
|
||||
Align, Direction, TagSelect, ToCommandString,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
|
@ -984,7 +984,7 @@ mod test {
|
|||
HlwmCommand::MergeTag { tag: _, target: _ } => (
|
||||
HlwmCommand::MergeTag {
|
||||
tag: "my_tag".into(),
|
||||
target: Some(Tag::Name("other_tag".into())),
|
||||
target: Some(TagSelect::Name("other_tag".into())),
|
||||
},
|
||||
"merge_tag\tmy_tag\tother_tag".into(),
|
||||
),
|
||||
|
|
|
@ -5,7 +5,7 @@ use strum::IntoEnumIterator;
|
|||
|
||||
use crate::gen_parse;
|
||||
|
||||
use super::{command::CommandParseError, window::Window, Monitor, Tag, ToCommandString};
|
||||
use super::{command::CommandParseError, window::Window, Monitor, TagSelect, ToCommandString};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
|
@ -38,7 +38,7 @@ pub enum Hook {
|
|||
},
|
||||
/// The flags (i.e. urgent or filled state) have been changed.
|
||||
TagFlags,
|
||||
TagAdded(Tag),
|
||||
TagAdded(TagSelect),
|
||||
TagRenamed {
|
||||
old: String,
|
||||
new: String,
|
||||
|
|
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||
use strum::IntoEnumIterator;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::hlwm::split;
|
||||
use crate::split;
|
||||
|
||||
use super::{
|
||||
command::{CommandParseError, HlwmCommand},
|
||||
|
@ -43,6 +43,39 @@ pub enum Key {
|
|||
Mouse(MouseButton),
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn is_standard(&self) -> bool {
|
||||
match self {
|
||||
Key::Char(_)
|
||||
| Key::F1
|
||||
| Key::F2
|
||||
| Key::F3
|
||||
| Key::F4
|
||||
| Key::F5
|
||||
| Key::F6
|
||||
| Key::F7
|
||||
| Key::F8
|
||||
| Key::F9
|
||||
| Key::F10
|
||||
| Key::F11
|
||||
| Key::F12 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MouseButton> for Key {
|
||||
fn from(value: MouseButton) -> Self {
|
||||
Key::Mouse(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Key {
|
||||
fn from(value: char) -> Self {
|
||||
Key::Char(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Key {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
|
|
@ -18,7 +18,7 @@ use self::{
|
|||
command::{CommandError, HlwmCommand},
|
||||
key::KeyParseError,
|
||||
setting::{Setting, SettingName},
|
||||
window::TagStatus,
|
||||
tag::TagStatus,
|
||||
};
|
||||
|
||||
pub mod attribute;
|
||||
|
@ -32,7 +32,7 @@ mod octal;
|
|||
pub mod pad;
|
||||
pub mod rule;
|
||||
pub mod setting;
|
||||
mod split;
|
||||
pub mod tag;
|
||||
pub mod theme;
|
||||
pub mod window;
|
||||
#[macro_use]
|
||||
|
@ -128,6 +128,7 @@ impl Client {
|
|||
Ok(lines)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn tag_status(&self) -> Result<Vec<TagStatus>, CommandError> {
|
||||
Ok(self
|
||||
.query(HlwmCommand::TagStatus { monitor: None })?
|
||||
|
@ -167,12 +168,12 @@ pub enum StringParseError {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Tag {
|
||||
pub enum TagSelect {
|
||||
Index(i32),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
impl FromStr for Tag {
|
||||
impl FromStr for TagSelect {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
|
@ -184,17 +185,17 @@ impl FromStr for Tag {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Tag {
|
||||
impl Default for TagSelect {
|
||||
fn default() -> Self {
|
||||
Self::Index(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Tag {
|
||||
impl std::fmt::Display for TagSelect {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Tag::Index(i) => write!(f, "{i}"),
|
||||
Tag::Name(name) => f.write_str(name),
|
||||
TagSelect::Index(i) => write!(f, "{i}"),
|
||||
TagSelect::Name(name) => f.write_str(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use crate::hlwm::split;
|
||||
use crate::split;
|
||||
|
||||
use super::StringParseError;
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@ use std::{
|
|||
use serde::{de::Expected, Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use super::{hlwmbool, hook::Hook, split, StringParseError, ToCommandString};
|
||||
use crate::split;
|
||||
|
||||
use super::{hlwmbool, hook::Hook, StringParseError, ToCommandString};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Rule {
|
||||
|
|
|
@ -3,15 +3,10 @@ use std::str::FromStr;
|
|||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{gen_parse, hlwm::command::CommandParseError};
|
||||
use crate::{gen_parse, hlwm::command::CommandParseError, split};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use super::{
|
||||
color::Color,
|
||||
hlwmbool::ToggleBool,
|
||||
split::{self},
|
||||
StringParseError, ToCommandString,
|
||||
};
|
||||
use super::{color::Color, hlwmbool::ToggleBool, StringParseError, ToCommandString};
|
||||
|
||||
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq, strum::EnumDiscriminants)]
|
||||
#[strum_discriminants(
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
use std::{
|
||||
fmt::{Display, Write},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use super::StringParseError;
|
||||
|
||||
pub struct TagStatus {
|
||||
name: String,
|
||||
state: TagState,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl TagStatus {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn state(&self) -> TagState {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TagStatus {
|
||||
type Err = StringParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut parts = s.chars();
|
||||
let state = parts
|
||||
.next()
|
||||
.ok_or(StringParseError::UnknownValue)?
|
||||
.try_into()?;
|
||||
let name = parts.collect();
|
||||
|
||||
Ok(Self { name, state })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, strum::EnumIter)]
|
||||
pub enum TagState {
|
||||
Empty,
|
||||
NotEmpty,
|
||||
/// The tag contains an urgent window
|
||||
Urgent,
|
||||
/// The tag is viewed on the specified MONITOR and it is focused
|
||||
SameMonitorFocused,
|
||||
/// The tag is viewed on the specified MONITOR, but this monitor is not focused
|
||||
SameMonitor,
|
||||
/// The tag is viewed on a different MONITOR and it is focused
|
||||
DifferentMonitorFocused,
|
||||
/// The tag is viewed on a different MONITOR, but this monitor is not focused
|
||||
DifferentMonitor,
|
||||
}
|
||||
|
||||
impl TryFrom<char> for TagState {
|
||||
type Error = StringParseError;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
Self::iter()
|
||||
.into_iter()
|
||||
.find(|i| char::from(i) == value)
|
||||
.ok_or(StringParseError::UnknownValue)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TagState {
|
||||
type Err = StringParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::iter()
|
||||
.into_iter()
|
||||
.find(|i| i.to_string() == s)
|
||||
.ok_or(StringParseError::UnknownValue)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TagState> for char {
|
||||
fn from(value: &TagState) -> Self {
|
||||
match value {
|
||||
TagState::SameMonitorFocused => '#',
|
||||
TagState::SameMonitor => '+',
|
||||
TagState::DifferentMonitorFocused => '%',
|
||||
TagState::DifferentMonitor => '-',
|
||||
TagState::Empty => '.',
|
||||
TagState::NotEmpty => ':',
|
||||
TagState::Urgent => '!',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TagState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_char(self.into())
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use serde::{de::Expected, Deserialize, Serialize};
|
|||
use strum::IntoEnumIterator;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::hlwm::split;
|
||||
use crate::split;
|
||||
|
||||
use super::{
|
||||
attribute::Attribute,
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use std::{
|
||||
fmt::{Display, Write},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
@ -60,95 +57,6 @@ impl std::fmt::Display for Window {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TagStatus {
|
||||
name: String,
|
||||
state: TagState,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl TagStatus {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn state(&self) -> TagState {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TagStatus {
|
||||
type Err = StringParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut parts = s.chars();
|
||||
let state = parts
|
||||
.next()
|
||||
.ok_or(StringParseError::UnknownValue)?
|
||||
.try_into()?;
|
||||
let name = parts.collect();
|
||||
|
||||
Ok(Self { name, state })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, strum::EnumIter)]
|
||||
pub enum TagState {
|
||||
Empty,
|
||||
NotEmpty,
|
||||
/// The tag contains an urgent window
|
||||
Urgent,
|
||||
/// The tag is viewed on the specified MONITOR and it is focused
|
||||
SameMonitorFocused,
|
||||
/// The tag is viewed on the specified MONITOR, but this monitor is not focused
|
||||
SameMonitor,
|
||||
/// The tag is viewed on a different MONITOR and it is focused
|
||||
DifferentMonitorFocused,
|
||||
/// The tag is viewed on a different MONITOR, but this monitor is not focused
|
||||
DifferentMonitor,
|
||||
}
|
||||
|
||||
impl TryFrom<char> for TagState {
|
||||
type Error = StringParseError;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
Self::iter()
|
||||
.into_iter()
|
||||
.find(|i| char::from(i) == value)
|
||||
.ok_or(StringParseError::UnknownValue)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TagState {
|
||||
type Err = StringParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::iter()
|
||||
.into_iter()
|
||||
.find(|i| i.to_string() == s)
|
||||
.ok_or(StringParseError::UnknownValue)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TagState> for char {
|
||||
fn from(value: &TagState) -> Self {
|
||||
match value {
|
||||
TagState::SameMonitorFocused => '#',
|
||||
TagState::SameMonitor => '+',
|
||||
TagState::DifferentMonitorFocused => '%',
|
||||
TagState::DifferentMonitor => '-',
|
||||
TagState::Empty => '.',
|
||||
TagState::NotEmpty => ':',
|
||||
TagState::Urgent => '!',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TagState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_char(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, strum::Display)]
|
||||
#[strum(serialize_all = "UPPERCASE")]
|
||||
pub enum WindowType {
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -4,13 +4,14 @@ use std::path::{Path, PathBuf};
|
|||
use clap::{Parser, Subcommand};
|
||||
use config::Config;
|
||||
use log::{error, info};
|
||||
use logerr::UnwrapLog;
|
||||
|
||||
pub mod cmd;
|
||||
mod config;
|
||||
pub mod environ;
|
||||
mod hlwm;
|
||||
pub mod logerr;
|
||||
mod panel;
|
||||
pub mod split;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(name = "hlctl")]
|
||||
|
@ -26,7 +27,8 @@ enum HlctlCommand {
|
|||
/// Save the currently loaded configuration to file
|
||||
#[command(long_about = r#"
|
||||
`save` Tries to find an existing config file. If not present, the default config is used.
|
||||
Whichever one is loaded, its tags are used. All other values are collected from the environment
|
||||
Whichever one is loaded, its mousebinds and attribute set are used.
|
||||
All other values are collected from the environment
|
||||
(or default values) and this new config is saved.
|
||||
|
||||
The configuration file located at $HOME/.config/herbstluftwm/hlctl.toml"#)]
|
||||
|
@ -59,8 +61,8 @@ fn load_config() -> Config {
|
|||
Err(err) => {
|
||||
error!("Could not load config. Error: {err}");
|
||||
error!("");
|
||||
error!("Hint: try calling `hlctl save` to save a default or collected config");
|
||||
std::process::exit(1);
|
||||
error!("Loading default config");
|
||||
Config::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +99,8 @@ fn init() {
|
|||
fn merged_config() -> Config {
|
||||
let default = load_config();
|
||||
let mut collected = Config::from_herbstluft();
|
||||
collected.tags = default.tags;
|
||||
collected.mousebinds = default.mousebinds;
|
||||
collected.attributes = default.attributes;
|
||||
|
||||
collected
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue