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::{
|
use std::{
|
||||||
borrow::BorrowMut,
|
borrow::BorrowMut,
|
||||||
cmp::Ordering,
|
collections::HashMap,
|
||||||
convert::Infallible,
|
|
||||||
env,
|
env,
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
fs::{self},
|
fs::{self},
|
||||||
|
@ -13,21 +12,19 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::{
|
use serde::{Deserialize, Serialize};
|
||||||
de::{Expected, Unexpected},
|
|
||||||
Deserialize, Serialize,
|
|
||||||
};
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
environ::{self, ActiveKeybinds, EnvironError},
|
||||||
hlwm::{
|
hlwm::{
|
||||||
self,
|
self,
|
||||||
attribute::Attribute,
|
attribute::Attribute,
|
||||||
color::Color,
|
color::Color,
|
||||||
command::{CommandError, HlwmCommand},
|
command::{CommandError, HlwmCommand},
|
||||||
hook::Hook,
|
hook::Hook,
|
||||||
key::{Key, KeyParseError, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction},
|
key::{Key, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction},
|
||||||
rule::{Condition, Consequence, FloatPlacement, Rule, Unrule},
|
rule::{Condition, Consequence, FloatPlacement, Rule, Unrule},
|
||||||
setting::{FrameLayout, Setting, ShowFrameDecoration, SmartFrameSurroundings},
|
setting::{FrameLayout, Setting, ShowFrameDecoration, SmartFrameSurroundings},
|
||||||
theme::ThemeAttr,
|
theme::ThemeAttr,
|
||||||
|
@ -54,10 +51,10 @@ pub enum ConfigError {
|
||||||
CommandError(#[from] CommandError),
|
CommandError(#[from] CommandError),
|
||||||
#[error("non-utf8 string error: {0}")]
|
#[error("non-utf8 string error: {0}")]
|
||||||
Utf8StringError(#[from] FromUtf8Error),
|
Utf8StringError(#[from] FromUtf8Error),
|
||||||
#[error("failed parsing keybind [{0}]: [{1}]")]
|
|
||||||
KeyParseError(String, KeyParseError),
|
|
||||||
#[error("failed parsing value from string: {0}")]
|
#[error("failed parsing value from string: {0}")]
|
||||||
StringParseError(#[from] StringParseError),
|
StringParseError(#[from] StringParseError),
|
||||||
|
#[error("getting from environment error: [{0}]")]
|
||||||
|
EnvironError(#[from] EnvironError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
@ -342,23 +339,7 @@ impl Config {
|
||||||
info!("loading tag command set");
|
info!("loading tag command set");
|
||||||
(&self.tags)
|
(&self.tags)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tag| (tag, tag.key()))
|
.map(|tag| tag.to_command_set(self.mod_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),
|
|
||||||
)),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.flatten()
|
.flatten()
|
||||||
.chain([HlwmCommand::And {
|
.chain([HlwmCommand::And {
|
||||||
separator: Separator::Comma,
|
separator: Separator::Comma,
|
||||||
|
@ -378,7 +359,9 @@ impl Config {
|
||||||
},
|
},
|
||||||
HlwmCommand::MergeTag {
|
HlwmCommand::MergeTag {
|
||||||
tag: "default".to_string(),
|
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,
|
/// Create a config gathered from the herbstluftwm configs,
|
||||||
/// using default mouse binds/tags
|
/// using default mouse binds/attributes set
|
||||||
pub fn from_herbstluft() -> Self {
|
pub fn from_herbstluft() -> Self {
|
||||||
fn setting<T, E: std::error::Error, F: FnOnce(&str) -> Result<T, E>>(
|
fn setting<T, E: std::error::Error, F: FnOnce(&str) -> Result<T, E>>(
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -404,6 +387,7 @@ impl Config {
|
||||||
}
|
}
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let default = Config::default();
|
let default = Config::default();
|
||||||
|
let mod_key = setting(Self::MOD_KEY, Key::from_str, default.mod_key);
|
||||||
Config {
|
Config {
|
||||||
font: client
|
font: client
|
||||||
.get_str_attr(Self::FONT.to_string())
|
.get_str_attr(Self::FONT.to_string())
|
||||||
|
@ -417,7 +401,7 @@ impl Config {
|
||||||
font_pango_bold: client
|
font_pango_bold: client
|
||||||
.get_str_attr(Self::FONT_PANGO_BOLD.to_string())
|
.get_str_attr(Self::FONT_PANGO_BOLD.to_string())
|
||||||
.unwrap_or_log(default.font_pango_bold),
|
.unwrap_or_log(default.font_pango_bold),
|
||||||
mod_key: setting(Self::MOD_KEY, Key::from_str, default.mod_key),
|
mod_key,
|
||||||
services: setting(
|
services: setting(
|
||||||
Self::SERVICES,
|
Self::SERVICES,
|
||||||
|v| serde_json::de::from_str(v),
|
|v| serde_json::de::from_str(v),
|
||||||
|
@ -444,26 +428,9 @@ impl Config {
|
||||||
.collect()
|
.collect()
|
||||||
})(),
|
})(),
|
||||||
},
|
},
|
||||||
keybinds: Self::active_keybinds(true).unwrap_or_log(default.keybinds),
|
keybinds: environ::active_keybinds(ActiveKeybinds::OmitNamedTagBinds)
|
||||||
tags: (|| -> Result<Vec<Tag>, _> {
|
.unwrap_or_log(default.keybinds),
|
||||||
Result::<_, ConfigError>::Ok({
|
tags: Self::active_tags(mod_key).unwrap_or_log(default.tags),
|
||||||
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),
|
|
||||||
rules: (|| -> Result<Vec<Rule>, ConfigError> {
|
rules: (|| -> Result<Vec<Rule>, ConfigError> {
|
||||||
Ok(
|
Ok(
|
||||||
String::from_utf8(client.execute(HlwmCommand::ListRules)?.stdout)?
|
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> {
|
fn active_tags(mod_key: Key) -> Result<Vec<Tag>, ConfigError> {
|
||||||
String::from_utf8(Client::new().execute(HlwmCommand::ListKeybinds)?.stdout)?
|
let mut by_tag: HashMap<String, Vec<Keybind>> = HashMap::new();
|
||||||
.split("\n")
|
environ::active_keybinds(ActiveKeybinds::OnlyNamedTagBinds)?
|
||||||
.filter(|i| {
|
.into_iter()
|
||||||
!omit_tag_binds
|
.filter(|bind| match bind.command.deref() {
|
||||||
|| match i.split("\t").skip(1).next() {
|
HlwmCommand::UseTag(_) | HlwmCommand::MoveTag(_) => true,
|
||||||
Some(command) => {
|
_ => false,
|
||||||
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,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.map(|row: &str| {
|
.for_each(|bind| match bind.command.deref() {
|
||||||
Keybind::from_str(row)
|
HlwmCommand::UseTag(tag) | HlwmCommand::MoveTag(tag) => match by_tag.get_mut(tag) {
|
||||||
.map_err(|err| ConfigError::KeyParseError(row.to_string(), err))
|
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)
|
||||||
})
|
})
|
||||||
.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 {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let resize_step = 0.1;
|
let resize_step = 0.1;
|
||||||
|
@ -915,17 +729,19 @@ impl Default for Config {
|
||||||
arguments: vec![String::from("panel")],
|
arguments: vec![String::from("panel")],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tags: (1..=5)
|
tags: [
|
||||||
.into_iter()
|
Tag::standard(".1", Key::Char('1')),
|
||||||
.map(|idx| Tag::FrontRow(idx.try_into().unwrap()))
|
Tag::standard(".2", Key::Char('2')),
|
||||||
.chain(vec![
|
Tag::standard(".3", Key::Char('3')),
|
||||||
Tag::FunctionRow(1.try_into().unwrap()),
|
Tag::standard(".4", Key::Char('4')),
|
||||||
Tag::FunctionRow(2.try_into().unwrap()),
|
Tag::standard(".5", Key::Char('5')),
|
||||||
Tag::FunctionRow(3.try_into().unwrap()),
|
Tag::standard("F1", Key::F1),
|
||||||
Tag::FunctionRow(4.try_into().unwrap()),
|
Tag::standard("F2", Key::F2),
|
||||||
Tag::FunctionRow(5.try_into().unwrap()),
|
Tag::standard("F3", Key::F3),
|
||||||
])
|
Tag::standard("F4", Key::F4),
|
||||||
.collect(),
|
Tag::standard("F5", Key::F5),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
mousebinds: vec![
|
mousebinds: vec![
|
||||||
Mousebind::new(
|
Mousebind::new(
|
||||||
mod_key,
|
mod_key,
|
||||||
|
@ -1019,6 +835,110 @@ pub struct Service {
|
||||||
pub arguments: Vec<String>,
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use pretty_assertions::assert_eq;
|
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 serde::{de::Expected, Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{gen_parse, hlwm::Client};
|
use crate::{gen_parse, hlwm::Client, split};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
attribute::{Attribute, AttributeError, AttributeOption},
|
attribute::{Attribute, AttributeError, AttributeOption},
|
||||||
|
@ -14,9 +14,9 @@ use super::{
|
||||||
pad::Pad,
|
pad::Pad,
|
||||||
rule::{Rule, Unrule},
|
rule::{Rule, Unrule},
|
||||||
setting::{FrameLayout, Setting, SettingName},
|
setting::{FrameLayout, Setting, SettingName},
|
||||||
split,
|
|
||||||
window::Window,
|
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)]
|
#[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
|
/// If `target` is None, the focused tag will be used
|
||||||
MergeTag {
|
MergeTag {
|
||||||
tag: String,
|
tag: String,
|
||||||
target: Option<Tag>,
|
target: Option<TagSelect>,
|
||||||
},
|
},
|
||||||
Cycle,
|
Cycle,
|
||||||
Focus(Direction),
|
Focus(Direction),
|
||||||
|
@ -848,7 +848,7 @@ mod test {
|
||||||
rule::{Condition, Consequence, Rule, RuleOperator},
|
rule::{Condition, Consequence, Rule, RuleOperator},
|
||||||
setting::{Setting, SettingName},
|
setting::{Setting, SettingName},
|
||||||
window::Window,
|
window::Window,
|
||||||
Align, Direction, Tag, ToCommandString,
|
Align, Direction, TagSelect, ToCommandString,
|
||||||
};
|
};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
@ -984,7 +984,7 @@ mod test {
|
||||||
HlwmCommand::MergeTag { tag: _, target: _ } => (
|
HlwmCommand::MergeTag { tag: _, target: _ } => (
|
||||||
HlwmCommand::MergeTag {
|
HlwmCommand::MergeTag {
|
||||||
tag: "my_tag".into(),
|
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(),
|
"merge_tag\tmy_tag\tother_tag".into(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,7 +5,7 @@ use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use crate::gen_parse;
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
|
@ -38,7 +38,7 @@ pub enum Hook {
|
||||||
},
|
},
|
||||||
/// The flags (i.e. urgent or filled state) have been changed.
|
/// The flags (i.e. urgent or filled state) have been changed.
|
||||||
TagFlags,
|
TagFlags,
|
||||||
TagAdded(Tag),
|
TagAdded(TagSelect),
|
||||||
TagRenamed {
|
TagRenamed {
|
||||||
old: String,
|
old: String,
|
||||||
new: String,
|
new: String,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::hlwm::split;
|
use crate::split;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
command::{CommandParseError, HlwmCommand},
|
command::{CommandParseError, HlwmCommand},
|
||||||
|
@ -43,6 +43,39 @@ pub enum Key {
|
||||||
Mouse(MouseButton),
|
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 {
|
impl Serialize for Key {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
|
|
@ -18,7 +18,7 @@ use self::{
|
||||||
command::{CommandError, HlwmCommand},
|
command::{CommandError, HlwmCommand},
|
||||||
key::KeyParseError,
|
key::KeyParseError,
|
||||||
setting::{Setting, SettingName},
|
setting::{Setting, SettingName},
|
||||||
window::TagStatus,
|
tag::TagStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod attribute;
|
pub mod attribute;
|
||||||
|
@ -32,7 +32,7 @@ mod octal;
|
||||||
pub mod pad;
|
pub mod pad;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
pub mod setting;
|
pub mod setting;
|
||||||
mod split;
|
pub mod tag;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -128,6 +128,7 @@ impl Client {
|
||||||
Ok(lines)
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
pub fn tag_status(&self) -> Result<Vec<TagStatus>, CommandError> {
|
pub fn tag_status(&self) -> Result<Vec<TagStatus>, CommandError> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.query(HlwmCommand::TagStatus { monitor: None })?
|
.query(HlwmCommand::TagStatus { monitor: None })?
|
||||||
|
@ -167,12 +168,12 @@ pub enum StringParseError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum Tag {
|
pub enum TagSelect {
|
||||||
Index(i32),
|
Index(i32),
|
||||||
Name(String),
|
Name(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Tag {
|
impl FromStr for TagSelect {
|
||||||
type Err = Infallible;
|
type Err = Infallible;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
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 {
|
fn default() -> Self {
|
||||||
Self::Index(Default::default())
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Tag::Index(i) => write!(f, "{i}"),
|
TagSelect::Index(i) => write!(f, "{i}"),
|
||||||
Tag::Name(name) => f.write_str(name),
|
TagSelect::Name(name) => f.write_str(name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use crate::hlwm::split;
|
use crate::split;
|
||||||
|
|
||||||
use super::StringParseError;
|
use super::StringParseError;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ use std::{
|
||||||
use serde::{de::Expected, Deserialize, Serialize};
|
use serde::{de::Expected, Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Rule {
|
pub struct Rule {
|
||||||
|
|
|
@ -3,15 +3,10 @@ use std::str::FromStr;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{gen_parse, hlwm::command::CommandParseError};
|
use crate::{gen_parse, hlwm::command::CommandParseError, split};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use super::{
|
use super::{color::Color, hlwmbool::ToggleBool, StringParseError, ToCommandString};
|
||||||
color::Color,
|
|
||||||
hlwmbool::ToggleBool,
|
|
||||||
split::{self},
|
|
||||||
StringParseError, ToCommandString,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq, strum::EnumDiscriminants)]
|
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq, strum::EnumDiscriminants)]
|
||||||
#[strum_discriminants(
|
#[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 strum::IntoEnumIterator;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::hlwm::split;
|
use crate::split;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
attribute::Attribute,
|
attribute::Attribute,
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use std::{
|
use std::str::FromStr;
|
||||||
fmt::{Display, Write},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
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)]
|
#[derive(Clone, Copy, strum::Display)]
|
||||||
#[strum(serialize_all = "UPPERCASE")]
|
#[strum(serialize_all = "UPPERCASE")]
|
||||||
pub enum WindowType {
|
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 clap::{Parser, Subcommand};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use logerr::UnwrapLog;
|
|
||||||
|
|
||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
mod config;
|
mod config;
|
||||||
|
pub mod environ;
|
||||||
mod hlwm;
|
mod hlwm;
|
||||||
pub mod logerr;
|
pub mod logerr;
|
||||||
mod panel;
|
mod panel;
|
||||||
|
pub mod split;
|
||||||
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[command(name = "hlctl")]
|
#[command(name = "hlctl")]
|
||||||
|
@ -26,7 +27,8 @@ enum HlctlCommand {
|
||||||
/// Save the currently loaded configuration to file
|
/// Save the currently loaded configuration to file
|
||||||
#[command(long_about = r#"
|
#[command(long_about = r#"
|
||||||
`save` Tries to find an existing config file. If not present, the default config is used.
|
`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.
|
(or default values) and this new config is saved.
|
||||||
|
|
||||||
The configuration file located at $HOME/.config/herbstluftwm/hlctl.toml"#)]
|
The configuration file located at $HOME/.config/herbstluftwm/hlctl.toml"#)]
|
||||||
|
@ -59,8 +61,8 @@ fn load_config() -> Config {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Could not load config. Error: {err}");
|
error!("Could not load config. Error: {err}");
|
||||||
error!("");
|
error!("");
|
||||||
error!("Hint: try calling `hlctl save` to save a default or collected config");
|
error!("Loading default config");
|
||||||
std::process::exit(1);
|
Config::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +99,8 @@ fn init() {
|
||||||
fn merged_config() -> Config {
|
fn merged_config() -> Config {
|
||||||
let default = load_config();
|
let default = load_config();
|
||||||
let mut collected = Config::from_herbstluft();
|
let mut collected = Config::from_herbstluft();
|
||||||
collected.tags = default.tags;
|
collected.mousebinds = default.mousebinds;
|
||||||
|
collected.attributes = default.attributes;
|
||||||
|
|
||||||
collected
|
collected
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue