mirror of
https://github.com/marcoallegretti/WEFT_OS.git
synced 2026-03-27 01:13:09 +00:00
feat(appd): include app_id in RunningApps response; update system UI
ipc.rs:
- Add SessionInfo { session_id: u64, app_id: String } struct.
- Change RunningApps { session_ids: Vec<u64> } to
RunningApps { sessions: Vec<SessionInfo> } so callers can display
meaningful app names without a follow-up QueryAppState round-trip.
- Add session_info_roundtrip test.
main.rs:
- Add SessionEntry { app_id: String, state: AppStateKind } to store
app_id alongside state in SessionRegistry.
- launch() stores app_id in the entry.
- running_sessions() replaces running_ids(); returns Vec<SessionInfo>.
- state() reads from SessionEntry.state.
- set_state() writes to SessionEntry.state.
- QueryRunning dispatch uses running_sessions().
- Test registry_running_ids_reflects_live_sessions renamed to
registry_running_sessions_reflects_live_sessions and updated to
assert both session_id and app_id fields.
- dispatch_query_running test asserts app_id values are present.
system-ui.html:
- RUNNING_APPS handler uses msg.sessions[].{session_id,app_id}.
- ensureTaskbarEntry(sessionId, appId): shows the last component of the
reverse-domain app ID as the taskbar label; sets data-app-id attribute;
tooltip shows full app ID and session number.
- LAUNCH_ACK handler passes null for appId (session ID only available
at launch time; app_id arrives in RUNNING_APPS on reconnect).
This commit is contained in:
parent
d6ede23183
commit
fdeb440766
3 changed files with 73 additions and 28 deletions
|
|
@ -10,6 +10,12 @@ pub enum Request {
|
||||||
QueryAppState { session_id: u64 },
|
QueryAppState { session_id: u64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SessionInfo {
|
||||||
|
pub session_id: u64,
|
||||||
|
pub app_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
|
|
@ -20,7 +26,7 @@ pub enum Response {
|
||||||
session_id: u64,
|
session_id: u64,
|
||||||
},
|
},
|
||||||
RunningApps {
|
RunningApps {
|
||||||
session_ids: Vec<u64>,
|
sessions: Vec<SessionInfo>,
|
||||||
},
|
},
|
||||||
AppState {
|
AppState {
|
||||||
session_id: u64,
|
session_id: u64,
|
||||||
|
|
@ -101,11 +107,21 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn session_info_roundtrip() {
|
||||||
|
let info = super::SessionInfo {
|
||||||
|
session_id: 3,
|
||||||
|
app_id: "com.example.app".into(),
|
||||||
|
};
|
||||||
|
let bytes = rmp_serde::to_vec(&info).unwrap();
|
||||||
|
let decoded: super::SessionInfo = rmp_serde::from_slice(&bytes).unwrap();
|
||||||
|
assert_eq!(decoded.session_id, 3);
|
||||||
|
assert_eq!(decoded.app_id, "com.example.app");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn frame_write_read_roundtrip() {
|
async fn frame_write_read_roundtrip() {
|
||||||
let resp = Response::RunningApps {
|
let resp = Response::RunningApps { sessions: vec![] };
|
||||||
session_ids: vec![1, 2, 3],
|
|
||||||
};
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
write_frame(&mut buf, &resp).await.unwrap();
|
write_frame(&mut buf, &resp).await.unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,18 @@ mod ipc;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
use ipc::{AppStateKind, Request, Response};
|
use ipc::{AppStateKind, Request, Response, SessionInfo};
|
||||||
|
|
||||||
pub(crate) type Registry = Arc<Mutex<SessionRegistry>>;
|
pub(crate) type Registry = Arc<Mutex<SessionRegistry>>;
|
||||||
|
|
||||||
|
struct SessionEntry {
|
||||||
|
app_id: String,
|
||||||
|
state: AppStateKind,
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionRegistry {
|
struct SessionRegistry {
|
||||||
next_id: u64,
|
next_id: u64,
|
||||||
sessions: std::collections::HashMap<u64, AppStateKind>,
|
sessions: std::collections::HashMap<u64, SessionEntry>,
|
||||||
broadcast: tokio::sync::broadcast::Sender<Response>,
|
broadcast: tokio::sync::broadcast::Sender<Response>,
|
||||||
abort_senders: std::collections::HashMap<u64, tokio::sync::oneshot::Sender<()>>,
|
abort_senders: std::collections::HashMap<u64, tokio::sync::oneshot::Sender<()>>,
|
||||||
}
|
}
|
||||||
|
|
@ -33,10 +38,16 @@ impl Default for SessionRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionRegistry {
|
impl SessionRegistry {
|
||||||
fn launch(&mut self, _app_id: &str) -> u64 {
|
fn launch(&mut self, app_id: &str) -> u64 {
|
||||||
self.next_id += 1;
|
self.next_id += 1;
|
||||||
let id = self.next_id;
|
let id = self.next_id;
|
||||||
self.sessions.insert(id, AppStateKind::Starting);
|
self.sessions.insert(
|
||||||
|
id,
|
||||||
|
SessionEntry {
|
||||||
|
app_id: app_id.to_owned(),
|
||||||
|
state: AppStateKind::Starting,
|
||||||
|
},
|
||||||
|
);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,20 +63,26 @@ impl SessionRegistry {
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn running_ids(&self) -> Vec<u64> {
|
fn running_sessions(&self) -> Vec<SessionInfo> {
|
||||||
self.sessions.keys().copied().collect()
|
self.sessions
|
||||||
|
.iter()
|
||||||
|
.map(|(&session_id, e)| SessionInfo {
|
||||||
|
session_id,
|
||||||
|
app_id: e.app_id.clone(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self, session_id: u64) -> AppStateKind {
|
fn state(&self, session_id: u64) -> AppStateKind {
|
||||||
self.sessions
|
self.sessions
|
||||||
.get(&session_id)
|
.get(&session_id)
|
||||||
.cloned()
|
.map(|e| e.state.clone())
|
||||||
.unwrap_or(AppStateKind::NotFound)
|
.unwrap_or(AppStateKind::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_state(&mut self, session_id: u64, state: AppStateKind) {
|
pub(crate) fn set_state(&mut self, session_id: u64, state: AppStateKind) {
|
||||||
if let Some(entry) = self.sessions.get_mut(&session_id) {
|
if let Some(entry) = self.sessions.get_mut(&session_id) {
|
||||||
*entry = state;
|
entry.state = state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,8 +233,8 @@ pub(crate) async fn dispatch(req: Request, registry: &Registry) -> Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Request::QueryRunning => {
|
Request::QueryRunning => {
|
||||||
let session_ids = registry.lock().await.running_ids();
|
let sessions = registry.lock().await.running_sessions();
|
||||||
Response::RunningApps { session_ids }
|
Response::RunningApps { sessions }
|
||||||
}
|
}
|
||||||
Request::QueryAppState { session_id } => {
|
Request::QueryAppState { session_id } => {
|
||||||
let state = registry.lock().await.state(session_id);
|
let state = registry.lock().await.state(session_id);
|
||||||
|
|
@ -315,7 +332,12 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
let resp = dispatch(Request::QueryRunning, ®).await;
|
let resp = dispatch(Request::QueryRunning, ®).await;
|
||||||
match resp {
|
match resp {
|
||||||
Response::RunningApps { session_ids } => assert_eq!(session_ids.len(), 2),
|
Response::RunningApps { sessions } => {
|
||||||
|
assert_eq!(sessions.len(), 2);
|
||||||
|
let mut ids: Vec<&str> = sessions.iter().map(|s| s.app_id.as_str()).collect();
|
||||||
|
ids.sort();
|
||||||
|
assert_eq!(ids, vec!["a", "b"]);
|
||||||
|
}
|
||||||
_ => panic!("expected RunningApps"),
|
_ => panic!("expected RunningApps"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -368,15 +390,20 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn registry_running_ids_reflects_live_sessions() {
|
fn registry_running_sessions_reflects_live_sessions() {
|
||||||
let mut reg = SessionRegistry::default();
|
let mut reg = SessionRegistry::default();
|
||||||
let id1 = reg.launch("a");
|
let id1 = reg.launch("com.example.a");
|
||||||
let id2 = reg.launch("b");
|
let id2 = reg.launch("com.example.b");
|
||||||
let mut ids = reg.running_ids();
|
let mut sessions = reg.running_sessions();
|
||||||
ids.sort();
|
sessions.sort_by_key(|s| s.session_id);
|
||||||
assert_eq!(ids, vec![id1, id2]);
|
assert_eq!(sessions.len(), 2);
|
||||||
|
assert_eq!(sessions[0].session_id, id1);
|
||||||
|
assert_eq!(sessions[0].app_id, "com.example.a");
|
||||||
|
assert_eq!(sessions[1].session_id, id2);
|
||||||
|
assert_eq!(sessions[1].app_id, "com.example.b");
|
||||||
reg.terminate(id1);
|
reg.terminate(id1);
|
||||||
assert_eq!(reg.running_ids(), vec![id2]);
|
assert_eq!(reg.running_sessions().len(), 1);
|
||||||
|
assert_eq!(reg.running_sessions()[0].session_id, id2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -208,23 +208,25 @@
|
||||||
removeTaskbarEntry(msg.session_id);
|
removeTaskbarEntry(msg.session_id);
|
||||||
}
|
}
|
||||||
} else if (msg.type === 'RUNNING_APPS') {
|
} else if (msg.type === 'RUNNING_APPS') {
|
||||||
msg.session_ids.forEach(function (id) {
|
msg.sessions.forEach(function (s) {
|
||||||
ensureTaskbarEntry(id);
|
ensureTaskbarEntry(s.session_id, s.app_id);
|
||||||
});
|
});
|
||||||
} else if (msg.type === 'LAUNCH_ACK') {
|
} else if (msg.type === 'LAUNCH_ACK') {
|
||||||
ensureTaskbarEntry(msg.session_id);
|
ensureTaskbarEntry(msg.session_id, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureTaskbarEntry(sessionId) {
|
function ensureTaskbarEntry(sessionId, appId) {
|
||||||
var id = 'task-' + sessionId;
|
var id = 'task-' + sessionId;
|
||||||
if (document.getElementById(id)) { return; }
|
if (document.getElementById(id)) { return; }
|
||||||
|
var label = appId ? appId.split('.').pop() : String(sessionId);
|
||||||
var el = document.createElement('weft-taskbar-app');
|
var el = document.createElement('weft-taskbar-app');
|
||||||
el.id = id;
|
el.id = id;
|
||||||
el.dataset.sessionId = sessionId;
|
el.dataset.sessionId = sessionId;
|
||||||
el.textContent = '● ' + sessionId;
|
if (appId) { el.dataset.appId = appId; }
|
||||||
|
el.textContent = '● ' + label;
|
||||||
el.style.cssText = 'font-size:12px;color:var(--text-secondary);padding:0 6px;cursor:pointer;';
|
el.style.cssText = 'font-size:12px;color:var(--text-secondary);padding:0 6px;cursor:pointer;';
|
||||||
el.title = 'Session ' + sessionId;
|
el.title = appId ? appId + ' (session ' + sessionId + ')' : 'Session ' + sessionId;
|
||||||
var taskbar = document.querySelector('weft-taskbar');
|
var taskbar = document.querySelector('weft-taskbar');
|
||||||
var clock = document.getElementById('clock');
|
var clock = document.getElementById('clock');
|
||||||
taskbar.insertBefore(el, clock);
|
taskbar.insertBefore(el, clock);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue