Open knausj85 opened 3 years ago
Can you go into more detail? I'm not familiar with optikey docking.
like the start menu, it is able to reserve space on some side. window operations like maximizing and such respect the reserved space
Any idea which api they use for that?
I'm afraid not, I will look into it when I have some downtime
an example of creating an application bar from c# at least
//Register a new app bar with Windows - this adds it to a list of app bars
var abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = windowHandle;
appBarCallBackId = PInvoke.RegisterWindowMessage("AppBarMessage"); //Get a system wide unique window message (id)
abd.uCallbackMessage = appBarCallBackId;
var result = PInvoke.SHAppBarMessage((int)AppBarMessages.New, ref abd);
updating an application bar:
var barData = new APPBARDATA();
barData.cbSize = Marshal.SizeOf(barData);
barData.hWnd = windowHandle;
barData.uEdge = dockPosition.ToAppBarEdge();
barData.rc.Left = (int)Math.Round(sizeAndPosition.Left);
barData.rc.Top = (int)Math.Round(sizeAndPosition.Top);
barData.rc.Right = (int)Math.Round(sizeAndPosition.Right);
barData.rc.Bottom = (int)Math.Round(sizeAndPosition.Bottom);
//Submit a query for the proposed dock size and position, which might be updated
PInvoke.SHAppBarMessage(AppBarMessages.QueryPos, ref barData);
...
//Then set the dock size and position, using the potentially updated barData
PInvoke.SHAppBarMessage(AppBarMessages.SetPos, ref barData);
I haven't tried this yet, but this looks promising for Python https://stackoverflow.com/questions/60567141/how-make-application-desktop-toolbar-ui-with-pyqt5
here is a working example; requires pywin32, PyQt5 for the moment
"""
Registering an Application Desktop Toolbar in Windows for PyQt5 window.
Taken from https://gist.github.com/swdevbali/495f902162446b30cd567b2a44d86d79 and
https://github.com/sabren/ceomatic/blob/master/wxappbars.py,
thanks for swdevbali and sabren
Adapted by Elendiar.
"""
import ctypes, sys
from ctypes import wintypes
from ctypes import *
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QDesktopWidget, QWidget
from PyQt5.QtCore import *
from copy import deepcopy
shell32 = windll.shell32
user32 = windll.user32
import win32api, win32gui
import win32.lib.win32con as win32con
class APPBARDATA(Structure):
_fields_ = [
("cbSize", wintypes.DWORD),
("hWnd", wintypes.HWND),
("uCallbackMessage", ctypes.c_ulong),
("uEdge", c_ulong),
("rc", wintypes.RECT),
("lParam", wintypes.LPARAM),
]
PAPPBARDATA = POINTER(APPBARDATA)
class ABEdge:
Left = 0
Top = 1
Right = 2
Bottom = 3
Float = 4
class ABMsg:
ABM_NEW = 0
ABM_REMOVE = 1
ABM_QUERYPOS = 2
ABM_SETPOS = 3
ABM_GETSTATE = 4
ABM_GETTASKBARPOS = 5
ABM_ACTIVATE = 6
ABM_GETAUTOHIDEBAR = 7
ABM_SETAUTOHIDEBAR = 8
ABM_WINDOWPOSCHANGED = 9
ABM_SETSTATE = 10
class ABNotify:
ABN_STATECHANGE = 0
ABN_POSCHANGED = 1
ABN_FULLSCREENAPP = 2
ABN_WINDOWARRANGE = 3
class RegisterInfo(object):
def __init__(self):
self._window = None
self.callbackId = 0
self.isRegistered = False
self.edge = ABEdge.Float
self.originalStyle = None
self.originalPosition = None
self.originalSize = None
self.originalResizeMode = None
@property
def window(self):
return self._window
@window.setter
def window(self, window):
self._window = window
self._hWnd = window.winId().__int__()
self._oldWndProc = win32gui.SetWindowLong(
self._hWnd, win32con.GWL_WNDPROC, self.WndProc
)
# http://wiki.wxpython.org/HookingTheWndProc
def WndProc(self, hWnd, msg, wParam, lParam):
if msg == win32con.WM_DESTROY:
self._restoreOldWndProc()
elif msg == self.callbackId:
if wParam == ABNotify.ABN_POSCHANGED:
_ABSetPos(self.edge, self.window)
else:
return win32gui.CallWindowProc(self._oldWndProc, hWnd, msg, wParam, lParam)
def _restoreOldWndProc(self):
win32api.SetWindowLong(self._hWnd, win32con.GWL_WNDPROC, self._oldWndProc)
_registeredWindowInfo = {}
def _GetRegisterInfo(appbarWindow):
geometry = appbarWindow.geometry()
reg = RegisterInfo()
reg.callBackId = 0
reg.window = appbarWindow
reg.isRegistered = False
reg.edge = ABEdge.Top
reg.originalStyle = appbarWindow.windowFlags()
reg.originalPosition = QPoint(geometry.x(), geometry.y())
reg.originalSize = QSize(geometry.width(), geometry.height())
_registeredWindowInfo[appbarWindow] = reg
return _registeredWindowInfo[appbarWindow]
def _RestoreWindow(appbarWindow):
info = _GetRegisterInfo(appbarWindow)
appbarWindow.setWindowFlags(info.originalStyle)
appbarWindow.move(info.originalPosition)
appbarWindow.resize(info.originalSize)
def SetAppBar(appbarWindow, edge, barSize):
info = _GetRegisterInfo(appbarWindow)
info.edge = edge
abd = APPBARDATA()
abd.cbSize = wintypes.DWORD(sizeof(abd))
abd.hWnd = wintypes.HWND(appbarWindow.winId().__int__())
if (edge == ABEdge.Float) and info.isRegistered:
shell32.SHAppBarMessage(ABMsg.ABM_REMOVE, PAPPBARDATA(abd))
info.isRegistered = False
_RestoreWindow(appbarWindow)
elif not info.isRegistered:
info.isRegistered = True
info.callbackId = win32api.RegisterWindowMessage("AppBarMessage")
shell32.SHAppBarMessage(ABMsg.ABM_NEW, PAPPBARDATA(abd))
appbarWindow.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
_ABSetPos(info.edge, appbarWindow, barSize)
def RemoveAppBar(appbarWindow):
print("REMOVING")
info = _GetRegisterInfo(appbarWindow)
abd = APPBARDATA()
shell32.SHAppBarMessage(ABMsg.ABM_REMOVE, PAPPBARDATA(abd))
info.isRegistered = False
# _RestoreWindow(appbarWindow)
def _DoResize(appBarWindow, rect):
appBarWindow.resize(rect.Width, rect.Height)
appBarWindow.move(rect.Left, rect.Top)
def _ABSetPos(edge, appbarWindow, barSize):
barData = APPBARDATA()
barData.cbSize = wintypes.DWORD(sizeof(barData))
barData.hWnd = appbarWindow.winId().__int__()
barData.uEdge = edge
screen = QDesktopWidget().screenGeometry()
deskW = screen.width()
deskH = screen.height()
if barData.uEdge == ABEdge.Left or barData.uEdge == ABEdge.Right:
winW = barSize
winH = deskH
barData.rc.top = 0
barData.rc.bottom = deskH
if barData.uEdge == ABEdge.Left:
barData.rc.left = 0
barData.rc.right = winW
else:
barData.rc.right = deskW
barData.rc.left = deskW - winW
else:
winW = deskW
winH = barSize
barData.rc.left = 0
barData.rc.right = deskW
if barData.uEdge == ABEdge.Top:
barData.rc.top = 0
barData.rc.bottom = winH
else:
barData.rc.bottom = deskH
barData.rc.top = deskH - winH
oldLeft = deepcopy(barData.rc.left) # deepcopy because after reopen x from 0
# become w -> 222 and window isnt visible.
shell32.SHAppBarMessage(ABMsg.ABM_QUERYPOS, PAPPBARDATA(barData))
shell32.SHAppBarMessage(ABMsg.ABM_SETPOS, PAPPBARDATA(barData))
barData.rc.left = deepcopy(oldLeft)
def _resize():
x = barData.rc.left
y = barData.rc.top
w = barData.rc.right - barData.rc.left
h = barData.rc.bottom - barData.rc.top
appbarWindow.resize(w, h)
appbarWindow.move(x, y)
# This is done async, because windows will send a resize after a new appbar is added.
# if we size right away, the windows resize comes last and overrides us.
QTimer.singleShot(300, _resize)
if __name__ == "__main__":
# DPI scaling
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
# QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
app = QtWidgets.QApplication(sys.argv)
win = QWidget()
win.setWindowFlags(
Qt.FramelessWindowHint
| Qt.WindowStaysOnTopHint
| Qt.CustomizeWindowHint
| Qt.Tool
)
btn = QtWidgets.QPushButton("Click-to-close", win)
btn.clicked.connect(QtWidgets.qApp.quit)
barSize = 222
SetAppBar(win, ABEdge.Left, barSize)
win.show()
sys.exit(app.exec_())
sorry for the spam, there's some good documentation here https://docs.microsoft.com/en-us/windows/win32/shell/application-desktop-toolbars
I don't know if this would work on other OSs besides Windows, but I think it would be very useful down the line to be able to dock an imgui, something like OptiKey.
this has many potential use cases, from OptiKey-like functionality to context aware buttons exposed in the UI in a consistent location that doesn't block other things on screen.