1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-03 19:51:03 +02:00

feat: new focused window module

This commit is contained in:
Jake Stanger 2022-08-14 20:40:11 +01:00
parent e416e03b0a
commit dc14cb003f
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
13 changed files with 222 additions and 88 deletions

View file

@ -1,142 +0,0 @@
use gtk::gdk_pixbuf::Pixbuf;
use gtk::prelude::*;
use gtk::{IconLookupFlags, IconTheme};
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::BufRead;
use std::path::PathBuf;
use walkdir::WalkDir;
/// Gets directories that should contain `.desktop` files
/// and exist on the filesystem.
fn find_application_dirs() -> Vec<PathBuf> {
let mut dirs = vec![PathBuf::from("/usr/share/applications")];
let user_dir = dirs::data_local_dir();
if let Some(mut user_dir) = user_dir {
user_dir.push("applications");
dirs.push(user_dir);
}
dirs.into_iter().filter(|dir| dir.exists()).collect()
}
/// Attempts to locate a `.desktop` file for an app id
/// (or app class).
///
/// A simple case-insensitive check is performed on filename == `app_id`.
pub fn find_desktop_file(app_id: &str) -> Option<PathBuf> {
let dirs = find_application_dirs();
for dir in dirs {
let mut walker = WalkDir::new(dir).max_depth(5).into_iter();
let entry = walker.find(|entry| match entry {
Ok(entry) => {
let file_name = entry.file_name().to_string_lossy().to_lowercase();
let test_name = format!("{}.desktop", app_id.to_lowercase());
file_name == test_name
}
_ => false,
});
if let Some(Ok(entry)) = entry {
let path = entry.path().to_owned();
return Some(path);
}
}
None
}
/// Parses a desktop file into a flat hashmap of keys/values.
fn parse_desktop_file(path: PathBuf) -> io::Result<HashMap<String, String>> {
let file = File::open(path)?;
let lines = io::BufReader::new(file).lines();
let mut map = HashMap::new();
for line in lines.flatten() {
let is_pair = line.contains('=');
if is_pair {
let (key, value) = line.split_once('=').unwrap();
map.insert(key.to_string(), value.to_string());
}
}
Ok(map)
}
/// Attempts to get the icon name from the app's `.desktop` file.
fn get_desktop_icon_name(app_id: &str) -> Option<String> {
match find_desktop_file(app_id) {
Some(file) => {
let map = parse_desktop_file(file);
match map {
Ok(map) => map.get("Icon").map(std::string::ToString::to_string),
Err(_) => None,
}
}
None => None,
}
}
enum IconLocation {
Theme(String),
File(PathBuf),
}
fn get_icon_location(theme: &IconTheme, app_id: &str, size: i32) -> Option<IconLocation> {
let has_icon = theme
.lookup_icon(app_id, size, IconLookupFlags::empty())
.is_some();
if has_icon {
return Some(IconLocation::Theme(app_id.to_string()));
}
let is_steam_game = app_id.starts_with("steam_app_");
if is_steam_game {
let steam_id: String = app_id.chars().skip("steam_app_".len()).collect();
let home_dir = dirs::data_dir().unwrap();
let path = home_dir.join(format!(
"icons/hicolor/32x32/apps/steam_icon_{}.png",
steam_id
));
return Some(IconLocation::File(path));
}
let icon_name = get_desktop_icon_name(app_id);
if let Some(icon_name) = icon_name {
let is_path = PathBuf::from(&icon_name).exists();
return if is_path {
Some(IconLocation::File(PathBuf::from(icon_name)))
} else {
return Some(IconLocation::Theme(icon_name));
};
}
None
}
/// Gets the icon associated with an app.
pub fn get_icon(theme: &IconTheme, app_id: &str, size: i32) -> Option<Pixbuf> {
let icon_location = get_icon_location(theme, app_id, size);
match icon_location {
Some(IconLocation::Theme(icon_name)) => {
let icon = theme.load_icon(&icon_name, size, IconLookupFlags::FORCE_SIZE);
match icon {
Ok(icon) => icon,
Err(_) => None,
}
}
Some(IconLocation::File(path)) => Pixbuf::from_file_at_scale(path, size, size, true).ok(),
None => None,
}
}

