Files
vsix-hpl-toolbox/standalone/main.go

213 lines
6.0 KiB
Go

package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"path/filepath"
"sync/atomic"
"syscall"
"time"
"unsafe"
webview "github.com/jchv/go-webview2"
)
var AppVersion = "dev"
var currentHwnd atomic.Uintptr
func setupFileLogging() {
logPath := filepath.Join(userDataDir(), "debug.log")
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return
}
log.SetOutput(f)
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func main() {
setupFileLogging()
log.Printf("HPL Toolbox starting (pid=%d)", os.Getpid())
if focusExistingInstance() {
log.Printf("Another instance is running; focused it and exiting.")
return
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
port := listener.Addr().(*net.TCPAddr).Port
log.Printf("Server listening on 127.0.0.1:%d", port)
mux := buildMux()
srv := &http.Server{Handler: mux}
go func() {
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
log.Printf("Server error: %v", err)
}
}()
if err := writeLockFile(port); err != nil {
log.Printf("Failed to write lock file: %v", err)
}
defer removeLockFile()
webviewTmp, err := os.MkdirTemp("", "hpltoolbox-wv2-*")
if err != nil {
log.Fatalf("Failed to create webview temp dir: %v", err)
}
defer os.RemoveAll(webviewTmp)
w := webview.NewWithOptions(webview.WebViewOptions{
Debug: false,
AutoFocus: true,
DataPath: webviewTmp,
WindowOptions: webview.WindowOptions{
Title: "HPL Toolbox " + AppVersion,
Width: 1100,
Height: 720,
IconId: 1,
Center: true,
},
})
if w == nil {
log.Fatalln("Failed to create webview.")
}
defer w.Destroy()
// Stash HWND for the /api/focus handler.
currentHwnd.Store(uintptr(w.Window()))
w.SetSize(1100, 720, webview.HintNone)
w.Navigate(fmt.Sprintf("http://127.0.0.1:%d/", port))
w.Run()
// Window closed — shut the server down cleanly.
log.Printf("Window closed; shutting down server.")
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
}
func buildMux() *http.ServeMux {
mux := http.NewServeMux()
// Pages
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && r.URL.Path != "/home" {
http.NotFound(w, r)
return
}
HomePage(w, r)
})
mux.HandleFunc("GET /plec", PlecPage)
mux.HandleFunc("GET /applovin", ApplovinPage)
mux.HandleFunc("GET /base64", Base64Page)
mux.HandleFunc("GET /mraid", MraidPage)
mux.HandleFunc("GET /mintegral", MintegralPage)
mux.HandleFunc("GET /mobile", MobilePage)
mux.HandleFunc("GET /playworks", PlayworksPage)
mux.HandleFunc("GET /device-simulator", DeviceSimulatorPage)
mux.HandleFunc("GET /changelog", ChangelogPage)
mux.HandleFunc("GET /assets/qrcode.min.js", MobileQrScript)
// Shared API
mux.HandleFunc("POST /api/clipboard", ClipboardEndpoint)
mux.HandleFunc("POST /api/open", OpenEndpoint)
mux.HandleFunc("POST /api/focus", FocusEndpoint)
mux.HandleFunc("POST /api/betaTools", BetaToolsEndpoint)
mux.HandleFunc("GET /api/update/check", UpdateCheckEndpoint)
mux.HandleFunc("POST /api/drop/resolve", DropResolveEndpoint)
// PLEC
mux.HandleFunc("POST /api/plec/pick", PlecPick)
mux.HandleFunc("POST /api/plec/pickFolder", PlecPickFolder)
mux.HandleFunc("POST /api/plec/upload", PlecUpload)
// AppLovin
mux.HandleFunc("POST /api/applovin/pick", ApplovinPick)
mux.HandleFunc("POST /api/applovin/pickFolder", ApplovinPickFolder)
mux.HandleFunc("POST /api/applovin/upload", ApplovinUpload)
mux.HandleFunc("POST /api/applovin/saveQr", ApplovinSaveQR)
mux.HandleFunc("POST /api/applovin/saveAllQrs", ApplovinSaveAllQRs)
// Base64
mux.HandleFunc("POST /api/base64/pickFolder", Base64PickFolder)
mux.HandleFunc("POST /api/base64/pickFiles", Base64PickFiles)
mux.HandleFunc("POST /api/base64/scan", Base64Scan)
// MRAID Checker
mux.HandleFunc("POST /api/mraid/pickFolder", MraidPickFolder)
mux.HandleFunc("POST /api/mraid/pickFiles", MraidPickFiles)
mux.HandleFunc("POST /api/mraid/scan", MraidScan)
// Mintegral Checker
mux.HandleFunc("POST /api/mintegral/pick", MintegralPick)
mux.HandleFunc("POST /api/mintegral/load", MintegralLoad)
mux.HandleFunc("GET /mintegral/runtime", MintegralRuntimePage)
mux.HandleFunc("GET /mintegral/ad", MintegralAd)
mux.HandleFunc("GET /mintegral/monitor.js", MintegralMonitor)
mux.HandleFunc("GET /mintegral/preview-util.js", MintegralPreviewUtil)
// Send To Mobile
mux.HandleFunc("POST /api/mobile/pick", MobilePick)
mux.HandleFunc("POST /api/mobile/pickFolder", MobilePickFolder)
mux.HandleFunc("POST /api/mobile/start", MobileStart)
mux.HandleFunc("POST /api/mobile/stop", MobileStop)
// Playworks Converter
mux.HandleFunc("POST /api/playworks/pickSource", PlayworksPickSource)
mux.HandleFunc("POST /api/playworks/pickOutput", PlayworksPickOutput)
mux.HandleFunc("POST /api/playworks/convert", PlayworksConvert)
// Device Simulator
mux.HandleFunc("POST /api/device-simulator/pick", DeviceSimulatorPick)
mux.HandleFunc("POST /api/device-simulator/load", DeviceSimulatorLoad)
mux.HandleFunc("GET /api/device-simulator/preview/", DeviceSimulatorPreview)
return mux
}
// FocusEndpoint brings the webview window to the foreground. Used by a second
// instance that wants to focus the running one before exiting.
func FocusEndpoint(w http.ResponseWriter, r *http.Request) {
hwnd := currentHwnd.Load()
if hwnd != 0 {
restoreAndFocus(hwnd)
}
w.WriteHeader(http.StatusOK)
}
var (
user32 = syscall.NewLazyDLL("user32.dll")
procShowWindow = user32.NewProc("ShowWindow")
procSetForeground = user32.NewProc("SetForegroundWindow")
procIsIconic = user32.NewProc("IsIconic")
)
const (
swRestore = 9
swShow = 5
)
func restoreAndFocus(hwnd uintptr) {
// If minimized, restore. Otherwise just ensure it's shown.
iconic, _, _ := procIsIconic.Call(hwnd)
if iconic != 0 {
_, _, _ = procShowWindow.Call(hwnd, swRestore)
} else {
_, _, _ = procShowWindow.Call(hwnd, swShow)
}
_, _, _ = procSetForeground.Call(hwnd)
}
// Silence "imported and not used" complaints if any.
var _ = unsafe.Sizeof(int(0))