adb-toolkit/app.go
jegly cf3fb26bd2 Add system tray, APK Installer, fix package visibility, new themes/fonts; bump to v1.3.0
- System tray (Linux): pure Go D-Bus StatusNotifierItem, no cgo/libappindicator.
  Close now offers Minimize to Tray vs Quit instead of always quitting.
- New APK Installer module: batch-install from a folder or hand-picked files,
  live per-file progress, select all/none.
- Fix Packages/App Inspector silently hiding packages disabled/uninstalled by
  Canta/Shizuku (pm list packages needs -u to see them). Added a state filter
  and per-row Restore/Enable/Disable/Uninstall actions.
- 6 new theme palettes (Nord, Tokyo Night, Solarized Dark/Light, Rosé Pine,
  Everforest - 26 total), custom text colour, 8 bundled display fonts,
  adjustable font size.
- Horizontal sidebar now shrinks label text progressively instead of
  scrolling when the window narrows; fixed a touchpad stuck-scrollbar-drag bug.
- Updated README and fixed stale Go 1.23 -> 1.25 build instructions.
2026-07-09 20:05:26 +10:00

178 lines
5.1 KiB
Go

package main
import (
"context"
"sync"
"time"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// DeviceMode represents whether a device is in ADB or Fastboot mode
type DeviceMode string
const (
DeviceModeUnknown DeviceMode = "unknown"
DeviceModeADB DeviceMode = "adb"
DeviceModeFastboot DeviceMode = "fastboot"
)
// Device represents a connected ADB device
type Device struct {
Serial string `json:"serial"`
Status string `json:"status"`
}
// DeviceInfo holds detailed information about a connected device
type DeviceInfo struct {
Model string `json:"model"`
AndroidVersion string `json:"androidVersion"`
BuildNumber string `json:"buildNumber"`
BatteryLevel string `json:"batteryLevel"`
Serial string `json:"serial"`
IPAddress string `json:"ipAddress"`
RootStatus string `json:"rootStatus"`
Codename string `json:"codename"`
RamTotal string `json:"ramTotal"`
StorageInfo string `json:"storageInfo"`
Brand string `json:"brand"`
DeviceName string `json:"deviceName"`
SecurityPatch string `json:"securityPatch"`
Uptime string `json:"uptime"`
BootloaderStatus string `json:"bootloaderStatus"`
ScreenResolution string `json:"screenResolution"`
BasebandVersion string `json:"basebandVersion"`
KernelVersion string `json:"kernelVersion"`
CPUArch string `json:"cpuArch"`
}
// FileEntry represents a file or directory on the device
type FileEntry struct {
Name string `json:"name"`
Type string `json:"type"`
Size string `json:"size"`
Permissions string `json:"permissions"`
Date string `json:"date"`
Time string `json:"time"`
}
// PackageInfo represents an installed app package
type PackageInfo struct {
PackageName string `json:"packageName"`
IsEnabled bool `json:"isEnabled"`
// IsInstalled is false when the package is present on the system image but
// uninstalled for user 0 (e.g. via `pm uninstall --user 0`, which is what
// Canta/Shizuku do to "remove" a system app). Such packages are still on
// disk and show up in `pm list packages -u`, but not in the plain listing.
IsInstalled bool `json:"isInstalled"`
}
// AdbConfig holds user-configurable ADB settings
type AdbConfig struct {
AdbPath string `json:"adbPath"`
FastbootPath string `json:"fastbootPath"`
}
// App is the main application struct
type App struct {
ctx context.Context
config AdbConfig
// binary path cache
binaryCache map[string]string
cacheMutex sync.RWMutex
// cancellation for long-running ops
currentCancel context.CancelFunc
opMutex sync.Mutex
// app-lock "require password for destructive actions" session window
dangerMu sync.Mutex
dangerUntil time.Time
// window visibility, tracked for the tray's show/hide toggle
windowMu sync.Mutex
windowShown bool
}
// NewApp creates a new App instance
func NewApp() *App {
return &App{
binaryCache: make(map[string]string),
config: AdbConfig{},
}
}
// Startup is called when the app starts
func (a *App) Startup(ctx context.Context) {
a.ctx = ctx
a.windowShown = true
// Frameless windows can open off-centre on some WMs; centre on launch.
runtime.WindowCenter(ctx)
initTray(a)
}
// Shutdown is called when the app is closing — tidy up spawned child processes
// (e.g. a scrcpy mirror) so they don't outlive the app as orphan windows.
// Exception: a mirror started in "detached" mode is left running on purpose.
func (a *App) Shutdown(ctx context.Context) {
scrcpyMu.Lock()
detached := scrcpyDetached
scrcpyMu.Unlock()
if !detached {
a.StopScrcpy()
}
closeTray()
}
// ShowWindow restores the main window - used by the tray's "Show ATK" /
// left-click action and by the frontend's "Minimize to tray" quit dialog.
func (a *App) ShowWindow() {
a.windowMu.Lock()
a.windowShown = true
a.windowMu.Unlock()
runtime.WindowShow(a.ctx)
runtime.WindowUnminimise(a.ctx)
}
// HideWindow parks the window (app keeps running, reachable from the tray).
func (a *App) HideWindow() {
a.windowMu.Lock()
a.windowShown = false
a.windowMu.Unlock()
runtime.WindowHide(a.ctx)
}
// ToggleWindow shows the window if hidden, hides it if shown - the tray's
// left-click / "Show ATK" menu action.
func (a *App) ToggleWindow() {
a.windowMu.Lock()
shown := a.windowShown
a.windowMu.Unlock()
if shown {
a.HideWindow()
} else {
a.ShowWindow()
}
}
// TrayAvailable reports whether a real system tray icon was registered (e.g.
// false on Linux if no StatusNotifierWatcher/AppIndicator host is running).
// The frontend uses this to decide whether "Minimize to tray" is even a
// sensible option to offer in the close-confirmation dialog.
func (a *App) TrayAvailable() bool {
return trayAvailable()
}
// QuitApp fully quits ATK (not just the window) - used by both the tray's
// "Quit ATK" menu item and the frontend's close-confirmation dialog.
func (a *App) QuitApp() {
runtime.Quit(a.ctx)
}
// OpenURL opens a link in the user's default system browser. A bare <a
// target="_blank"> isn't reliable inside the embedded webview - this is
// Wails' supported way to hand off to the OS.
func (a *App) OpenURL(url string) {
runtime.BrowserOpenURL(a.ctx, url)
}