View file

@ -1,9 +1,9 @@
use crate::collection::Collection;
use crate::modules::launcher::icon::{find_desktop_file, get_icon};
use crate::modules::launcher::node::SwayNode;
use crate::icon::{find_desktop_file, get_icon};
use crate::modules::launcher::popup::Popup;
use crate::modules::launcher::FocusEvent;
use crate::popup::PopupAlignment;
use crate::sway::SwayNode;
use gtk::prelude::*;
use gtk::{Button, IconTheme, Image};
use std::process::{Command, Stdio};

View file

@ -1,13 +1,12 @@
mod icon;
mod item;
mod node;
mod popup;
use crate::collection::Collection;
use crate::modules::launcher::item::{ButtonConfig, LauncherItem, LauncherWindow};
use crate::modules::launcher::node::{get_open_windows, SwayNode};
use crate::modules::launcher::popup::Popup;
use crate::modules::{Module, ModuleInfo};
use crate::sway::node::get_open_windows;
use crate::sway::{SwayNode, WindowEvent};
use gtk::prelude::*;
use gtk::{IconTheme, Orientation};
use ksway::{Client, IpcEvent};
@ -20,28 +19,14 @@ use tokio::task::spawn_blocking;
#[derive(Debug, Deserialize, Clone)]
pub struct LauncherModule {
favorites: Option<Vec<String>>,
#[serde(default = "default_false")]
#[serde(default = "crate::config::default_false")]
show_names: bool,
#[serde(default = "default_true")]
#[serde(default = "crate::config::default_true")]
show_icons: bool,
icon_theme: Option<String>,
}
const fn default_false() -> bool {
false
}
const fn default_true() -> bool {
true
}
#[derive(Debug, Deserialize)]
struct WindowEvent {
change: String,
container: SwayNode,
}
#[derive(Debug)]
pub enum FocusEvent {
AppId(String),
@ -181,7 +166,6 @@ impl Launcher {
} else {
windows.get_mut(&window.id).unwrap().name = Some(name);
}
}
}
@ -207,7 +191,12 @@ impl Module<gtk::Box> for LauncherModule {
let mut sway = Client::connect().unwrap();
let popup = Popup::new("popup-launcher", info.app, Orientation::Vertical, info.bar_position);
let popup = Popup::new(
"popup-launcher",
info.app,
Orientation::Vertical,
info.bar_position,
);
let container = gtk::Box::new(Orientation::Horizontal, 0);
let (ui_tx, mut ui_rx) = mpsc::channel(32);

View file

@ -1,65 +0,0 @@
use ksway::{Client, IpcCommand};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct SwayNode {
#[serde(rename = "type")]
pub node_type: String,
pub id: i32,
pub name: Option<String>,
pub app_id: Option<String>,
pub focused: bool,
pub urgent: bool,
pub nodes: Vec<SwayNode>,
pub floating_nodes: Vec<SwayNode>,
pub shell: Option<String>,
pub window_properties: Option<WindowProperties>,
}
#[derive(Debug, Deserialize)]
pub struct WindowProperties {
pub class: String,
}
impl SwayNode {
pub fn get_id(&self) -> &str {
self.app_id.as_ref().map_or_else(
|| {
&self
.window_properties
.as_ref()
.expect("cannot find node name")
.class
},
|app_id| app_id,
)
}
pub fn is_xwayland(&self) -> bool {
self.shell == Some(String::from("xwayland"))
}
}
fn check_node(node: SwayNode, window_nodes: &mut Vec<SwayNode>) {
if node.name.is_some() && (node.node_type == "con" || node.node_type == "floating_con") {
window_nodes.push(node);
} else {
node.nodes.into_iter().for_each(|node| {
check_node(node, window_nodes);
});
node.floating_nodes.into_iter().for_each(|node| {
check_node(node, window_nodes);
});
}
}
pub fn get_open_windows(sway: &mut Client) -> Vec<SwayNode> {
let raw = sway.ipc(IpcCommand::GetTree).unwrap();
let root_node = serde_json::from_slice::<SwayNode>(&raw).unwrap();
let mut window_nodes = vec![];
check_node(root_node, &mut window_nodes);
window_nodes
}