diff --git a/dash-frontend/assets/gui/tab/games.xml b/dash-frontend/assets/gui/tab/games.xml
index 2ee00a03..8d5b2fa7 100644
--- a/dash-frontend/assets/gui/tab/games.xml
+++ b/dash-frontend/assets/gui/tab/games.xml
@@ -1,8 +1,10 @@
+
+
\ No newline at end of file
diff --git a/dash-frontend/assets/gui/tab/monado.xml b/dash-frontend/assets/gui/tab/monado.xml
index cd89474c..35907a30 100644
--- a/dash-frontend/assets/gui/tab/monado.xml
+++ b/dash-frontend/assets/gui/tab/monado.xml
@@ -31,7 +31,7 @@
-
+
diff --git a/dash-frontend/assets/gui/view/game_list.xml b/dash-frontend/assets/gui/view/game_list.xml
index eee56463..d7b334c4 100644
--- a/dash-frontend/assets/gui/view/game_list.xml
+++ b/dash-frontend/assets/gui/view/game_list.xml
@@ -1,7 +1,7 @@
-
-
-
+
\ No newline at end of file
diff --git a/dash-frontend/assets/gui/view/running_games_list.xml b/dash-frontend/assets/gui/view/running_games_list.xml
new file mode 100644
index 00000000..8a2c18e0
--- /dev/null
+++ b/dash-frontend/assets/gui/view/running_games_list.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json
index 323195ef..e342707e 100644
--- a/dash-frontend/assets/lang/de.json
+++ b/dash-frontend/assets/lang/de.json
@@ -111,12 +111,13 @@
"HIDE": "Verbergen",
"REMOVE": "Entfernen",
"SHOW": "Anzeigen",
- "PROCESS_LIST": {},
"FAILED_TO_LAUNCH_APPLICATION": "Fehler beim Starten der Anwendung:",
"APPLICATION_STARTED": "Anwendung gestartet",
"CLOSE_WINDOW": "Fenster schließen",
"GAME_LIST": {
- "NO_GAMES_FOUND": "Keine Spiele gefunden"
+ "NO_GAMES_FOUND": "Keine Spiele gefunden",
+ "RUNNING_GAMES_LIST": "Liste der laufenden Spiele",
+ "NO_RUNNING_GAME_FOUND": "Kein laufendes Spiel gefunden"
},
"TERMINATE_PROCESS": "Prozess beenden",
"GAME_LAUNCHED": "Spiel gestartet",
@@ -146,5 +147,11 @@
"AUTOSTART": "Automatisch beim Start ausführen",
"LAUNCH": "Starten"
},
- "DISPLAY_BRIGHTNESS": "Bildschirmhelligkeit"
+ "DISPLAY_BRIGHTNESS": "Bildschirmhelligkeit",
+ "PROCESS_LIST": "Prozessliste",
+ "REFRESH": "Aktualisieren",
+ "PROCESS": {
+ "STOP": "Stopp",
+ "FORCE_KILL": "Erzwinge Beenden"
+ }
}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json
index f94e11a9..07735226 100644
--- a/dash-frontend/assets/lang/en.json
+++ b/dash-frontend/assets/lang/en.json
@@ -31,7 +31,7 @@
"APP_SETTINGS": {
"ALLOW_SLIDING": "Stick interaction during grab",
"ANIMATION_SPEED": "UI Animation speed",
- "UI_GRADIENT_INTENSITY": "UI Gradient intensity",
+ "AUTOSTART_APPS": "Apps to run on startup",
"BLOCK_GAME_INPUT": "Block game input",
"BLOCK_GAME_INPUT_HELP": "Blocks all input when an overlay is hovered",
"BLOCK_GAME_INPUT_IGNORE_WATCH": "Ignore watch when blocking input",
@@ -52,6 +52,8 @@
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
"FEATURES": "Features",
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
+ "HANDSFREE_POINTER": "Handsfree mode",
+ "HANDSFREE_POINTER_HELP": "Input to use when motion\ncontrollers are unavailable.\nLeft pinch is grab, right is click.",
"HIDE_GRAB_HELP": "Hide grab help",
"HIDE_USERNAME": "Hide username",
"INVERT_SCROLL_DIRECTION_X": "Invert horizontal scroll direction",
@@ -68,11 +70,11 @@
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
"OPAQUE_BACKGROUND": "Opaque background",
"OPTION": {
- "NONE": "None",
- "HMD_PINCH": "HMD + pinch",
- "EYE_PINCH": "Eye + pinch",
"AUTO": "Automatic",
"AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.",
+ "EYE_PINCH": "Eye + pinch",
+ "HMD_PINCH": "HMD + pinch",
+ "NONE": "None",
"PIPEWIRE_HELP": "Fast GPU capture,\nstandard on all desktops.",
"PW_FALLBACK_HELP": "Slow method with high CPU usage.\nTry in case PipeWire GPU doesn't work",
"SCREENCOPY_GPU_HELP": "Fast, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway",
@@ -90,6 +92,7 @@
"SPACE_DRAG_UNLOCKED": "Allow space drag on all axes",
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
"TROUBLESHOOTING": "Troubleshooting",
+ "UI_GRADIENT_INTENSITY": "UI Gradient intensity",
"UPRIGHT_SCREEN_FIX": "Upright screen fix",
"UPRIGHT_SCREEN_FIX_HELP": "Fixes upright screens on some desktops",
"USE_PASSTHROUGH": "Enable passthrough",
@@ -100,10 +103,7 @@
"XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity",
"XR_CLICK_SENSITIVITY_RELEASE": "XR release sensitivity",
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Must be lower than click",
- "XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default",
- "HANDSFREE_POINTER": "Handsfree mode",
- "HANDSFREE_POINTER_HELP": "Input to use when motion\ncontrollers are unavailable.\nLeft pinch is grab, right is click.",
- "AUTOSTART_APPS": "Apps to run on startup"
+ "XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default"
},
"APPLICATION_LAUNCHER": "Application launcher",
"APPLICATION_STARTED": "Application started",
@@ -127,7 +127,9 @@
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
"GAME_LAUNCHED": "Game launched",
"GAME_LIST": {
- "NO_GAMES_FOUND": "No games found"
+ "NO_GAMES_FOUND": "No games found",
+ "RUNNING_GAMES_LIST": "List of running games",
+ "NO_RUNNING_GAME_FOUND": "No running game found"
},
"GAMES": "Games",
"GENERAL_SETTINGS": "General settings",
@@ -140,6 +142,12 @@
"POPUP_ADD_DISPLAY": {
"RESOLUTION": "Resolution"
},
+ "PROCESS": {
+ "STOP": "Stop",
+ "FORCE_KILL": "Force-kill"
+ },
+ "PROCESS_LIST": "Process list",
+ "REFRESH": "Refresh",
"REMOVE": "Remove",
"SETTINGS": "Settings",
"SHOW": "Show",
diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json
index 54fee60f..7f1f7475 100644
--- a/dash-frontend/assets/lang/es.json
+++ b/dash-frontend/assets/lang/es.json
@@ -111,12 +111,13 @@
"HIDE": "Ocultar",
"REMOVE": "Eliminar",
"SHOW": "Mostrar",
- "PROCESS_LIST": {},
"FAILED_TO_LAUNCH_APPLICATION": "No se pudo iniciar la aplicación:",
"APPLICATION_STARTED": "Aplicación iniciada",
"CLOSE_WINDOW": "Cerrar ventana",
"GAME_LIST": {
- "NO_GAMES_FOUND": "No se encontraron juegos"
+ "NO_GAMES_FOUND": "No se encontraron juegos",
+ "RUNNING_GAMES_LIST": "Lista de juegos en ejecución",
+ "NO_RUNNING_GAME_FOUND": "No se encontró ningún juego en ejecución"
},
"TERMINATE_PROCESS": "Finalizar proceso",
"GAME_LAUNCHED": "Juego lanzado",
@@ -146,5 +147,11 @@
"AUTOSTART": "Ejecutar automáticamente al inicio",
"LAUNCH": "Iniciar"
},
- "DISPLAY_BRIGHTNESS": "Brillo de la pantalla"
+ "DISPLAY_BRIGHTNESS": "Brillo de la pantalla",
+ "PROCESS_LIST": "Lista de procesos",
+ "REFRESH": "Actualizar",
+ "PROCESS": {
+ "STOP": "Detener",
+ "FORCE_KILL": "Forzar cierre"
+ }
}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json
index 833efa16..7bd173c5 100644
--- a/dash-frontend/assets/lang/it.json
+++ b/dash-frontend/assets/lang/it.json
@@ -127,7 +127,9 @@
"FAILED_TO_LAUNCH_APPLICATION": "Impossibile avviare l'applicazione:",
"GAME_LAUNCHED": "Gioco lanciato",
"GAME_LIST": {
- "NO_GAMES_FOUND": "Nessun gioco trovato"
+ "NO_GAMES_FOUND": "Nessun gioco trovato",
+ "RUNNING_GAMES_LIST": "Lista dei giochi in esecuzione",
+ "NO_RUNNING_GAME_FOUND": "Nessun gioco in esecuzione trovato"
},
"GAMES": "Giochi",
"GENERAL_SETTINGS": "Impostazioni generali",
@@ -140,10 +142,15 @@
"POPUP_ADD_DISPLAY": {
"RESOLUTION": "Risoluzione"
},
- "PROCESS_LIST": {},
"REMOVE": "Rimuovi",
"SETTINGS": "Impostazioni",
"SHOW": "Mostra",
"TERMINATE_PROCESS": "Termina processo",
- "WIDTH": "Larghezza"
+ "WIDTH": "Larghezza",
+ "PROCESS_LIST": "Elenco processi",
+ "REFRESH": "Aggiorna",
+ "PROCESS": {
+ "STOP": "Interrompi",
+ "FORCE_KILL": "Uccidi forzatamente"
+ }
}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json
index 0b9309fa..c647069f 100644
--- a/dash-frontend/assets/lang/ja.json
+++ b/dash-frontend/assets/lang/ja.json
@@ -111,12 +111,13 @@
"HIDE": "隠す",
"REMOVE": "削除",
"SHOW": "表示",
- "PROCESS_LIST": {},
"FAILED_TO_LAUNCH_APPLICATION": "アプリケーションの起動に失敗しました:",
"APPLICATION_STARTED": "アプリケーションが起動しました",
"CLOSE_WINDOW": "ウィンドウを閉じる",
"GAME_LIST": {
- "NO_GAMES_FOUND": "ゲームが見つかりませんでした"
+ "NO_GAMES_FOUND": "ゲームが見つかりませんでした",
+ "RUNNING_GAMES_LIST": "実行中のゲーム一覧",
+ "NO_RUNNING_GAME_FOUND": "実行中のゲームが見つかりません"
},
"TERMINATE_PROCESS": "プロセスを終了する",
"GAME_LAUNCHED": "ゲームが起動しました",
@@ -146,5 +147,11 @@
"AUTOSTART": "起動時に自動実行",
"LAUNCH": "起動"
},
- "DISPLAY_BRIGHTNESS": "ディスプレイの明るさ"
+ "DISPLAY_BRIGHTNESS": "ディスプレイの明るさ",
+ "PROCESS_LIST": "プロセスリスト",
+ "REFRESH": "更新",
+ "PROCESS": {
+ "STOP": "停止",
+ "FORCE_KILL": "強制終了"
+ }
}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json
index a316309f..e9fb87f1 100644
--- a/dash-frontend/assets/lang/pl.json
+++ b/dash-frontend/assets/lang/pl.json
@@ -111,12 +111,13 @@
"SETTINGS": "Ustawienia",
"SHOW": "Pokaż",
"WIDTH": "Szerokość",
- "PROCESS_LIST": {},
"FAILED_TO_LAUNCH_APPLICATION": "Nie udało się uruchomić aplikacji:",
"APPLICATION_STARTED": "Aplikacja uruchomiona",
"CLOSE_WINDOW": "Zamknij okno",
"GAME_LIST": {
- "NO_GAMES_FOUND": "Nie znaleziono gier"
+ "NO_GAMES_FOUND": "Nie znaleziono gier",
+ "RUNNING_GAMES_LIST": "Lista uruchomionych gier",
+ "NO_RUNNING_GAME_FOUND": "Nie znaleziono uruchomionej gry"
},
"TERMINATE_PROCESS": "Zakończ proces",
"GAME_LAUNCHED": "Gra uruchomiona",
@@ -146,5 +147,11 @@
"AUTOSTART": "Uruchom automatycznie przy starcie",
"LAUNCH": "Uruchom"
},
- "DISPLAY_BRIGHTNESS": "Jasność wyświetlacza"
+ "DISPLAY_BRIGHTNESS": "Jasność wyświetlacza",
+ "PROCESS_LIST": "Lista procesów",
+ "REFRESH": "Odśwież",
+ "PROCESS": {
+ "STOP": "Zatrzymaj",
+ "FORCE_KILL": "Wymuś zakończenie"
+ }
}
\ No newline at end of file
diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json
index 5e5576cb..faf4b74e 100644
--- a/dash-frontend/assets/lang/zh_CN.json
+++ b/dash-frontend/assets/lang/zh_CN.json
@@ -127,7 +127,9 @@
"FAILED_TO_LAUNCH_APPLICATION": "启动应用失败:",
"GAME_LAUNCHED": "游戏已启动",
"GAME_LIST": {
- "NO_GAMES_FOUND": "未找到游戏"
+ "NO_GAMES_FOUND": "未找到游戏",
+ "RUNNING_GAMES_LIST": "正在运行的游戏列表",
+ "NO_RUNNING_GAME_FOUND": "未找到正在运行的游戏"
},
"GAMES": "游戏",
"GENERAL_SETTINGS": "通用设置",
@@ -140,10 +142,15 @@
"POPUP_ADD_DISPLAY": {
"RESOLUTION": "分辨率"
},
- "PROCESS_LIST": {},
"REMOVE": "移除",
"SETTINGS": "设置",
"SHOW": "显示",
"TERMINATE_PROCESS": "终止进程",
- "WIDTH": "宽度"
+ "WIDTH": "宽度",
+ "PROCESS_LIST": "进程列表",
+ "REFRESH": "刷新",
+ "PROCESS": {
+ "STOP": "停止",
+ "FORCE_KILL": "强制关闭"
+ }
}
\ No newline at end of file
diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs
index 190cfaaa..6fb66255 100644
--- a/dash-frontend/src/frontend.rs
+++ b/dash-frontend/src/frontend.rs
@@ -14,7 +14,11 @@ use wgui::{
widget::{label::WidgetLabel, rectangle::WidgetRectangle},
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
};
-use wlx_common::{audio, dash_interface::BoxDashInterface, timestep::Timestep};
+use wlx_common::{
+ audio,
+ dash_interface::BoxDashInterface,
+ timestep::{self, Timestep},
+};
use crate::{
assets,
@@ -218,8 +222,10 @@ impl Frontend {
self.process_task(&mut params, task)?;
}
+ let time_ms = timestep::get_micros() / 1000;
+
if let Some(mut tab) = self.current_tab.take() {
- tab.update(self, params.data)?;
+ tab.update(self, time_ms as u32, params.data)?;
self.current_tab = Some(tab);
}
diff --git a/dash-frontend/src/tab/apps.rs b/dash-frontend/src/tab/apps.rs
index c5ab3514..cd2a5150 100644
--- a/dash-frontend/src/tab/apps.rs
+++ b/dash-frontend/src/tab/apps.rs
@@ -46,7 +46,7 @@ impl Tab for TabApps {
TabType::Apps
}
- fn update(&mut self, frontend: &mut Frontend, data: &mut T) -> anyhow::Result<()> {
+ fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
let mut state = self.state.borrow_mut();
for task in self.tasks.drain() {
diff --git a/dash-frontend/src/tab/games.rs b/dash-frontend/src/tab/games.rs
index a7578038..fcdc3cad 100644
--- a/dash-frontend/src/tab/games.rs
+++ b/dash-frontend/src/tab/games.rs
@@ -9,7 +9,8 @@ use wgui::{
use crate::{
frontend::Frontend,
tab::{Tab, TabType},
- views::game_list,
+ util::steam_utils::SteamUtils,
+ views::{game_list, running_games_list},
};
pub struct TabGames {
@@ -17,6 +18,8 @@ pub struct TabGames {
pub state: ParserState,
view_game_list: game_list::View,
+ view_running_games_list: running_games_list::View,
+ steam_utils: SteamUtils,
marker: PhantomData,
}
@@ -25,17 +28,22 @@ impl Tab for TabGames {
TabType::Games
}
- fn update(&mut self, frontend: &mut Frontend, _data: &mut T) -> anyhow::Result<()> {
- self.view_game_list.update(&mut frontend.layout, &frontend.executor)?;
+ fn update(&mut self, frontend: &mut Frontend, time_ms: u32, _data: &mut T) -> anyhow::Result<()> {
+ self
+ .view_game_list
+ .update(&mut frontend.layout, &mut self.steam_utils, &frontend.executor)?;
+ self.view_running_games_list.update(&mut frontend.layout, time_ms)?;
Ok(())
}
}
impl TabGames {
pub fn new(frontend: &mut Frontend, parent_id: WidgetID) -> anyhow::Result {
+ let globals = frontend.layout.state.globals.clone();
+
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
- globals: frontend.layout.state.globals.clone(),
+ globals: globals.clone(),
path: AssetPath::BuiltIn("gui/tab/games.xml"),
extra: Default::default(),
},
@@ -44,19 +52,32 @@ impl TabGames {
)?;
let game_list_parent = state.get_widget_id("game_list_parent")?;
+ let id_running_games_list_parent = state.get_widget_id("running_games_list_parent")?;
let view_game_list = game_list::View::new(game_list::Params {
executor: frontend.executor.clone(),
frontend_tasks: frontend.tasks.clone(),
- globals: frontend.layout.state.globals.clone(),
+ globals: globals.clone(),
layout: &mut frontend.layout,
parent_id: game_list_parent,
})?;
+ let mut steam_utils = SteamUtils::new()?;
+
+ let view_running_games_list = running_games_list::View::new(running_games_list::Params {
+ globals: globals.clone(),
+ layout: &mut frontend.layout,
+ parent_id: id_running_games_list_parent,
+ steam_utils: &mut steam_utils,
+ frontend_tasks: frontend.tasks.clone(),
+ })?;
+
Ok(Self {
state,
view_game_list,
+ view_running_games_list,
marker: PhantomData,
+ steam_utils,
})
}
}
diff --git a/dash-frontend/src/tab/mod.rs b/dash-frontend/src/tab/mod.rs
index 9ca07b09..68e7fb03 100644
--- a/dash-frontend/src/tab/mod.rs
+++ b/dash-frontend/src/tab/mod.rs
@@ -19,7 +19,7 @@ pub trait Tab {
#[allow(dead_code)]
fn get_type(&self) -> TabType;
- fn update(&mut self, _: &mut Frontend, _: &mut T) -> anyhow::Result<()> {
+ fn update(&mut self, _frontend: &mut Frontend, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
Ok(())
}
}
diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs
index 30abf19c..844ad634 100644
--- a/dash-frontend/src/tab/monado.rs
+++ b/dash-frontend/src/tab/monado.rs
@@ -42,7 +42,7 @@ impl Tab for TabMonado {
TabType::Games
}
- fn update(&mut self, frontend: &mut Frontend, data: &mut T) -> anyhow::Result<()> {
+ fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
for task in self.tasks.drain() {
match task {
Task::Refresh => self.refresh(frontend, data)?,
diff --git a/dash-frontend/src/tab/settings.rs b/dash-frontend/src/tab/settings.rs
index 45564de7..91b54ebb 100644
--- a/dash-frontend/src/tab/settings.rs
+++ b/dash-frontend/src/tab/settings.rs
@@ -80,7 +80,7 @@ impl Tab for TabSettings {
TabType::Settings
}
- fn update(&mut self, frontend: &mut Frontend, data: &mut T) -> anyhow::Result<()> {
+ fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
let mut changed = false;
for task in self.tasks.drain() {
match task {
diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs
index c263cde1..67d18d3b 100644
--- a/dash-frontend/src/util/mod.rs
+++ b/dash-frontend/src/util/mod.rs
@@ -5,3 +5,4 @@ pub mod popup_manager;
pub mod steam_utils;
pub mod toast_manager;
pub mod various;
+pub mod wgui_simple;
diff --git a/dash-frontend/src/util/steam_utils.rs b/dash-frontend/src/util/steam_utils.rs
index cb86e778..a2c6e778 100644
--- a/dash-frontend/src/util/steam_utils.rs
+++ b/dash-frontend/src/util/steam_utils.rs
@@ -35,6 +35,7 @@ pub struct AppManifest {
// TODO @oo8dev: game sort methods
#[allow(dead_code)]
pub enum GameSortMethod {
+ None,
NameAsc,
NameDesc,
PlayDateDesc,
@@ -127,9 +128,6 @@ pub fn launch(app_id: &AppID) -> anyhow::Result<()> {
Ok(())
}
-// TODO @oo8dev: running games list (#398)
-/*
-
pub fn stop(app_id: AppID, force_kill: bool) -> anyhow::Result<()> {
log::info!("Stopping Steam game with AppID {}", app_id);
@@ -220,7 +218,7 @@ pub fn list_running_games() -> anyhow::Result> {
}
Ok(res)
-} */
+}
fn call_steam(arg: &str) -> anyhow::Result<()> {
match std::process::Command::new("xdg-open").arg(arg).spawn() {
@@ -286,6 +284,7 @@ impl SteamUtils {
.collect();
match sort_method {
+ GameSortMethod::None => {}
GameSortMethod::NameAsc => {
games.sort_by(|a, b| a.name.cmp(&b.name));
}
diff --git a/dash-frontend/src/util/wgui_simple.rs b/dash-frontend/src/util/wgui_simple.rs
new file mode 100644
index 00000000..f68a467f
--- /dev/null
+++ b/dash-frontend/src/util/wgui_simple.rs
@@ -0,0 +1,23 @@
+use wgui::{
+ i18n::Translation,
+ layout::{Layout, WidgetID},
+ renderer_vk::text::TextStyle,
+ widget::label::{WidgetLabel, WidgetLabelParams},
+};
+
+pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
+ let label = WidgetLabel::create(
+ &mut layout.state.globals.get(),
+ WidgetLabelParams {
+ content,
+ style: TextStyle {
+ wrap: true,
+ ..Default::default()
+ },
+ },
+ );
+
+ layout.add_child(parent, label, Default::default())?;
+
+ Ok(())
+}
diff --git a/dash-frontend/src/views/app_launcher.rs b/dash-frontend/src/views/app_launcher.rs
index 8f134b3d..405fdd20 100644
--- a/dash-frontend/src/views/app_launcher.rs
+++ b/dash-frontend/src/views/app_launcher.rs
@@ -180,7 +180,7 @@ impl View {
let tasks = tasks.clone();
Box::new(move |_, ev| {
if let Some(mode) = ev.value.and_then(|v| {
- CompositorMode::from_str(&*v)
+ CompositorMode::from_str(&v)
.inspect_err(|_| {
log::error!(
"Invalid value for compositor: '{v}'. Valid values are: {:?}",
@@ -199,7 +199,7 @@ impl View {
let tasks = tasks.clone();
Box::new(move |_, ev| {
if let Some(mode) = ev.value.and_then(|v| {
- ResMode::from_str(&*v)
+ ResMode::from_str(&v)
.inspect_err(|_| {
log::error!(
"Invalid value for resolution: '{v}'. Valid values are: {:?}",
@@ -237,7 +237,7 @@ impl View {
let tasks = tasks.clone();
Box::new(move |_, ev| {
if let Some(mode) = ev.value.and_then(|v| {
- OrientationMode::from_str(&*v)
+ OrientationMode::from_str(&v)
.inspect_err(|_| {
log::error!(
"Invalid value for orientation: '{v}'. Valid values are: {:?}",
diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs
index 0dc47177..72e50a54 100644
--- a/dash-frontend/src/views/game_list.rs
+++ b/dash-frontend/src/views/game_list.rs
@@ -55,7 +55,6 @@ pub struct View {
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
id_list_parent: WidgetID,
- steam_utils: steam_utils::SteamUtils,
cells: HashMap,
game_cover_view_common: game_cover::ViewCommon,
executor: AsyncExecutor,
@@ -75,8 +74,6 @@ impl View {
let tasks = Tasks::new();
- let steam_utils = SteamUtils::new()?;
-
tasks.push(Task::Refresh);
Ok(Self {
@@ -85,7 +82,6 @@ impl View {
frontend_tasks: params.frontend_tasks,
globals: params.globals.clone(),
id_list_parent: list_parent.id,
- steam_utils,
cells: HashMap::new(),
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
state: Rc::new(RefCell::new(State { view_launcher: None })),
@@ -93,7 +89,12 @@ impl View {
})
}
- pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
+ pub fn update(
+ &mut self,
+ layout: &mut Layout,
+ steam_utils: &mut SteamUtils,
+ executor: &AsyncExecutor,
+ ) -> anyhow::Result<()> {
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
@@ -101,7 +102,7 @@ impl View {
}
for task in tasks {
match task {
- Task::Refresh => self.refresh(layout, executor)?,
+ Task::Refresh => self.refresh(layout, steam_utils, executor)?,
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(layout, app_id, cover_art),
Task::CloseLauncher => self.state.borrow_mut().view_launcher = None,
@@ -162,20 +163,23 @@ fn fill_game_list(
}
impl View {
- fn game_list(&self) -> anyhow::Result {
- let manifests = self
- .steam_utils
- .list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)?;
+ fn game_list(&self, steam_utils: &mut SteamUtils) -> anyhow::Result {
+ let manifests = steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)?;
Ok(Games { manifests })
}
- fn refresh(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
+ fn refresh(
+ &mut self,
+ layout: &mut Layout,
+ steam_utils: &mut SteamUtils,
+ executor: &AsyncExecutor,
+ ) -> anyhow::Result<()> {
layout.remove_children(self.id_list_parent);
self.cells.clear();
let mut text: Option = None;
- match self.game_list() {
+ match self.game_list(steam_utils) {
Ok(list) => {
if list.manifests.is_empty() {
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs
index 299392e9..d2cd9369 100644
--- a/dash-frontend/src/views/mod.rs
+++ b/dash-frontend/src/views/mod.rs
@@ -3,3 +3,4 @@ pub mod audio_settings;
pub mod game_cover;
pub mod game_launcher;
pub mod game_list;
+pub mod running_games_list;
diff --git a/dash-frontend/src/views/running_games_list.rs b/dash-frontend/src/views/running_games_list.rs
new file mode 100644
index 00000000..45b32416
--- /dev/null
+++ b/dash-frontend/src/views/running_games_list.rs
@@ -0,0 +1,178 @@
+use wgui::{
+ assets::AssetPath,
+ components::button::ComponentButton,
+ globals::WguiGlobals,
+ i18n::Translation,
+ layout::{Layout, WidgetID},
+ parser::{Fetchable, ParseDocumentParams, ParserState},
+ task::Tasks,
+ widget::label::WidgetLabel,
+};
+
+use crate::{
+ frontend::{FrontendTask, FrontendTasks},
+ util::{
+ steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils},
+ wgui_simple,
+ },
+};
+
+#[derive(Clone)]
+enum Task {
+ Refresh,
+ StopGame(AppID, bool /* kill */),
+}
+
+pub struct Params<'a> {
+ pub globals: WguiGlobals,
+ pub layout: &'a mut Layout,
+ pub parent_id: WidgetID,
+ pub steam_utils: &'a mut SteamUtils,
+ pub frontend_tasks: FrontendTasks,
+}
+
+pub struct View {
+ #[allow(dead_code)]
+ state: ParserState,
+ tasks: Tasks,
+ last_update_ms: u32,
+ id_list_parent: WidgetID,
+ installed_games: Vec,
+ frontend_tasks: FrontendTasks,
+}
+
+fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
+ ParseDocumentParams {
+ globals,
+ path: AssetPath::BuiltIn("gui/view/running_games_list.xml"),
+ extra: Default::default(),
+ }
+}
+
+impl View {
+ pub fn new(params: Params) -> anyhow::Result {
+ let state = wgui::parser::parse_from_assets(&doc_params(params.globals.clone()), params.layout, params.parent_id)?;
+ let btn_refresh = state.fetch_component_as::("btn_refresh")?;
+ let id_list_parent = state.get_widget_id("list_parent")?;
+
+ let installed_games = params
+ .steam_utils
+ .list_installed_games(GameSortMethod::None)
+ .unwrap_or(Vec::new());
+
+ let tasks = Tasks::::new();
+
+ tasks.handle_button(&btn_refresh, Task::Refresh);
+ tasks.push(Task::Refresh);
+
+ Ok(Self {
+ state,
+ tasks,
+ last_update_ms: 0,
+ id_list_parent,
+ installed_games,
+ frontend_tasks: params.frontend_tasks,
+ })
+ }
+
+ pub fn update(&mut self, layout: &mut Layout, time_ms: u32) -> anyhow::Result<()> {
+ loop {
+ let tasks = self.tasks.drain();
+ if tasks.is_empty() {
+ break;
+ }
+
+ for task in tasks {
+ match task {
+ Task::Refresh => self.refresh(layout)?,
+ Task::StopGame(app_id, kill) => self.stop_game(app_id, kill),
+ }
+ }
+ }
+
+ if self.last_update_ms + 5000 < time_ms {
+ self.last_update_ms = time_ms;
+ self.tasks.push(Task::Refresh);
+ }
+
+ Ok(())
+ }
+
+ fn extract_name_from_appid<'a>(app_id: &AppID, manifests: &[AppManifest]) -> String {
+ for manifest in manifests {
+ if manifest.app_id == *app_id {
+ return manifest.name.clone();
+ }
+ }
+
+ format!("Unknown AppID {}", app_id)
+ }
+
+ fn fill_list(&mut self, layout: &mut Layout, games: Vec) -> anyhow::Result<()> {
+ if games.is_empty() {
+ wgui_simple::create_label(
+ layout,
+ self.id_list_parent,
+ Translation::from_translation_key("GAME_LIST.NO_RUNNING_GAME_FOUND"),
+ )?;
+ return Ok(());
+ }
+
+ for game in games {
+ let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
+
+ let t = self.state.parse_template(
+ &doc_params(layout.state.globals.clone()),
+ "RunningGameCell",
+ layout,
+ self.id_list_parent,
+ Default::default(),
+ )?;
+
+ let mut label_name = t.fetch_widget_as::(&layout.state, "label_name")?;
+
+ self.tasks.handle_button(
+ &t.fetch_component_as::("btn_stop")?,
+ Task::StopGame(game.app_id.clone(), false),
+ );
+
+ self.tasks.handle_button(
+ &t.fetch_component_as::("btn_kill")?,
+ Task::StopGame(game.app_id, true),
+ );
+
+ label_name.set_text_simple(
+ &mut layout.state.globals.get(),
+ Translation::from_raw_text_string(game_name),
+ );
+ }
+
+ Ok(())
+ }
+
+ fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
+ log::debug!("refreshing running games list");
+
+ layout.remove_children(self.id_list_parent);
+
+ match steam_utils::list_running_games() {
+ Ok(games) => self.fill_list(layout, games)?,
+ Err(e) => {
+ log::error!("failed to list games: {}", e);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn stop_game(&mut self, app_id: AppID, kill: bool) {
+ if let Err(e) = steam_utils::stop(app_id, kill) {
+ self
+ .frontend_tasks
+ .push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
+ "Error: {}",
+ e
+ ))));
+ }
+ }
+}