184 lines
4.6 KiB
Go
184 lines
4.6 KiB
Go
//go:build windows
|
|||
|
|
|
||
|
|
package capture
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net/url"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"runtime"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
"unsafe"
|
||
|
|
)
|
||
|
|
|
||
|
|
type ShellDropHit struct {
|
||
|
|
Directory string
|
||
|
|
ItemName string
|
||
|
|
Desktop bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func ResolveShellDropHit(x, y int32) ShellDropHit {
|
||
|
|
result := make(chan ShellDropHit, 1)
|
||
|
|
go func() {
|
||
|
|
runtime.LockOSThread()
|
||
|
|
defer runtime.UnlockOSThread()
|
||
|
|
|
||
|
|
out := ShellDropHit{}
|
||
|
|
hr, _, _ := procCoInitializeEx.Call(0, COINIT_MULTITHREADED)
|
||
|
|
if hr != 0 && hr != 1 {
|
||
|
|
result <- out
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer procCoUninitialize.Call()
|
||
|
|
|
||
|
|
var automation *iuiAutomation
|
||
|
|
hr, _, _ = procCoCreateInstance.Call(
|
||
|
|
uintptr(unsafe.Pointer(&CLSID_CUIAutomation)), 0, CLSCTX_INPROC_SERVER,
|
||
|
|
uintptr(unsafe.Pointer(&IID_IUIAutomation)), uintptr(unsafe.Pointer(&automation)),
|
||
|
|
)
|
||
|
|
if hr != S_OK || automation == nil {
|
||
|
|
result <- out
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer automation.Release()
|
||
|
|
walker := automation.GetControlViewWalker()
|
||
|
|
if walker == nil {
|
||
|
|
result <- out
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer walker.Release()
|
||
|
|
|
||
|
|
elem := automation.ElementFromPoint(point{x: x, y: y})
|
||
|
|
if elem == nil {
|
||
|
|
result <- out
|
||
|
|
return
|
||
|
|
}
|
||
|
|
out.ItemName = strings.TrimSpace(elem.GetCurrentName())
|
||
|
|
sawCabinet := false
|
||
|
|
sawShellList := false
|
||
|
|
for current, depth := elem, 0; current != nil && depth < 32; depth++ {
|
||
|
|
className := current.GetCurrentClassName()
|
||
|
|
if className == "CabinetWClass" {
|
||
|
|
sawCabinet = true
|
||
|
|
out.Directory = explorerDirectoryFromUIA(walker, current)
|
||
|
|
}
|
||
|
|
if className == "SysListView32" || className == "SHELLDLL_DefView" {
|
||
|
|
sawShellList = true
|
||
|
|
}
|
||
|
|
parent := walker.GetParentElement(current)
|
||
|
|
current.Release()
|
||
|
|
current = parent
|
||
|
|
}
|
||
|
|
if sawCabinet {
|
||
|
|
out.Desktop = false
|
||
|
|
} else if sawShellList {
|
||
|
|
out.Desktop = true
|
||
|
|
if home, err := os.UserHomeDir(); err == nil {
|
||
|
|
out.Directory = filepath.Join(home, "Desktop")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
result <- out
|
||
|
|
}()
|
||
|
|
select {
|
||
|
|
case hit := <-result:
|
||
|
|
return hit
|
||
|
|
case <-time.After(4 * time.Second):
|
||
|
|
return ShellDropHit{}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func explorerDirectoryFromUIA(walker *iuiAutomationTreeWalker, root *iuiAutomationElement) string {
|
||
|
|
visited := 0
|
||
|
|
var walk func(*iuiAutomationElement) string
|
||
|
|
walk = func(parent *iuiAutomationElement) string {
|
||
|
|
for child := shellDropFirstChild(walker, parent); child != nil && visited < 2500; {
|
||
|
|
visited++
|
||
|
|
next := shellDropNextSibling(walker, child)
|
||
|
|
name := strings.TrimSpace(child.GetCurrentName())
|
||
|
|
if strings.HasPrefix(strings.ToLower(name), "address:") {
|
||
|
|
candidate := shellDirectoryCandidate(name)
|
||
|
|
if candidate == "" {
|
||
|
|
// Continue into the element; localized or virtual shell paths
|
||
|
|
// may not be filesystem destinations.
|
||
|
|
} else {
|
||
|
|
child.Release()
|
||
|
|
if next != nil {
|
||
|
|
next.Release()
|
||
|
|
}
|
||
|
|
return candidate
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if pattern := child.GetCurrentPattern(uiaValuePatternID); pattern != nil {
|
||
|
|
valuePattern := (*iuiAutomationValuePattern)(unsafe.Pointer(pattern))
|
||
|
|
candidate := shellDirectoryCandidate(valuePattern.GetCurrentValue())
|
||
|
|
valuePattern.Release()
|
||
|
|
if candidate != "" {
|
||
|
|
child.Release()
|
||
|
|
if next != nil {
|
||
|
|
next.Release()
|
||
|
|
}
|
||
|
|
return candidate
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if candidate := walk(child); candidate != "" {
|
||
|
|
child.Release()
|
||
|
|
if next != nil {
|
||
|
|
next.Release()
|
||
|
|
}
|
||
|
|
return candidate
|
||
|
|
}
|
||
|
|
child.Release()
|
||
|
|
child = next
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
return walk(root)
|
||
|
|
}
|
||
|
|
|
||
|
|
func shellDropFirstChild(walker *iuiAutomationTreeWalker, elem *iuiAutomationElement) *iuiAutomationElement {
|
||
|
|
var child *iuiAutomationElement
|
||
|
|
hr := callSyscallN(walker.lpVtbl.GetFirstChildElement,
|
||
|
|
uintptr(unsafe.Pointer(walker)),
|
||
|
|
uintptr(unsafe.Pointer(elem)),
|
||
|
|
uintptr(unsafe.Pointer(&child)),
|
||
|
|
)
|
||
|
|
if hr != S_OK || child == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return child
|
||
|
|
}
|
||
|
|
|
||
|
|
func shellDropNextSibling(walker *iuiAutomationTreeWalker, elem *iuiAutomationElement) *iuiAutomationElement {
|
||
|
|
var sibling *iuiAutomationElement
|
||
|
|
hr := callSyscallN(walker.lpVtbl.GetNextSiblingElement,
|
||
|
|
uintptr(unsafe.Pointer(walker)),
|
||
|
|
uintptr(unsafe.Pointer(elem)),
|
||
|
|
uintptr(unsafe.Pointer(&sibling)),
|
||
|
|
)
|
||
|
|
if hr != S_OK || sibling == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return sibling
|
||
|
|
}
|
||
|
|
|
||
|
|
func shellDirectoryCandidate(value string) string {
|
||
|
|
value = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(value), "Address:"))
|
||
|
|
if strings.HasPrefix(strings.ToLower(value), "file:") {
|
||
|
|
if parsed, err := url.Parse(value); err == nil {
|
||
|
|
value = parsed.Path
|
||
|
|
if len(value) >= 3 && value[0] == '/' && value[2] == ':' {
|
||
|
|
value = value[1:]
|
||
|
|
}
|
||
|
|
value, _ = url.PathUnescape(value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !filepath.IsAbs(value) {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
if info, err := os.Stat(value); err == nil && info.IsDir() {
|
||
|
|
return filepath.Clean(value)
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|