github.com/iDigitalFlame/xmt@v0.5.4/device/winapi/windows.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  // Copyright (C) 2020 - 2023 iDigitalFlame
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU General Public License
    17  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    18  //
    19  
    20  package winapi
    21  
    22  import (
    23  	"sync"
    24  	"syscall"
    25  	"unsafe"
    26  
    27  	"github.com/iDigitalFlame/xmt/data"
    28  )
    29  
    30  var winCb struct {
    31  	e []Window
    32  	sync.Mutex
    33  }
    34  var enumWindowsOnce struct {
    35  	_ [0]func()
    36  	sync.Once
    37  	f uintptr
    38  }
    39  
    40  type key struct {
    41  	// DO NOT REORDER
    42  	Key   uint16
    43  	_     uint16
    44  	Flags uint32
    45  	_     uint32
    46  	_     uintptr
    47  }
    48  type input struct {
    49  	// DO NOT REORDER
    50  	Type uint32
    51  	Key  key
    52  	_    uint64
    53  }
    54  
    55  // Window is a struct that represents a Windows Window. The handles are the same
    56  // for the duration of the Window's existence.
    57  type Window struct {
    58  	_             [0]func()
    59  	Name          string
    60  	Flags         uint8
    61  	Handle        uintptr
    62  	X, Y          int32
    63  	Width, Height int32
    64  }
    65  type windowInfo struct {
    66  	// DO NOT REORDER
    67  	Size    uint32
    68  	Window  rect
    69  	Client  rect
    70  	Style   uint32
    71  	ExStyle uint32
    72  	Status  uint32
    73  	_, _    uint32
    74  	_, _    uint16
    75  }
    76  
    77  func initWindowsEnumFunc() {
    78  	enumWindowsOnce.f = syscall.NewCallback(enumWindowsCallback)
    79  }
    80  func sendText(s string) error {
    81  	var b [256]input
    82  	if len(s) < 64 {
    83  		return sendKeys(&b, s)
    84  	}
    85  	for i, e := 0, 0; i < len(s); {
    86  		if e = i + 64; e > len(s) {
    87  			e = len(s)
    88  		}
    89  		if err := sendKeys(&b, s[i:e]); err != nil {
    90  			return err
    91  		}
    92  		i = e
    93  	}
    94  	return nil
    95  }
    96  
    97  // CloseWindow is a helper function that sends the WM_DESTROY to the supplied
    98  // Window handle.
    99  //
   100  // If the value of h is 0, this will target ALL FOUND WINDOWS.
   101  func CloseWindow(h uintptr) error {
   102  	if h > 0 {
   103  		return closeWindow(h)
   104  	}
   105  	w, err := TopLevelWindows()
   106  	if err != nil {
   107  		return err
   108  	}
   109  	for i := range w {
   110  		closeWindow(w[i].Handle)
   111  	}
   112  	w = nil
   113  	return err
   114  }
   115  func closeWindow(h uintptr) error {
   116  	r, _, err := syscallN(funcSendNotifyMessage.address(), h, 0x0002, 0, 0)
   117  	if r == 0 {
   118  		return unboxError(err)
   119  	}
   120  	return nil
   121  }
   122  
   123  // IsMinimized returns true if the Window state was minimized at the time of
   124  // discovery.
   125  func (i Window) IsMinimized() bool {
   126  	return i.Flags&0x2 != 0
   127  }
   128  
   129  // IsMaximized returns true if the Window state was maximized at the time of
   130  // discovery.
   131  func (i Window) IsMaximized() bool {
   132  	return i.Flags&0x1 != 0
   133  }
   134  func keyCode(k byte) (uint16, bool) {
   135  	if k > 47 && k < 58 {
   136  		return uint16(0x30 + (k - 48)), false
   137  	}
   138  	if k > 64 && k < 91 {
   139  		return uint16(0x41 + (k - 65)), true
   140  	}
   141  	if k > 96 && k < 123 {
   142  		return uint16(0x41 + (k - 97)), false
   143  	}
   144  	switch k {
   145  	case 9:
   146  		return 0x09, false
   147  	case '\r', '\n':
   148  		return 0x0D, false
   149  	case '-':
   150  		return 0xBD, false
   151  	case '=':
   152  		return 0xBB, false
   153  	case ';':
   154  		return 0xBA, false
   155  	case '[':
   156  		return 0xDB, false
   157  	case ']':
   158  		return 0xDD, false
   159  	case '\\':
   160  		return 0xDC, false
   161  	case ',':
   162  		return 0xBC, false
   163  	case '.':
   164  		return 0xBE, false
   165  	case '`':
   166  		return 0xC0, false
   167  	case '/':
   168  		return 0xBF, false
   169  	case ' ':
   170  		return 0x20, false
   171  	case '\'':
   172  		return 0xDE, false
   173  	case '~':
   174  		return 0xC0, true
   175  	case '!':
   176  		return 0x31, true
   177  	case '@':
   178  		return 0x32, true
   179  	case '#':
   180  		return 0x33, true
   181  	case '$':
   182  		return 0x34, true
   183  	case '%':
   184  		return 0x35, true
   185  	case '^':
   186  		return 0x36, true
   187  	case '&':
   188  		return 0x37, true
   189  	case '*':
   190  		return 0x38, true
   191  	case '(':
   192  		return 0x39, true
   193  	case ')':
   194  		return 0x30, true
   195  	case '_':
   196  		return 0xBD, true
   197  	case '+':
   198  		return 0xBB, true
   199  	case '{':
   200  		return 0xDB, true
   201  	case '}':
   202  		return 0xDD, true
   203  	case '|':
   204  		return 0xDC, true
   205  	case ':':
   206  		return 0xBA, true
   207  	case '"':
   208  		return 0xDE, true
   209  	case '<':
   210  		return 0xBC, true
   211  	case '>':
   212  		return 0xBE, true
   213  	default:
   214  	}
   215  	return 0xBF, true
   216  }
   217  
   218  // TopLevelWindows returns a list of the current (non-dialog) Windows as a
   219  // slice with their Name, Handle, Size and Position.
   220  //
   221  // The handles may be used for multiple functions and are valid until the window
   222  // is closed.
   223  func TopLevelWindows() ([]Window, error) {
   224  	winCb.Lock()
   225  	enumWindowsOnce.Do(initWindowsEnumFunc)
   226  	var (
   227  		e         []Window
   228  		r, _, err = syscallN(funcEnumWindows.address(), enumWindowsOnce.f, 0)
   229  	)
   230  	e, winCb.e = winCb.e, nil
   231  	if winCb.Unlock(); r == 0 {
   232  		return nil, unboxError(err)
   233  	}
   234  	return e, nil
   235  }
   236  
   237  // SetForegroundWindow Windows API Call
   238  //
   239  //	Brings the thread that created the specified window into the foreground and
   240  //	activates the window. Keyboard input is directed to the window, and various
   241  //	visual cues are changed for the user. The system assigns a slightly higher
   242  //	priority to the thread that created the foreground window than it does to
   243  //	other threads.
   244  //
   245  // This function is supplemented with the "SetFocus" function, as this will allow
   246  // for requesting THEN setting the foreground window without user interaction.
   247  //
   248  // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow
   249  func SetForegroundWindow(h uintptr) error {
   250  	// Set it first before asking, that way the function below doesn't fail.
   251  	syscallN(funcSetFocus.address(), h)
   252  	r, _, err := syscallN(funcSetForegroundWindow.address(), h)
   253  	if r == 0 {
   254  		return unboxError(err)
   255  	}
   256  	return nil
   257  }
   258  
   259  // SendInput will attempt to set the window 'h' to the front (activate) and will
   260  // perform input typing of the supplied string as input events.
   261  //
   262  // The window handle can be zero to ignore targeting a window.
   263  func SendInput(h uintptr, s string) error {
   264  	if h > 0 {
   265  		// NOTE(dij): This function call error is ignored as it has a fit it
   266  		//            focus is requested and the user doesn't give it attention.
   267  		SetForegroundWindow(h)
   268  	}
   269  	if len(s) == 0 {
   270  		return nil
   271  	}
   272  	return sendText(s)
   273  }
   274  func sendKeys(b *[256]input, s string) error {
   275  	var (
   276  		n int
   277  		k uint16
   278  		u bool
   279  	)
   280  	for i := 0; i < len(s) && i < 64 && n < 256; i++ {
   281  		if k, u = keyCode(s[i]); u {
   282  			(*b)[n].Type, (*b)[n].Key.Key, (*b)[n].Key.Flags = 1, 0x10, 0
   283  			n++
   284  		}
   285  		(*b)[n].Type, (*b)[n].Key.Key, (*b)[n].Key.Flags = 1, k, 0
   286  		(*b)[n+1].Type, (*b)[n+1].Key.Key, (*b)[n+1].Key.Flags = 1, k, 2
   287  		if n += 2; u || k == 0x20 {
   288  			(*b)[n].Type, (*b)[n].Key.Key, (*b)[n].Key.Flags = 1, 0x10, 2
   289  			n++
   290  		}
   291  	}
   292  	if r, _, err := syscallN(funcSendInput.address(), uintptr(n), uintptr(unsafe.Pointer(b)), unsafe.Sizeof(b[0])); int(r) != n {
   293  		return unboxError(err)
   294  	}
   295  	return nil
   296  }
   297  func enumWindowsCallback(h, _ uintptr) uintptr {
   298  	n, _, _ := syscallN(funcGetWindowTextLength.address(), h)
   299  	if n == 0 {
   300  		return 1
   301  	}
   302  	i := windowInfo{Size: 60}
   303  	if r, _, _ := syscallN(funcGetWindowInfo.address(), h, uintptr(unsafe.Pointer(&i))); r == 0 {
   304  		return 1
   305  	}
   306  	// 0x80000000 - WS_POPUP
   307  	// 0x20000000 - WS_MINIMIZE
   308  	// 0x10000000 - WS_VISIBLE
   309  	// 0x00000400 - WS_EX_CONTEXTHELP
   310  	//
   311  	// Removes popup windows that were created hidden or minimized. Most of them
   312  	// are built-in system dialogs.
   313  	if (i.Style&0x80000000 != 0 && i.Style&0x10000000 == 0) || i.Style&0x10000000 == 0 || i.Style&0x00000400 != 0 {
   314  		return 1
   315  	}
   316  	v := make([]uint16, n+1)
   317  	if n, _, _ = syscallN(funcGetWindowText.address(), h, uintptr(unsafe.Pointer(&v[0])), n+1); n == 0 {
   318  		return 1
   319  	}
   320  	var t uint8
   321  	if i.Style&0x1000000 == 0 {
   322  		t |= 0x1
   323  	}
   324  	if i.Style&0x20000000 == 0 {
   325  		t |= 0x2
   326  	}
   327  	if i.Style&0x1E000000 == 0x1E000000 {
   328  		t |= 0x80
   329  	}
   330  	winCb.e = append(winCb.e, Window{
   331  		X:      i.Window.Left,
   332  		Y:      i.Window.Top,
   333  		Name:   UTF16ToString(v[:n]),
   334  		Flags:  t,
   335  		Width:  i.Window.Right - i.Window.Left,
   336  		Height: i.Window.Bottom - i.Window.Top,
   337  		Handle: h,
   338  	})
   339  	return 1
   340  }
   341  
   342  // ShowWindow Windows API Call
   343  //
   344  //	Sets the specified window's show state.
   345  //
   346  // The provided Sw* constants can be used to specify a show type.
   347  //
   348  // The resulting boolean is if the window was previously shown, or false if
   349  // it was hidden. (This value is always false if 'AllWindows'/0 is passed
   350  // as the handle.)
   351  //
   352  // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
   353  //
   354  // If the value of h is 0, this will target ALL FOUND WINDOWS.
   355  func ShowWindow(h uintptr, t uint8) (bool, error) {
   356  	if h > 0 {
   357  		return showWindow(h, uint32(t))
   358  	}
   359  	w, err := TopLevelWindows()
   360  	if err != nil {
   361  		return false, err
   362  	}
   363  	for i := range w {
   364  		showWindow(w[i].Handle, uint32(t))
   365  	}
   366  	w = nil
   367  	return false, err
   368  }
   369  func showWindow(h uintptr, v uint32) (bool, error) {
   370  	r, _, err := syscallN(funcShowWindow.address(), h, uintptr(v))
   371  	if err > 0 {
   372  		return r > 0, unboxError(err)
   373  	}
   374  	return r > 0, nil
   375  }
   376  
   377  // MarshalStream transforms this struct into a binary format and writes to the
   378  // supplied data.Writer.
   379  func (i Window) MarshalStream(w data.Writer) error {
   380  	if err := w.WriteUint64(uint64(i.Handle)); err != nil {
   381  		return err
   382  	}
   383  	if err := w.WriteString(i.Name); err != nil {
   384  		return err
   385  	}
   386  	if err := w.WriteUint8(i.Flags); err != nil {
   387  		return err
   388  	}
   389  	if err := w.WriteInt32(i.X); err != nil {
   390  		return err
   391  	}
   392  	if err := w.WriteInt32(i.Y); err != nil {
   393  		return err
   394  	}
   395  	if err := w.WriteInt32(i.Width); err != nil {
   396  		return err
   397  	}
   398  	return w.WriteInt32(i.Height)
   399  }
   400  
   401  // EnableWindow Windows API Call
   402  //
   403  //	Enables or disables mouse and keyboard input to the specified window or
   404  //	control. When input is disabled, the window does not receive input such as
   405  //	mouse clicks and key presses. When input is enabled, the window receives
   406  //	all input.
   407  //
   408  // The resulting boolean is if the window was previously enabled, or false if
   409  // it was disabled. (This value is always false if 'AllWindows'/0 is passed
   410  // as the handle.)
   411  //
   412  // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow
   413  //
   414  // If the value of h is 0, this will target ALL FOUND WINDOWS.
   415  func EnableWindow(h uintptr, e bool) (bool, error) {
   416  	var v uint32
   417  	if e {
   418  		v = 1
   419  	}
   420  	if h > 0 {
   421  		return enableWindow(h, v)
   422  	}
   423  	w, err := TopLevelWindows()
   424  	if err != nil {
   425  		return false, err
   426  	}
   427  	for i := range w {
   428  		enableWindow(w[i].Handle, v)
   429  	}
   430  	w = nil
   431  	return false, err
   432  }
   433  
   434  // SetWindowTransparency will attempt to set the transparency of the window handle
   435  // to 0-255, 0 being completely transparent and 255 being opaque.
   436  //
   437  // If the value of h is 0, this will target ALL FOUND WINDOWS.
   438  func SetWindowTransparency(h uintptr, t uint8) error {
   439  	if h > 0 {
   440  		return setWindowTransparency(h, t)
   441  	}
   442  	w, err := TopLevelWindows()
   443  	if err != nil {
   444  		return err
   445  	}
   446  	for i := range w {
   447  		setWindowTransparency(w[i].Handle, t)
   448  	}
   449  	w = nil
   450  	return err
   451  }
   452  func setWindowTransparency(h uintptr, t uint8) error {
   453  	// layeredPtr (-20) - GWL_EXSTYLE
   454  	r, _, err := syscallN(funcGetWindowLongW.address(), h, layeredPtr)
   455  	if r == 0 && err > 0 {
   456  		return unboxError(err)
   457  	}
   458  	// 0x80000 - WS_EX_LAYERED
   459  	syscallN(funcSetWindowLongW.address(), h, layeredPtr, r|0x80000)
   460  	if r, _, err = syscallN(funcSetLayeredWindowAttributes.address(), h, 0, uintptr(t), 3); r == 0 {
   461  		return unboxError(err)
   462  	}
   463  	return nil
   464  }
   465  func enableWindow(h uintptr, v uint32) (bool, error) {
   466  	r, _, err := syscallN(funcEnableWindow.address(), h, uintptr(v))
   467  	if err > 0 {
   468  		return r > 0, unboxError(err)
   469  	}
   470  	return r > 0, nil
   471  }
   472  
   473  // SetWindowPos Windows API Call
   474  //
   475  //	Changes the size, position, and Z order of a child, pop-up, or top-level
   476  //	window. These windows are ordered according to their appearance on the screen.
   477  //	The topmost window receives the highest rank and is the first window in the
   478  //	Z order.
   479  //
   480  // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos
   481  //
   482  // Use '-1' for both the 'x' and 'y' arguments to ignore changing the position and
   483  // just change the size OR use '-1' for both the 'width' and 'height' arguments to
   484  // only change the window position.
   485  //
   486  // This implementation does NOT change the active state of Z index of the window.
   487  func SetWindowPos(h uintptr, x, y, width, height int32) error {
   488  	// 0x14 - SWP_NOZORDER | SWP_NOACTIVATE
   489  	f := uint32(0x14)
   490  	if width == -1 && height == -1 {
   491  		// 0x1 - SWP_NOSIZE
   492  		f |= 0x1
   493  	} else if x == -1 && y == -1 {
   494  		// 0x2 - SWP_NOMOVE
   495  		f |= 0x2
   496  	}
   497  	r, _, err := syscallN(funcSetWindowPos.address(), h, 0, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(f))
   498  	if r == 0 {
   499  		return unboxError(err)
   500  	}
   501  	return nil
   502  }
   503  
   504  // MessageBox Windows API Call
   505  //
   506  //	Displays a modal dialog box that contains a system icon, a set of buttons,
   507  //	and a brief application-specific message, such as status or error information.
   508  //	The message box returns an integer value that indicates which button the user
   509  //	clicked.
   510  //
   511  // If the handle 'h' is '-1', "CurrentProcess" or "^uintptr(0)", this will attempt
   512  // to target the Desktop window, which will fall back to '0' if it fails.
   513  //
   514  // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw
   515  func MessageBox(h uintptr, text, title string, f uint32) (uint32, error) {
   516  	var (
   517  		t, d *uint16
   518  		err  error
   519  	)
   520  	if len(title) > 0 {
   521  		if t, err = UTF16PtrFromString(title); err != nil {
   522  			return 0, err
   523  		}
   524  	}
   525  	if len(text) > 0 {
   526  		if d, err = UTF16PtrFromString(text); err != nil {
   527  			return 0, err
   528  		}
   529  	}
   530  	if h == invalid { // If handle is '-1', target the Desktop window.
   531  		if w, err := TopLevelWindows(); err == nil {
   532  			for i := range w {
   533  				if w[i].Flags&0x80 != 0 {
   534  					h = w[i].Handle
   535  					break
   536  				}
   537  			}
   538  		}
   539  		if h == invalid {
   540  			h = 0 // Fallback
   541  		}
   542  	}
   543  	r, _, err1 := syscallN(funcMessageBox.address(), h, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(t)), uintptr(f))
   544  	if r == 0 {
   545  		return 0, unboxError(err1)
   546  	}
   547  	return uint32(r), nil
   548  }