Skip to content

Commit

Permalink
enh(icons): improve icon extraction performance
Browse files Browse the repository at this point in the history
  • Loading branch information
eythaann committed Jan 7, 2025
1 parent e744dda commit ff8260f
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 146 deletions.
16 changes: 13 additions & 3 deletions src/apps/seelen_rofi/modules/launcher/infra/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import { SeelenCommand } from '@seelen-ui/lib';
import { path } from '@tauri-apps/api';
import { convertFileSrc, invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { Dropdown, Menu } from 'antd';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';

import { OverflowTooltip } from 'src/apps/shared/components/OverflowTooltip';
import { useIcon } from 'src/apps/shared/hooks';

import { StartMenuApp } from '../../shared/store/domain';

const MISSING_ICON_SRC = convertFileSrc(await path.resolveResource('static/icons/missing.png'));

export const Item = memo(({ item, hidden }: { item: StartMenuApp; hidden: boolean }) => {
const { label, icon, path } = item;
const { path, umid } = item;

const { t } = useTranslation();

const icon = useIcon({ path, umid: umid ?? undefined });

function onClick() {
invoke(SeelenCommand.OpenFile, { path });
getCurrentWindow().hide();
}

const parts = path.split('\\');
const filename = parts.at(-1);

const shortPath = path.slice(path.indexOf('\\Programs\\') + 10);
const displayName = filename?.slice(0, filename.lastIndexOf('.')) || filename || '';

return (
<Dropdown
Expand Down Expand Up @@ -50,8 +60,8 @@ export const Item = memo(({ item, hidden }: { item: StartMenuApp; hidden: boolea
className="launcher-item"
onClick={onClick}
>
<img className="launcher-item-icon" src={convertFileSrc(icon)} alt={label} />
<OverflowTooltip className="launcher-item-label" text={label} />
<img className="launcher-item-icon" src={icon || MISSING_ICON_SRC} />
<OverflowTooltip className="launcher-item-label" text={displayName} />
<OverflowTooltip className="launcher-item-path" text={shortPath} />
</button>
</Dropdown>
Expand Down
2 changes: 1 addition & 1 deletion src/apps/seelen_rofi/modules/launcher/infra/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function Launcher() {
<Item
key={item.path}
item={item}
hidden={!item.label.toLowerCase().includes(command)}
hidden={!item.path.toLowerCase().includes(command)}
/>
))}
</div>
Expand Down
3 changes: 1 addition & 2 deletions src/apps/seelen_rofi/modules/shared/store/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { SeelenLauncherSettings } from '@seelen-ui/lib/types';
import { IRootState } from 'src/shared.interfaces';

export interface StartMenuApp {
label: string;
icon: string;
path: string;
umid: string | null;
}

export interface LauncherState extends IRootState<SeelenLauncherSettings> {
Expand Down
2 changes: 1 addition & 1 deletion src/apps/shared/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function useIcon(args: { path: string; umid?: string }): string | null {
// this will run asynchronously on end `iconPackManager.onChange` will be triggered
IconPackManager.extractIcon(args);
}
}, [iconSrc]);
}, []);

return iconSrc;
}
3 changes: 2 additions & 1 deletion src/apps/toolbar/modules/item/infra/Inner.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SeelenCommand } from '@seelen-ui/lib';
import { ToolbarItem } from '@seelen-ui/lib/types';
import { convertFileSrc, invoke } from '@tauri-apps/api/core';
import { Tooltip } from 'antd';
Expand Down Expand Up @@ -92,7 +93,7 @@ class StringToElement extends React.PureComponent<StringToElementProps, StringTo
if (this.isExe()) {
const [_, _size, path] = this.props.text.split(StringToElement.splitter);
if (path) {
invoke<string | null>('get_icon', { path })
invoke<string | null>(SeelenCommand.GetIcon, { path })
.then(this.setExeIcon.bind(this))
.catch(console.error);
}
Expand Down
14 changes: 10 additions & 4 deletions src/background/exposed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::seelen_weg::icon_extractor::{
use crate::utils::{
is_running_as_appx_package, is_virtual_desktop_supported as virtual_desktop_supported,
};
use crate::windows_api::window::Window;
use crate::windows_api::WindowsApi;
use crate::winevent::{SyntheticFullscreenData, WinEvent};
use crate::{log_error, utils};
Expand Down Expand Up @@ -128,10 +129,15 @@ fn send_keys(keys: String) -> Result<()> {
}

#[tauri::command(async)]
fn get_icon(path: String) -> Option<PathBuf> {
if path.starts_with("shell:AppsFolder") {
let umid = path.replace("shell:AppsFolder\\", "");
return extract_and_save_icon_umid(&umid).ok();
fn get_icon(path: String, umid: Option<String>) -> Option<PathBuf> {
if let Some(umid) = umid {
if WindowsApi::is_uwp_package_id(&umid) {
return extract_and_save_icon_umid(&umid).ok();
}
let shortcut = Window::search_shortcut_with_same_umid(&umid);
if let Some(shortcut) = shortcut {
return extract_and_save_icon_from_file(&shortcut).ok();
}
}
extract_and_save_icon_from_file(&path).ok()
}
Expand Down
6 changes: 2 additions & 4 deletions src/background/seelen_rofi/handler.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use crate::{seelen::SEELEN, trace_lock};

use super::SeelenRofiApp;
use crate::{modules::start::domain::StartMenuItem, seelen::SEELEN, trace_lock};

#[tauri::command(async)]
pub fn launcher_get_apps() -> Vec<SeelenRofiApp> {
pub fn launcher_get_apps() -> Vec<StartMenuItem> {
if let Some(rofi) = trace_lock!(SEELEN).rofi() {
return rofi.apps.clone();
}
Expand Down
67 changes: 7 additions & 60 deletions src/background/seelen_rofi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
pub mod cli;
pub mod handler;

use std::{ffi::OsStr, path::PathBuf};

use base64::Engine;
use serde::Serialize;
use tauri::{path::BaseDirectory, Manager, WebviewWindow};
use tauri::WebviewWindow;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE;

use crate::{
error_handler::Result, log_error, seelen::get_app_handle,
seelen_weg::icon_extractor::extract_and_save_icon_from_file, utils::constants::Icons,
error_handler::Result,
log_error,
modules::start::{application::START_MENU_ITEMS, domain::StartMenuItem},
seelen::get_app_handle,
windows_api::WindowsApi,
};

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SeelenRofiApp {
pub label: String,
pub icon: PathBuf,
pub path: PathBuf,
}

pub struct SeelenRofi {
window: WebviewWindow,
pub apps: Vec<SeelenRofiApp>,
pub apps: Vec<StartMenuItem>,
}

impl Drop for SeelenRofi {
Expand All @@ -42,55 +33,11 @@ impl SeelenRofi {
log::info!("Creating {}", Self::TARGET);
Ok(Self {
// apps should be loaded first because it takes a long time on start and its needed by webview
apps: Self::load_apps()?,
apps: START_MENU_ITEMS.load().to_vec(),
window: Self::create_window()?,
})
}

fn load_dir(dir: PathBuf) -> Result<Vec<SeelenRofiApp>> {
let mut apps = Vec::new();
for entry in std::fs::read_dir(dir)?.flatten() {
let file_type = entry.file_type()?;
let path = entry.path();

if file_type.is_dir() {
match Self::load_dir(path) {
Ok(app) => apps.extend(app),
Err(e) => log::error!("{:?}", e),
}
continue;
}

if file_type.is_file() && path.extension() != Some(OsStr::new("ini")) {
apps.push(SeelenRofiApp {
label: path.file_stem().unwrap().to_string_lossy().to_string(),
icon: extract_and_save_icon_from_file(&path)
.unwrap_or_else(|_| Icons::missing_app()),
path,
})
}
}
Ok(apps)
}

fn load_apps() -> Result<Vec<SeelenRofiApp>> {
let mut result = Vec::new();

let apps = Self::load_dir(PathBuf::from(
r"C:\ProgramData\Microsoft\Windows\Start Menu\Programs",
))?;
result.extend(apps);

let apps = Self::load_dir(get_app_handle().path().resolve(
r"Microsoft\Windows\Start Menu\Programs",
BaseDirectory::Data,
)?)?;
result.extend(apps);

result.sort_by_key(|app| app.label.to_lowercase());
Ok(result)
}

pub fn show(&self) -> Result<()> {
let rc_monitor = WindowsApi::monitor_info(WindowsApi::monitor_from_cursor_point())?
.monitorInfo
Expand Down
12 changes: 9 additions & 3 deletions src/background/seelen_weg/icon_extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,15 +268,19 @@ pub fn extract_and_save_icon_from_file<T: AsRef<Path>>(path: T) -> Result<PathBu
// try get icons for URLs
if ext == Some(OsStr::new("url")) {
let icon = get_icon_from_url_file(path)?;
state.add_system_icon(path.to_string_lossy().as_ref(), &icon_filename);
state.write_system_icon_pack()?;
state.skip_modification(icon_path.clone());
icon.save(&icon_path)?;
state.push_and_save_system_icon(path.to_string_lossy().as_ref(), &icon_filename)?;
return Ok(icon_path);
}

// try get the icon directly from the file
if let Ok(icon) = get_icon_from_file(path) {
state.add_system_icon(path.to_string_lossy().as_ref(), &icon_filename);
state.write_system_icon_pack()?;
state.skip_modification(icon_path.clone());
icon.save(&icon_path)?;
state.push_and_save_system_icon(path.to_string_lossy().as_ref(), &icon_filename)?;
return Ok(icon_path);
}

Expand Down Expand Up @@ -305,7 +309,9 @@ pub fn extract_and_save_icon_umid<T: AsRef<str>>(app_umid: T) -> Result<PathBuf>
.icons_path()
.join("system")
.join(&relative_path);
state.add_system_icon(app_umid, &relative_path);
state.write_system_icon_pack()?;
state.skip_modification(image_path.clone());
std::fs::copy(app_icon, &image_path)?;
state.push_and_save_system_icon(app_umid, &relative_path)?;
Ok(image_path)
}
10 changes: 1 addition & 9 deletions src/background/state/application/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use itertools::Itertools;
use seelen_core::handlers::SeelenEvent;
use tauri::Emitter;

use crate::{error_handler::Result, seelen::get_app_handle, trace_lock};
use crate::{error_handler::Result, seelen::get_app_handle};

use super::FullState;

Expand Down Expand Up @@ -43,12 +43,4 @@ impl FullState {
get_app_handle().emit(SeelenEvent::StateHistoryChanged, self.launcher_history())?;
Ok(())
}

pub(super) fn emit_icon_packs(&self) -> Result<()> {
get_app_handle().emit(
SeelenEvent::StateIconPacksChanged,
trace_lock!(self.icon_packs()).values().collect_vec(),
)?;
Ok(())
}
}
41 changes: 27 additions & 14 deletions src/background/state/application/icons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,25 @@ use std::{
path::{Path, PathBuf},
};

use seelen_core::state::IconPack;
use itertools::Itertools;
use seelen_core::{handlers::SeelenEvent, state::IconPack};
use tauri::Emitter;

use crate::{error_handler::Result, trace_lock, utils::constants::SEELEN_COMMON};
use crate::{
error_handler::Result, seelen::get_app_handle, trace_lock, utils::constants::SEELEN_COMMON,
};

use super::FullState;

impl FullState {
pub fn emit_icon_packs(&self) -> Result<()> {
get_app_handle().emit(
SeelenEvent::StateIconPacksChanged,
trace_lock!(self.icon_packs()).values().collect_vec(),
)?;
Ok(())
}

fn load_icon_pack_from_dir(dir_path: &Path) -> Result<IconPack> {
let file = dir_path.join("metadata.yml");
if !file.exists() {
Expand Down Expand Up @@ -45,32 +57,33 @@ impl FullState {
icon_pack.info.description = "Icons from Windows and Program Files".to_string();
icon_pack.info.filename = "system".to_string();

self.write_system_icon_pack(&icon_pack)?;
trace_lock!(self.icon_packs).insert(icon_pack.info.filename.clone(), icon_pack);
self.write_system_icon_pack()?;
}
Ok(())
}

pub fn write_system_icon_pack(&self, icon_pack: &IconPack) -> Result<()> {
pub fn write_system_icon_pack(&self) -> Result<()> {
let folder = SEELEN_COMMON.icons_path().join("system");
let file_path = folder.join("metadata.yml");
std::fs::create_dir_all(&folder)?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(folder.join("metadata.yml"))?;
serde_yaml::to_writer(&mut file, icon_pack)?;
.open(&file_path)?;
let icons = trace_lock!(self.icon_packs);
let system_pack = icons.get("system").unwrap();
self.skip_modification(file_path);
serde_yaml::to_writer(&mut file, system_pack)?;
Ok(())
}

pub fn push_and_save_system_icon(&self, key: &str, icon: &Path) -> Result<()> {
let mut icon_packs = trace_lock!(self.icon_packs);
let default_icon_pack = icon_packs.get_mut("system").unwrap();
default_icon_pack
.apps
.insert(key.trim_start_matches(r"\\?\").to_string(), icon.to_owned());
self.write_system_icon_pack(default_icon_pack)?;
Ok(())
pub fn add_system_icon(&self, key: &str, icon: &Path) {
let mut icons = trace_lock!(self.icon_packs);
let system_pack = icons.get_mut("system").unwrap();
let key: String = key.trim_start_matches(r"\\?\").to_string();
system_pack.apps.insert(key, icon.to_owned());
}

/// Get icon pack by app user model id, filename or path
Expand Down
Loading

0 comments on commit ff8260f

Please sign in to comment.