polishing scratchpad functionality

This commit is contained in:
Penelope Gwen 2026-01-22 00:53:44 -08:00
parent 41aeab7bda
commit 09bff58900
19 changed files with 741 additions and 450 deletions

14
Cargo.lock generated
View file

@ -690,16 +690,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "freedesktop-file-parser"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1998b5ee36700463150124ac6d6d6128a5a4da453a6f6777a26810f05539f41"
dependencies = [
"freedesktop-icons",
"thiserror 2.0.16",
]
[[package]]
name = "freedesktop-icons"
version = "0.4.0"
@ -1837,14 +1827,12 @@ dependencies = [
[[package]]
name = "sway-de-utils"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"clap",
"confy",
"dialog",
"dirs 6.0.0",
"env_logger",
"freedesktop-file-parser",
"freedesktop-icons",
"hex",
"image",

View file

@ -1,6 +1,6 @@
[package]
name = "sway-de-utils"
version = "0.1.2"
version = "0.1.3"
authors = ["Penelope Gwen <support@pogmom.me>"]
edition = "2024"
license-file = "LICENSE.md"
@ -14,9 +14,7 @@ path = "src/main.rs"
clap = { version = "4.5.45", features = ["derive"] }
confy = "1.0.0"
dialog = "0.3.0"
dirs = "6.0.0"
env_logger = "0.11.8"
freedesktop-file-parser = "0.3.1"
freedesktop-icons = "0.4.0"
hex = "0.4.3"
image = "0.25.8"

6
debian/changelog vendored
View file

@ -1,3 +1,9 @@
sway-de-utils (0.1.3-1) unstable; urgency=medium
* finish scratchpad implementation
-- Penelope Gwen <support@pogmom.me> Thu, 22 Jan 2026 00:12:43 -0800
sway-de-utils (0.1.2-1) unstable; urgency=medium
* partial implementation of sdu sway get scratchpad

View file

@ -1,42 +1,39 @@
use {
serde::{Deserialize, Serialize},
std::collections::HashMap,
serde::{
Deserialize,
Serialize
}
};
#[derive(Serialize,Deserialize,Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WindowIcon {
pub icon: String,
pub substring: String
pub substring: String,
}
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Profile {
pub name: String,
pub icon: String,
pub program_args: Option<HashMap<String, Vec<String>>>,
pub scripts: Option<Vec<ScriptConf>>
pub scripts: Option<Vec<ScriptConf>>,
}
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Programs {
pub name: Option<String>,
pub command: String,
pub arguments: Vec<String>
pub arguments: Vec<String>,
}
#[derive(Serialize,Deserialize,Clone,Debug,Default)]
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct LockConf {
pub wallpaper_path: Option<String>,
pub blur: f32,
pub scale: f32,
}
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ScriptConf {
pub name: String,
pub icon: String,
pub command: String
pub command: String,
}
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Config {
pub preserve_keyboard_order: bool,
pub window_icons: Vec<WindowIcon>,
@ -47,5 +44,18 @@ pub struct Config {
}
impl ::std::default::Default for Config {
fn default() -> Self { Self { preserve_keyboard_order: false, window_icons: vec![], programs: HashMap::new(), lock: LockConf { wallpaper_path: None, blur: 10.0, scale: 0.75 }, profiles: vec![], scripts: vec![] } }
}
fn default() -> Self {
Self {
preserve_keyboard_order: false,
window_icons: vec![],
programs: HashMap::new(),
lock: LockConf {
wallpaper_path: None,
blur: 10.0,
scale: 0.75,
},
profiles: vec![],
scripts: vec![],
}
}
}

View file

@ -17,7 +17,7 @@ pub struct Cli {
#[command(subcommand)]
pub(crate) command: Commands,
// command: Commands,
// command: Commands,
}
#[derive(Subcommand)]
@ -32,7 +32,7 @@ pub enum Commands {
#[clap(alias = "L")]
Launch {
#[arg(short, long)]
program: String
program: String,
},
/// Set up blurred wallpaper for screen lock, and locks screen
#[clap(alias = "l")]
@ -49,52 +49,47 @@ pub enum Commands {
/// Lists global/profile shortcuts and optionally executes them
#[clap(alias = "scut")]
Shortcuts {
/// List shortcuts from global shortcut list rather than
// #[arg(short, long, action = ArgAction::SetTrue)]
// global: Option<bool>,
/// List shortcuts from global shortcut list rather than
// #[arg(short, long, action = ArgAction::SetTrue)]
// global: Option<bool>,
#[command(subcommand)]
shortcut_command: ShortcutCommand,
// /// Execute foud command
// #[arg(short, long, action = ArgAction::SetTrue)]
// execute: bool,
// /// Mode to print shortcut information
// #[command(subcommand)]
// mode: Option<ShortcutMode>
// /// Execute foud command
// #[arg(short, long, action = ArgAction::SetTrue)]
// execute: bool,
// /// Mode to print shortcut information
// #[command(subcommand)]
// mode: Option<ShortcutMode>
},
/// List or Execute Power/Session Commands
#[clap(alias = "pow")]
Power {
#[command(subcommand)]
power_command: Option<PowerCommand>,
}
},
}
#[derive(Clone)]
#[derive(Subcommand)]
#[derive(Clone, Subcommand)]
pub enum ShortcutCommand {
/// Execute command by passed name
#[clap(alias = "exec")]
Execute {
shortcut_name: String
},
Execute { shortcut_name: String },
/// Mode to print shortcut information
#[clap(alias = "g")]
Get {
#[arg(short,long)]
mode: Option<ShortcutMode>
}
#[arg(short, long)]
mode: Option<ShortcutMode>,
},
}
#[derive(Clone, Copy, ValueEnum)]
#[derive(Subcommand)]
#[derive(Clone, Copy, ValueEnum, Subcommand)]
pub enum ShortcutMode {
/// Print formatted for dmenu/wofi
#[clap(alias = "d")]
Dmenu,
/// Print formatted as json/for eww
#[clap(alias = "j")]
Json
Json,
}
#[derive(Subcommand)]
@ -122,7 +117,7 @@ pub enum SwayGetCommand {
Scratchpad,
/// Get Window and Workspace Information in one JSON output
#[clap(alias = "f")]
Full
Full,
}
#[derive(Subcommand)]
@ -173,7 +168,7 @@ pub enum ProfileCommand {
},
/// Initialize Sway-DE-Utils
#[clap(alias = "i")]
Init
Init,
}
#[derive(Subcommand)]
@ -196,13 +191,11 @@ pub enum ProfileSwitchCommand {
#[arg(short, long)]
index: Option<usize>,
/// Target Profile Name or Index
query: Option<String>
}
query: Option<String>,
},
}
#[derive(Clone)]
#[derive(Subcommand)]
#[derive(Clone, Subcommand)]
pub enum ProfileGetCommand {
/// Get Profile Details in JSON Format
#[clap(alias = "j")]
@ -212,5 +205,5 @@ pub enum ProfileGetCommand {
Name,
/// Get Profile Icon in Plaintext
#[clap(alias = "i")]
Icon
}
Icon,
}

View file

