ebiten: add RunGameOptions.SkipTaskbar

Closes #1518
This commit is contained in:
Hajime Hoshi 2022-12-13 01:03:19 +09:00
parent 1dd7c1cce3
commit 0bec1e65fa
8 changed files with 112 additions and 5 deletions

View File

@ -145,6 +145,7 @@ func main() {
op := &ebiten.RunGameOptions{}
op.ScreenTransparent = true
op.SkipTaskbar = true
if err := ebiten.RunGameWithOptions(&mascot{}, op); err != nil {
log.Fatal(err)
}

View File

@ -17,15 +17,37 @@
package ui
import (
"errors"
"fmt"
"runtime"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
_SM_CYCAPTION = 4
_CLSCTX_INPROC_SERVER = 0x1
_CLSCTX_LOCAL_SERVER = 0x4
_CLSCTX_REMOTE_SERVER = 0x10
_CLSCTX_SERVER = _CLSCTX_INPROC_SERVER | _CLSCTX_LOCAL_SERVER | _CLSCTX_REMOTE_SERVER
_MONITOR_DEFAULTTONEAREST = 2
_SM_CYCAPTION = 4
)
var (
_CLSID_TaskbarList = windows.GUID{
Data1: 0x56FDF344,
Data2: 0xFD6D,
Data3: 0x11D0,
Data4: [...]byte{0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90},
}
_IID_ITaskbarList = windows.GUID{
Data1: 0x56FDF342,
Data2: 0xFD6D,
Data3: 0x11D0,
Data4: [...]byte{0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90},
}
)
type _RECT struct {
@ -48,14 +70,28 @@ type _POINT struct {
}
var (
ole32 = windows.NewLazySystemDLL("ole32.dll")
user32 = windows.NewLazySystemDLL("user32.dll")
procCoCreateInstance = ole32.NewProc("CoCreateInstance")
procGetSystemMetrics = user32.NewProc("GetSystemMetrics")
procMonitorFromWindow = user32.NewProc("MonitorFromWindow")
procGetMonitorInfoW = user32.NewProc("GetMonitorInfoW")
procGetCursorPos = user32.NewProc("GetCursorPos")
)
func _CoCreateInstance(rclsid *windows.GUID, pUnkOuter unsafe.Pointer, dwClsContext uint32, riid *windows.GUID) (unsafe.Pointer, error) {
var ptr unsafe.Pointer
r, _, _ := procCoCreateInstance.Call(uintptr(unsafe.Pointer(rclsid)), uintptr(pUnkOuter), uintptr(dwClsContext), uintptr(unsafe.Pointer(riid)), uintptr(unsafe.Pointer(&ptr)))
runtime.KeepAlive(rclsid)
runtime.KeepAlive(riid)
if uint32(r) != uint32(windows.S_OK) {
return nil, fmt.Errorf("ui: CoCreateInstance failed: error code: HRESULT(%d)", uint32(r))
}
return ptr, nil
}
func _GetSystemMetrics(nIndex int) (int32, error) {
r, _, _ := procGetSystemMetrics.Call(uintptr(nIndex))
if int32(r) == 0 {
@ -77,7 +113,7 @@ func _GetMonitorInfoW(hMonitor uintptr) (_MONITORINFO, error) {
r, _, e := procGetMonitorInfoW.Call(hMonitor, uintptr(unsafe.Pointer(&mi)))
if int32(r) == 0 {
if e != nil && e != windows.ERROR_SUCCESS {
if e != nil && !errors.Is(e, windows.ERROR_SUCCESS) {
return _MONITORINFO{}, fmt.Errorf("ui: GetMonitorInfoW failed: error code: %w", e)
}
return _MONITORINFO{}, fmt.Errorf("ui: GetMonitorInfoW failed: returned 0")
@ -89,10 +125,38 @@ func _GetCursorPos() (int32, int32, error) {
var pt _POINT
r, _, e := procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt)))
if int32(r) == 0 {
if e != nil && e != windows.ERROR_SUCCESS {
if e != nil && !errors.Is(e, windows.ERROR_SUCCESS) {
return 0, 0, fmt.Errorf("ui: GetCursorPos failed: error code: %w", e)
}
return 0, 0, fmt.Errorf("ui: GetCursorPos failed: returned 0")
}
return pt.x, pt.y, nil
}
type _ITaskbarList struct {
vtbl *_ITaskbarList_Vtbl
}
type _ITaskbarList_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
HrInit uintptr
AddTab uintptr
DeleteTab uintptr
ActivateTab uintptr
SetActiveAlt uintptr
}
func (i *_ITaskbarList) DeleteTab(hwnd windows.HWND) error {
r, _, _ := syscall.Syscall(i.vtbl.DeleteTab, 2, uintptr(unsafe.Pointer(i)), uintptr(hwnd), 0)
if uint32(r) != uint32(windows.S_OK) {
return fmt.Errorf("ui: ITaskbarList::DeleteTab failed: HRESULT(%d)", uint32(r))
}
return nil
}
func (i *_ITaskbarList) Release() {
syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0)
}

View File

@ -100,4 +100,5 @@ type RunOptions struct {
GraphicsLibrary GraphicsLibrary
InitUnfocused bool
ScreenTransparent bool
SkipTaskbar bool
}

View File

@ -940,6 +940,11 @@ func (u *userInterfaceImpl) init(options *RunOptions) error {
u.setWindowResizingModeForOS(u.windowResizingMode)
if options.SkipTaskbar {
// Ignore the error.
_ = u.skipTaskbar()
}
u.window.Show()
if g, ok := u.graphicsDriver.(interface{ SetWindow(uintptr) }); ok {

View File

@ -385,3 +385,7 @@ func initializeWindowAfterCreation(w *glfw.Window) {
delegate := objc.ID(class_EbitengineWindowDelegate).Send(objc.RegisterName("alloc")).Send(objc.RegisterName("initWithOrigDelegate:"), nswindow.Send(sel_delegate))
nswindow.Send(objc.RegisterName("setDelegate:"), delegate)
}
func (u *userInterfaceImpl) skipTaskbar() error {
return nil
}

View File

@ -229,3 +229,7 @@ func initializeWindowAfterCreation(w *glfw.Window) {
// Apparently the window state is inconsistent just after the window is created, but we are not sure.
// For more details, see the discussion in #1829.
}
func (u *userInterfaceImpl) skipTaskbar() error {
return nil
}

View File

@ -185,3 +185,24 @@ func (u *userInterfaceImpl) setWindowResizingModeForOS(mode WindowResizingMode)
func initializeWindowAfterCreation(w *glfw.Window) {
}
func (u *userInterfaceImpl) skipTaskbar() error {
if err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED); err != nil {
return err
}
defer windows.CoUninitialize()
ptr, err := _CoCreateInstance(&_CLSID_TaskbarList, nil, _CLSCTX_SERVER, &_IID_ITaskbarList)
if err != nil {
return err
}
t := (*_ITaskbarList)(ptr)
defer t.Release()
if err := t.DeleteTab(windows.HWND(u.window.GetWin32Window())); err != nil {
return err
}
return nil
}

11
run.go
View File

@ -236,17 +236,23 @@ type RunGameOptions struct {
// The default (zero) value is GraphicsLibraryAuto, which lets Ebitengine choose the graphics library.
GraphicsLibrary GraphicsLibrary
// InitUnfocused represents whether the window is unfocused or not on launching.
// InitUnfocused indicates whether the window is unfocused or not on launching.
// InitUnfocused is valid on desktops and browsers.
//
// The default (zero) value is false, which means that the window is focused.
InitUnfocused bool
// ScreenTransparent represents whether the window is transparent or not.
// ScreenTransparent indicates whether the window is transparent or not.
// ScreenTransparent is valid on desktops and browsers.
//
// The default (zero) value is false, which means that the window is not transparent.
ScreenTransparent bool
// SkipTaskbar indicates whether an application icon is shown on a taskbar or not.
// SkipTaskbar is valid only on Windows.
//
// The default (zero) value is false, which means that an icon is shown on a taskbar.
SkipTaskbar bool
}
// RunGameWithOptions starts the main loop and runs the game with the specified options.
@ -656,5 +662,6 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary),
InitUnfocused: options.InitUnfocused,
ScreenTransparent: options.ScreenTransparent,
SkipTaskbar: options.SkipTaskbar,
}
}