Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though

This commit is contained in:
Ryan Ward
2026-05-07 11:29:34 -05:00
parent f4e2475c60
commit a41cbd8cc7
2433 changed files with 14208 additions and 9409 deletions
@@ -0,0 +1,166 @@
use std::{env, path::PathBuf};
use tini::Ini;
static CURSOR_DIR_NAME: &'static &str = &"cursors";
static CURSOR_INDEX_FILE_NAME: &'static &str = &"index.theme";
static THEME_FILE_CURSOR_SECTION: &'static &str = &"Icon Theme";
static THEME_FILE_INHERITS_KEY: &'static &str = &"Inherits";
static ENV_DATA_DIRS: &'static &str = &"XDG_DATA_DIRS";
static ENV_CURSOR_THEME: &'static &str = &"XCURSOR_THEME";
static DEFAULT_THEME: &'static &str = &"default";
static KNOWN_THEMES: &[&str] = &["Yaru", "Adwaita"];
pub fn ensure_cursor_theme() {
// If the XCURSOR_THEME value is explicitly set,
// then we do not want to modify the user's environment
if env::var(ENV_CURSOR_THEME).is_ok() {
return;
}
let crawler = CursorThemeCrawler::new();
if let Some(theme) = crawler.determine_cursor_theme() {
// winit and it's dependencies will automatically check for
// the default theme, so we do not need to mess with the
// env var here.
if theme != *DEFAULT_THEME {
env::set_var(ENV_CURSOR_THEME, theme);
}
}
}
struct CursorThemeCrawler {
/// Directories to search when looking for a cursor theme.
/// Directories are searched in vec order from first to last.
/// However, because themes can reference other themes, it is
/// possible for search results to traverse multiple directories.
/// For example, a default theme can be found in directories[1]
/// that inherits from a theme in directories[3], which itself
/// inherits from a theme in directories[0]
directories: Vec<PathBuf>,
}
fn non_empty_var(name: &str) -> Option<String> {
env::var(name).ok().filter(|val| !val.is_empty())
}
impl CursorThemeCrawler {
pub fn new() -> Self {
// Per https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#directory_layout,
// we search:
// - $HOME/.icons (for backwards compatibility)
// - $XDG_DATA_HOME/icons (technically this should be part of XDG_DATA_DIRS, but we add it in here)
// - Defaults to $HOME/.local/share
// - $XDG_DATA_DIRS/icons
// - Defaults to /usr/local/share/:/usr/share/
// - /usr/share/pixmaps
let mut directories = vec![];
let xdg_data_dirs = non_empty_var(ENV_DATA_DIRS)
.or_else(|| Some("/usr/local/share/:/usr/share/".to_string()));
if let Some(home) = dirs::home_dir() {
directories.push(home.join(".icons"));
}
if let Some(xdg_data_home) = dirs::data_dir() {
directories.push(xdg_data_home.join("icons"));
}
if let Some(xdg_data_dirs) = xdg_data_dirs {
for dir in xdg_data_dirs.split(':') {
if !dir.is_empty() {
directories.push(PathBuf::from(dir).join("icons"));
}
}
}
directories.push(PathBuf::from("/usr/share/pixmaps"));
Self { directories }
}
/// First checks to see if there is a default cursor theme set.
/// If there is no default set, we check a list of known themes.
/// The first theme to be confirmed exist is returned, else None
/// is returned.
fn determine_cursor_theme(&self) -> Option<String> {
if self.check_cursor_theme(DEFAULT_THEME) {
return Some(DEFAULT_THEME.to_string());
}
for theme in KNOWN_THEMES {
if self.check_cursor_theme(theme) {
return Some((*theme).to_string());
}
}
None
}
/// Returns true if an icon theme exists and has a `cursors/`
/// folder, indicating that the cursors for that theme are installed.
/// Per the specification, an icon theme can exist along multiple
/// directories. As long as at least one of those directories
/// contains the `cursors/` subdir, we consider it valid
fn check_cursor_theme_installed(&self, theme: &str) -> bool {
for dir in &self.directories {
if dir.join(theme).join(CURSOR_DIR_NAME).exists() {
return true;
}
}
false
}
/// Checks that a given icon theme is a valid cursor theme.
///
/// When we check a cursor theme, we verify that either:
/// a. The icon theme has a cursors/ folder
/// b. The icon theme inherits from an existing cursor theme.
///
/// This can cause us to traverse multiple themes as part of our validation.
/// we do this verification to handle cases like the `adwaita-icon-theme`
/// deb packages, which sets Adwaita to the default icon theme without
/// installing a cursor theme.
fn check_cursor_theme(&self, root_theme: &str) -> bool {
let mut visited = std::collections::HashSet::from([root_theme.to_string()]);
let mut pending = std::collections::VecDeque::from([root_theme.to_string()]);
while let Some(theme) = pending.pop_front() {
if self.check_cursor_theme_installed(&theme) {
return true;
}
// Per the spec, the **first** index.theme found when traversing
// the directories is used
let inherited_themes = &self
.directories
.iter()
.filter_map(|index_dir| {
let index_path = index_dir.join(&theme).join(CURSOR_INDEX_FILE_NAME);
if let Ok(theme_file) = Ini::from_file(&index_path) {
theme_file.get_vec_with_sep::<String>(
THEME_FILE_CURSOR_SECTION,
THEME_FILE_INHERITS_KEY,
",",
)
} else {
None
}
})
.next();
if let Some(inherited_themes) = inherited_themes {
for new_theme in inherited_themes {
if !visited.contains(new_theme) {
visited.insert(new_theme.clone());
pending.push_back(new_theme.clone());
}
}
}
}
false
}
}
#[cfg(test)]
#[path = "cursor_theme_tests.rs"]
mod tests;