Files
DevPack/internal/platform/windows.go
2026-03-06 11:59:00 +08:00

195 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package platform
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"github.com/user/devpack/internal/logging"
"golang.org/x/sys/windows/registry"
)
// WindowsPlatform Windows 平台实现
type WindowsPlatform struct {
homeDir string
}
// NewWindowsPlatform 创建 Windows 平台实例
func NewWindowsPlatform() *WindowsPlatform {
return &WindowsPlatform{
homeDir: os.Getenv("USERPROFILE"),
}
}
func (p *WindowsPlatform) OS() string { return "windows" }
func (p *WindowsPlatform) Arch() string { return os.Getenv("PROCESSOR_ARCHITECTURE") }
func (p *WindowsPlatform) HomeDir() string {
return p.homeDir
}
func (p *WindowsPlatform) ConfigDir() string {
if dir := os.Getenv("APPDATA"); dir != "" {
return dir
}
return p.homeDir + "\\AppData\\Roaming"
}
func (p *WindowsPlatform) DataDir() string {
if dir := os.Getenv("LOCALAPPDATA"); dir != "" {
return dir
}
return p.homeDir + "\\AppData\\Local"
}
func (p *WindowsPlatform) GetEnvVar(key string) string {
return os.Getenv(key)
}
func (p *WindowsPlatform) SetEnvVar(ctx context.Context, key, value string) error {
cmd := exec.CommandContext(ctx, "setx", key, value)
return cmd.Run()
}
func (p *WindowsPlatform) AddToPath(ctx context.Context, dir string) error {
currentPath := os.Getenv("PATH")
newPath := dir + ";" + currentPath
return p.SetEnvVar(ctx, "PATH", newPath)
}
func (p *WindowsPlatform) IsAdmin() bool {
// 检查 Windows 上是否以管理员身份运行
cmd := exec.Command("net", "session")
err := cmd.Run()
return err == nil
}
func (p *WindowsPlatform) PackageManagers() []string {
var pms []string
if _, err := exec.LookPath("scoop"); err == nil {
pms = append(pms, "scoop")
}
if _, err := exec.LookPath("choco"); err == nil {
pms = append(pms, "chocolatey")
}
if _, err := exec.LookPath("winget"); err == nil {
pms = append(pms, "winget")
}
return pms
}
func (p *WindowsPlatform) DefaultShell() string {
if _, err := exec.LookPath("pwsh"); err == nil {
return "pwsh" // PowerShell 7+ 版本
}
return "powershell" // Windows PowerShell 5.1
}
// InstallFont 安装字体到用户级字体目录并注册到注册表
// 支持 .ttf (TrueType) 和 .otf (OpenType) 格式
func (p *WindowsPlatform) InstallFont(ctx context.Context, fontPath string) error {
log := logging.G()
// 验证源字体文件是否存在且可访问
info, err := os.Stat(fontPath)
if err != nil {
log.Errorf("无法访问字体文件: %v", err)
return fmt.Errorf("无法访问字体文件: %w", err)
}
if info.IsDir() {
return fmt.Errorf("字体路径是目录而非文件: %s", fontPath)
}
// 检查字体格式是否受支持
fontName := filepath.Base(fontPath)
ext := filepath.Ext(fontName)
var regSuffix string
switch ext {
case ".ttf":
regSuffix = " (TrueType)"
case ".otf":
regSuffix = " (OpenType)"
default:
log.Errorf("不支持的字体格式: %s", ext)
return fmt.Errorf("不支持的字体格式: %s仅支持 .ttf 和 .otf", ext)
}
// 目标路径为用户级字体目录
// C:\Users\<User>\AppData\Local\Microsoft\Windows\Fonts
destDir := filepath.Join(os.Getenv("LOCALAPPDATA"), "Microsoft", "Windows", "Fonts")
log.Debugf("正在安装字体: %s 到 %s", fontPath, destDir)
// 确保目标目录存在
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("创建字体目录失败: %w", err)
}
// 检查字体是否已安装
destPath := filepath.Join(destDir, fontName)
if _, err := os.Stat(destPath); err == nil {
log.Infof("字体已存在,跳过安装: %s", destPath)
return nil
}
// 复制字体文件到目标路径
if err := copyFile(fontPath, destPath); err != nil {
return fmt.Errorf("复制字体文件失败: %w", err)
}
// 写注册表以注册字体(用户级,无需管理员权限)
key, _, err := registry.CreateKey(
registry.CURRENT_USER,
`Software\Microsoft\Windows NT\CurrentVersion\Fonts`,
registry.ALL_ACCESS,
)
if err != nil {
// 注册表写入失败时清理已复制的字体文件
os.Remove(destPath)
return fmt.Errorf("打开字体注册表失败: %w", err)
}
defer key.Close()
// 注册表键名:去掉扩展名的字体名 + 类型后缀
baseName := fontName[:len(fontName)-len(ext)]
regName := baseName + regSuffix
if err := key.SetStringValue(regName, destPath); err != nil {
os.Remove(destPath)
return fmt.Errorf("注册字体失败: %w", err)
}
log.Infof("字体安装成功: %s", fontName)
return nil
}
// copyFile 复制文件,失败时自动清理目标文件
func copyFile(src, dst string) error {
input, err := os.Open(src)
if err != nil {
return err
}
defer input.Close()
output, err := os.Create(dst)
if err != nil {
return err
}
_, err = io.Copy(output, input)
// 先关闭文件确保数据写入磁盘
closeErr := output.Close()
if err != nil {
os.Remove(dst) // 复制失败,清理残留文件
return err
}
if closeErr != nil {
os.Remove(dst)
return closeErr
}
return nil
}