commit 200a67cd7b02ab7809256b4a4f41dc2788f182f6 Author: ShuheiKubota Date: Sat Nov 9 15:26:01 2019 +0900 initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b65242e --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/shu-go/vvin + +go 1.13 + +require ( + github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b + github.com/shu-go/gli v0.0.0-20191003020935-c6817caa1c0a + github.com/shu-go/rog v0.0.0-20190826055139-09f31aeaaebd +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ff92e53 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b h1:9+ke9YJ9KGWw5ANXK6ozjoK47uI3uNbXv4YVINBnGm8= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/shu-go/cliparser v0.0.0-20190822025044-17b54d2ae1aa h1:/o4k5mrlZXF78Ql4IbmbQwaqGklOYMPd0uQpyGxmYLg= +github.com/shu-go/cliparser v0.0.0-20190822025044-17b54d2ae1aa/go.mod h1:b89EpZSowgrnN/iRGjwCHlRcIMMQjOGm3NmffyFpF9M= +github.com/shu-go/clise v0.0.0-20190822023516-79849fb81cfe/go.mod h1:VLiMEzXMBozBLD37i3id3qPflaupus48v/979ipQ43s= +github.com/shu-go/gli v0.0.0-20191003020935-c6817caa1c0a h1:N7WMOChI8J5Dx22ZNbg4xaogju9gKARbjWmNaLo5irM= +github.com/shu-go/gli v0.0.0-20191003020935-c6817caa1c0a/go.mod h1:F0pECDJ/7sP7e6/BlCGNScgK3O3dOLo/0d8CeH1Z7Ks= +github.com/shu-go/gotwant v0.0.0-20190822031422-724391433f13/go.mod h1:UxvcvxZEQUBw6lS9UgXOPh1outjvx2+bvDlEOpCTuGo= +github.com/shu-go/rog v0.0.0-20190826055139-09f31aeaaebd h1:4HEm7TeZfpMp8mrjUoRt0MxTCvJrakuRn3rSbOSOBqE= +github.com/shu-go/rog v0.0.0-20190826055139-09f31aeaaebd/go.mod h1:z2vjwV5lUEhVBjFneBZQECuBiel1kmKuU0sVJCwysDY= diff --git a/vvin.go b/vvin.go new file mode 100644 index 0000000..428e30e --- /dev/null +++ b/vvin.go @@ -0,0 +1,268 @@ +package main + +import ( + "errors" + "fmt" + "os" + "strings" + "syscall" + "unsafe" + + "github.com/mitchellh/go-ps" + "github.com/shu-go/gli" + "github.com/shu-go/rog" +) + +type globalCmd struct { + Target string `cli:"target,t=WINDOW_TITLE" help:"default to current window"` + Debug bool + + Minimize minCmd `cli:"minimize,min"` + Maximize maxCmd `cli:"maximize,max"` + Resize resizeCmd `cli:"resize"` + Move moveCmd `cli:"move,mv"` + + targetHandle syscall.Handle + + scrWidth, scrHeight int +} + +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 { + Restore bool `cli:"restore,r"` +} + +func (c minCmd) Run(g globalCmd) { + if c.Restore { + showWindow.Call(uintptr(g.targetHandle), SW_RESTORE) + } else { + showWindow.Call(uintptr(g.targetHandle), SW_MINIMIZE) + } +} + +type maxCmd struct { + Restore bool `cli:"restore,r"` +} + +func (c maxCmd) Run(g globalCmd) { + if c.Restore { + showWindow.Call(uintptr(g.targetHandle), SW_RESTORE) + } else { + showWindow.Call(uintptr(g.targetHandle), SW_MAXIMIZE) + } +} + +type resizeCmd struct { + Left int `cli:"left,x"` + Top int `cli:"top,y"` + Width int `cli:"width,w"` + Height int `cli:"height,h"` + + NoRestorable bool `cli:"norestorable"` +} + +func (c resizeCmd) Run(g globalCmd) { + if !c.NoRestorable { + showWindow.Call(uintptr(g.targetHandle), SW_MAXIMIZE) + } + setWindowPos.Call( + uintptr(g.targetHandle), + 0, + uintptr(c.Left), + uintptr(c.Top), + uintptr(c.Width), + uintptr(c.Height), + SWP_NOACTIVATE|SWP_NOZORDER) +} + +type moveCmd struct { + Left int `cli:"left,x"` + Top int `cli:"top,y"` + + NoRestorable bool `cli:"norestorable"` +} + +func (c moveCmd) Run(g globalCmd) { + rect := struct { + Left, Top, Right, Bottom int32 + }{} + + if !c.NoRestorable { + getWindowRect.Call(uintptr(g.targetHandle), uintptr(unsafe.Pointer(&rect))) + showWindow.Call(uintptr(g.targetHandle), SW_MAXIMIZE) + setWindowPos.Call( + uintptr(g.targetHandle), + 0, + uintptr(c.Left), + uintptr(c.Top), + uintptr(rect.Right-rect.Left), + uintptr(rect.Bottom-rect.Top), + SWP_NOACTIVATE|SWP_NOZORDER) + } else { + setWindowPos.Call( + uintptr(g.targetHandle), + 0, + uintptr(c.Left), + uintptr(c.Top), + 0, + 0, + SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOSIZE) + } +} + +func main() { + app := gli.NewWith(&globalCmd{}) + app.Name = "vvin" + app.Desc = "" + app.Version = "0.0.0" + 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 + + SWP_NOACTIVATE = 0x0010 + SWP_NOSIZE = 0x0001 + SWP_NOZORDER = 0x0004 + + SM_CXVIRTUALSCREEN = 78 + SM_CYVIRTUALSCREEN = 79 +) + +type ( + Window struct { + Title string + Handle syscall.Handle + PID int + } +) + +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 +}