Skip to content
Merged
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
7 changes: 7 additions & 0 deletions app/src/main/assets/de_packages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ des:
- alias: xfce4
display_name: "XFCE4"
tier: 1
# Run inside the container's already-logged-in boot_command session
# (see ProotExec.bootCommandTemplate()) once the X11 socket is ready —
# NOT a separate proot invocation.
session_command: "dbus-launch --exit-with-session startxfce4"
package_managers:
pacman: ["xfce4", "xfce4-goodies", "tigervnc", "dbus"]
dnf: ["@xfce-desktop-environment", "tigervnc-server"]
Expand All @@ -48,6 +52,7 @@ des:
- alias: lxqt
display_name: "LXQt"
tier: 1
session_command: "dbus-launch --exit-with-session startlxqt"
package_managers:
pacman: ["lxqt", "tigervnc", "dbus"]
dnf: ["@lxqt-desktop-environment", "tigervnc-server"]
Expand All @@ -56,6 +61,7 @@ des:
- alias: mate
display_name: "MATE"
tier: 1
session_command: "dbus-launch --exit-with-session mate-session"
package_managers:
pacman: ["mate", "tigervnc", "dbus"]
dnf: ["@mate-desktop-environment", "tigervnc-server"]
Expand All @@ -64,6 +70,7 @@ des:
- alias: i3wm
display_name: "i3wm"
tier: 1
session_command: "dbus-launch --exit-with-session i3"
package_managers:
pacman: ["i3-wm", "i3status", "dmenu", "tigervnc", "dbus"]
dnf: ["@i3-desktop-environment", "tigervnc-server"]
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/com/fct/tc4/ui/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import com.fct.tc4.ui.misc.WipeLayout
import com.fct.tc4.ui.page.ContainerInstallFragment
import com.fct.tc4.ui.page.ContainerMainFragment
import com.fct.tc4.ui.page.ContainerManageFragment
import com.fct.tc4.ui.page.DeSelectFragment
import com.fct.tc4.ui.page.DistroSelectFragment
import com.fct.tc4.ui.misc.Global
import com.fct.tc4.ui.page.TerminalFragment
import com.fct.tc4.ui.page.ContainerManageViewModel
Expand Down Expand Up @@ -165,6 +167,10 @@ class MainActivity : AppCompatActivity() {
fragment?.onImportAction()
true
}
R.id.containerBuildNew -> {
viewModel.navigateTo(MainViewModel.Screen.DistroSelect)
true
}
R.id.enterGui -> {
val fragment = supportFragmentManager.findFragmentByTag("ContainerMain") as? ContainerMainFragment
fragment?.onEnterGui()
Expand Down Expand Up @@ -215,6 +221,14 @@ class MainActivity : AppCompatActivity() {
}
tag = "ContainerMain"
}
is MainViewModel.Screen.DistroSelect -> {
fragment = DistroSelectFragment()
tag = "DistroSelect"
}
is MainViewModel.Screen.DeSelect -> {
fragment = DeSelectFragment.newInstance(screen.distroAlias)
tag = "DeSelect"
}
}
if (supportFragmentManager.findFragmentByTag(tag) != null) return
supportFragmentManager.commit {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/fct/tc4/ui/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
data object ContainerManage : Screen()
data class ContainerInstall(val code: String, val webpage: String?) : Screen()
data class ContainerMain(val code: String) : Screen()
/** First step of the on-device distro/DE build flow — pick a base distro. */
data object DistroSelect : Screen()
/** Second step — pick a desktop environment for the [distroAlias] chosen in DistroSelect. */
data class DeSelect(val distroAlias: String) : Screen()
}


Expand Down
12 changes: 11 additions & 1 deletion app/src/main/java/com/fct/tc4/ui/misc/DeRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ data class DeEntry(
val requiresBridge: Boolean,
/** package_manager family name -> list of packages/groups to install. */
val packageManagers: Map<PackageManagerFamily, List<String>>,
/**
* Command run inside the container's already-logged-in boot_command
* session once the X11 socket is ready (see TinyYamlBuilder.kt) — null
* for every current tier-2 entry, since how a Wayland-only compositor
* actually gets a picture onto Termux:X11's X-server-shaped bridge is
* exactly the unprototyped question the task brief asked to flag rather
* than guess at.
*/
val sessionCommand: String?,
) {
/** Null if this DE has no known install command for [family] yet (see de_packages.yaml). */
fun packagesFor(family: PackageManagerFamily): List<String>? = packageManagers[family]
Expand Down Expand Up @@ -83,7 +92,8 @@ object DeRegistry {
val packageManagers = packageManagersRaw.entries.associate { (key, value) ->
PackageManagerFamily.fromString(key) to value
}
val sessionCommand = raw["session_command"] as? String

return DeEntry(alias, displayName, tier, requiresBridge, packageManagers)
return DeEntry(alias, displayName, tier, requiresBridge, packageManagers, sessionCommand)
}
}
77 changes: 77 additions & 0 deletions app/src/main/java/com/fct/tc4/ui/misc/TinyYamlBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// TinyYamlBuilder.kt -- This file is part of tiny_container.
//
// Copyright (C) 2026 Caten Hu
//
// Tiny Container is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or any later version.
//
// Tiny Container is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.

package com.fct.tc4.ui.misc

/**
* Synthesizes the config map a freshly on-device-built (distro + DE)
* container needs — every other container's config comes from an
* upstream-authored .tiny.yaml imported wholesale; this is the one built by
* this app itself rather than imported.
*/
object TinyYamlBuilder {

/** Matches the DISPLAY=:6 baked into ProotExec.bootCommandTemplate(). */
private const val X11_DISPLAY = "6"

/**
* @throws IllegalStateException if [de] is non-null but has no
* [DeEntry.sessionCommand] (every current tier-2 entry) — there is
* nothing correct to write into `feature[].command` in that case, so
* this refuses rather than shipping a container with a guessed,
* probably-broken session launch command. Callers should check
* `de.sessionCommand != null` before offering a tier-2 DE as buildable.
*/
fun build(code: String, distro: DistroEntry, de: DeEntry?): Map<String, Any> {
val sessionCommand = de?.let {
it.sessionCommand
?: throw IllegalStateException(
"DE '${it.alias}' has no session_command yet — its X11/Wayland launch " +
"path isn't validated, so there's nothing correct to write here.",
)
}

val name = "${distro.displayName}${de?.let { " (${it.displayName})" } ?: ""}"
val description = buildString {
append("Built on-device from ${distro.displayName}")
if (de != null) append(" with ${de.displayName}")
append(".")
}

val config = mutableMapOf<String, Any>(
"code" to code,
"name" to name,
"description" to description,
"boot_command" to ProotExec.bootCommandTemplate(),
)

if (sessionCommand != null) {
config["feature"] = listOf(
mapOf(
"type" to "x11",
"name" to "X11",
"description" to "Termux:X11 graphical session.",
"enabled" to true,
"args" to listOf(":$X11_DISPLAY", "-extension", "MIT-SHM"),
"command" to sessionCommand,
),
)
}

return config
}
}
Loading
Loading