golang知识集

golang圆阵列图记:天灵灵地灵灵图标排圆形

2025-11-12  本文已影响0人  Mgx_无心

夫 Windows 之桌面,图标林立,如市井杂陈,无序可言。或左或右,或上或下,杂然纷呈,观之令人心烦。余尝思:何不使诸图标环列如星,布阵若圆,以彰秩序之美乎?遂作此《圆阵列图记》,以志其事。

一、缘起

昔者,余偶见一友,其桌面图标排列如北斗七星,熠熠生辉,问其故,答曰:"吾以程序御之,使图标绕圆而居。"余闻之大奇,遂求其术。友笑曰:"此非仙术,乃 Windows API 之妙用也。"余归而研之,终得其法,遂著此文,以飨同好。

二、其理何在?

Windows 桌面之图标,实乃"SysListView32"控件所载,藏于"SHELLDLL_DefView"之中,而此窗又匿于"Progman"或"WorkerW"之内。欲驭图标,必先寻其巢穴,如捕龙须先知其穴。

吾程序首务,乃遍历窗口,寻得此"列表视图"之句柄(HWND),而后方可号令图标。其法有三:

得其句柄,便可调用 LVM_SETITEMPOSITION 之令,指定某图标于某坐标。此即"点兵布阵"之术也。

三、圆阵之法

既得图标之数,复知屏幕之广(GetSystemMetrics 可得),便可布圆阵矣。

设屏幕宽高为 W、H,则圆心居中:

X₀ = W/2,Y₀ = H/2

图标 N 枚,均布于半径 R 之圆周,则第 i 图标之位为:

X = X₀ + R·cos(2πi/N)

Y = Y₀ + R·sin(2πi/N)

为免图标贴边如"临崖勒马",更设边距四十像素,使诸图标进退有度,不至"坠入虚空"。

四、关键一诀:禁"自动排列"

然有一大忌,不可不察!

Windows 有"自动排列图标"之癖,若此癖未除,则吾所设之位,顷刻被系统抹去,如沙上书字,潮至即没。故必先入注册表,改 AutoArrange 为 0,方可成事。

路径:HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced

吾程序虽可自动禁之,然有时系统顽固,需手动右键桌面 → 查看 → 取消"自动排列图标",方保万全。此乃成败之枢机,切记!切记!

五、刷新与重启

布阵既毕,需告之系统:"吾已更易阵型,速速显之!"

可用 WM_SETTINGCHANGE 之信,或干脆重启 explorer.exe,如更衣换甲,焕然一新。

然重启资源管理器,风险稍高,或致桌面暂隐。故吾程序今但发刷新之令,辅以提示:"君可按 F5 自行刷新",更为稳妥。

六、源码

package main

import (
    "fmt"
    "math"
    "os/exec"
    "runtime"
    "strconv"
    "strings"
    "syscall"
    "time"
    "unsafe"

    "golang.org/x/sys/windows"
    "golang.org/x/sys/windows/registry"
)

// Windows API 常量
const (
    // 屏幕分辨率获取常量
    SM_CXSCREEN = 0
    SM_CYSCREEN = 1

    // 桌面窗口类名
    DESKTOP_CLASS_NAME = "Progman"

    // 图标窗口类名
    SHELL_DEF_VIEW = "SHELLDLL_DefView"

    // 列表视图常量
    LVM_GETITEMCOUNT    = 0x1004
    LVM_SETITEMPOSITION = 0x100F

    // 通知常量
    SHCNE_ASSOCCHANGED = 0x08000000
    SHCNF_IDLIST       = 0x0000
    WM_SETTINGCHANGE   = 0x001A
)

// Windows API 声明
var (
    user32               = windows.NewLazySystemDLL("user32.dll")
    procGetSystemMetrics = user32.NewProc("GetSystemMetrics")
    procFindWindowW      = user32.NewProc("FindWindowW")
    procFindWindowExW    = user32.NewProc("FindWindowExW")
    procSendMessageW     = user32.NewProc("SendMessageW")
    procEnumChildWindows = user32.NewProc("EnumChildWindows")
    shell32              = windows.NewLazySystemDLL("shell32.dll")
    procSHChangeNotify   = shell32.NewProc("SHChangeNotify")
)

// 回调函数类型
type EnumWindowsProc func(hwnd HWND, lParam uintptr) uintptr

// 确保我们使用syscall类型与Windows API兼容
type HWND = uintptr
type WPARAM = uintptr
type LPARAM = uintptr

type POINT struct {
    X int32
    Y int32
}

type RECT struct {
    Left   int32
    Top    int32
    Right  int32
    Bottom int32
}

// 查找桌面列表视图窗口
func findDesktopListView() HWND {
    // 查找 Progman 窗口
    progman, _, _ := procFindWindowW.Call(
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(DESKTOP_CLASS_NAME))),
        0,
    )

    if progman == 0 {
        fmt.Println("无法找到桌面窗口 (Progman)")
        return 0
    }

    // 查找 SHELLDLL_DefView 窗口
    shelldllDefView, _, _ := procFindWindowExW.Call(
        progman,
        0,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SHELL_DEF_VIEW))),
        0,
    )

    if shelldllDefView == 0 {
        // 在某些Windows版本上,我们可能需要特殊处理
        // 向Progman发送消息以获取WorkerW窗口链
        user32.NewProc("SendMessageTimeoutW").Call(
            progman,
            0x052C, // WM_USER + 0x52C
            0,
            0,
            0,
            500,
            0,
        )

        // 尝试查找WorkerW窗口
        var workerw HWND
        workerw = 0
        procFindWindowExW.Call(
            0,
            0,
            uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("WorkerW"))),
            0,
        )

        // 遍历所有WorkerW窗口,找到包含SHELLDLL_DefView的那个
        for workerw != 0 {
            temp, _, _ := procFindWindowExW.Call(
                workerw,
                0,
                uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SHELL_DEF_VIEW))),
                0,
            )

            if temp != 0 {
                shelldllDefView = temp
                break
            }

            workerw, _, _ = procFindWindowExW.Call(
                0,
                workerw,
                uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("WorkerW"))),
                0,
            )
        }
    }

    if shelldllDefView == 0 {
        fmt.Println("无法找到SHELLDLL_DefView窗口")
        return 0
    }

    // 查找列表视图窗口
    listView, _, _ := procFindWindowExW.Call(
        shelldllDefView,
        0,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("SysListView32"))),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("FolderView"))),
    )

    if listView == 0 {
        fmt.Println("无法找到桌面列表视图窗口")
        return 0
    }

    return listView
}

// 获取桌面图标数量
func getDesktopIconCount(listView HWND) int {
    count, _, _ := procSendMessageW.Call(
        uintptr(listView),
        LVM_GETITEMCOUNT,
        0,
        0,
    )
    return int(count)
}

// 设置图标位置
func setIconPosition(listView HWND, index int, x, y int32) bool {
    result, _, _ := procSendMessageW.Call(
        uintptr(listView),
        LVM_SETITEMPOSITION,
        uintptr(index),
        uintptr((int32(y)<<16)|int32(x)),
    )
    return result != 0
}

// 获取屏幕分辨率
func getScreenResolution() (int, int) {
    width, _, _ := procGetSystemMetrics.Call(SM_CXSCREEN)
    height, _, _ := procGetSystemMetrics.Call(SM_CYSCREEN)
    return int(width), int(height)
}

// 通知系统刷新桌面
func refreshDesktop() {
    fmt.Println("正在刷新桌面...")
    // 简单的刷新方式,避免复杂的API调用导致卡住
    // 直接发送WM_SETTINGCHANGE消息给Program Manager
    hwnd, _, _ := procFindWindowW.Call(
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Progman"))),
        0,
    )
    if hwnd != 0 {
        procSendMessageW.Call(
            hwnd,
            WM_SETTINGCHANGE,
            0,
            0,
        )
        fmt.Println("已发送刷新信号到桌面窗口")
    } else {
        fmt.Println("桌面窗口未找到,但图标位置已设置")
    }
}

