Skip to content

Commit

Permalink
Fix app icon missing on windows
Browse files Browse the repository at this point in the history
  • Loading branch information
qianlifeng committed May 11, 2024
1 parent 1f02d13 commit 9da9d4a
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 4 deletions.
1 change: 1 addition & 0 deletions Wox/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
Expand Down
3 changes: 3 additions & 0 deletions Wox/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mat/besticon v0.0.0-20231103204413-ee089084f347 h1:FSfjh3/9PRsWzjCDdNYLsXeCz5S3D2/iwK/8lnzOu2U=
github.com/mat/besticon v0.0.0-20231103204413-ee089084f347/go.mod h1:bzMBPMkFE6oCncbLySBPWc4XB5AuglYIkqKL/j9vp3c=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
Expand Down Expand Up @@ -252,6 +254,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
3 changes: 2 additions & 1 deletion Wox/plugin/system/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/tidwall/pretty"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -134,7 +135,7 @@ func (a *ApplicationPlugin) Query(ctx context.Context, query plugin.Query) []plu
Name: "i18n:plugin_app_open_containing_folder",
Icon: plugin.NewWoxImageSvg(`<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="48" height="48" viewBox="0 0 48 48"><path fill="#FFA000" d="M40,12H22l-4-4H8c-2.2,0-4,1.8-4,4v8h40v-4C44,13.8,42.2,12,40,12z"></path><path fill="#FFCA28" d="M40,12H8c-2.2,0-4,1.8-4,4v20c0,2.2,1.8,4,4,4h32c2.2,0,4-1.8,4-4V16C44,13.8,42.2,12,40,12z"></path></svg>`),
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
runErr := util.ShellOpen(path.Dir(info.Path))
runErr := util.ShellOpen(filepath.Dir(info.Path))
if runErr != nil {
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error opening app %s: %s", info.Path, runErr.Error()))
}
Expand Down
103 changes: 101 additions & 2 deletions Wox/plugin/system/app/app_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@ package app
import (
"context"
"errors"
"fmt"
"github.com/lxn/win"
"github.com/parsiya/golnk"
"image"
"image/color"
"strings"
"syscall"
"unsafe"
"wox/plugin"
"wox/util"
)

var (
// Load shell32.dll instead of user32.dll
shell32 = syscall.NewLazyDLL("shell32.dll")
// Get the address of ExtractIconExW from shell32.dll
extractIconEx = shell32.NewProc("ExtractIconExW")
)

var appRetriever = &WindowsRetriever{}

type WindowsRetriever struct {
Expand All @@ -27,7 +40,7 @@ func (a *WindowsRetriever) GetAppDirectories(ctx context.Context) []appDirectory
return []appDirectory{
{
Path: "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs",
Recursive: false,
Recursive: true,
},
}
}
Expand Down Expand Up @@ -57,12 +70,98 @@ func (a *WindowsRetriever) parseShortcut(ctx context.Context, path string) (appI
if f.LinkInfo.LocalBasePathUnicode != "" {
targetPath = f.LinkInfo.LocalBasePathUnicode
}
if targetPath == "" {
if targetPath == "" || !strings.HasSuffix(targetPath, ".exe") {
return appInfo{}, errors.New("no target path found")
}

// use default icon if no icon is found
icon := appIcon
img, iconErr := a.GetAppIcon(ctx, targetPath)
if iconErr != nil {
util.GetLogger().Error(ctx, fmt.Sprintf("Error getting icon for %s: %s", targetPath, iconErr.Error()))
} else {
woxIcon, imgErr := plugin.NewWoxImage(img)
if imgErr != nil {
util.GetLogger().Error(ctx, fmt.Sprintf("Error converting icon for %s: %s", targetPath, imgErr.Error()))
} else {
icon = woxIcon
}
}

return appInfo{
Name: f.StringData.NameString,
Path: targetPath,
Icon: icon,
}, nil
}

func (a *WindowsRetriever) GetAppIcon(ctx context.Context, path string) (image.Image, error) {
// 将文件路径转换为UTF16
lpIconPath, err := syscall.UTF16PtrFromString(path)
if err != nil {
return nil, err
}

// 使用ExtractIconEx获取图标句柄
var largeIcon win.HICON
var smallIcon win.HICON
ret, _, _ := extractIconEx.Call(
uintptr(unsafe.Pointer(lpIconPath)),
0,
uintptr(unsafe.Pointer(&largeIcon)),
uintptr(unsafe.Pointer(&smallIcon)),
1,
)
if ret == 0 {
return nil, fmt.Errorf("no icons found in file")
}
defer win.DestroyIcon(largeIcon) // 确保释放图标资源

// 获取图标信息
var iconInfo win.ICONINFO
if win.GetIconInfo(largeIcon, &iconInfo) == false {
return nil, fmt.Errorf("failed to get icon info")
}
defer win.DeleteObject(win.HGDIOBJ(iconInfo.HbmColor))
defer win.DeleteObject(win.HGDIOBJ(iconInfo.HbmMask))

// 创建设备无关位图(DIB)来接收图像数据
hdc := win.GetDC(0)
defer win.ReleaseDC(0, hdc)

var bmpInfo win.BITMAPINFO
bmpInfo.BmiHeader.BiSize = uint32(unsafe.Sizeof(bmpInfo.BmiHeader))
bmpInfo.BmiHeader.BiWidth = int32(iconInfo.XHotspot * 2)
bmpInfo.BmiHeader.BiHeight = -int32(iconInfo.YHotspot * 2) // 负值表示自顶向下的DIB
bmpInfo.BmiHeader.BiPlanes = 1
bmpInfo.BmiHeader.BiBitCount = 32
bmpInfo.BmiHeader.BiCompression = win.BI_RGB

// 分配内存来存储位图数据
bits := make([]byte, iconInfo.XHotspot*2*iconInfo.YHotspot*2*4)
if win.GetDIBits(hdc, win.HBITMAP(iconInfo.HbmColor), 0, uint32(iconInfo.YHotspot*2), &bits[0], &bmpInfo, win.DIB_RGB_COLORS) == 0 {
return nil, fmt.Errorf("failed to get DIB bits")
}

width := int(iconInfo.XHotspot * 2)
height := int(iconInfo.YHotspot * 2)
img := image.NewRGBA(image.Rect(0, 0, width, height))

// Copy the bitmap data into the img.Pix slice.
// Note: Windows bitmaps are stored in BGR format, so we need to swap the red and blue channels.
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
base := y*width*4 + x*4
// The bitmap data in bits is in BGRA format.
b := bits[base+0]
g := bits[base+1]
r := bits[base+2]
a := bits[base+3]
// Set the pixel in the image.
// Note: image.RGBA expects data in RGBA format, so we swap R and B.
img.SetRGBA(x, y, color.RGBA{R: r, G: g, B: b, A: a})
}
}

return img, nil
}
2 changes: 1 addition & 1 deletion Wox/util/open_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

func ShellOpen(path string) error {
return exec.Command("cmd", "/C", "start", path).Start()
return exec.Command("cmd", "/C", "start", "explorer.exe", path).Start()
}

func ShellRun(name string, arg ...string) (*exec.Cmd, error) {
Expand Down

0 comments on commit 9da9d4a

Please sign in to comment.