mirror of
https://github.com/ChrisTitusTech/linutil.git
synced 2024-11-05 13:15:21 +00:00
feat: Add logic for runtime tab creation based on JSON data
This commit is contained in:
parent
548cfa8e36
commit
c18c76c694
40
Cargo.lock
generated
40
Cargo.lock
generated
|
@ -420,6 +420,12 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
|
@ -644,6 +650,38 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.205"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.205"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.122"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serial"
|
name = "serial"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -844,6 +882,8 @@ dependencies = [
|
||||||
"oneshot",
|
"oneshot",
|
||||||
"portable-pty",
|
"portable-pty",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tempdir",
|
"tempdir",
|
||||||
"tui-term",
|
"tui-term",
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,6 +14,8 @@ ratatui = "0.27.0"
|
||||||
tui-term = "0.1.12"
|
tui-term = "0.1.12"
|
||||||
include_dir = "0.7.4"
|
include_dir = "0.7.4"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
|
serde_json = "1.0.122"
|
||||||
|
serde = { version = "1.0.205", features = ["derive"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "linutil"
|
name = "linutil"
|
||||||
|
|
|
@ -13,7 +13,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread::JoinHandle,
|
thread::JoinHandle,
|
||||||
};
|
};
|
||||||
|
@ -22,10 +22,10 @@ use tui_term::{
|
||||||
widget::PseudoTerminal,
|
widget::PseudoTerminal,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Raw(&'static str),
|
Raw(String),
|
||||||
LocalFile(&'static str),
|
LocalFile(PathBuf),
|
||||||
None, // Directory
|
None, // Directory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
src/state.rs
30
src/state.rs
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
float::{Float, FloatContent},
|
float::{Float, FloatContent},
|
||||||
floating_text::FloatingText,
|
floating_text::FloatingText,
|
||||||
running_command::{Command, RunningCommand},
|
running_command::{Command, RunningCommand},
|
||||||
tabs::{ListNode, TABS},
|
tabs::{ListNode, Tab},
|
||||||
theme::Theme,
|
theme::Theme,
|
||||||
};
|
};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
||||||
|
@ -23,6 +23,8 @@ pub struct AppState {
|
||||||
temp_path: PathBuf,
|
temp_path: PathBuf,
|
||||||
/// Currently focused area
|
/// Currently focused area
|
||||||
focus: Focus,
|
focus: Focus,
|
||||||
|
/// List of tabs
|
||||||
|
tabs: Vec<Tab>,
|
||||||
/// Current tab
|
/// Current tab
|
||||||
current_tab: ListState,
|
current_tab: ListState,
|
||||||
/// Current search query
|
/// Current search query
|
||||||
|
@ -52,11 +54,13 @@ struct ListEntry {
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(theme: Theme, temp_path: PathBuf) -> Self {
|
pub fn new(theme: Theme, temp_path: PathBuf) -> Self {
|
||||||
let root_id = TABS[0].tree.root().id();
|
let tabs = crate::tabs::get_tabs(&temp_path, true);
|
||||||
|
let root_id = tabs[0].tree.root().id();
|
||||||
let mut state = Self {
|
let mut state = Self {
|
||||||
theme,
|
theme,
|
||||||
temp_path,
|
temp_path,
|
||||||
focus: Focus::List,
|
focus: Focus::List,
|
||||||
|
tabs,
|
||||||
current_tab: ListState::default().with_selected(Some(0)),
|
current_tab: ListState::default().with_selected(Some(0)),
|
||||||
search_query: String::new(),
|
search_query: String::new(),
|
||||||
items: vec![],
|
items: vec![],
|
||||||
|
@ -67,7 +71,8 @@ impl AppState {
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
pub fn draw(&mut self, frame: &mut Frame) {
|
pub fn draw(&mut self, frame: &mut Frame) {
|
||||||
let longest_tab_display_len = TABS
|
let longest_tab_display_len = self
|
||||||
|
.tabs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tab| tab.name.len() + self.theme.tab_icon.len())
|
.map(|tab| tab.name.len() + self.theme.tab_icon.len())
|
||||||
.max()
|
.max()
|
||||||
|
@ -85,7 +90,11 @@ impl AppState {
|
||||||
.constraints([Constraint::Length(3), Constraint::Min(1)])
|
.constraints([Constraint::Length(3), Constraint::Min(1)])
|
||||||
.split(horizontal[0]);
|
.split(horizontal[0]);
|
||||||
|
|
||||||
let tabs = TABS.iter().map(|tab| tab.name).collect::<Vec<_>>();
|
let tabs = self
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.map(|tab| tab.name.as_str())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let tab_hl_style = if let Focus::TabList = self.focus {
|
let tab_hl_style = if let Focus::TabList = self.focus {
|
||||||
Style::default().reversed().fg(self.theme.tab_color)
|
Style::default().reversed().fg(self.theme.tab_color)
|
||||||
|
@ -186,7 +195,7 @@ impl AppState {
|
||||||
self.focus = Focus::List
|
self.focus = Focus::List
|
||||||
}
|
}
|
||||||
KeyCode::Char('j') | KeyCode::Down
|
KeyCode::Char('j') | KeyCode::Down
|
||||||
if self.current_tab.selected().unwrap() + 1 < TABS.len() =>
|
if self.current_tab.selected().unwrap() + 1 < self.tabs.len() =>
|
||||||
{
|
{
|
||||||
self.current_tab.select_next();
|
self.current_tab.select_next();
|
||||||
self.refresh_tab();
|
self.refresh_tab();
|
||||||
|
@ -220,7 +229,7 @@ impl AppState {
|
||||||
}
|
}
|
||||||
pub fn update_items(&mut self) {
|
pub fn update_items(&mut self) {
|
||||||
if self.search_query.is_empty() {
|
if self.search_query.is_empty() {
|
||||||
let curr = TABS[self.current_tab.selected().unwrap()]
|
let curr = self.tabs[self.current_tab.selected().unwrap()]
|
||||||
.tree
|
.tree
|
||||||
.get(*self.visit_stack.last().unwrap())
|
.get(*self.visit_stack.last().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -237,7 +246,7 @@ impl AppState {
|
||||||
self.items.clear();
|
self.items.clear();
|
||||||
|
|
||||||
let query_lower = self.search_query.to_lowercase();
|
let query_lower = self.search_query.to_lowercase();
|
||||||
for tab in TABS.iter() {
|
for tab in self.tabs.iter() {
|
||||||
let mut stack = vec![tab.tree.root().id()];
|
let mut stack = vec![tab.tree.root().id()];
|
||||||
while let Some(node_id) = stack.pop() {
|
while let Some(node_id) = stack.pop() {
|
||||||
let node = tab.tree.get(node_id).unwrap();
|
let node = tab.tree.get(node_id).unwrap();
|
||||||
|
@ -255,7 +264,7 @@ impl AppState {
|
||||||
stack.extend(node.children().map(|child| child.id()));
|
stack.extend(node.children().map(|child| child.id()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.items.sort_by(|a, b| a.node.name.cmp(b.node.name));
|
self.items.sort_by(|a, b| a.node.name.cmp(&b.node.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Checks ehther the current tree node is the root node (can we go up the tree or no)
|
/// Checks ehther the current tree node is the root node (can we go up the tree or no)
|
||||||
|
@ -319,7 +328,10 @@ impl AppState {
|
||||||
self.update_items();
|
self.update_items();
|
||||||
}
|
}
|
||||||
fn refresh_tab(&mut self) {
|
fn refresh_tab(&mut self) {
|
||||||
self.visit_stack = vec![TABS[self.current_tab.selected().unwrap()].tree.root().id()];
|
self.visit_stack = vec![self.tabs[self.current_tab.selected().unwrap()]
|
||||||
|
.tree
|
||||||
|
.root()
|
||||||
|
.id()];
|
||||||
self.selection.select(Some(0));
|
self.selection.select(Some(0));
|
||||||
self.update_items();
|
self.update_items();
|
||||||
}
|
}
|
||||||
|
|
319
src/tabs.rs
319
src/tabs.rs
|
@ -1,162 +1,179 @@
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
use ego_tree::{tree, Tree};
|
|
||||||
|
|
||||||
use crate::running_command::Command;
|
use crate::running_command::Command;
|
||||||
|
use ego_tree::{tree, NodeId, Tree};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ScriptInfo {
|
||||||
|
ui_path: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
description: String,
|
||||||
|
#[serde(default)]
|
||||||
|
preconditions: Option<Vec<Precondition>>,
|
||||||
|
#[serde(default)]
|
||||||
|
command: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScriptInfo {
|
||||||
|
fn is_supported(&self) -> bool {
|
||||||
|
self.preconditions.as_deref().map_or(true, |preconditions| {
|
||||||
|
preconditions.iter().all(
|
||||||
|
|Precondition {
|
||||||
|
matches,
|
||||||
|
data,
|
||||||
|
values,
|
||||||
|
}| {
|
||||||
|
match data {
|
||||||
|
SystemDataType::Environment(var_name) => std::env::var(var_name)
|
||||||
|
.map_or(false, |var| values.contains(&var) == *matches),
|
||||||
|
SystemDataType::File(path) => {
|
||||||
|
std::fs::read_to_string(path).map_or(false, |data| {
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.any(|matching_value| data.contains(matching_value))
|
||||||
|
== *matches
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Precondition {
|
||||||
|
// If true, the data must be contained within the list of values.
|
||||||
|
// Otherwise, the data must not be contained within the list of values
|
||||||
|
matches: bool,
|
||||||
|
data: SystemDataType,
|
||||||
|
values: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
enum SystemDataType {
|
||||||
|
#[serde(rename = "environment")]
|
||||||
|
Environment(String),
|
||||||
|
#[serde(rename = "file")]
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Hash, Eq, PartialEq)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub name: &'static str,
|
pub name: String,
|
||||||
pub tree: Tree<ListNode>,
|
pub tree: Tree<ListNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
pub struct ListNode {
|
pub struct ListNode {
|
||||||
pub name: &'static str,
|
pub name: String,
|
||||||
pub command: Command,
|
pub command: Command,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static TABS: LazyLock<Vec<Tab>> = LazyLock::new(|| {
|
pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec<Tab> {
|
||||||
vec![
|
let scripts = get_script_list(command_dir);
|
||||||
Tab {
|
|
||||||
name: "System Setup",
|
let mut paths: HashMap<Vec<String>, (String, NodeId)> = HashMap::new();
|
||||||
tree: tree!(ListNode {
|
let mut tabs: Vec<Tab> = Vec::new();
|
||||||
name: "root",
|
|
||||||
command: Command::None,
|
for (json_file, script) in scripts {
|
||||||
} => {
|
let json_text = std::fs::read_to_string(&json_file).unwrap();
|
||||||
ListNode {
|
let script_info: ScriptInfo =
|
||||||
name: "Full System Update",
|
serde_json::from_str(&json_text).expect("Unexpected JSON input");
|
||||||
command: Command::LocalFile("system-update.sh"),
|
if validate && !script_info.is_supported() {
|
||||||
},
|
continue;
|
||||||
ListNode {
|
}
|
||||||
name: "Build Prerequisites",
|
if script_info.ui_path.len() < 2 {
|
||||||
command: Command::LocalFile("system-setup/1-compile-setup.sh"),
|
panic!(
|
||||||
},
|
"UI path must contain a tab. Ensure that {} has correct data",
|
||||||
ListNode {
|
json_file.display()
|
||||||
name: "Gaming Dependencies",
|
);
|
||||||
command: Command::LocalFile("system-setup/2-gaming-setup.sh"),
|
}
|
||||||
},
|
let command = match script_info.command {
|
||||||
ListNode {
|
Some(command) => Command::Raw(command),
|
||||||
name: "Global Theme",
|
None if script.exists() => Command::LocalFile(script),
|
||||||
command: Command::LocalFile("system-setup/3-global-theme.sh"),
|
_ => panic!(
|
||||||
},
|
"Command not specified & matching script does not exist for JSON {}",
|
||||||
ListNode {
|
json_file.display()
|
||||||
name: "Remove Snaps",
|
),
|
||||||
command: Command::LocalFile("system-setup/4-remove-snaps.sh"),
|
};
|
||||||
|
for path_index in 1..script_info.ui_path.len() {
|
||||||
|
let path = script_info.ui_path[..path_index].to_vec();
|
||||||
|
if !paths.contains_key(&path) {
|
||||||
|
let tab_name = script_info.ui_path[0].clone();
|
||||||
|
if path_index == 1 {
|
||||||
|
let tab = Tab {
|
||||||
|
name: tab_name.clone(),
|
||||||
|
tree: Tree::new(ListNode {
|
||||||
|
name: "root".to_string(),
|
||||||
|
command: Command::None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let root_id = tab.tree.root().id();
|
||||||
|
tabs.push(tab);
|
||||||
|
paths.insert(path, (tab_name, root_id));
|
||||||
|
} else {
|
||||||
|
let parent_path = &script_info.ui_path[..path_index - 1];
|
||||||
|
let (tab, parent_id) = paths.get(parent_path).unwrap();
|
||||||
|
let tab = tabs
|
||||||
|
.iter_mut()
|
||||||
|
.find(|Tab { name, .. }| name == tab)
|
||||||
|
.unwrap();
|
||||||
|
let mut parent = tab.tree.get_mut(*parent_id).unwrap();
|
||||||
|
let new_node = ListNode {
|
||||||
|
name: script_info.ui_path[path_index - 1].clone(),
|
||||||
|
command: Command::None,
|
||||||
|
};
|
||||||
|
let new_id = parent.append(new_node).id();
|
||||||
|
paths.insert(path, (tab_name, new_id));
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
},
|
}
|
||||||
Tab {
|
let (tab, parent_id) = paths
|
||||||
name: "Applications Setup",
|
.get(&script_info.ui_path[..script_info.ui_path.len() - 1])
|
||||||
tree: tree!(ListNode {
|
.unwrap();
|
||||||
name: "root",
|
let tab = tabs
|
||||||
command: Command::None,
|
.iter_mut()
|
||||||
} => {
|
.find(|Tab { name, .. }| name == tab)
|
||||||
ListNode {
|
.unwrap();
|
||||||
name: "Alacritty",
|
let mut parent = tab.tree.get_mut(*parent_id).unwrap();
|
||||||
command: Command::LocalFile("applications-setup/alacritty-setup.sh"),
|
|
||||||
},
|
let command = ListNode {
|
||||||
ListNode {
|
name: script_info.ui_path.last().unwrap().clone(),
|
||||||
name: "Bash Prompt",
|
command,
|
||||||
command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\""),
|
};
|
||||||
},
|
parent.append(command);
|
||||||
ListNode {
|
}
|
||||||
name: "DWM-Titus",
|
if tabs.is_empty() {
|
||||||
command: Command::LocalFile("applications-setup/dwmtitus-setup.sh")
|
panic!("No tabs found.");
|
||||||
},
|
}
|
||||||
ListNode {
|
tabs
|
||||||
name: "Kitty",
|
}
|
||||||
command: Command::LocalFile("applications-setup/kitty-setup.sh")
|
|
||||||
},
|
fn get_script_list(directory: &Path) -> Vec<(PathBuf, PathBuf)> {
|
||||||
ListNode {
|
let mut entries = std::fs::read_dir(directory)
|
||||||
name: "Neovim",
|
.expect("Command directory does not exist.")
|
||||||
command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\""),
|
.flatten()
|
||||||
},
|
.collect::<Vec<_>>();
|
||||||
ListNode {
|
entries.sort_by_key(|d| d.path());
|
||||||
name: "Rofi",
|
|
||||||
command: Command::LocalFile("applications-setup/rofi-setup.sh"),
|
entries
|
||||||
},
|
.into_iter()
|
||||||
ListNode {
|
.filter_map(|entry| {
|
||||||
name: "ZSH Prompt",
|
let path = entry.path();
|
||||||
command: Command::LocalFile("applications-setup/zsh-setup.sh"),
|
// Recursively iterate through directories
|
||||||
}
|
if entry.file_type().map_or(false, |f| f.is_dir()) {
|
||||||
}),
|
Some(get_script_list(&path))
|
||||||
},
|
} else {
|
||||||
Tab {
|
let is_json = path.extension().map_or(false, |ext| ext == "json");
|
||||||
name: "Security",
|
let script = path.with_extension("sh");
|
||||||
tree: tree!(ListNode {
|
(is_json).then_some(vec![(path, script)])
|
||||||
name: "root",
|
}
|
||||||
command: Command::None,
|
})
|
||||||
} => {
|
.flatten()
|
||||||
ListNode {
|
.collect()
|
||||||
name: "Firewall Baselines (CTT)",
|
}
|
||||||
command: Command::LocalFile("security/firewall-baselines.sh"),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Tab {
|
|
||||||
name: "Utilities",
|
|
||||||
tree: tree!(ListNode {
|
|
||||||
name: "root",
|
|
||||||
command: Command::None,
|
|
||||||
} => {
|
|
||||||
ListNode {
|
|
||||||
name: "Wifi Manager",
|
|
||||||
command: Command::LocalFile("utils/wifi-control.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Bluetooth Manager",
|
|
||||||
command: Command::LocalFile("utils/bluetooth-control.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "MonitorControl(xorg)",
|
|
||||||
command: Command::None,
|
|
||||||
} => {
|
|
||||||
ListNode {
|
|
||||||
name: "Set Resolution",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/set_resolutions.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Duplicate Displays",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/duplicate_displays.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Extend Displays",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/extend_displays.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Auto Detect Displays",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/auto_detect_displays.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Enable Monitor",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/enable_monitor.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Disable Monitor",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/disable_monitor.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Set Primary Monitor",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/set_primary_monitor.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Change Orientation",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/change_orientation.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Manage Arrangement",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/manage_arrangement.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Scale Monitors",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/scale_monitor.sh"),
|
|
||||||
},
|
|
||||||
ListNode {
|
|
||||||
name: "Reset Scaling",
|
|
||||||
command: Command::LocalFile("utils/monitor-control/reset_scaling.sh"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user