2019-11-09 01:26:01 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-11-09 02:20:38 -05:00
|
|
|
"math"
|
2019-11-09 01:26:01 -05:00
|
|
|
"os"
|
2019-11-09 02:20:38 -05:00
|
|
|
"strconv"
|
2019-11-09 01:26:01 -05:00
|
|
|
"strings"
|
|
|
|
"syscall"
|
2019-11-09 01:29:48 -05:00
|
|
|
"time"
|
2019-11-09 01:26:01 -05:00
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"github.com/mitchellh/go-ps"
|
|
|
|
"github.com/shu-go/gli"
|
|
|
|
"github.com/shu-go/rog"
|
|
|
|
)
|
|
|
|
|
2019-11-09 01:29:48 -05:00
|
|
|
// Version is app version
|
|
|
|
var Version string
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
if Version == "" {
|
|
|
|
Version = "dev-" + time.Now().Format("20060102")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-09 01:26:01 -05:00
|
|
|
type globalCmd struct {
|
|
|
|
Target string `cli:"target,t=WINDOW_TITLE" help:"default to current window"`
|
|
|
|
Debug bool
|
|
|
|
|
2019-11-09 03:19:38 -05:00
|
|
|
Minimize minCmd `cli:"minimize,min"`
|
|
|
|
Maximize maxCmd `cli:"maximize,max"`
|
|
|
|
Restore restoreCmd `cli:"restore"`
|
|
|
|
Resize resizeCmd `cli:"resize"`
|
|
|
|
Move moveCmd `cli:"move,mv"`
|
2019-11-09 01:26:01 -05:00
|
|
|
|
|
|
|
targetHandle syscall.Handle
|
|
|
|
|
2019-11-09 02:20:38 -05:00
|
|
|
scrWidth, scrHeight int
|
|
|
|
frameWidth, frameHeight int
|
2019-11-09 01:26:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *globalCmd) Before() error {
|
|
|
|
wins, err := listAllWindows()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
an := ancestors()
|
|
|
|
t := strings.ToLower(c.Target)
|
|
|
|
|
|
|
|
for _, w := range wins {
|
|
|
|
ancestor := false
|
|
|
|
for _, p := range an {
|
|
|
|
if w.PID == p {
|
|
|
|
ancestor = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Debug {
|
|
|
|
rog.Printf("win: %#v (ancestor? %v)", w, ancestor)
|
|
|
|
}
|
|
|
|
if t != "" && !ancestor {
|
|
|
|
wt := strings.ToLower(w.Title)
|
|
|
|
|
|
|
|
if strings.Contains(wt, t) {
|
|
|
|
c.targetHandle = w.Handle
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else if t == "" && ancestor {
|
|
|
|
c.targetHandle = w.Handle
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.targetHandle == 0 {
|
|
|
|
return errors.New("no target")
|
|
|
|
}
|
|
|
|
|
|
|
|
w, _, _ := getSystemMetrics.Call(SM_CXVIRTUALSCREEN)
|
|
|
|
h, _, _ := getSystemMetrics.Call(SM_CYVIRTUALSCREEN)
|
|
|
|
c.scrWidth = int(w)
|
|
|
|
c.scrHeight = int(h)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type minCmd struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c minCmd) Run(g globalCmd) {
|
2019-11-09 03:19:38 -05:00
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_MINIMIZE)
|
2019-11-09 01:26:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type maxCmd struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c maxCmd) Run(g globalCmd) {
|
2019-11-09 03:19:38 -05:00
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_MAXIMIZE)
|
|
|
|
}
|
|
|
|
|
|
|
|
type restoreCmd struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c restoreCmd) Run(g globalCmd) {
|
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_RESTORE)
|
2019-11-09 01:26:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type resizeCmd struct {
|
2019-11-09 03:17:19 -05:00
|
|
|
Left string `cli:"left,x"`
|
|
|
|
Top string `cli:"top,y"`
|
|
|
|
Width string `cli:"width,w"`
|
|
|
|
Height string `cli:"height,h"`
|
2019-11-09 01:26:01 -05:00
|
|
|
|
|
|
|
NoRestorable bool `cli:"norestorable"`
|
2019-11-09 03:17:19 -05:00
|
|
|
|
|
|
|
rect RECT
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *resizeCmd) Before(g globalCmd) error {
|
|
|
|
if c.Left == "" && c.Top == "" && c.Width == "" && c.Height == "" {
|
|
|
|
return errors.New("no options")
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindowRect.Call(uintptr(g.targetHandle), uintptr(unsafe.Pointer(&c.rect)))
|
|
|
|
|
|
|
|
oldrect := c.rect
|
|
|
|
|
|
|
|
if g.Debug {
|
|
|
|
rog.Print(oldrect)
|
|
|
|
}
|
|
|
|
if c.Left != "" {
|
|
|
|
c.rect.Left = toInt(c.Left, g.scrWidth)
|
|
|
|
}
|
|
|
|
if c.Top != "" {
|
|
|
|
c.rect.Top = toInt(c.Top, g.scrHeight)
|
|
|
|
}
|
|
|
|
if c.Width != "" {
|
|
|
|
c.rect.Right = c.rect.Left + toInt(c.Width, g.scrWidth)
|
|
|
|
} else {
|
|
|
|
c.rect.Right = c.rect.Left + (oldrect.Right - oldrect.Left)
|
|
|
|
}
|
|
|
|
if c.Height != "" {
|
|
|
|
c.rect.Bottom = c.rect.Top + toInt(c.Height, g.scrHeight)
|
|
|
|
} else {
|
|
|
|
c.rect.Bottom = c.rect.Top + (oldrect.Bottom - oldrect.Top)
|
|
|
|
}
|
|
|
|
if g.Debug {
|
|
|
|
rog.Print(c.rect)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2019-11-09 01:26:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c resizeCmd) Run(g globalCmd) {
|
|
|
|
if !c.NoRestorable {
|
2019-11-09 02:30:08 -05:00
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_HIDE)
|
2019-11-09 01:26:01 -05:00
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_MAXIMIZE)
|
|
|
|
}
|
|
|
|
setWindowPos.Call(
|
|
|
|
uintptr(g.targetHandle),
|
|
|
|
0,
|
2019-11-09 03:17:19 -05:00
|
|
|
uintptr(c.rect.Left),
|
|
|
|
uintptr(c.rect.Top),
|
|
|
|
uintptr(c.rect.Right-c.rect.Left),
|
|
|
|
uintptr(c.rect.Bottom-c.rect.Top),
|
2019-11-09 01:26:01 -05:00
|
|
|
SWP_NOACTIVATE|SWP_NOZORDER)
|
2019-11-09 02:30:08 -05:00
|
|
|
if !c.NoRestorable {
|
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_SHOWNA)
|
|
|
|
}
|
2019-11-09 01:26:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type moveCmd struct {
|
2019-11-09 03:17:19 -05:00
|
|
|
Left string `cli:"left,x"`
|
|
|
|
Top string `cli:"top,y"`
|
2019-11-09 01:26:01 -05:00
|
|
|
|
|
|
|
NoRestorable bool `cli:"norestorable"`
|
2019-11-09 03:17:19 -05:00
|
|
|
|
|
|
|
rect RECT
|
2019-11-09 01:26:01 -05:00
|
|
|
}
|
|
|
|
|
2019-11-09 03:17:19 -05:00
|
|
|
func (c *moveCmd) Before(g globalCmd) error {
|
|
|
|
if c.Left == "" && c.Top == "" {
|
|
|
|
return errors.New("no options")
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindowRect.Call(uintptr(g.targetHandle), uintptr(unsafe.Pointer(&c.rect)))
|
2019-11-09 01:26:01 -05:00
|
|
|
|
2019-11-09 03:17:19 -05:00
|
|
|
if g.Debug {
|
|
|
|
rog.Print(c.rect)
|
|
|
|
}
|
|
|
|
if c.Left != "" {
|
|
|
|
old := c.rect.Left
|
|
|
|
c.rect.Left = toInt(c.Left, g.scrWidth)
|
|
|
|
c.rect.Right += -old + c.rect.Left
|
|
|
|
}
|
|
|
|
if c.Top != "" {
|
|
|
|
old := c.rect.Top
|
|
|
|
c.rect.Top = toInt(c.Top, g.scrHeight)
|
|
|
|
c.rect.Bottom += -old + c.rect.Top
|
|
|
|
}
|
|
|
|
if g.Debug {
|
|
|
|
rog.Print(c.rect)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c moveCmd) Run(g globalCmd) {
|
2019-11-09 01:26:01 -05:00
|
|
|
if !c.NoRestorable {
|
2019-11-09 02:30:08 -05:00
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_HIDE)
|
2019-11-09 01:26:01 -05:00
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_MAXIMIZE)
|
|
|
|
setWindowPos.Call(
|
|
|
|
uintptr(g.targetHandle),
|
|
|
|
0,
|
2019-11-09 03:17:19 -05:00
|
|
|
uintptr(c.rect.Left),
|
|
|
|
uintptr(c.rect.Top),
|
|
|
|
uintptr(c.rect.Right-c.rect.Left),
|
|
|
|
uintptr(c.rect.Bottom-c.rect.Top),
|
2019-11-09 01:26:01 -05:00
|
|
|
SWP_NOACTIVATE|SWP_NOZORDER)
|
2019-11-09 02:30:08 -05:00
|
|
|
showWindow.Call(uintptr(g.targetHandle), SW_SHOWNA)
|
2019-11-09 01:26:01 -05:00
|
|
|
} else {
|
|
|
|
setWindowPos.Call(
|
|
|
|
uintptr(g.targetHandle),
|
|
|
|
0,
|
2019-11-09 03:17:19 -05:00
|
|
|
uintptr(c.rect.Left),
|
|
|
|
uintptr(c.rect.Top),
|
2019-11-09 01:26:01 -05:00
|
|
|
0,
|
|
|
|
0,
|
|
|
|
SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOSIZE)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
app := gli.NewWith(&globalCmd{})
|
|
|
|
app.Name = "vvin"
|
|
|
|
app.Desc = ""
|
2019-11-09 01:29:48 -05:00
|
|
|
app.Version = Version
|
2019-11-09 01:26:01 -05:00
|
|
|
app.Usage = ``
|
|
|
|
app.Copyright = "(C) 2019 Shuhei Kubota"
|
|
|
|
app.Run(os.Args)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
|
|
enumWindows = user32.NewProc("EnumWindows")
|
|
|
|
getWindowText = user32.NewProc("GetWindowTextW")
|
|
|
|
getWindowTextLength = user32.NewProc("GetWindowTextLengthW")
|
|
|
|
getWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId")
|
|
|
|
isWindow = user32.NewProc("IsWindow")
|
|
|
|
isWindowVisible = user32.NewProc("IsWindowVisible")
|
|
|
|
showWindow = user32.NewProc("ShowWindow")
|
|
|
|
setWindowPos = user32.NewProc("SetWindowPos")
|
|
|
|
getWindowRect = user32.NewProc("GetWindowRect")
|
|
|
|
getSystemMetrics = user32.NewProc("GetSystemMetrics")
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
SW_MAXIMIZE = 3
|
|
|
|
SW_MINIMIZE = 6
|
|
|
|
SW_RESTORE = 9
|
2019-11-09 02:30:08 -05:00
|
|
|
SW_HIDE = 0
|
|
|
|
SW_SHOWNA = 8
|
2019-11-09 01:26:01 -05:00
|
|
|
|
|
|
|
SWP_NOACTIVATE = 0x0010
|
|
|
|
SWP_NOSIZE = 0x0001
|
|
|
|
SWP_NOZORDER = 0x0004
|
|
|
|
|
|
|
|
SM_CXVIRTUALSCREEN = 78
|
|
|
|
SM_CYVIRTUALSCREEN = 79
|
2019-11-09 02:20:38 -05:00
|
|
|
SM_CXSIZEFRAME = 32
|
|
|
|
SM_CYSIZEFRAME = 33
|
2019-11-09 01:26:01 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
Window struct {
|
|
|
|
Title string
|
|
|
|
Handle syscall.Handle
|
|
|
|
PID int
|
|
|
|
}
|
2019-11-09 03:17:19 -05:00
|
|
|
|
|
|
|
RECT struct {
|
|
|
|
Left, Top, Right, Bottom int32
|
|
|
|
}
|
2019-11-09 01:26:01 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
func listAllWindows() (wins []*Window, err error) {
|
|
|
|
cb := syscall.NewCallback(func(hwnd syscall.Handle, lparam uintptr) uintptr {
|
|
|
|
b, _, _ := isWindow.Call(uintptr(hwnd))
|
|
|
|
if b == 0 {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
b, _, _ = isWindowVisible.Call(uintptr(hwnd))
|
|
|
|
if b == 0 {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
title := ""
|
|
|
|
tlen, _, _ := getWindowTextLength.Call(uintptr(hwnd))
|
|
|
|
if tlen != 0 {
|
|
|
|
tlen++
|
|
|
|
buff := make([]uint16, tlen)
|
|
|
|
getWindowText.Call(
|
|
|
|
uintptr(hwnd),
|
|
|
|
uintptr(unsafe.Pointer(&buff[0])),
|
|
|
|
uintptr(tlen),
|
|
|
|
)
|
|
|
|
title = syscall.UTF16ToString(buff)
|
|
|
|
}
|
|
|
|
|
|
|
|
var processID uintptr
|
|
|
|
getWindowThreadProcessId.Call(
|
|
|
|
uintptr(hwnd),
|
|
|
|
uintptr(unsafe.Pointer(&processID)),
|
|
|
|
)
|
|
|
|
|
|
|
|
win := &Window{
|
|
|
|
Title: title,
|
|
|
|
Handle: hwnd,
|
|
|
|
PID: int(processID),
|
|
|
|
}
|
|
|
|
wins = append(wins, win)
|
|
|
|
|
|
|
|
return 1
|
|
|
|
})
|
|
|
|
|
|
|
|
a, _, _ := enumWindows.Call(cb, 0)
|
|
|
|
if a == 0 {
|
|
|
|
return nil, fmt.Errorf("USER32.EnumWindows returned FALSE")
|
|
|
|
}
|
|
|
|
|
|
|
|
return wins, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ancestors() []int {
|
|
|
|
curr := os.Getpid()
|
|
|
|
|
|
|
|
an := []int{curr}
|
|
|
|
|
|
|
|
for {
|
|
|
|
p, err := ps.FindProcess(curr)
|
|
|
|
if p == nil || err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
curr = p.PPid()
|
|
|
|
an = append(an, curr)
|
|
|
|
}
|
|
|
|
|
|
|
|
return an
|
|
|
|
}
|
2019-11-09 02:20:38 -05:00
|
|
|
|
2019-11-09 03:17:19 -05:00
|
|
|
func toInt(s string, max int) int32 {
|
2019-11-09 02:20:38 -05:00
|
|
|
if strings.HasSuffix(s, "%") {
|
|
|
|
i, err := strconv.Atoi(s[:len(s)-1])
|
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if i > 100 {
|
|
|
|
i = 100
|
|
|
|
}
|
2019-11-09 03:17:19 -05:00
|
|
|
return int32(math.Trunc(float64(max*i) / 100))
|
2019-11-09 02:20:38 -05:00
|
|
|
} else {
|
|
|
|
i, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
2019-11-09 03:17:19 -05:00
|
|
|
return int32(i)
|
2019-11-09 02:20:38 -05:00
|
|
|
}
|
|
|
|
}
|