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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ makepad-photos = { git = "https://github.com/OctoSense-org/makepad.git", rev = "

[features]
default = []
# The standalone mobile shell: the Android phone shell fills the window, no
# desk bar, no style switcher, no desktop styles. Android builds get it
# without the flag (build.rs turns the same `mobile_only` cfg on for
# `target_os = "android"`); a desktop build is universal unless this is on.
mobile-only = []
# Also usable on desktop for testing the same embedded apps with --module.
mobile-apps = ["app-reference", "app-sheets", "app-photos", "app-appcard"]
app-reference = ["dep:octosense-reference"]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ Makepad otherwise derives it from the checkout directory, which may still be
named `makeos`. Rebuild and relaunch after updating; Cargo regenerates the
development `Info.plist` automatically.

The build target selects the startup shell automatically: Android uses the Android phone layout, iOS uses the iOS phone layout, and desktop/web builds keep Omarchy. You can still switch styles from the shell's style menu. Native phone toolbars are 48 points high and respect the window's safe-area insets.
The build target selects the shell. **Android builds are the standalone mobile shell**: the Android phone shell fills the screen inside the platform's safe-area insets, with no desk bar, no style menu and no other desktop style compiled in — Light/Dark is the **Dark mode** tile in the shade's controls, and rotation follows the device. **Desktop builds are universal** — every desktop style plus the two phone styles, switched from the bar — unless built with `cargo run --features mobile-only`, which is the same standalone shell in a phone-sized window (`build.rs` turns one `mobile_only` cfg on for that feature and for `target_os = "android"`). iOS builds still start in the iOS phone layout of the universal shell.

The default desktop starts empty. AI assistant startup, background app prewarming, demo filesystem generation, and wallpaper downloads are off. **System → Quit OctoSense** closes the desktop and its hosted processes.

Omarchy starts with a bundled Tokyo Night wallpaper, so the background works offline on a fresh install. Installed images in `~/.octosense/wm/themes/tokyo-night/backgrounds/` take precedence. Use `cargo run -- --download-wallpapers` to fetch the theme’s full wallpaper set; **⌘CtrlSpace** cycles installed backgrounds. Asset provenance is in [resources/wallpapers/README.md](resources/wallpapers/README.md).

Eight desktop styles are available, including **OctoSense**, a floating desktop with Liquid Glass window frames, dock, bar and popups. Press **⌘Space**, type **OctoSense**, and press Enter to select its light appearance. Click **Light / Dark** beside the style name in the top bar to switch appearances, or search for **OctoSense Dark** directly. Light uses pearl and pale aqua surfaces with dark text; Dark keeps the ink-blue palette. Both include matching versions of **Abyssal Currents**, the original oceanic wallpaper, and rounded hosted surfaces. The wallpaper switches with the appearance, fills the window with a centered crop and works offline. Desktop startup remains Omarchy; Android retains its animated background.
Eight desktop styles are available, including **OctoSense**, a floating desktop with Liquid Glass window frames, dock, bar and popups. Press **⌘Space**, type **OctoSense**, and press Enter to select its light appearance. Click **Light / Dark** beside the style name in the top bar to switch appearances, or search for **OctoSense Dark** directly. Light uses pearl and pale aqua surfaces with dark text; Dark keeps the ink-blue palette. Both include matching versions of **Abyssal Currents**, the original oceanic wallpaper, and rounded hosted surfaces. The wallpaper switches with the appearance, fills the window with a centered crop and works offline. Desktop startup remains Omarchy; Android retains its animated background. None of this bar or its styles exists in the standalone mobile shell (Android, or `--features mobile-only`).

Use **⌘Space** for the menu, **⌘W** to close a tile, **⌘F** for tile fullscreen, **⌘1…0** to switch workspaces, and **⌘Shift1…0** to move the focused tile. The menu's **Learn → Keybindings** lists the inherited bindings; shortcuts for apps absent from your catalog report that the app is unavailable.

Expand Down
15 changes: 15 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! The one place the standalone-shell condition is written.
//!
//! `mobile_only` is set for Android builds (cargo-makepad passes no features,
//! and a Cargo feature cannot be target-conditional) and for any build with
//! `--features mobile-only`. Code reads it as `#[cfg(mobile_only)]` /
//! `cfg!(mobile_only)`, never as the feature or the target directly.
fn main() {
println!("cargo:rustc-check-cfg=cfg(mobile_only)");
let feature = std::env::var_os("CARGO_FEATURE_MOBILE_ONLY").is_some();
let android = std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("android");
if feature || android {
println!("cargo:rustc-cfg=mobile_only");
}
println!("cargo:rerun-if-changed=build.rs");
}
12 changes: 12 additions & 0 deletions docs/android-appcard-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ on the makepad fork's AppCard framework line and needs three things the stock
(`octos-app`'s `stdio_spawn`) finds it there and runs `octos serve --stdio`
with `HOME=<files>/octos-home`.

## The shell an Android build is

Android builds are the standalone mobile shell: the Android phone shell fills
the screen inside the safe-area insets, with no desk bar (no style dropdown,
Desktop/Phone toggle, Light/Dark or rotate button) and no desktop style
compiled in — Light/Dark is the **Dark mode** tile in the shade's controls.
`build.rs` turns the `mobile_only` cfg on for `target_os = "android"`, so the
plain `cargo makepad android run` below needs no feature flag. Desktop builds
stay universal (every desktop style and the bar) unless built with
`--features mobile-only`, which is the same standalone shell in a phone-sized
window — the way to check a phone change without the phone.

## Step by step

```sh
Expand Down
10 changes: 7 additions & 3 deletions src/desk/phone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,12 @@ impl WmDesk {
}
}
}
pub(super) fn draw_phone_scene(&mut self,cx:&mut Cx2d,scope:&mut Scope,screen:Rect) {
pub(super) fn draw_phone_scene(&mut self,cx:&mut Cx2d,scope:&mut Scope,full:Rect) {
let state=scope.data.get_mut::<WmState>().unwrap();
// The wallpaper fills the desk; the shell lays out inside the
// platform's safe area (the notch, the system bars): the status bar
// under the notch, the navigation band above Android's gesture bar.
let screen=state.phone.insets.inset(full);
state.phone.viewport=screen;
// The frame's exclusion zones are rebuilt from what is drawn: cleared
// once here, then every surface adds its own (the shade's sheet, the
Expand Down Expand Up @@ -235,8 +239,8 @@ impl WmDesk {
let app=mobile::app_rect(screen);
self.compositor.get_or_insert_with(||BackdropCompositor::new(cx)).begin(cx);
self.phone_ui.begin();
self.phone_ui.draw_wallpaper(cx,screen,style,dark,phone.wallpaper_time);
self.compositor.as_mut().unwrap().content(screen);
self.phone_ui.draw_wallpaper(cx,full,style,dark,phone.wallpaper_time);
self.compositor.as_mut().unwrap().content(full);
let home_backdrop=if style==crate::desktop::DesktopStyle::Ios && phone.openness<0.999 {
Some(self.compositor.as_mut().unwrap().backdrop(cx,PhoneSurface::home_dock(screen),4.0))
}else{None};
Expand Down
2 changes: 2 additions & 0 deletions src/desktop_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ impl App {
state.roles = roles;
state.dragging.clear();
state.style.select(style);
state.phone.shade.dark = dark;
if changes_size { state.style.step(1.0); }
state.layout.desktop.enabled = style.floating();
for c in state.layout.all_clients() {
Expand Down Expand Up @@ -197,6 +198,7 @@ impl App {
let style = self.state_mut().style.target;
self.set_desktop_style(cx, style);
}
#[cfg(not(mobile_only))]
pub(super) fn open_style_menu(&mut self, cx: &mut Cx) {
let anchor = if self.state_mut().style.target.mobile() {
self.ui.widget(cx, ids!(phone_controls)).borrow::<crate::mobile_surface::PhoneSurface>()
Expand Down
43 changes: 41 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ mod shell;
mod theme;
mod tile;

/// The standalone mobile shell: the Android phone shell fills the window
/// and nothing else is offered — no desk bar, no style switcher, no
/// desktop styles. build.rs sets the `mobile_only` cfg for Android builds
/// and for `--features mobile-only`; this is its one Rust-side name.
pub(crate) const MOBILE_ONLY: bool = cfg!(mobile_only);

use std::collections::HashMap;

use binds::{match_bind_armed, WmAction};
Expand Down Expand Up @@ -130,9 +136,13 @@ script_mod! {
flow: Down
// The wallpaper layer: the theme's image (crop-to-fill)
// over the theme's deep background.
// The desk bar: the shell bar on a desktop style, the
// phone's style/appearance/rotate strip on a phone
// style. The standalone shell has neither.
bar := SolidView{
width: Fill
height: 26
visible: #(!MOBILE_ONLY)
flow: Overlay
draw_bg +: {
color: mod.wm_theme.background
Expand Down Expand Up @@ -218,8 +228,10 @@ const BAR_HEIGHT_FALLBACK: f64 = 26.0;
/// The bar's height and the left padding its content starts at. macOS puts
/// its traffic lights on the left; Linux and Windows put caption buttons on
/// the right, where they must not push the left cluster off screen.
#[cfg(not(mobile_only))]
type BarMetrics = (f64, f64);

#[cfg(not(mobile_only))]
fn bar_metrics_for_geom(geom: &WindowGeom, native_mobile: bool) -> BarMetrics {
if native_mobile {
return (48.0, 8.0);
Expand All @@ -238,7 +250,7 @@ fn bar_metrics_for_geom(geom: &WindowGeom, native_mobile: bool) -> BarMetrics {
(height, pad_left)
}

#[cfg(test)]
#[cfg(all(test, not(mobile_only)))]
mod bar_chrome_tests {
use super::*;

Expand Down Expand Up @@ -322,6 +334,7 @@ pub struct App {
tick: Timer,
/// Bar height + left padding last applied from the OS window buttons.
#[rust]
#[cfg(not(mobile_only))]
bar_metrics: Option<BarMetrics>,
/// SUPER+mouse:272/273 move & resize (tiling.lua).
#[rust]
Expand Down Expand Up @@ -2651,6 +2664,7 @@ impl App {
fn shell_menu_activate(&mut self, cx: &mut Cx, target: &str) {
if target=="start.documents" {self.launch_app(cx,"files");return;}
if target=="start.power" {self.toggle_shell_panel(cx,BarModule::Power);return;}
#[cfg(not(mobile_only))]
if let Some(name) = target.strip_prefix("desktop.") {
if let Some(style) = desktop::DesktopStyle::parse(name) {
if style.supports_dark() { self.state_mut().style.dark = name.ends_with("-dark"); }
Expand Down Expand Up @@ -2865,6 +2879,26 @@ impl App {
/// caption buttons affect the height only; the bar's left cluster stays
/// at its normal edge inset.
fn update_bar_chrome(&mut self, cx: &mut Cx, geom: &WindowGeom) {
// The standalone shell fills the window and keeps the insets for
// itself: the wallpaper runs edge to edge, the status bar and the
// navigation band sit inside the safe area (desk/phone.rs).
#[cfg(mobile_only)]
{
let i = geom.safe_area_insets;
let insets = mobile_gestures::SafeInsets { top: i.top, right: i.right, bottom: i.bottom, left: i.left };
if self.state.is_some() && self.state_mut().phone.insets != insets {
log!("wm: safe-area insets top {} right {} bottom {} left {}", i.top, i.right, i.bottom, i.left);
self.state_mut().phone.insets = insets;
self.redraw_all(cx);
}
return;
}
#[cfg(not(mobile_only))]
self.update_desk_bar_chrome(cx, geom);
}

#[cfg(not(mobile_only))]
fn update_desk_bar_chrome(&mut self, cx: &mut Cx, geom: &WindowGeom) {
let native_mobile = cfg!(any(target_os = "ios", target_os = "android"));
// Insets can change without changing the toolbar (rotation, system
// navigation mode), so update them before the metrics cache check.
Expand Down Expand Up @@ -4123,7 +4157,9 @@ impl MatchEvent for App {
// activations, the flyouts' controls.
match wa.cast::<ShellBarAction>() {
ShellBarAction::Press(module) => match module {
#[cfg(not(mobile_only))]
BarModule::Appearance => self.toggle_desktop_appearance(cx),
#[cfg(not(mobile_only))]
BarModule::Style => self.open_style_menu(cx),
BarModule::Menu => self.toggle_launcher(cx),
BarModule::Workspace(i) => {
Expand All @@ -4147,7 +4183,9 @@ impl MatchEvent for App {
},
ShellBarAction::RightPress(module) => match module {
// The Omarchy button's right click opens a terminal.
#[cfg(not(mobile_only))]
BarModule::Appearance => self.toggle_desktop_appearance(cx),
#[cfg(not(mobile_only))]
BarModule::Style => self.open_style_menu(cx),
BarModule::Menu => self.do_action(cx, WmAction::LaunchTerminal),
BarModule::ActiveWindow => {
Expand Down Expand Up @@ -4374,7 +4412,7 @@ impl AppMain for App {
// The shell bar's own modules are BUTTONS, not a drag handle:
// where it claims a point, the press reaches the widget.
let bar = self.ui.view(cx, ids!(bar)).area();
if cfg!(any(target_os = "ios", target_os = "android"))
if cfg!(any(target_os = "ios", target_os = "android")) || MOBILE_ONLY
|| self.phone_toolbar_hit(cx,dq.abs).is_some() || self.shell_bar_claims(cx, dq.abs) {
dq.response.set(WindowDragQueryResponse::Client);
} else if bar.is_valid(cx) && bar.rect(cx).contains(dq.abs) {
Expand All @@ -4383,6 +4421,7 @@ impl AppMain for App {
dq.response.set(WindowDragQueryResponse::Client);
}
}
#[cfg(not(mobile_only))]
if self.state.is_some() && !self.state_mut().style.target.mobile() {
if let Event::MouseDown(e)=event {
let module=self.ui.widget(cx,ids!(shell_bar)).borrow::<shell::bar::ShellBar>().and_then(|b|b.module_at(e.abs));
Expand Down
26 changes: 20 additions & 6 deletions src/mobile.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Phone shell state and geometry. Application viewports remain stable while
//! their compositor surfaces move between home, foreground and the task viewer.
use crate::{desktop::DesktopStyle, hub::ClientId, mobile_tiles::HomeTiles};
use crate::{desktop::DesktopStyle, hub::ClientId, mobile_gestures::SafeInsets, mobile_tiles::HomeTiles};
use makepad_widgets::*;
use std::collections::HashMap;

Expand All @@ -10,7 +10,13 @@ pub enum PhoneScreen { #[default] Home, App, Recents, Drawer }
#[derive(Clone, Debug, PartialEq)]
pub enum PhoneHit {
App(String), Card(ClientId), Home, Recents, Drawer, Back,
Rotate, Style, Appearance, Desktop, Key(String), Shift, Symbols, HideKeyboard,
/// The desk bar's phone strip (universal builds only): rotate the
/// window, the style menu, Light/Dark, back to the desktop.
#[cfg(not(mobile_only))] Rotate,
#[cfg(not(mobile_only))] Style,
#[cfg(not(mobile_only))] Appearance,
#[cfg(not(mobile_only))] Desktop,
Key(String), Shift, Symbols, HideKeyboard,
ClearSearch, CancelSearch,
Shade(crate::mobile_shade::ShadeHit),
/// A page indicator dot: jump the home pager there (mobile_pages.rs).
Expand Down Expand Up @@ -58,10 +64,15 @@ pub struct PhoneState {
pub ime: HashMap<ClientId, makepad_platform::ime::HostedImeState>,
pub shift: bool,
pub symbols: bool,
pub desktop_size: Option<Vec2d>,
pub desktop_clients: Vec<ClientId>,
pub desktop_style: DesktopStyle,
/// What the desk bar's Desktop toggle restores (universal builds only).
#[cfg(not(mobile_only))] pub desktop_size: Option<Vec2d>,
#[cfg(not(mobile_only))] pub desktop_clients: Vec<ClientId>,
#[cfg(not(mobile_only))] pub desktop_style: DesktopStyle,
/// The rect the shell lays out in: the desk's rect inside `insets`.
pub viewport: Rect,
/// The platform's safe-area insets (the notch, the system bars): the
/// wallpaper runs under them, everything else stays inside.
pub insets: SafeInsets,
/// The home page's live app tiles (mobile_tiles.rs): which client shows
/// which tile and in which face.
pub tiles: HomeTiles,
Expand All @@ -87,7 +98,10 @@ impl Default for PhoneState {
keyboard: 0.0, keyboard_target: 0.0, keyboard_sent_height: 0.0, keyboard_client: None,
search_query: String::new(), search_focused: false, search_scroll: 0.0,
ime: HashMap::new(), shift: false, symbols: false,
desktop_size: None, desktop_clients: Vec::new(), desktop_style: DesktopStyle::Omarchy, viewport: Rect::default(),
#[cfg(not(mobile_only))] desktop_size: None,
#[cfg(not(mobile_only))] desktop_clients: Vec::new(),
#[cfg(not(mobile_only))] desktop_style: DesktopStyle::Omarchy,
viewport: Rect::default(), insets: SafeInsets::default(),
tiles: HomeTiles::default(),
gesture_out: None,
exclusions: Default::default(),
Expand Down
Loading