@ -3,35 +3,45 @@ use {
config::Profile,
lib::{
cli::ProfileGetCommand,
profile::{active_profile_index, profile_from_index}
}
profile::{active_profile_index, profile_from_index},
},
},
miette::{IntoDiagnostic, Result},
serde_json::json,
watchexec::Watchexec,
watchexec_events::Tag
watchexec_events::Tag,
};
pub fn profile_info(profiles: Vec<Profile>,info: ProfileGetCommand) -> String {
pub fn profile_info(profiles: Vec<Profile>, info: ProfileGetCommand) -> String {
let active_profile = profile_from_index(profiles, active_profile_index());
match info {
ProfileGetCommand::Json => json!(active_profile).to_string(),
ProfileGetCommand::Name => active_profile.name,
ProfileGetCommand::Icon => active_profile.icon
ProfileGetCommand::Icon => active_profile.icon,
}
}
#[tokio::main]
pub async fn watch(profiles_config: Vec<Profile>,info: ProfileGetCommand,watch_path: String) -> Result<()> {
pub async fn watch(
profiles_config: Vec<Profile>,
info: ProfileGetCommand,
watch_path: String,
) -> Result<()> {
let wx = Watchexec::new(move |mut action| {
//yeah, this is... fine
action.events.iter().find(|_event|_event.tags.iter().any(|tag| match tag {
Tag::FileEventKind(watchexec_events::filekind::FileEventKind::Access(watchexec_events::filekind::AccessKind::Close(watchexec_events::filekind::AccessMode::Write))) => {
println!("{}",profile_info(profiles_config.clone(),info.clone()));
true
},
_ => false
}));
action.events.iter().find(|_event| {
_event.tags.iter().any(|tag| match tag {
Tag::FileEventKind(watchexec_events::filekind::FileEventKind::Access(
watchexec_events::filekind::AccessKind::Close(
watchexec_events::filekind::AccessMode::Write,
),
)) => {
println!("{}", profile_info(profiles_config.clone(), info.clone()));
true
}
_ => false,
})
});
if action.signals().next().is_some() {
println!("Quitting");
action.quit();
@ -43,4 +53,4 @@ pub async fn watch(profiles_config: Vec<Profile>,info: ProfileGetCommand,watch_p
let _ = main.await.into_diagnostic();
//todo: handle this return properly
Ok(())
}
}

View file

@ -2,44 +2,63 @@ use std::collections::HashMap;
use log::debug;
use crate::{utils::SDUError, config::{Profile, Programs}, lib::profile::active_profile, lib::sway_ipc::{get_sway_connection, run_sway_command}};
use crate::{
config::{Profile, Programs},
lib::profile::active_profile,
lib::sway_ipc::{get_sway_connection, run_sway_command},
utils::SDUError,
};
pub fn launch( exec: &String, profiles: Vec<Profile>, programs: HashMap<String, Programs> ) -> Result<(), SDUError> {
debug!("{}",exec);
pub fn launch(
exec: &String,
profiles: Vec<Profile>,
programs: HashMap<String, Programs>,
) -> Result<(), SDUError> {
debug!("{}", exec);
match profile_launch(profiles, programs, exec) {
Ok(p) => {
debug!("{}",p);
run_sway_command(&mut get_sway_connection(),p)
},
debug!("{}", p);
run_sway_command(&mut get_sway_connection(), p)
}
Err(e) => Err(e),
}
}
pub fn append_arguments(mut command: String, args: Vec<String>) -> String {
for a in args {
command = format!("{} {}",command,a);
command = format!("{} {}", command, a);
}
command
}
pub fn profile_launch(profiles: Vec<Profile>, programs_config: HashMap<String, Programs>, program_arg: &String) -> Result<String,SDUError> {
match programs_config.iter().find(|x|x.0.eq(program_arg)) {
pub fn profile_launch(
profiles: Vec<Profile>,
programs_config: HashMap<String, Programs>,
program_arg: &String,
) -> Result<String, SDUError> {
match programs_config.iter().find(|x| x.0.eq(program_arg)) {
Some(p) => {
let mut swaymsg_command = format!("exec {}", p.1.command);
swaymsg_command = append_arguments(swaymsg_command, p.1.arguments.clone());
match active_profile(profiles) {
Ok(profile) => {
println!("{:#?}",profile);
if let Some(profile_args) = profile.program_args.unwrap_or_default().iter().find(|x|x.0.eq(p.0)) {
swaymsg_command = append_arguments(swaymsg_command, profile_args.1.to_owned());
println!("{:#?}", profile);
if let Some(profile_args) = profile
.program_args
.unwrap_or_default()
.iter()
.find(|x| x.0.eq(p.0))
{
swaymsg_command =
append_arguments(swaymsg_command, profile_args.1.to_owned());
}
Ok(swaymsg_command)
},
}
Err(e) => Err(e),
}
},
None => {
Err(SDUError { message: String::from("No matching program found") })
},
}
None => Err(SDUError {
message: String::from("No matching program found"),
}),
}
}
}

View file

@ -1,79 +1,127 @@
use std::{env::consts, fs::{self, DirBuilder, write}, io, path::PathBuf, vec};
use log::debug;
use std::{
env::consts,
fs::{self, DirBuilder, write},
io,
path::PathBuf,
vec,
};
use walkdir::WalkDir;
use image::{imageops::GaussianBlurParameters, DynamicImage};
use image::{DynamicImage, imageops::GaussianBlurParameters};
use serde::{Deserialize, Serialize};
use serde_json::json;
use sha2::{Sha256, Digest};
use xdg::BaseDirectories;
use sha2::{Digest, Sha256};
use shellexpand::tilde;
use xdg::BaseDirectories;
use crate::{utils::DirectoryType, utils::SDUError, config::LockConf, lib::sway_ipc::get_sway_connection};
use crate::{
config::LockConf, lib::sway_ipc::get_sway_connection, utils::DirectoryType, utils::SDUError,
};
#[derive(Serialize,Deserialize,Debug)]
#[derive(Serialize, Deserialize, Debug)]
pub struct WallpaperCache {
pub display: String,
pub hash: String
pub hash: String,
}
pub fn lock_screen(lock_config: LockConf,force_bg_render: bool) -> Result<(),SDUError> {
pub fn lock_screen(lock_config: LockConf, force_bg_render: bool) -> Result<(), SDUError> {
let mut sway_connection = get_sway_connection();
match &lock_config.wallpaper_path {
Some(wallpaper_path) => {
let wallpaper_root_path = PathBuf::from(tilde(&wallpaper_path).to_string());
let wp_cache_dir = get_path(DirectoryType::Cache, "sway-profiles-rs/lock/");
DirBuilder::new().recursive(true).create(&wp_cache_dir).expect("Failed to create cache directory");
DirBuilder::new()
.recursive(true)
.create(&wp_cache_dir)
.expect("Failed to create cache directory");
let wp_hash_json = wallpaper_check_hash(wp_cache_dir.join("hashes.json"));
debug!("{:?}",wp_hash_json);
debug!("{:?}", wp_hash_json);
let mut wp_hash_array: Vec<WallpaperCache> = vec![];
let sway_visible_workspaces = match sway_connection.get_workspaces() {
Ok(w) => w.iter().filter(|w|w.visible).cloned().collect(),
Ok(w) => w.iter().filter(|w| w.visible).cloned().collect(),
Err(_) => vec![],
};
for workspace in sway_visible_workspaces {
debug!("operating on workspace {} ({})",workspace.num, workspace.name);
debug!(
"operating on workspace {} ({})",
workspace.num, workspace.name
);
let wp_output_path = wallpaper_root_path.join(&workspace.output);
let output_image_path = wp_output_path.join(format!("{:0>2}.jpg",workspace.num));
let output_image_path = wp_output_path.join(format!("{:0>2}.jpg", workspace.num));
let mut wp_sha256 = Sha256::new();
let img_file_data = match fs::File::open(&output_image_path) {
Ok(mut img_file_data) => Ok(io::copy(&mut img_file_data, &mut wp_sha256).unwrap_or_default()),
Err(_) => Err(SDUError { message: format!("Could not open wallpaper image for output {}", workspace.output) }),
Ok(mut img_file_data) => {
Ok(io::copy(&mut img_file_data, &mut wp_sha256).unwrap_or_default())
}
Err(_) => Err(SDUError {
message: format!(
"Could not open wallpaper image for output {}",
workspace.output
),
}),
};
if img_file_data.is_ok() {
let wp_hash = hex::encode(wp_sha256.finalize());
let wp_image = image::open(output_image_path).expect("Could not open wallpaper file as image");
let output_cache_image_path = wp_cache_dir.join(format!("{}.jpg",workspace.output));
let wp_image = image::open(output_image_path)
.expect("Could not open wallpaper file as image");
let output_cache_image_path =
wp_cache_dir.join(format!("{}.jpg", workspace.output));
match wp_hash_json.iter().find(|wc|wc.display == workspace.output) {
Some (saved_hash) => {
if !(saved_hash.hash == wp_hash && output_cache_image_path.exists()) || force_bg_render {
generate_image(wp_image,output_cache_image_path, lock_config.clone())
match wp_hash_json
.iter()
.find(|wc| wc.display == workspace.output)
{
Some(saved_hash) => {
if !(saved_hash.hash == wp_hash && output_cache_image_path.exists())
|| force_bg_render
{
generate_image(
wp_image,
output_cache_image_path,
lock_config.clone(),
)
};
},
None => generate_image(wp_image, output_cache_image_path, lock_config.clone())
}
None => {
generate_image(wp_image, output_cache_image_path, lock_config.clone())
}
}
wp_hash_array.push(WallpaperCache { display: workspace.output, hash: wp_hash });
wp_hash_array.push(WallpaperCache {
display: workspace.output,
hash: wp_hash,
});
};
};
}
debug!("{:?}", wp_hash_array);
debug!("{:?}",wp_hash_array);
let new_json = json!(wp_hash_array);
let _ = write(wp_cache_dir.join("hashes.json"), new_json.to_string());
let gtklock_modules_dir = format!("/usr/lib/{}-linux-gnu/gtklock/",consts::ARCH);//.to_owned() + consts::ARCH + "-linux-gnu/gtklock/";
let gtklock_modules_dir = format!("/usr/lib/{}-linux-gnu/gtklock/", consts::ARCH);
let gktlock_modules_path = PathBuf::from(gtklock_modules_dir);
let mut gtklock_args: Vec<String> = vec![];
for gtklock_module in WalkDir::new(gktlock_modules_path).into_iter().filter(|x|x.as_ref().expect("TODO").file_name().to_str().unwrap_or_default().ends_with("-module.so")) {
let gtklock_module_path = gtklock_module.expect("TODO").path().to_string_lossy().to_string();
for gtklock_module in WalkDir::new(gktlock_modules_path).into_iter().filter(|x| {
x.as_ref()
.expect("TODO")
.file_name()
.to_str()
.unwrap_or_default()
.ends_with("-module.so")
}) {
let gtklock_module_path = gtklock_module
.expect("TODO")
.path()
.to_string_lossy()
.to_string();
debug!("Foud gtklock module: {}", gtklock_module_path);
gtklock_args.push("-m".to_owned());
gtklock_args.push(gtklock_module_path);
@ -86,14 +134,19 @@ pub fn lock_screen(lock_config: LockConf,force_bg_render: bool) -> Result<(),SDU
match sway_connection.run_command(gtklock_command) {
Ok(_) => Ok(()),
Err(_) => Err(SDUError { message: "Could not lock screen!".to_owned() }),
Err(_) => Err(SDUError {
message: "Could not lock screen!".to_owned(),
}),
}
},
None => Err(SDUError { message: "Wallpaper path is not set!".to_owned() }),
}
None => Err(SDUError {
message: "Wallpaper path is not set!".to_owned(),
}),
}
}
pub fn generate_image(orig_img_data: DynamicImage,gen_image_path: PathBuf, lock_conf: LockConf) {// Result<(), ImageError> {
pub fn generate_image(orig_img_data: DynamicImage, gen_image_path: PathBuf, lock_conf: LockConf) {
// Result<(), ImageError> {
let w = orig_img_data.width() as f32 * lock_conf.scale;
let h = orig_img_data.height() as f32 * lock_conf.scale;
let blurred_img_data = orig_img_data
@ -101,13 +154,15 @@ pub fn generate_image(orig_img_data: DynamicImage,gen_image_path: PathBuf, lock_
.blur_advanced(GaussianBlurParameters::new_from_sigma(lock_conf.blur));
match blurred_img_data.save(gen_image_path) {
Ok(_) => println!("Image saved successfully"),
Err(e) => println!("error: {:?}",e),
Err(e) => println!("error: {:?}", e),
}
}
pub fn wallpaper_check_hash(hashes_json_path: PathBuf) -> Vec<WallpaperCache> {
let wallpaper_hashes: Vec<WallpaperCache> = fs::File::open(hashes_json_path).ok()
.and_then(|f| serde_json::from_reader(f).ok()).unwrap_or(vec![]);
let wallpaper_hashes: Vec<WallpaperCache> = fs::File::open(hashes_json_path)
.ok()
.and_then(|f| serde_json::from_reader(f).ok())
.unwrap_or_default();
wallpaper_hashes
}
@ -116,6 +171,7 @@ pub fn get_path(dir_type: DirectoryType, suffix: &str) -> PathBuf {
let base_dir = match dir_type {
DirectoryType::Cache => base_directories.cache_home,
DirectoryType::Config => base_directories.config_home,
}.expect("HOME could not be found");
}
.expect("HOME could not be found");
base_dir.join(suffix)
}

View file

@ -1,29 +1,26 @@
mod windows;
mod workspaces;
mod cli;
mod get;
mod launch;
mod lock;
mod power;
mod profile;
mod shortcuts;
mod launch;
mod get;
mod sway_ipc;
mod sway;
mod lock;
mod scratchpad;
mod shortcuts;
mod sway;
mod sway_ipc;
mod windows;
mod workspaces;
pub use {
windows::get_window_info,
workspaces::get_workspace_info,
cli::{Cli,Commands},
cli::{Cli, Commands},
launch::launch,
lock::lock_screen,
power::power_fn,
profile::profile_fn,
scratchpad::get_scratchpad_info,
shortcuts::shortcuts_fn,
launch::launch,
sway_ipc::{
run_sway_command,
get_sway_connection
},
sway::sway_fn,
lock::lock_screen,
scratchpad::get_scratchpad_info
};
sway_ipc::{get_sway_connection, run_sway_command},
windows::get_window_info,
workspaces::get_workspace_info,
};

View file

@ -1,53 +1,76 @@
use {
crate::{
lib::lock_screen,
utils::SDUError, lib::cli::PowerCommand, config::LockConf, run_sway_command, lib::sway_ipc::get_sway_connection
}, dialog::DialogBox, log::debug
config::LockConf, lib::cli::PowerCommand, lib::lock_screen,
lib::sway_ipc::get_sway_connection, run_sway_command, utils::SDUError,
},
dialog::DialogBox,
log::debug,
};
pub fn power_fn(power_command_arg: &Option<PowerCommand>, lock_config: LockConf ) -> Result<(),SDUError> {
pub fn power_fn(
power_command_arg: &Option<PowerCommand>,
lock_config: LockConf,
) -> Result<(), SDUError> {
match power_command_arg {
Some(power_command) => {
let action_prompt = match power_command {
PowerCommand::Shutdown => vec!["shut down","poweroff.target"],
PowerCommand::Reboot => vec!["restart","reboot.target"],
PowerCommand::Suspend => vec!["suspend","suspend.target"],
PowerCommand::Lock { force_render_background: _ } => vec!["lock"],
PowerCommand::Shutdown => vec!["shut down", "poweroff.target"],
PowerCommand::Reboot => vec!["restart", "reboot.target"],
PowerCommand::Suspend => vec!["suspend", "suspend.target"],
PowerCommand::Lock {
force_render_background: _,
} => vec!["lock"],
PowerCommand::Logout => vec!["log out"],
};
let action = action_prompt.first().unwrap_or(&"not found");
if let Ok(dialog::Choice::Yes) = dialog::Question::new(format!("Are you sure you would like to {}?",action)).title(format!("Confirm {}...",action)).show() {
if let Ok(dialog::Choice::Yes) =
dialog::Question::new(format!("Are you sure you would like to {}?", action))
.title(format!("Confirm {}...", action))
.show()
{
match power_command {
PowerCommand::Lock { force_render_background } => {
PowerCommand::Lock {
force_render_background,
} => {
debug!("running lock_screen function");
return lock_screen(lock_config, force_render_background.unwrap_or_default())
},
return lock_screen(
lock_config,
force_render_background.unwrap_or_default(),
);
}
PowerCommand::Logout => {
debug!("sending exit command to sway");
return run_sway_command(&mut get_sway_connection(), "exit".to_string())
},
return run_sway_command(&mut get_sway_connection(), "exit".to_string());
}
_ => {
let target = action_prompt.get(1).unwrap_or(&"not found");
let systemctl = systemctl::SystemCtl::default();
debug!("looking for {}",target);
debug!("looking for {}", target);
if let Ok(true) = systemctl.exists(target) {
debug!("found and starting {}",target);
debug!("found and starting {}", target);
match systemctl.start(target) {
Ok(_) => {
debug!("started target [{}] successfully", target);
return Ok(())
},
Err(_) => return Err(SDUError { message: format!("Could not start systemctl target [{}]", target) }),
return Ok(());
}
Err(_) => {
return Err(SDUError {
message: format!(
"Could not start systemctl target [{}]",
target
),
});
}
}
}
}
}
};
Ok(())
},
}
None => {
println!("⏻ Shutdown\n Reboot\n Suspend\n Lock\n Logout");
Ok(())
},
}
}
}
}

View file

@ -1,172 +1,258 @@
use {
crate::{
utils::SDUError,
lib::{
cli::{
ProfileCommand,
ProfileGetCommand,
ProfileSwitchCommand
},
get,
shortcuts_fn,
run_sway_command,
get_sway_connection
},
config::{ Config, Profile},
config::{Config, Profile},
//lib::get,
get_xdg_dirs,
lib::{
cli::{ProfileCommand, ProfileGetCommand, ProfileSwitchCommand},
get, get_sway_connection, run_sway_command, shortcuts_fn,
},
setup_runtime_dir,
//lib::shortcuts::shortcuts_fn,
//lib::sway_ipc::{get_sway_connection, run_sway_command}
utils::SDUError,
},
log::{debug, error},
serde_json::json,
std::fs::{self, DirBuilder, write},
swayipc::Connection,
xdg::BaseDirectories
xdg::BaseDirectories,
};
pub fn profile_fn(profile_command: &ProfileCommand, profiles_config: Vec<Profile>, preserve_keyboard_order: bool) -> Result<(),SDUError> {
pub fn profile_fn(
profile_command: &ProfileCommand,
profiles_config: Vec<Profile>,
preserve_keyboard_order: bool,
) -> Result<(), SDUError> {
let xdg_dirs = get_xdg_dirs();
setup_runtime_dir(xdg_dirs);
match profile_command {
ProfileCommand::Init => {
let active_profile_index = active_profile_index();
match switch_by_index(profiles_config, active_profile_index, preserve_keyboard_order) {
match switch_by_index(
profiles_config,
active_profile_index,
preserve_keyboard_order,
) {
Ok(_) => {
let _: () = debug!("successfully initialized sway-de-utils");
Ok(())
},
}
Err(e) => Err(e),
}
},
ProfileCommand::Switch { switch_command} => {
match switch_command {
Some(ProfileSwitchCommand::To { index,name,query }) => {
match index {
Some(i) => {
switch_by_index(profiles_config, *i, preserve_keyboard_order)
},
None => {
match name {
Some(n) => {
switch_by_name(profiles_config, n.to_string(), preserve_keyboard_order)
},
None => match query {
Some(q) => match q.parse::<usize>() {
Ok(i) => match switch_by_index(profiles_config.clone(), i, preserve_keyboard_order) {
Ok(o) => Ok(o),
Err(_) => switch_by_name(profiles_config, q.to_string(), preserve_keyboard_order),
},
Err(_) => switch_by_name(profiles_config, q.to_string(), preserve_keyboard_order),
},
None => Err(SDUError { message: "No profile index or name provided.".to_string() }),
},
}
},
}
ProfileCommand::Switch { switch_command } => match switch_command {
Some(ProfileSwitchCommand::To { index, name, query }) => match index {
Some(i) => switch_by_index(profiles_config, *i, preserve_keyboard_order),
None => match name {
Some(n) => {
switch_by_name(profiles_config, n.to_string(), preserve_keyboard_order)
}
None => match query {
Some(q) => match q.parse::<usize>() {
Ok(i) => match switch_by_index(
profiles_config.clone(),
i,
preserve_keyboard_order,
) {
Ok(o) => Ok(o),
Err(_) => switch_by_name(
profiles_config,
q.to_string(),
preserve_keyboard_order,
),
},
Err(_) => switch_by_name(
profiles_config,
q.to_string(),
preserve_keyboard_order,
),
},
None => Err(SDUError {
message: "No profile index or name provided.".to_string(),
}),
},
},
Some(ProfileSwitchCommand::Next) => match next(profiles_config, preserve_keyboard_order) {
},
Some(ProfileSwitchCommand::Next) => {
match next(profiles_config, preserve_keyboard_order) {
Ok(_) => {
debug!("Successfully switched to next profile");
Ok(())
},
}
Err(e) => Err(e),
},
Some(ProfileSwitchCommand::Prev) => match previous(profiles_config, preserve_keyboard_order) {
}
}
Some(ProfileSwitchCommand::Prev) => {
match previous(profiles_config, preserve_keyboard_order) {
Ok(_) => {
debug!("successfully switched to previous profile");
Ok(())
},
}
Err(e) => Err(e),
},
None => {
for profile in profiles_config {
println!("{} {}", profile.icon, profile.name);
};
Ok(())
},
}
}
None => {
for profile in profiles_config {
println!("{} {}", profile.icon, profile.name);
}
Ok(())
}
},
ProfileCommand::Get { get_command, monitor } => {
ProfileCommand::Get {
get_command,
monitor,
} => {
let profile_detail = get_command.clone().unwrap_or(ProfileGetCommand::Json);
println!("{}",get::profile_info(profiles_config.clone(),profile_detail.clone()));
println!(
"{}",
get::profile_info(profiles_config.clone(), profile_detail.clone())
);
let _: () = if monitor.unwrap_or_default() {
let _ = get::watch(profiles_config, profile_detail,get_xdg_dirs().runtime_dir.expect("XDG Runtime dir could not be found").join("sway-de-utils/active-profile.json").to_str().expect("could not create str from path").to_string());
let _ = get::watch(
profiles_config,
profile_detail,
get_xdg_dirs()
.runtime_dir
.expect("XDG Runtime dir could not be found")
.join("sway-de-utils/active-profile.json")
.to_str()
.expect("could not create str from path")
.to_string(),
);
};
Ok(())
}
ProfileCommand::Shortcuts { shortcut_command } => {
let active_profile = active_profile(profiles_config).expect("could not determine active profile");
shortcuts_fn(shortcut_command, active_profile.scripts.expect("could not find scripts for profile"))
},
let active_profile =
active_profile(profiles_config).expect("could not determine active profile");
shortcuts_fn(
shortcut_command,
active_profile
.scripts
.expect("could not find scripts for profile"),
)
}
}
}
fn initialize(mut sway_connection: Connection, profile: Profile,uindex: usize, preserve_keyboard_order: bool) -> Result<(),SDUError> { //payload: String) {
fn initialize(
mut sway_connection: Connection,
profile: Profile,
uindex: usize,
preserve_keyboard_order: bool,
) -> Result<(), SDUError> {
//payload: String) {
for i in 0..10 {
let workspace = (uindex*10) + match i.eq(&0) && !preserve_keyboard_order {
true => 10,
false => i,
};
debug!("key {}: workspace {}",i,workspace);
debug!("bindsym $mod+{} workspace number {}:{}",i,workspace,profile.icon);
let workspace = (uindex * 10)
+ match i.eq(&0) && !preserve_keyboard_order {
true => 10,
false => i,
};
debug!("key {}: workspace {}", i, workspace);
debug!(
"bindsym $mod+{} workspace number {}:{}",
i, workspace, profile.icon
);
match run_sway_command(&mut sway_connection, format!("bindsym $mod+{} workspace number {}:{}",i,workspace,profile.icon)) {
match run_sway_command(
&mut sway_connection,
format!(
"bindsym $mod+{} workspace number {}:{}",
i, workspace, profile.icon
),
) {
Ok(_) => (),
Err(_) => todo!(),
}
match run_sway_command(&mut sway_connection, format!("bindsym $mod+Shift+{} move container to workspace number {}:{}",i,workspace,profile.icon)) {
match run_sway_command(
&mut sway_connection,
format!(
"bindsym $mod+Shift+{} move container to workspace number {}:{}",
i, workspace, profile.icon
),
) {
Ok(_) => (),
Err(_) => todo!(),
}
};
}
let base_directories = BaseDirectories::new();
let active_profile_cache = base_directories.get_runtime_directory().expect("why tf do xdg dirs not exist").join("sway-de-utils/active-profile.json");
let active_profile_cache = base_directories
.get_runtime_directory()
.expect("why tf do xdg dirs not exist")
.join("sway-de-utils/active-profile.json");
let active_profile = json!(uindex);
DirBuilder::new().recursive(true).create(active_profile_cache.parent().expect("Unable to determine cache dir")).expect("Failed to create cache directory");
DirBuilder::new()
.recursive(true)
.create(
active_profile_cache
.parent()
.expect("Unable to determine cache dir"),
)
.expect("Failed to create cache directory");
match write(active_profile_cache, active_profile.to_string()) {
Ok(o) => debug!("{:#?}",o),
Err(e) => debug!("{:#?}",e),
Ok(o) => debug!("{:#?}", o),
Err(e) => debug!("{:#?}", e),
};
match run_sway_command(&mut sway_connection, format!("workspace number {}:{}",(uindex*10)+1,profile.icon)) {
match run_sway_command(
&mut sway_connection,
format!("workspace number {}:{}", (uindex * 10) + 1, profile.icon),
) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
pub fn profile_from_index(profiles: Vec<Profile>, index: usize) -> Profile {
profiles.get(index).expect("Profile not found for index").clone()
profiles
.get(index)
.expect("Profile not found for index")
.clone()
}
pub fn _profile_from_name(config: Config, name: String) -> Result<Profile,SDUError> {
match config.profiles.iter().find(|x|x.name == name) {
pub fn _profile_from_name(config: Config, name: String) -> Result<Profile, SDUError> {
match config.profiles.iter().find(|x| x.name == name) {
Some(p) => Ok(p.clone()),
None => Err(SDUError { message: format!("Profile not found with name {}",name) }),
None => Err(SDUError {
message: format!("Profile not found with name {}", name),
}),
}
}
fn switch_by_index(profiles_config: Vec<Profile>,index: usize, preserve_keyboard_order: bool) -> Result<(),SDUError> {
fn switch_by_index(
profiles_config: Vec<Profile>,
index: usize,
preserve_keyboard_order: bool,
) -> Result<(), SDUError> {
let profile = profile_from_index(profiles_config, index);
match initialize(get_sway_connection(), profile, index, preserve_keyboard_order) {
match initialize(
get_sway_connection(),
profile,
index,
preserve_keyboard_order,
) {
Ok(_) => {
println!("successfully switched to profile at index {}",index);
println!("successfully switched to profile at index {}", index);
Ok(())
},
}
Err(e) => Err(e),
}
}
fn switch_by_name(profiles_config: Vec<Profile>,name: String,preserve_keyboard_order: bool) -> Result<(),SDUError> {
fn switch_by_name(
profiles_config: Vec<Profile>,
name: String,
preserve_keyboard_order: bool,
) -> Result<(), SDUError> {
match index_from_name(profiles_config.clone(), name.clone()) {
Ok(index) => match switch_by_index(profiles_config, index,preserve_keyboard_order) {
Ok(index) => match switch_by_index(profiles_config, index, preserve_keyboard_order) {
Ok(_) => {
println!("Successfully switched to profile with name {}",name);
println!("Successfully switched to profile with name {}", name);
Ok(())
},
}
Err(e) => Err(e),
},
Err(e) => Err(e),
@ -175,11 +261,15 @@ fn switch_by_name(profiles_config: Vec<Profile>,name: String,preserve_keyboard_o
pub fn active_profile_index() -> usize {
let base_directories = BaseDirectories::new();
let active_profile_cache_json = base_directories.get_runtime_directory().expect("xdg dirs do not exist?").join("sway-de-utils/active-profile.json");
let active_profile_cache_json = base_directories
.get_runtime_directory()
.expect("xdg dirs do not exist?")
.join("sway-de-utils/active-profile.json");
if active_profile_cache_json.exists() {
fs::File::open(active_profile_cache_json).ok()
.and_then(|f|serde_json::from_reader::<fs::File,usize>(f).ok())
fs::File::open(active_profile_cache_json)
.ok()
.and_then(|f| serde_json::from_reader::<fs::File, usize>(f).ok())
.expect("could not parse active profile cache file")
} else {
error!("no active profile cache file");
@ -187,14 +277,16 @@ pub fn active_profile_index() -> usize {
}
}
pub fn active_profile(profiles: Vec<Profile>) -> Result<Profile,SDUError> {
pub fn active_profile(profiles: Vec<Profile>) -> Result<Profile, SDUError> {
match profiles.get(active_profile_index()) {
Some(p) => Ok(p.clone()),
None => Err(SDUError { message: "Could not get profile by index".to_string() }),
None => Err(SDUError {
message: "Could not get profile by index".to_string(),
}),
}
}
fn next(profiles_config: Vec<Profile>, preserve_keyboard_order: bool) -> Result<(),SDUError> {
fn next(profiles_config: Vec<Profile>, preserve_keyboard_order: bool) -> Result<(), SDUError> {
let profile_count = profiles_config.len();
let mut next_profile = active_profile_index() + 1;
if next_profile.ge(&profile_count) {
@ -202,14 +294,14 @@ fn next(profiles_config: Vec<Profile>, preserve_keyboard_order: bool) -> Result<
}
match switch_by_index(profiles_config, next_profile, preserve_keyboard_order) {
Ok(_) => {
println!("switched to next profile ({})",next_profile);
println!("switched to next profile ({})", next_profile);
Ok(())
},
}
Err(e) => Err(e),
}
}
fn previous(profiles_config: Vec<Profile>, preserve_keyboard_order: bool) -> Result<(),SDUError> {
fn previous(profiles_config: Vec<Profile>, preserve_keyboard_order: bool) -> Result<(), SDUError> {
let profile_index = active_profile_index();
let prev_profile: usize = if profile_index.eq(&0) {
profiles_config.len() - 1
@ -218,16 +310,18 @@ fn previous(profiles_config: Vec<Profile>, preserve_keyboard_order: bool) -> Res
};
match switch_by_index(profiles_config, prev_profile, preserve_keyboard_order) {
Ok(_) => {
println!("switched to prev profile ({})",prev_profile);
println!("switched to prev profile ({})", prev_profile);
Ok(())
},
}
Err(e) => Err(e),
}
}
pub fn index_from_name(profiles_config: Vec<Profile>, name: String) -> Result<usize, SDUError>{
match profiles_config.iter().position(|x|x.name == name) {
pub fn index_from_name(profiles_config: Vec<Profile>, name: String) -> Result<usize, SDUError> {
match profiles_config.iter().position(|x| x.name == name) {
Some(i) => Ok(i),
None => Err(SDUError { message: "Index not found for profile?".to_string() }),
None => Err(SDUError {
message: "Index not found for profile?".to_string(),
}),
}
}
}

View file

@ -1,82 +1,90 @@
use std::path::PathBuf;
use std::{path::PathBuf, time::Instant};
use freedesktop_icons::lookup;
use log::debug;
use serde_json::{Value, json};
use swayipc::{Node, ScratchpadState};
use crate::lib::get_sway_connection;
#[derive(serde::Serialize)]
pub struct ScratchpadInfo {
icon: PathBuf,
visible: bool,
name: Option<String>,
title: Option<String>,
window_id: i64
window_id: i64,
}
fn search_node(node: Node) -> Vec<Node> {
let mut node_vec: Vec<Node> = vec![];
if node.scratchpad_state.eq(&Some(ScratchpadState::Fresh)) {
node_vec.push(node);
} else {
for node in node.nodes {
let node_vec_recurse = search_node(node);
for n in node_vec_recurse {
node_vec.push(n);
}
};
for floating_node in node.floating_nodes {
let node_vec_recurse = search_node(floating_node);
for n in node_vec_recurse {
node_vec.push(n);
}
};
}
node_vec
}
fn icon_lookup(name : &str, size: u16) -> Option<PathBuf> {lookup(name).with_cache().with_size(size).find()}
fn app_icon_lookup(name: &str) -> Option<PathBuf> {
//TODO better & faster icon searching
//let sizes: Vec<u16> = vec![512, 256, 128, 64, 24, 22, 16];
//sizes.into_iter().find_map(|s|icon_lookup(name, s))
lookup(name).with_cache().with_theme("breeze").find()
lookup(name)
.with_theme("breeze")
.with_theme("Adwaita")
.with_size(64)
.with_cache()
.find()
}
fn get_app_icon(window: Node) -> PathBuf {
app_icon_lookup(&window.sandbox_app_id.unwrap_or_default()).or(
match window.window_properties {
app_icon_lookup(&window.sandbox_app_id.unwrap_or_default())
.or(match window.window_properties {
Some(w) => app_icon_lookup(&w.class.unwrap_or_default()),
None => app_icon_lookup(&window.app_id.unwrap_or_default()),
})
.unwrap_or("/usr/share/icons/breeze-dark/mimetypes/32/unknown.svg".into())
}
fn node_finder(mut node: Node) -> Option<Vec<Node>> {
let mut node_list = Vec::<Node>::new();
if node.scratchpad_state.eq(&Some(ScratchpadState::Fresh)) {
node_list.push(node);
} else {
node.nodes.append(&mut node.floating_nodes);
for node in node.nodes {
if let Some(mut nodes) = node_finder(node) {
node_list.append(&mut nodes);
}
}
).unwrap_or("/usr/share/icons/breeze-dark/mimetypes/32/unknown.svg".into())
};
if !node_list.is_empty() {
Some(node_list)
} else {
None
}
}
fn scratchpad_strip(node: Node) -> ScratchpadInfo {
let start = Instant::now();
let window_icon = get_app_icon(node.clone());
debug!("found window icon in {:?}", start.elapsed());
let title = match node.window_properties.clone() {
Some(w) => w.class,
None => node.app_id,
};
ScratchpadInfo {
icon: window_icon,
visible: node.visible.unwrap_or_default(),
name: node.name,
title,
window_id: node.id,
}
}
pub fn get_scratchpad_info() -> Value {
let tree = get_sway_connection().get_tree().expect("todo");
let mut scratchpad_vec: Vec<ScratchpadInfo> = vec![];
let scratch_start = Instant::now();
// $20 says this is NOT the best way to recursively check all nodes but i'll fix it later
for output in tree.nodes {
for workspace in output.nodes {
for node_list in [workspace.nodes,workspace.floating_nodes] {
for node in node_list {
let sp_nodes = search_node(node);
for n in sp_nodes {
let app_icon = get_app_icon(n.clone());
let title = match n.window_properties.clone() {
Some(w) => w.class,
None => n.app_id,
};
let scratch_node: ScratchpadInfo = ScratchpadInfo { icon: app_icon, visible: n.visible.unwrap_or_default(), name: n.name, title: title, window_id: n.id };
scratchpad_vec.push(scratch_node);
}
}
};
}
}
json!(scratchpad_vec)
}
let mut scratchpad_windows: Vec<ScratchpadInfo> = tree
.nodes
.iter()
.filter_map(|x| node_finder(x.clone()))
.flatten()
.map(|node| scratchpad_strip(node))
.collect();
scratchpad_windows.sort_by(|x, y| x.window_id.cmp(&y.window_id));
debug!(
"parsed scratchpad window info in {:?}",
scratch_start.elapsed()
);
json!(scratchpad_windows)
}

View file

@ -1,37 +1,51 @@
use log::debug;
use serde_json::json;
use crate::{utils::SDUError, lib::cli::{ShortcutCommand, ShortcutMode}, config::ScriptConf, lib::sway_ipc::{get_sway_connection, run_sway_command}};
use crate::{
config::ScriptConf,
lib::cli::{ShortcutCommand, ShortcutMode},
lib::sway_ipc::{get_sway_connection, run_sway_command},
utils::SDUError,
};
fn print_shortcuts(mode: &ShortcutMode, shortcuts: Vec<ScriptConf>) -> String {
match mode {
ShortcutMode::Dmenu => {
let mut dmenu_shortcuts: String = String::new();
for shortcut in shortcuts {
dmenu_shortcuts = format!("{}\n{} {}",dmenu_shortcuts,shortcut.icon, shortcut.name)
};
dmenu_shortcuts =
format!("{}\n{} {}", dmenu_shortcuts, shortcut.icon, shortcut.name)
}
dmenu_shortcuts
},
ShortcutMode::Json => {
json!(shortcuts).to_string()
},
}
ShortcutMode::Json => json!(shortcuts).to_string(),
}
}
fn exec_shortcut(shortcut_name: String, shortcuts: Vec<ScriptConf>) -> Result<(),SDUError> {
let shortcut = shortcuts.iter().find(|x|x.name.eq(&shortcut_name)).expect("cannot find shortcut with name");
fn exec_shortcut(shortcut_name: String, shortcuts: Vec<ScriptConf>) -> Result<(), SDUError> {
let shortcut = shortcuts
.iter()
.find(|x| x.name.eq(&shortcut_name))
.expect("cannot find shortcut with name");
debug!("running [{}]", shortcut.command);
run_sway_command(&mut get_sway_connection(), format!("exec {}",shortcut.command.clone()))
run_sway_command(
&mut get_sway_connection(),
format!("exec {}", shortcut.command.clone()),
)
}
pub fn shortcuts_fn(shortcut_command: &ShortcutCommand, shortcuts: Vec<ScriptConf> ) -> Result<(),SDUError> {
pub fn shortcuts_fn(
shortcut_command: &ShortcutCommand,
shortcuts: Vec<ScriptConf>,
) -> Result<(), SDUError> {
match shortcut_command {
ShortcutCommand::Execute { shortcut_name } => exec_shortcut(shortcut_name.to_string(), shortcuts),
ShortcutCommand::Execute { shortcut_name } => {
exec_shortcut(shortcut_name.to_string(), shortcuts)
}
ShortcutCommand::Get { mode } => {
let shortcuts_string = print_shortcuts(&mode.unwrap_or(ShortcutMode::Json), shortcuts);
println!("{}", shortcuts_string);
Ok(())
},
}
}
}
}

View file

@ -1,21 +1,27 @@
use {
crate::{
config::{Profile, WindowIcon}, lib::{
cli::{
SwayCommand,
SwayGetCommand
}, get_scratchpad_info, get_window_info, get_workspace_info, sway_ipc::{
self,
get_sway_info
}
}, utils::SDUError
config::{Profile, WindowIcon},
lib::{
cli::{SwayCommand, SwayGetCommand},
get_scratchpad_info, get_window_info, get_workspace_info,
sway_ipc::{self, get_sway_info},
},
utils::SDUError,
},
swayipc::EventType
swayipc::EventType,
};
pub fn sway_fn( sway_command: &SwayCommand, window_icons: &[WindowIcon], profile_list: Vec<Profile>, kb_order: bool) -> Result<(),SDUError> {
pub fn sway_fn(
sway_command: &SwayCommand,
window_icons: &[WindowIcon],
profile_list: Vec<Profile>,
kb_order: bool,
) -> Result<(), SDUError> {
match sway_command {
SwayCommand::Get { monitor, get_command } => {
SwayCommand::Get {
monitor,
get_command,
} => {
let requested_info = get_command.as_ref().unwrap_or(&SwayGetCommand::Full);
let sway_json = match requested_info {
SwayGetCommand::Workspaces => get_workspace_info(profile_list.clone(), kb_order),
@ -23,16 +29,24 @@ pub fn sway_fn( sway_command: &SwayCommand, window_icons: &[WindowIcon], profile
SwayGetCommand::Full => get_sway_info(window_icons, profile_list.clone(), kb_order),
SwayGetCommand::Scratchpad => get_scratchpad_info(),
};
println!("{}",sway_json);
println!("{}", sway_json);
let _: () = if monitor.unwrap_or_default() {
let event_monitors = match requested_info {
SwayGetCommand::Workspaces => vec![EventType::Workspace],
SwayGetCommand::Scratchpad => vec![EventType::Window],
SwayGetCommand::Window | SwayGetCommand::Full => vec![EventType::Window,EventType::Workspace],
SwayGetCommand::Window | SwayGetCommand::Full => {
vec![EventType::Window, EventType::Workspace]
}
};
sway_ipc::monitor_events(event_monitors, requested_info, window_icons, profile_list, kb_order);
sway_ipc::monitor_events(
event_monitors,
requested_info,
window_icons,
profile_list,
kb_order,
);
};
Ok(())
},
}
}
}
}

View file

@ -1,58 +1,81 @@
use {
crate::{
config::{Profile, WindowIcon}, lib::{cli::SwayGetCommand, get_scratchpad_info, windows::get_window_info, workspaces::get_workspace_info}, utils::SDUError
}, log::debug, serde_json::{Value, json}, std::time::Instant, swayipc::{
Connection,
EventType
}
config::{Profile, WindowIcon},
lib::{
cli::SwayGetCommand, get_scratchpad_info, windows::get_window_info,
workspaces::get_workspace_info,
},
utils::SDUError,
},
log::debug,
serde_json::{Value, json},
std::time::Instant,
swayipc::{Connection, EventType},
};
#[derive(serde::Serialize)]
pub struct SwayInfo {
window_info: Value,
workspace_info: Value,
scratchpad_info: Value
scratchpad_info: Value,
}
pub fn get_sway_connection() -> Connection {
Connection::new().expect("Unable to connect to Sway IPC")
}
pub fn run_sway_command(sway_connection: &mut Connection,payload: String) -> Result<(),SDUError> {
pub fn run_sway_command(sway_connection: &mut Connection, payload: String) -> Result<(), SDUError> {
match sway_connection.run_command(&payload) {
Ok(sway_result) => {
debug!("{:#?}", sway_result);
debug!("Command [{}] ran successfully",payload);
debug!("Command [{}] ran successfully", payload);
Ok(())
},
Err(e) => Err(SDUError{ message: e.to_string() }),
}
Err(e) => Err(SDUError {
message: e.to_string(),
}),
}
}
pub fn get_sway_info(window_icons: &[WindowIcon], profile_list: Vec<Profile>, kb_order: bool) -> Value {
pub fn get_sway_info(
window_icons: &[WindowIcon],
profile_list: Vec<Profile>,
kb_order: bool,
) -> Value {
let window_info = get_window_info(window_icons);
let workspace_info = get_workspace_info(profile_list, kb_order);
let scratchpad_info = get_scratchpad_info();
let full_info = SwayInfo{ window_info, workspace_info, scratchpad_info };
let full_info = SwayInfo {
window_info,
workspace_info,
scratchpad_info,
};
json!(full_info)
}
pub fn monitor_events(event_type: Vec<EventType>, get_command: &SwayGetCommand, window_icons: &[WindowIcon], profile_list: Vec<Profile>, kb_order: bool) {
pub fn monitor_events(
event_type: Vec<EventType>,
get_command: &SwayGetCommand,
window_icons: &[WindowIcon],
profile_list: Vec<Profile>,
kb_order: bool,
) {
let sway_connection = get_sway_connection();
for event in sway_connection.subscribe(event_type).expect("unable to subscribe to sway events") {
for event in sway_connection
.subscribe(event_type)
.expect("unable to subscribe to sway events")
{
let start = Instant::now();
let sway_json = match event.expect("unable to parse sway event") {
swayipc::Event::Workspace(_) | swayipc::Event::Window(_) => {
match get_command {
SwayGetCommand::Window => get_window_info(window_icons),
SwayGetCommand::Workspaces => get_workspace_info(profile_list.clone(), kb_order),
SwayGetCommand::Full => get_sway_info(window_icons, profile_list.clone(), kb_order),
SwayGetCommand::Scratchpad => get_scratchpad_info(),
}
swayipc::Event::Workspace(_) | swayipc::Event::Window(_) => match get_command {
SwayGetCommand::Window => get_window_info(window_icons),
SwayGetCommand::Workspaces => get_workspace_info(profile_list.clone(), kb_order),
SwayGetCommand::Full => get_sway_info(window_icons, profile_list.clone(), kb_order),
SwayGetCommand::Scratchpad => get_scratchpad_info(),
},
_ => unreachable!(),
};
println!("{}",sway_json);
log::debug!("time taken: {:?}",start.elapsed());
println!("{}", sway_json);
log::debug!("time taken: {:?}", start.elapsed());
}
}
}

View file

@ -1,47 +1,58 @@
use {
log::debug,
serde_json::{
Value,
json
},
swayipc::Connection,
crate::config::WindowIcon,
log::debug,
serde_json::{Value, json},
swayipc::Connection,
};
#[derive(serde::Serialize)]
pub struct WindowInfo {
pub title: String,
pub window_count: usize
pub window_count: usize,
}
pub fn get_window_info(window_icons: &[WindowIcon]) -> Value {
let mut sway_connection = Connection::new().expect("Unable to connect to sway ipc socket");
let nodes_tree = sway_connection.get_tree().expect("Unable to parse Sway node tree");
let focused_workspace_raw = nodes_tree.iter()
.find(|x|x.node_type.eq(&swayipc::NodeType::Workspace) && x.iter().any(|x|x.focused));
let nodes_tree = sway_connection
.get_tree()
.expect("Unable to parse Sway node tree");
let focused_workspace_raw = nodes_tree
.iter()
.find(|x| x.node_type.eq(&swayipc::NodeType::Workspace) && x.iter().any(|x| x.focused));
let window_count = match focused_workspace_raw {
Some(ws) => ws.iter().filter(|x|(x.node_type.eq(&swayipc::NodeType::Con) || x.node_type.eq(&swayipc::NodeType::FloatingCon)) && x.name.is_some()).count(),
Some(ws) => ws
.iter()
.filter(|x| {
(x.node_type.eq(&swayipc::NodeType::Con)
|| x.node_type.eq(&swayipc::NodeType::FloatingCon))
&& x.name.is_some()
})
.count(),
None => 0,
};
debug!("{:#?}",window_count);
debug!("{:#?}", window_count);
let window_title = if window_count.gt(&0) {
let window_node = nodes_tree.iter().find(|x|x.focused);
debug!("{:#?}",window_node);
let window_node = nodes_tree.iter().find(|x| x.focused);
//debug!("{:#?}",window_node);
match window_node {
Some(n) => {
let mut window_name = n.name.clone().unwrap_or_default();
for pair in window_icons {
if window_name.contains(&pair.substring) {
window_name = format!("{} {}", pair.icon, window_name.replace(&pair.substring, ""))
window_name =
format!("{} {}", pair.icon, window_name.replace(&pair.substring, ""))
}
}
Some(window_name)
},
}
None => None,
}
} else {
None
};
let window_info = WindowInfo { title: window_title.unwrap_or_default(), window_count};
let window_info = WindowInfo {
title: window_title.unwrap_or_default(),
window_count,
};
json!(window_info)
}
}

View file

@ -1,8 +1,7 @@
use {
crate::{config::Profile, lib::profile}, serde_json::{
Value,
json,
}, swayipc::Connection
crate::{config::Profile, lib::profile},
serde_json::{Value, json},
swayipc::Connection,
};
#[derive(serde::Serialize)]
@ -12,34 +11,58 @@ pub struct WorkspaceInfo {
pub profile: usize,
pub profile_name: String,
pub is_focused: bool,
pub position: char
pub position: char,
}
fn get_workspace_profile(profile_index: usize, profile_list: Vec<Profile>, kb_order: bool) -> String {
fn get_workspace_profile(
profile_index: usize,
profile_list: Vec<Profile>,
kb_order: bool,
) -> String {
let ws_profile_index = if kb_order {
(profile_index - 1) / 10
} else {
profile_index / 10
};
profile_list.iter().nth(ws_profile_index).expect("could not find profile for workspace").clone().name
profile_list
.get(ws_profile_index)
.expect("could not find profile for workspace")
.clone()
.name
}
pub fn get_workspace_info(profile_list: Vec<Profile>, kb_order: bool) -> Value {
let workspaces = Connection::new().expect("unable to connect to sway IPC").get_workspaces().expect("Unable to parse sway workspaces");
let current_ws = workspaces.iter().find(|&x| x.focused).expect("unable to find focused workspace").num;
let workspaces = Connection::new()
.expect("unable to connect to sway IPC")
.get_workspaces()
.expect("Unable to parse sway workspaces");
let current_ws = workspaces
.iter()
.find(|&x| x.focused)
.expect("unable to find focused workspace")
.num;
let mut workspaces_info: Vec<WorkspaceInfo> = vec![];
for workspace in workspaces {
let mut ws_position = 'c';
if current_ws.lt(&workspace.num) {
ws_position = 'r';
} else if current_ws.gt(&workspace.num){
} else if current_ws.gt(&workspace.num) {
ws_position = 'l';
}
let ws_profile = get_workspace_profile(workspace.num as usize, profile_list.clone(), kb_order);
let ws_profile_num = profile::index_from_name(profile_list.clone(), ws_profile.clone()).expect("could not determine profile index of workspace");
let workspace_info = WorkspaceInfo { num: workspace.num, name: workspace.name, profile: ws_profile_num, profile_name: ws_profile, is_focused: workspace.focused, position: ws_position };
let ws_profile =
get_workspace_profile(workspace.num as usize, profile_list.clone(), kb_order);
let ws_profile_num = profile::index_from_name(profile_list.clone(), ws_profile.clone())
.expect("could not determine profile index of workspace");
let workspace_info = WorkspaceInfo {
num: workspace.num,
name: workspace.name,
profile: ws_profile_num,
profile_name: ws_profile,
is_focused: workspace.focused,
position: ws_position,
};
workspaces_info.push(workspace_info);
}
workspaces_info.sort_by(|a, b| a.num.cmp(&b.num));
json!(workspaces_info)
}
}

View file

@ -1,36 +1,32 @@
#![warn(unused_crate_dependencies)]
#![allow(clippy::style)]
//#![allow(clippy::style)]
mod config;
mod utils;
#[path = "lib/mod.rs"]
mod lib;
mod utils;
use {
crate::{
lib::{
Cli,
Commands,
launch,
lock_screen,
sway_fn,
profile_fn,
run_sway_command,
power_fn,
shortcuts_fn
},
config::Config,
utils::{setup_runtime_dir,get_xdg_dirs}
lib::{
Cli, Commands, launch, lock_screen, power_fn, profile_fn, run_sway_command,
shortcuts_fn, sway_fn,
},
utils::{get_xdg_dirs, setup_runtime_dir},
},
clap::Parser,
log::{debug, error},
std::process::exit
std::process::exit,
};
fn main() -> Result<(),()> {
fn main() -> Result<(), ()> {
env_logger::init();
let cli = Cli::parse();
let config = if confy::get_configuration_file_path("sway-de-utils", "config").expect("Unable to determine config file location").exists() {
let config = if confy::get_configuration_file_path("sway-de-utils", "config")
.expect("Unable to determine config file location")
.exists()
{
confy::load("sway-de-utils", "config").unwrap_or_default()
} else {
let _ = confy::store("sway-de-utils", "config", Config::default());
@ -38,12 +34,23 @@ fn main() -> Result<(),()> {
};
let sdu_result = match &cli.command {
Commands::Sway { sway_command } => sway_fn(sway_command, &config.window_icons, config.profiles, config.preserve_keyboard_order),
Commands::Sway { sway_command } => sway_fn(
sway_command,
&config.window_icons,
config.profiles,
config.preserve_keyboard_order,
),
Commands::Launch { program } => launch(program, config.profiles, config.programs),
Commands::Lock { force_render_background } => lock_screen(config.lock, force_render_background.unwrap_or_default()),
Commands::Profile { profile_command} => profile_fn(profile_command, config.profiles, config.preserve_keyboard_order),
Commands::Lock {
force_render_background,
} => lock_screen(config.lock, force_render_background.unwrap_or_default()),
Commands::Profile { profile_command } => profile_fn(
profile_command,
config.profiles,
config.preserve_keyboard_order,
),
Commands::Shortcuts { shortcut_command } => shortcuts_fn(shortcut_command, config.scripts),
Commands::Power { power_command } => power_fn(power_command, config.lock)
Commands::Power { power_command } => power_fn(power_command, config.lock),
};
match sdu_result {
Ok(_) => debug!("Command ran successfully"),

View file

@ -1,16 +1,13 @@
use {
log::debug,
xdg::BaseDirectories
};
use {log::debug, xdg::BaseDirectories};
#[derive(Debug)]
pub struct SDUError {
pub message: String
pub message: String,
}
pub enum DirectoryType {
Cache,
Config
Config,
}
pub fn setup_runtime_dir(xdg_directories: BaseDirectories) {
@ -22,4 +19,4 @@ pub fn setup_runtime_dir(xdg_directories: BaseDirectories) {
pub fn get_xdg_dirs() -> BaseDirectories {
BaseDirectories::new()
}
}