omegafox/launcher/main.go
daijro.dev@gmail.com 0448ea1c20 Launcher fixes for Windows
- Use GetEnvironmentVariableW to get CAMOU_CONFIG in UTF-16 instead of ANSI on Windows. Prevents data loss.
- Added ability to pass in CAMOU_CONFIG in multiple environment variables
- Split environment variables into 2047 character chunks on Windows
- Use github.com/goccy/go-json for faster JSON encoding
- Fix Windows not finding relative camoufox.exe file
2024-08-06 05:40:54 -05:00

227 lines
5.1 KiB
Go

package main
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"unicode/utf8"
json "github.com/goccy/go-json"
"github.com/mileusna/useragent"
)
func main() {
configPath, remainingArgs := parseArgs(os.Args[1:]) // Parse args arguments
configMap := readAndParseConfig(configPath) // Read and parse the config file
userAgentOS := determineUserAgentOS(configMap) // Determine the user agent OS
// OS specific font config
updateFonts(configMap, userAgentOS)
setEnvironmentVariables(configMap, userAgentOS)
// Run the Camoufox executable
execName := getExecutableName()
runCamoufox(execName, remainingArgs)
}
func parseArgs(args []string) (string, []string) {
var configPath string
var remainingArgs []string
for i := 0; i < len(args); i++ {
if args[i] == "--config" {
if i+1 < len(args) {
configPath = args[i+1]
remainingArgs = append(args[:i], args[i+2:]...)
break
} else {
fmt.Println("Error: --config flag requires a value")
os.Exit(1)
}
}
}
// If no config data is provided, fallback to an empty object
if configPath == "" {
configPath = "{}"
}
return configPath, remainingArgs
}
func readAndParseConfig(configInput string) map[string]interface{} {
var configData []byte
// Check if the input is a file path or inline JSON
if _, err := os.Stat(configInput); err == nil {
configData, err = os.ReadFile(configInput)
if err != nil {
fmt.Printf("Error reading config file: %v\n", err)
os.Exit(1)
}
} else {
// Assume it's inline JSON
configData = []byte(configInput)
}
var configMap map[string]interface{}
if err := json.Unmarshal(configData, &configMap); err != nil {
fmt.Printf("Invalid JSON in config: %v\n", err)
os.Exit(1)
}
return configMap
}
func determineUserAgentOS(configMap map[string]interface{}) string {
defaultOS := normalizeOS(runtime.GOOS)
if ua, ok := configMap["navigator.userAgent"].(string); ok {
parsedUA := useragent.Parse(ua)
if parsedUA.OS != "" {
return normalizeOS(parsedUA.OS)
}
}
return defaultOS
}
func normalizeOS(osName string) string {
osName = strings.ToLower(osName)
switch {
case osName == "darwin" || strings.Contains(osName, "mac"):
return "macos"
case strings.Contains(osName, "win"):
return "windows"
default:
return "linux"
}
}
func updateFonts(configMap map[string]interface{}, userAgentOS string) {
fonts, ok := configMap["fonts"].([]interface{})
if !ok {
fonts = []interface{}{}
}
existingFonts := make(map[string]bool)
for _, font := range fonts {
if f, ok := font.(string); ok {
existingFonts[f] = true
}
}
for _, font := range FontsByOS[userAgentOS] {
if !existingFonts[font] {
fonts = append(fonts, font)
}
}
configMap["fonts"] = fonts
}
func setEnvironmentVariables(configMap map[string]interface{}, userAgentOS string) {
updatedConfigData, err := json.Marshal(configMap)
if err != nil {
fmt.Printf("Error updating config: %v\n", err)
os.Exit(1)
}
// Validate utf8
if !utf8.Valid(updatedConfigData) {
fmt.Println("Config is not valid UTF-8")
os.Exit(1)
}
// Split the config into chunks of 2047 characters if the OS is Windows,
// otherwise split into 32767 characters
var chunkSize int
if normalizeOS(runtime.GOOS) == "windows" {
chunkSize = 2047
} else {
chunkSize = 32767
}
configStr := string(updatedConfigData)
for i := 0; i < len(configStr); i += chunkSize {
end := i + chunkSize
if end > len(configStr) {
end = len(configStr)
}
chunk := configStr[i:end]
envName := fmt.Sprintf("CAMOU_CONFIG_%d", (i/chunkSize)+1)
if err := os.Setenv(envName, chunk); err != nil {
fmt.Printf("Error setting %s: %v\n", envName, err)
os.Exit(1)
}
}
if normalizeOS(runtime.GOOS) == "linux" {
fontconfigPath := filepath.Join("fontconfig", userAgentOS)
os.Setenv("FONTCONFIG_PATH", fontconfigPath)
}
}
func getExecutableName() string {
switch normalizeOS(runtime.GOOS) {
case "linux":
return "./camoufox-bin"
case "macos":
return "./Camoufox.app"
case "windows":
return "./camoufox.exe"
default:
// This should never be reached due to the check in normalizeOS
return ""
}
}
func runCamoufox(execName string, args []string) {
cmd := exec.Command(execName, args...)
// Create pipes for stdout and stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("Error creating stdout pipe: %v\n", err)
os.Exit(1)
}
stderr, err := cmd.StderrPipe()
if err != nil {
fmt.Printf("Error creating stderr pipe: %v\n", err)
os.Exit(1)
}
// Start the command
if err := cmd.Start(); err != nil {
fmt.Printf("Error starting %s: %v\n", execName, err)
os.Exit(1)
}
// Create a channel to signal when we're done copying output
done := make(chan bool)
// Copy stdout and stderr to the console
go func() {
io.Copy(os.Stdout, stdout)
done <- true
}()
go func() {
io.Copy(os.Stderr, stderr)
done <- true
}()
// Wait for both stdout and stderr to finish
<-done
<-done
// Wait for the command to finish
if err := cmd.Wait(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode())
} else {
fmt.Printf("Error running %s: %v\n", execName, err)
os.Exit(1)
}
}
}