// 重启资源管理器
func restartExplorer() {
    fmt.Println("正在重启Windows资源管理器以应用更改...")

    // 终止资源管理器
    fmt.Println("  终止资源管理器进程...")
    cmdKill := exec.Command("taskkill", "/F", "/IM", "explorer.exe")
    cmdKill.SysProcAttr = &syscall.SysProcAttr{
        HideWindow: true,
    }
    if err := cmdKill.Run(); err != nil {
        fmt.Printf("  警告: 终止资源管理器时出错: %v\n", err)
    }

    // 等待资源管理器完全关闭
    time.Sleep(2 * time.Second)

    // 重启资源管理器
    fmt.Println("  重启资源管理器进程...")
    cmdStart := exec.Command("explorer.exe")
    cmdStart.SysProcAttr = &syscall.SysProcAttr{
        HideWindow: false,
    }
    if err := cmdStart.Start(); err != nil {
        fmt.Printf("  错误: 重启资源管理器时出错: %v\n", err)
    } else {
        fmt.Println("  资源管理器已重启")
    }

    // 等待资源管理器完全启动
    time.Sleep(3 * time.Second)
}

// 最小最大值函数
func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

// 禁用桌面自动排列图标
func disableAutoArrange() bool {
    fmt.Println("正在检查并禁用桌面自动排列图标...")

    // 注册表路径
    keyPath := `Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced`

    // 打开注册表键
    k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE|registry.SET_VALUE)
    if err != nil {
        fmt.Printf("警告: 无法打开注册表项: %v\n", err)
        return false
    }
    defer k.Close()

    // 检查当前值
    currentValue, _, err := k.GetIntegerValue("AutoArrange")
    if err != nil && err != registry.ErrNotExist {
        fmt.Printf("警告: 读取注册表值时出错: %v\n", err)
    } else if currentValue == 0 {
        fmt.Println("自动排列图标已经是禁用状态")
        return true
    }

    // 设置为0(禁用)
    err = k.SetDWordValue("AutoArrange", 0)
    if err != nil {
        fmt.Printf("警告: 无法修改自动排列设置: %v\n", err)
        return false
    }

    fmt.Println("已成功禁用自动排列图标")
    return true
}

// 圆形排列图标
func arrangeIconsInCircle(listView HWND, iconCount int, screenW, screenH int, radius float64) {
    // 计算圆心
    centerX := screenW / 2
    centerY := screenH / 2

    fmt.Printf("开始圆形排列 %d 个图标\n", iconCount)
    fmt.Printf("圆心: (%d, %d), 半径: %.0f 像素\n\n", centerX, centerY, radius)

    // 为每个图标计算位置
    for i := 0; i < iconCount; i++ {
        // 计算角度 (弧度)
        angle := 2 * math.Pi * float64(i) / float64(iconCount)

        // 计算圆形位置
        x := centerX + int(radius*math.Cos(angle))
        y := centerY + int(radius*math.Sin(angle))

        // 确保图标不会超出屏幕边界 (留出边距)
        margin := 40
        x = max(margin, min(x, screenW-margin))
        y = max(margin, min(y, screenH-margin))

        // 设置图标位置
        success := setIconPosition(listView, i, int32(x), int32(y))

        // 输出进度信息
        if i < 5 || i == iconCount-1 {
            status := "✓"
            if !success {
                status = "✗"
            }
            fmt.Printf("图标 %d: (%d, %d) %s\n", i+1, x, y, status)
        } else if i == 5 {
            fmt.Println("...")
        }
    }

    fmt.Println("\n图标位置设置完成")
}

