Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ makepad-wm-api = { git = "https://github.com/OctoSense-org/makepad.git", rev = "
makepad-ai-services = { git = "https://github.com/OctoSense-org/makepad.git", rev = "75e3e24dbfb37381edb2745f62eba3615b335897" }
makepad-strict-json = { git = "https://github.com/OctoSense-org/makepad.git", rev = "75e3e24dbfb37381edb2745f62eba3615b335897" }
makepad-app-module = { git = "https://github.com/OctoSense-org/makepad.git", rev = "75e3e24dbfb37381edb2745f62eba3615b335897" }
makepad-terminal = { git = "https://github.com/OctoSense-org/makepad.git", rev = "75e3e24dbfb37381edb2745f62eba3615b335897" }
makepad-wm-theme = { git = "https://github.com/OctoSense-org/makepad.git", rev = "75e3e24dbfb37381edb2745f62eba3615b335897" }
makepad-map-nav = { git = "https://github.com/OctoSense-org/makepad.git", rev = "75e3e24dbfb37381edb2745f62eba3615b335897" }
makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "75e3e24dbfb37381edb2745f62eba3615b335897", default-features = false }
Expand Down Expand Up @@ -196,6 +197,7 @@ makepad-wm-api = { path = ".sources/makepad/libs/wm_api" }
makepad-ai-services = { path = ".sources/makepad/libs/ai/services" }
makepad-strict-json = { path = ".sources/makepad/libs/strict_json" }
makepad-app-module = { path = ".sources/makepad/libs/app_module" }
makepad-terminal = { path = ".sources/makepad/apps/terminal" }
makepad-wm-theme = { path = ".sources/makepad/libs/wm_theme" }
makepad-map-nav = { path = ".sources/makepad/libs/map_nav" }
makepad-sheets = { path = ".sources/makepad/apps/sheets" }
Expand Down
4 changes: 4 additions & 0 deletions crates/shell/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ makepad-strict-json = { workspace = true }
makepad-app-module = { workspace = true }
makepad-wm-theme = { workspace = true }
makepad-sheets = { workspace = true, optional = true }
makepad-terminal = { workspace = true, optional = true }
octoscript-makepad = { workspace = true }
# The L0 checker the glance service admits published cards with (glance.rs).
octoscript-ui-l0 = { workspace = true }
Expand Down Expand Up @@ -88,6 +89,9 @@ app-rinx = ["dep:rinx", "octos-core"]
app-hub = ["dep:octosense-app-hub-app", "dep:octosense-appstore", "dep:octosense-mail-service", "dep:octosense-news-service", "octosense-ai-host/llm"]
app-reference = ["dep:octosense-reference"]
app-sheets = ["dep:makepad-sheets"]
# The terminal as a system app: linked in-process and module-hosted by
# default on the desktop (apps.rs), unlike the other desktop modules.
app-terminal = ["dep:makepad-terminal"]
# Native Photos, News and OctosMap modules; without them these are the system
# script apps (`os.photos`, `os.news`, `os.maps`, ADR 0004).
app-photos = ["dep:octosense-photos"]
Expand Down
27 changes: 25 additions & 2 deletions crates/shell/src/apps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ fn linked_modules() -> Vec<&'static dyn AppModule> {
out.push(&octosense_reference::REFERENCE_MODULE);
#[cfg(any(feature = "app-sheets", native_mobile))]
out.push(&makepad_sheets::SHEETS_MODULE);
// Terminal is a system app on the desktop: a login shell in a tile. Its
// PTY helper on macOS is this executable (`--exec-pty`, handled in
// `Cx::pre_start`), so it needs no second binary shipped beside it.
#[cfg(feature = "app-terminal")]
out.push(&makepad_terminal::TERMINAL_MODULE);
// The native Photos library, albums and Memories. Without it Photos is
// the system app (`os.photos`); a linked module of the same id wins.
#[cfg(feature = "app-photos")]
Expand Down Expand Up @@ -391,7 +396,7 @@ impl AppRegistry {
// the person switched them.
// (Rinx is Robrix renamed: under the old id the desktop fell through
// to a process launch and failed with "binary not found: rinx".)
if matches!(id, "rinx" | "finance") && self.module(id).is_some() && !self.overrides.contains_key(id) {
if matches!(id, "rinx" | "finance" | "terminal") && self.module(id).is_some() && !self.overrides.contains_key(id) {
return Hosting::Module;
}
// A system or installed app has no process form anywhere: the `card`
Expand Down Expand Up @@ -596,7 +601,8 @@ mod tests {
let registry = AppRegistry::load(Path::new("/nonexistent/apps.splash"), &["--module".to_string(), "sheets".to_string(), "--module".to_string(), "files".to_string()]);
// files has no linked module: the flag cannot make it one.
assert_eq!(registry.hosting("files"), Hosting::Process);
assert_eq!(registry.hosting("terminal"), Hosting::Process);
#[cfg(not(feature = "app-terminal"))]
assert_eq!(registry.hosting("terminal"), Hosting::Process, "no linked terminal: a process");
#[cfg(feature = "app-sheets")]
{
assert_eq!(registry.hosting("sheets"), Hosting::Module);
Expand All @@ -606,6 +612,23 @@ mod tests {
}
}

/// Terminal is a system app: linked, it is in-process by default on every
/// target, and a person who switched it to a process keeps that.
#[cfg(feature = "app-terminal")]
#[test]
fn the_linked_terminal_is_a_module_unless_switched() {
let plain = AppRegistry::default();
assert!(plain.linked_ids().contains(&"terminal"));
assert_eq!(plain.hosting("terminal"), Hosting::Module, "a system app: in-process by default");
let mut switched = AppRegistry::default();
switched.overrides.insert("terminal".into(), Hosting::Process);
if crate::host::processes_available() {
assert_eq!(switched.hosting("terminal"), Hosting::Process, "the person's switch wins");
}
let term: &'static dyn AppModule = &makepad_terminal::TERMINAL_MODULE;
assert!(module_open(term, &card_row("terminal".into(), "Terminal".into(), Vec::new())).is_ok());
}

#[test]
fn rinx_is_module_hosted_on_the_desktop_by_default() {
let plain = AppRegistry::default();
Expand Down
34 changes: 34 additions & 0 deletions crates/shell/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ pub struct App {
/// re-asserted when that client's first frame arrives.
#[rust]
pub pending_focus: Option<ClientId>,
/// The client whose tile last took the keyboard. It lets go before
/// another tile takes it: a module tile never moves key focus itself
/// (its widgets claim it), so without this the hidden tab of a group
/// kept holding the keyboard and every key was dropped.
#[rust]
pub keyboard_holder: Option<ClientId>,
/// `--gallery`: the shell-surface gallery instead of a desktop.
#[rust]
pub gallery: bool,
Expand Down Expand Up @@ -1702,12 +1708,22 @@ impl App {
// Area only after its first draw — a focus at launch time lands on
// nothing. Keep it pending and re-assert when the child's first
// frame arrives (the PresentableDraw path below).
if let Some(previous) = self.keyboard_holder.filter(|previous| *previous != client) {
let _ = self
.desk(cx)
.borrow_mut::<WmDesk>()
.and_then(|mut d| d.with_tile(cx, previous, |cx, v| v.release_keyboard(cx)));
self.keyboard_holder = None;
}
let focused = self
.desk(cx)
.borrow_mut::<WmDesk>()
.and_then(|mut d| d.with_tile(cx, client, |cx, v| v.focus_keyboard(cx)))
.unwrap_or(false);
self.pending_focus = if focused { None } else { Some(client) };
if focused {
self.keyboard_holder = Some(client);
}
self.update_bar(cx);
self.redraw_all(cx);
}
Expand Down Expand Up @@ -5114,6 +5130,24 @@ impl App {
} else {
self.ui.handle_event(cx, event, &mut Scope::empty());
}
// A focus that couldn't land at launch (tile not yet drawn) is
// re-asserted for a process tile when its first frame arrives
// (PresentableDraw). A module tile sends no frame, so retry after the
// draw that may have created it; nothing forces a redraw, so a tile
// that still cannot take it just tries again on the next draw.
if let (Event::Draw(_), Some(client)) = (event, self.pending_focus) {
if self.module_host.is_module(client) {
let focused = self
.desk(cx)
.borrow_mut::<WmDesk>()
.and_then(|mut d| d.with_tile(cx, client, |cx, v| v.focus_keyboard(cx)))
.unwrap_or(false);
if focused {
self.pending_focus = None;
self.keyboard_holder = Some(client);
}
}
}
self.sync_phone_keyboard(cx);
// Style reloads and phone capture teardown may retire draw lists during
// this event. Remove their pass roots before upstream scans GPU demand.
Expand Down
7 changes: 5 additions & 2 deletions desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ octosense-shell = { workspace = true }
[features]
# App Hub is part of the standard launcher: the store, the Card runner and
# the system apps (News, Photos, Maps, Mail, AI providers; Camera is phone-only) it runs;
# the octos kernel is a shell service in every standard build.
default = ["app-hub", "octos-core", "app-rinx"]
# the octos kernel is a shell service in every standard build. Terminal is a
# system app too, linked in-process.
default = ["app-hub", "octos-core", "app-rinx", "app-terminal"]
# The embedded apps, also usable on the desktop with --module. AppCard is not
# among them: it ships only with `--features app-appcard`.
mobile-apps = ["octosense-shell/mobile-apps"]
Expand All @@ -58,6 +59,8 @@ octos-core = ["octosense-shell/octos-core"]
app-hub = ["octosense-shell/app-hub"]
app-reference = ["octosense-shell/app-reference"]
app-sheets = ["octosense-shell/app-sheets"]
# The Terminal system app: a login shell in a tile, in-process by default.
app-terminal = ["octosense-shell/app-terminal"]
# Native Photos, News and OctosMap modules instead of the system script apps.
app-photos = ["octosense-shell/app-photos"]
app-news = ["octosense-shell/app-news"]
Expand Down
3 changes: 2 additions & 1 deletion desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ A relocatable `.app`, installers and a Linux session compositor are not provided
| `app-rinx` | on | Links [Rinx](https://github.com/hagency-org/Rinx), the Matrix client, as a module; implies `octos-core` (its assistant is the shell's). |
| `app-reference` | off | Links Reference (`../apps/reference`) as a module. |
| `app-sheets` | off | Links Makepad's Sheets as a module. |
| `app-terminal` | on | Links Makepad's Terminal as a system app: a login shell in a tile, in-process by default (a `terminal: Process` line in `wm/apps.splash` under the state directory switches it to a process). The assistant gets its read tools (`read_screen`, `read_scrollback`); `run` is not offered. On macOS the shell's PTY helper is `octosense` itself, so no second binary ships beside it. |
| `app-photos` | off | Links Makepad's native Photos module; it replaces the Photos system app of the same id (for comparison). |
| `app-appcard` | off | Links the AppCard assistant module (`../apps/appcard/module`); implies `octos-core`. Opt-in on every target, phones included; not shipped for now. |
| `app-aichat` | off | Links Makepad's AI chat as a module, without its model engine. |
Expand Down Expand Up @@ -187,7 +188,7 @@ Open **App Hub**, choose the app, **Get**, scroll to **Install**, then **Open**:

### Developer programs and the catalog

`config/apps.json` lists Reference and Makepad's own apps (Browser, Files, Terminal, Sheets, Notes, Calendar, Director under the id `studio`, and more). The Image, PDF and AI helpers also appear in the launcher unless their ids (`image`, `pdf`, `aichat`) are listed in `wm/launcher.hides` under the state directory.
`config/apps.json` lists Reference and Makepad's own apps (Browser, Files, Terminal, Sheets, Notes, Calendar, Director under the id `studio`, and more). Terminal is also linked (`app-terminal`, on by default) and opens in-process; its `config/apps.json` row is the process form a person can switch to. The Image, PDF and AI helpers also appear in the launcher unless their ids (`image`, `pdf`, `aichat`) are listed in `wm/launcher.hides` under the state directory.

Catalog lookup: `--apps <file>` if given, else `~/.octosense/apps.json` if it exists, else `config/apps.json`. A catalog is a JSON array; each entry picks one launch target:

Expand Down
3 changes: 2 additions & 1 deletion desktop/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ cargo run --release -p octosense
| `app-rinx` | 开 | 以模块形式链接 Matrix 客户端 [Rinx](https://github.com/hagency-org/Rinx);隐含 `octos-core`(它的助手就是 Shell 的助手)。 |
| `app-reference` | 关 | 以模块形式链接 Reference(`../apps/reference`)。 |
| `app-sheets` | 关 | 以模块形式链接 Makepad 的 Sheets。 |
| `app-terminal` | 开 | 以系统应用形式链接 Makepad 的 Terminal:在磁贴中运行的登录 Shell,默认在进程内运行(在状态目录下的 `wm/apps.splash` 中写一行 `terminal: Process` 可改为独立进程)。助手只获得读取工具(`read_screen`、`read_scrollback`),不提供 `run`。在 macOS 上,Shell 的 PTY 辅助程序就是 `octosense` 本身,因此无需在旁边附带第二个二进制文件。 |
| `app-photos` | 关 | 链接 Makepad 的原生 Photos 模块;它会替换同 id 的相册系统应用(用于对比)。 |
| `app-appcard` | 关 | 链接 AppCard 助手模块(`../apps/appcard/module`);隐含 `octos-core`。在所有目标平台(包括手机)上都需显式启用;目前不随产品发布。 |
| `app-aichat` | 关 | 以模块形式链接 Makepad 的 AI chat,不含其模型引擎。 |
Expand Down Expand Up @@ -187,7 +188,7 @@ OCTOSENSE_HUB=<mirror dir> OCTOSENSE_HUB_ANCHOR=<anchor hex> \

### 开发者程序与目录

`config/apps.json` 列出 Reference 和 Makepad 自带的应用(Browser、Files、Terminal、Sheets、Notes、Calendar、id 为 `studio` 的 Director 等)。Image、PDF 和 AI 辅助应用也会出现在 launcher 中,除非它们的 id(`image`、`pdf`、`aichat`)写在状态目录下的 `wm/launcher.hides` 中。
`config/apps.json` 列出 Reference 和 Makepad 自带的应用(Browser、Files、Terminal、Sheets、Notes、Calendar、id 为 `studio` 的 Director 等)。Terminal 同时以链接方式提供(`app-terminal`,默认开启),在进程内打开;它在 `config/apps.json` 中的条目是可以切换成的独立进程形式。Image、PDF 和 AI 辅助应用也会出现在 launcher 中,除非它们的 id(`image`、`pdf`、`aichat`)写在状态目录下的 `wm/launcher.hides` 中。

目录查找顺序:给了 `--apps <file>` 就用它;否则若存在 `~/.octosense/apps.json` 就用它;否则用 `config/apps.json`。目录是一个 JSON 数组,每个条目选择一种启动目标:

Expand Down
Loading