Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
95b106d
:star: feat: implement new scrcpy for web
vangie Sep 10, 2025
79c378d
chore: remove unused package.json and pnpm-lock.yaml files; refactor …
vangie Sep 12, 2025
fb8eeed
feat: enhance logging and add verbose flag support in CLI
vangie Sep 14, 2025
d840d0e
feat: enhance device connection and streaming capabilities
vangie Sep 18, 2025
55559af
feat: enhance audio streaming and connection handling
vangie Sep 19, 2025
36ae62f
feat: enhance control service and event handling
vangie Sep 20, 2025
79d3380
feat: enhance logging and error handling in device control events
vangie Sep 22, 2025
11fd9e5
feat: enhance video handling and resize logic in AndroidLiveView and …
vangie Sep 22, 2025
070868d
feat: refactor logging and error handling in device control and strea…
vangie Sep 23, 2025
bdf670f
feat: update dependencies and improve logging in device control
vangie Sep 26, 2025
55eafbf
feat: refactor API and device handling for improved routing and funct…
vangie Sep 27, 2025
8a3e3cb
feat: implement new streaming headers and refactor device audio handling
vangie Sep 27, 2025
b21fcc4
feat: remove deprecated H.264 and WebRTC control handlers, streamline…
vangie Sep 27, 2025
b05f490
feat: remove fMP4 stream writer and related tests to streamline trans…
vangie Sep 27, 2025
5c827c4
integrate with ap
mingshun Oct 15, 2025
5b6dd9e
refactor: integrate util package for table rendering in CLI commands
vangie Oct 27, 2025
1c6db08
fix: device-connect on https
mingshun Oct 23, 2025
5271704
feat: enhance device output formatting and API integration
vangie Oct 28, 2025
6a8c70c
add api for running adb command
mingshun Oct 29, 2025
6934c18
:star: feat: enhance device registration with reg_id support (#255)
vangie Oct 30, 2025
e256565
:star: feat: restart server after binary recompile (#256)
vangie Oct 30, 2025
343063b
feat: add debugging steps to CI workflow for live-view package
vangie Oct 30, 2025
bf324c2
:star: feat: enhance device metadata with display resolution (#257)
vangie Oct 31, 2025
49630fb
:star: feat: refactor device connection commands to use DeviceDTO (#259)
vangie Oct 31, 2025
d2c67bf
:star: feat: support linux device connect (#260)
zthreefires Nov 3, 2025
af95996
:shirt: refactor: remove deprecated Linux device command (#261)
vangie Nov 5, 2025
d0b8ed5
:star: feat: enhance device connection management (#262)
vangie Nov 6, 2025
61a8f47
:star: feat: enhance device reconnection management (#263)
vangie Nov 7, 2025
c144e9c
:star: feat: implement Appium request proxying and enhance device unr…
vangie Nov 11, 2025
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
58 changes: 58 additions & 0 deletions .github/workflows/test-live-view.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Live View Tests

on:
push:
branches: ["**"]
paths:
- "packages/live-view/**"
pull_request:
branches: ["**"]
paths:
- "packages/live-view/**"

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: packages/live-view/package.json

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: packages/live-view/pnpm-lock.yaml

- name: Install dependencies
run: |
cd packages/live-view
pnpm install --frozen-lockfile

- name: Run lint
run: |
cd packages/live-view
pnpm lint

- name: Run type-check
run: |
cd packages/live-view
pnpm type-check

- name: Run tests
run: |
cd packages/live-view
pnpm test

- name: Run build
run: |
cd packages/live-view
pnpm build
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"gbox",
"KUBECFG",
"kubeconfig",
"modelcontextprotocol"
"modelcontextprotocol",
"serialno"
],
"python.analysis.extraPaths": [
"./packages/sdk/python"
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ gbox-darwin-*
gbox-linux-*
gbox-windows-*
gbox
gbox-test
gbox-test

assets/scrcpy-server*.jar
internal/server/static/live-view/
28 changes: 26 additions & 2 deletions packages/cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,33 @@ clean: ## Clean the build directory
@rm -f $(BINARY_NAME)*
@echo "Cleaning completed"

# Build dependencies (live-view and scrcpy-server)
build-deps: build-live-view download-scrcpy-server ## Build all dependencies

# Build live-view static files and copy to CLI static directory
build-live-view: ## Build live-view static files
@echo "Building live-view static files..."
@$(MAKE) -C ../live-view build
@echo "Cleaning old live-view static files..."
@rm -rf internal/server/static/live-view
@echo "Copying live-view static files to CLI..."
@mkdir -p internal/server/static/live-view
@cp -r ../live-view/static/* internal/server/static/live-view/
@echo "✅ Live-view static files ready for embedding"

# Download scrcpy-server.jar
download-scrcpy-server: ## Download scrcpy-server.jar
@if [ ! -f "assets/scrcpy-server.jar" ]; then \
echo "Downloading scrcpy-server.jar..."; \
./scripts/download-scrcpy-server.sh; \
else \
echo "scrcpy-server.jar already exists"; \
fi

# Build binary for a single platform
binary: ## Build binary for the current platform (GOOS/GOARCH)
binary: build-deps ## Build binary for the current platform (GOOS/GOARCH)
@echo "Building $(BINARY_NAME) binary ($(GOOS)/$(GOARCH))..."
@echo "Note: live-view static files will be embedded in the binary"
CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build $(LDFLAGS) -o $(BINARY_NAME) $(MAIN_FILE)
@echo "Binary built: $(BINARY_NAME)"

Expand All @@ -74,7 +98,7 @@ test: ## Run tests
go test ./... -v

# Build binaries for all supported platforms
binary-all: ## Build binaries for all supported platforms
binary-all: build-deps ## Build binaries for all supported platforms
@echo "Building binaries for all supported platforms..."
@for platform in $(PLATFORMS); do \
os=$$(echo $$platform | cut -d- -f1); \
Expand Down
44 changes: 17 additions & 27 deletions packages/cli/cmd/adb_expose.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func NewAdbExposeStartCommand() *cobra.Command {
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeBoxIDs(cmd, args, toComplete)
},
SilenceUsage: true, // Don't show usage on error
SilenceErrors: true, // Don't show errors (we handle them ourselves)
}

cmd.Flags().IntVarP(&opts.LocalPort, "port", "p", 0, "Local port to bind to (default: auto-find available port starting from 5555)")
Expand All @@ -103,6 +105,8 @@ func NewAdbExposeStopCommand() *cobra.Command {
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeBoxIDs(cmd, args, toComplete)
},
SilenceUsage: true, // Don't show usage on error
SilenceErrors: true, // Don't show errors (we handle them ourselves)
}
return cmd
}
Expand All @@ -111,11 +115,13 @@ func NewAdbExposeListCommand() *cobra.Command {
opts := &AdbExposeListOptions{}
cmd := &cobra.Command{
Use: "list",
Short: "List all running adb-expose processes",
Short: "List all exposed ADB ports",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return ExecuteAdbExposeList(cmd, opts)
},
SilenceUsage: true, // Don't show usage on error
SilenceErrors: true, // Don't show errors (we handle them ourselves)
}

cmd.Flags().StringVarP(&opts.OutputFormat, "output", "o", "table", "Output format (table|json)")
Expand All @@ -142,19 +148,14 @@ func ExecuteAdbExposeInteractive(cmd *cobra.Command, opts *AdbExposeOptions) err
return fmt.Errorf("interactive mode not available in daemon process")
}

// Get current exposures without running cleanup logic
infos, err := adb_expose.ListPidFiles()
if err != nil {
return fmt.Errorf("failed to list current exposures: %v", err)
}

// Only show current exposures section if there are any
if len(infos) > 0 {
fmt.Println("Current ADB port exposures:")
fmt.Println("============================")
printAdbExposeTable(infos)
fmt.Println()
// Use the new client-server architecture to list current exposures
fmt.Println("Current ADB port exposures:")
fmt.Println("============================")
if err := adb_expose.ListCommand(""); err != nil {
// If server is not running, just show a message
fmt.Println("ADB Expose server is not running")
}
fmt.Println()

// Get available boxes
sdkClient, err := client.NewClientFromProfile()
Expand All @@ -172,22 +173,11 @@ func ExecuteAdbExposeInteractive(cmd *cobra.Command, opts *AdbExposeOptions) err
return nil
}

// Filter running Android boxes and exclude already exposed ones
// Filter running Android boxes
var availableBoxes []client.BoxInfo
exposedBoxIDs := make(map[string]bool)

// Use the infos variable we already got above
for _, info := range infos {
if adb_expose.IsProcessAlive(info.Pid) {
exposedBoxIDs[info.BoxID] = true
}
}

for _, box := range boxes {
if box.Status == "running" && strings.HasPrefix(box.Type, "android") {
if !exposedBoxIDs[box.ID] {
availableBoxes = append(availableBoxes, box)
}
availableBoxes = append(availableBoxes, box)
}
}

Expand Down Expand Up @@ -283,7 +273,7 @@ func ExecuteAdbExposeInteractive(cmd *cobra.Command, opts *AdbExposeOptions) err
// ExecuteAdbExposeStop stops adb-expose processes for a specific box
// This function is now implemented in adb_expose_stop.go

// ExecuteAdbExposeList lists all running adb-expose processes
// ExecuteAdbExposeList lists all exposed ADB ports
// This function is now implemented in adb_expose_list.go

func boxValid(boxID string) bool {
Expand Down
154 changes: 3 additions & 151 deletions packages/cli/cmd/adb_expose_list.go
Original file line number Diff line number Diff line change
@@ -1,160 +1,12 @@
package cmd

import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strconv"
"strings"

"github.com/babelcloud/gbox/packages/cli/internal/adb_expose"
"github.com/spf13/cobra"
)

// ExecuteAdbExposeList lists all running adb-expose processes
// ExecuteAdbExposeList lists all exposed ADB ports using the new client-server architecture
func ExecuteAdbExposeList(cmd *cobra.Command, opts *AdbExposeListOptions) error {
// Step 1: Find all running gbox adb-expose processes (cross-platform, best effort)
psCmd := exec.Command("ps", "aux")
psOut, err := psCmd.Output()
if err != nil {
return fmt.Errorf("failed to run ps aux: %v", err)
}
lines := strings.Split(string(psOut), "\n")
var runningPids = make(map[int]bool)
for _, line := range lines {
if strings.Contains(line, "gbox adb-expose") && !strings.Contains(line, "grep") {
// ignore gbox adb-expose list process itself
if strings.Contains(line, "gbox adb-expose list") {
continue
}
fields := strings.Fields(line)
if len(fields) > 1 {
pid, err := strconv.Atoi(fields[1])
if err == nil {
runningPids[pid] = true
}
}
}
}
// Step 2: List all pid files (registered adb-exposes)
infos, err := adb_expose.ListPidFiles()
if err != nil {
return err
}
registeredPids := make(map[int]adb_expose.PidInfo)
for _, info := range infos {
registeredPids[info.Pid] = info
}
// Step 3: Check for running processes not in pid files
for pid := range runningPids {
if _, ok := registeredPids[pid]; !ok {
fmt.Printf("[WARN] Found running adb-expose process (pid=%d) not in registry. If you want to stop it, run: gbox adb-expose stop <box_id>\n\n", pid)
}
}
// Step 4: Check for pid files whose process is not running, and clean up
for pid, info := range registeredPids {
if !runningPids[pid] && !adb_expose.IsProcessAlive(pid) {
fmt.Printf("[CLEANUP] Removing stale pid file for dead process (pid=%d, boxId=%s, localPorts=%v)\n", pid, info.BoxID, info.LocalPorts)
for _, lp := range info.LocalPorts {
adb_expose.RemovePidFile(info.BoxID, lp)
adb_expose.RemoveLogFile(info.BoxID, lp)
}
}
}
// Step 5: For those pid files exist and process is running, check the box status, if the box is not running, clean up the pid file and kill the process
for pid, info := range registeredPids {
if runningPids[pid] && adb_expose.IsProcessAlive(pid) {
if !boxValid(info.BoxID) {
fmt.Printf("[CLEANUP] Box %s is not running, killing adb-expose process (pid=%d) and removing pid file(s)\n", info.BoxID, pid)
proc, err := os.FindProcess(pid)
if err == nil {
proc.Kill()
}
for _, lp := range info.LocalPorts {
adb_expose.RemovePidFile(info.BoxID, lp)
adb_expose.RemoveLogFile(info.BoxID, lp)
}
}
}
}

// Step 6: Print the current valid adb-exposes
updatedInfos, err := adb_expose.ListPidFiles()
if err != nil {
return fmt.Errorf("failed to list pid files after cleanup: %v", err)
}

// Output based on format
if opts.OutputFormat == "json" {
printAdbExposeJSON(updatedInfos)
} else {
printAdbExposeTable(updatedInfos)
}
return nil
}

// printAdbExposeTable prints the ADB expose table in a formatted way
func printAdbExposeTable(infos []adb_expose.PidInfo) {
if len(infos) == 0 {
fmt.Println("No ADB port exposures found")
return
}

fmt.Printf("| %-8s | %-36s | %-10s | %-8s | %-20s |\n", "PID", "BoxID", "Port", "Status", "StartedAt")
fmt.Println("|----------|--------------------------------------|------------|----------|----------------------|")
for _, info := range infos {
status := "Dead"
if adb_expose.IsProcessAlive(info.Pid) {
status = "Alive"
}
for i := 0; i < len(info.LocalPorts); i++ {
fmt.Printf("| %-8d | %-36s | %-10d | %-8s | %-20s |\n", info.Pid, info.BoxID, info.LocalPorts[i], status, info.StartedAt.Format("2006-01-02 15:04:05"))
}
}
}

// printAdbExposeJSON prints the ADB expose information in JSON format
func printAdbExposeJSON(infos []adb_expose.PidInfo) {
// Debug: check if infos is nil or empty
if infos == nil {
fmt.Println("[]")
return
}

type AdbExposeInfo struct {
PID int `json:"pid"`
BoxID string `json:"boxId"`
LocalPorts []int `json:"localPorts"`
Status string `json:"status"`
StartedAt string `json:"startedAt"`
}

var jsonData []AdbExposeInfo
for _, info := range infos {
status := "Dead"
if adb_expose.IsProcessAlive(info.Pid) {
status = "Alive"
}

jsonInfo := AdbExposeInfo{
PID: info.Pid,
BoxID: info.BoxID,
LocalPorts: info.LocalPorts,
Status: status,
StartedAt: info.StartedAt.Format("2006-01-02T15:04:05Z"),
}
jsonData = append(jsonData, jsonInfo)
}

// Ensure we always output a valid JSON array, even if empty
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "Error marshaling JSON: %v\n", err)
// Fallback to empty array if marshaling fails
fmt.Println("[]")
return
}

fmt.Println(string(jsonBytes))
// Use the new client-server architecture
return adb_expose.ListCommand(opts.OutputFormat)
}
Loading