// 主函数
func main() {
    fmt.Println("===== Windows桌面图标圆形排列工具 =====")
    fmt.Println("使用Windows API直接操作桌面图标位置\n")

    // 检查是否在Windows上运行
    if runtime.GOOS != "windows" {
        fmt.Println("此程序仅支持Windows操作系统")
        fmt.Println("按Enter键退出...")
        fmt.Scanln()
        return
    }

    // 找到桌面列表视图窗口
    fmt.Println("正在查找桌面窗口...")
    listView := findDesktopListView()
    if listView == 0 {
        fmt.Println("错误: 无法访问桌面窗口")
        fmt.Println("请确保您有管理员权限")
        fmt.Println("按Enter键退出...")
        fmt.Scanln()
        return
    }

    fmt.Println("找到桌面列表视图窗口")

    // 禁用自动排列图标
    disableAutoArrange()

    // 获取实际的桌面图标数量
    iconCount := getDesktopIconCount(listView)
    fmt.Printf("检测到 %d 个桌面图标\n\n", iconCount)

    // 让用户确认或修改图标数量
    fmt.Printf("请输入要排列的图标数量 (默认: %d, 按Enter使用默认值): ", iconCount)
    var input string
    fmt.Scanln(&input)
    input = strings.TrimSpace(input)

    if input != "" {
        customCount, err := strconv.Atoi(input)
        if err == nil && customCount > 0 && customCount <= iconCount {
            fmt.Printf("使用您输入的图标数量: %d\n", customCount)
            iconCount = customCount
        } else {
            fmt.Printf("输入无效,继续使用检测到的图标数量: %d\n", iconCount)
        }
    }

    // 获取屏幕分辨率
    screenW, screenH := getScreenResolution()
    if screenW <= 0 || screenH <= 0 {
        fmt.Println("错误: 无法获取屏幕分辨率")
        fmt.Println("按Enter键退出...")
        fmt.Scanln()
        return
    }

    fmt.Printf("屏幕分辨率: %dx%d\n\n", screenW, screenH)

    // 建议的默认半径 (屏幕较小边的30%)
    defaultRadius := float64(min(screenW, screenH)) * 0.3

    // 获取用户指定的半径
    fmt.Printf("请输入圆的半径 (默认: %.0f 像素): ", defaultRadius)
    fmt.Scanln(&input)
    input = strings.TrimSpace(input)

    radius := defaultRadius
    if input != "" {
        customRadius, err := strconv.ParseFloat(input, 64)
        if err == nil && customRadius > 0 {
            // 限制最大半径
            maxRadius := float64(min(screenW, screenH)) * 0.4
            if customRadius > maxRadius {
                fmt.Printf("半径过大,已调整为最大允许值: %.0f 像素\n", maxRadius)
                radius = maxRadius
            } else {
                radius = customRadius
            }
        } else {
            fmt.Printf("输入无效,使用默认半径: %.0f 像素\n", defaultRadius)
        }
    }

    // 执行圆形排列
    arrangeIconsInCircle(listView, iconCount, screenW, screenH, radius)

    // 刷新桌面
    fmt.Println("\n刷新桌面...")
    refreshDesktop()

    // 不再询问重启资源管理器,避免可能的卡住问题
    fmt.Println("\n💡 提示: 图标位置已成功设置")
    fmt.Println("  如果需要,可以手动按F5刷新桌面以确保更改可见")

    fmt.Println("\n🎉 桌面图标圆形排列完成!")
    fmt.Println("📋 提示:")
    fmt.Println("  1. 如果图标位置未正确更新,请按F5刷新桌面")
    fmt.Println("  2. 或者手动重启Windows资源管理器")
    fmt.Println("  3. 如有需要,请以管理员权限运行此程序")

    fmt.Println("\n按Enter键退出...")
    fmt.Scanln()
}

七、结语

此术虽小,然融汇窗口枚举、消息发送、注册表操作、三角函数布阵于一体,可谓"麻雀虽小,五脏俱全"。非为炫技,实乃悦目养心耳。

若君试之,见图标环列如日月绕辰,必莞尔曰:"此乐何极!"

注:运行此程序,宜以管理员身份,否则或被系统拒之门外,如叩关不得入,徒叹奈何。

庚子年秋月 于虚拟案前

往期部分文章列表

上一篇 下一篇

猜你喜欢

热点阅读