From b3bf740efe39bd5312f690f957d38f54be4db6d1 Mon Sep 17 00:00:00 2001 From: imawizard Date: Wed, 17 Apr 2024 03:09:39 +0200 Subject: [PATCH 1/2] fix(focusindicator): Don't update for unmanaged windows that are invisible --- lib/miguru/miguru.ahk | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/miguru/miguru.ahk b/lib/miguru/miguru.ahk index 336a4fc..8809071 100644 --- a/lib/miguru/miguru.ahk +++ b/lib/miguru/miguru.ahk @@ -268,12 +268,23 @@ class MiguruWM extends WMEvents { ;; e.g. appear for the first time by unhiding instead of ;; creation, add new windows on any event. window := this._manage(event, hwnd) + if !window { + ;; A new explorer window actually seems to get focused while + ;; still being invisible. So in case that same window is shown + ;; later on it should be additionally focused. if event == EV_WINDOW_FOCUSED && !WinExist("ahk_id" hwnd " ahk_group MIGURU_IGNORE") { - debug("Set active to non-managed {}", WinInfo(hwnd)) + + debug("Set maybe-active to non-managed {}", WinInfo(hwnd)) this._maybeActiveWindow := hwnd - this._focusIndicator.Unmanaged(hwnd) + + ;; Only update the focus indicator for non-managed windows + ;; that are visible, though. + if DllCall("IsWindowVisible", "Ptr", hwnd, "Int") + || !IsWindowCloaked(hwnd) { + this._focusIndicator.Unmanaged(hwnd) + } } return } @@ -342,7 +353,6 @@ class MiguruWM extends WMEvents { PINNED_WINDOW_FOCUSED, ) } - } else if event == EV_WINDOW_REPOSITIONED { debug(() => ["Repositioned: D={} WS={} {}", monitor.Index, ws.Index, WinInfo(hwnd)]) From 358077c7e19cdabc38049f02450c4a70de3ad6cc Mon Sep 17 00:00:00 2001 From: imawizard Date: Fri, 1 Mar 2024 09:59:28 +0100 Subject: [PATCH 2/2] feat!: Add WhichSpace-like tray icon --- README.md | 2 + lib/gdi.ahk | 610 ++++++++++++++++++++++++++++++++ lib/miguru/miguru.ahk | 42 ++- lib/miguru/utils.ahk | 56 +++ lib/miguru/utils/WhichSpace.ahk | 107 ++++++ mwm.ahk | 35 +- 6 files changed, 825 insertions(+), 27 deletions(-) create mode 100644 lib/gdi.ahk create mode 100644 lib/miguru/utils/WhichSpace.ahk diff --git a/README.md b/README.md index 411d429..cae3d81 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ There already are a bunch of really nice tiling window managers for Windows – The goal is a basic but hopefully stable window manager that should behave somewhere close to Amethyst/xmonad. +Additional features include a focus indicator similiar to [HazeOver](https://hazeover.com) and a tray icon similiar to [WhichSpace](https://github.com/gechr/WhichSpace)/[SpaceId](https://github.com/dshnkao/SpaceId). + ## Caveats - Uses native virtual desktops as workspaces, *which can't be switched on per-monitor basis*, integrate however with: diff --git a/lib/gdi.ahk b/lib/gdi.ahk new file mode 100644 index 0000000..72d0b0a --- /dev/null +++ b/lib/gdi.ahk @@ -0,0 +1,610 @@ +;; See https://learn.microsoft.com/de-de/windows/win32/gdiplus/-gdiplus-flatapi-flat + +PixelFormatIndexed := 0x00010000 +PixelFormatGDI := 0x00020000 +PixelFormatAlpha := 0x00040000 +PixelFormatPAlpha := 0x00080000 +PixelFormatExtended := 0x00100000 +PixelFormatCanonical := 0x00200000 + +PixelFormat32bppRGB := 9 | (32 << 8) | PixelFormatGDI +PixelFormat32bppARGB := 10 | (32 << 8) | PixelFormatAlpha | PixelFormatGDI | PixelFormatCanonical +PixelFormat32bppPARGB := 11 | (32 << 8) | PixelFormatAlpha | PixelFormatPAlpha | PixelFormatGDI + +_gdiplus := GdiPlus() + +class GdiPlus { + __New() { + token := 0 + input := Buffer(4 + A_PtrSize + 4 + 4, 0) + NumPut("UInt", 1, input, 0) + if DllCall( + "gdiplus.dll\GdiplusStartup", + "UInt*", &token, + "Ptr", input, + "Ptr", 0, + "Int", + ) { + throw "GDI+ startup failed" + } + this._token := token + } + + __Delete() { + token := this._token + DllCall( + "gdiplus.dll\GdiplusShutdown", + "UInt*", &token, + ) + } +} + +class GdiBitmap { + __New(width, height, format := PixelFormat32bppARGB) { + handle := 0 + DllCall( + "gdiplus.dll\GdipCreateBitmapFromScan0", + "Int", width, + "Int", height, + "Int", 0, + "UInt", format, + "Ptr", 0, + "Ptr*", &handle, + "Int", + ) + this._handle := handle + this._graphics := GdiGraphics.FromImage(this) + this._width := width + this._height := height + } + + __Delete() { + DllCall( + "gdiplus.dll\GdipDisposeImage", + "Ptr", this, + "Int", + ) + } + + Ptr => this._handle + Canvas => this._graphics + Width => this._width + Height => this._height + + ToIcon() { + icon := 0 + DllCall( + "gdiplus.dll\GdipCreateHICONFromBitmap", + "Ptr", this, + "Ptr*", &icon, + "Int", + ) + return icon + } +} + +FontStyleRegular := 0 +FontStyleBold := 1 << 0 +FontStyleItalic := 1 << 1 +FontStyleBoldItalic := FontStyleBold | FontStyleItalic +FontStyleUnderline := 1 << 2 +FontStyleStrikeout := 1 << 3 + +class GdiFont { + __New(name, size, style := FontStyleRegular) { + this._size := size + this._style := style + + family := 0 + DllCall( + "gdiplus.dll\GdipCreateFontFamilyFromName", + "Str", name, + "Ptr", 0, + "Ptr*", &family, + "Int", + ) + this._family := family + + handle := 0 + DllCall( + "gdiplus.dll\GdipCreateFont", + "Ptr", family, + "Float", size, + "Int", style, + "UInt", 0, + "Ptr*", &handle, + "Int", + ) + this._handle := handle + } + + __Delete() { + DllCall( + "gdiplus.dll\GdipDeleteFontFamily", + "Ptr", this._family, + "Int", + ) + DllCall( + "gdiplus.dll\GdipDeleteFont", + "Ptr", this, + "Int", + ) + } + + Ptr => this._handle + Family => this._family + Size => this._size + Style => this._style +} + +class GdiBrush { + __New(color) { + handle := 0 + DllCall( + "gdiplus.dll\GdipCreateSolidFill", + "UInt", color, + "Ptr*", &handle, + "Int", + ) + this._handle := handle + } + + __Delete() { + DllCall( + "gdiplus.dll\GdipDeleteBrush", + "Ptr", this, + "Int", + ) + } + + Ptr => this._handle +} + +class GdiPen { + __New(color, width := 1) { + handle := 0 + DllCall( + "gdiplus.dll\GdipCreatePen1", + "UInt", color, + "Float", width, + "UInt", 0, + "Ptr*", &handle, + "Int", + ) + this._handle := handle + } + + __Delete() { + DllCall( + "gdiplus.dll\GdipDeletePen", + "Ptr", this, + "Int", + ) + } + + Ptr => this._handle +} + +class GdiRect { + __New(x, y, width, height) { + buf := Buffer(4 * 4) + NumPut("Float", x, buf, 0 * 4) + NumPut("Float", y, buf, 1 * 4) + NumPut("Float", width, buf, 2 * 4) + NumPut("Float", height, buf, 3 * 4) + this.buf := buf + } + + Ptr => this.buf.Ptr +} + +StringFormatFlagsDirectionRightToLeft := 0x00000001 +StringFormatFlagsDirectionVertical := 0x00000002 +StringFormatFlagsNoFitBlackBox := 0x00000004 +StringFormatFlagsDisplayFormatControl := 0x00000020 +StringFormatFlagsNoFontFallback := 0x00000400 +StringFormatFlagsMeasureTrailingSpaces := 0x00000800 +StringFormatFlagsNoWrap := 0x00001000 +StringFormatFlagsLineLimit := 0x00002000 +StringFormatFlagsNoClip := 0x00004000 +StringFormatFlagsBypassGDI := 0x80000000 + +StringAlignmentNear := 0 +StringAlignmentCenter := 1 +StringAlignmentFar := 2 + +class GdiStringFormat { + __New(flags := StringFormatFlagsNoClip) { + handle := 0 + DllCall( + "gdiplus.dll\GdipCreateStringFormat", + "UInt", flags, + "Int", 0, + "Ptr*", &handle, + "Int", + ) + this._handle := handle + } + + __Delete() { + DllCall( + "gdiplus.dll\GdipDeleteStringFormat", + "Ptr", this, + "Int", + ) + } + + Ptr => this._handle + + Align { + set { + DllCall( + "gdiplus.dll\GdipSetStringFormatAlign", + "Ptr", this, + "UInt", value, + "Int", + ) + } + } + + LineAlign { + set { + DllCall( + "gdiplus.dll\GdipSetStringFormatLineAlign", + "Ptr", this, + "UInt", value, + "Int", + ) + } + } +} + +FillModeAlternate := 0 +FillModeWinding := 1 + +class GdiPath { + __New(fillMode := FillModeAlternate) { + handle := 0 + DllCall( + "gdiplus.dll\GdipCreatePath", + "UInt", fillMode, + "Ptr*", &handle, + "Int", + ) + this._handle := handle + } + + __Delete() { + DllCall( + "gdiplus.dll\GdipDeletePath", + "Ptr", this, + "Int", + ) + } + + Ptr => this._handle + + Close() { + DllCall( + "gdiplus.dll\GdipClosePathFigure", + "Ptr", this, + "Int", + ) + } + + MoveTo(x, y) { + DllCall( + "gdiplus.dll\GdipAddPathLine", + "Ptr", this, + "Float", x, + "Float", y, + "Float", x, + "Float", y, + "Int", + ) + } + + LineTo(x, y) { + buf := Buffer(4 * 2) + DllCall( + "gdiplus.dll\GdipGetPathLastPoint", + "Ptr", this, + "Ptr", buf, + "Int", + ) + x1 := NumGet(buf, 0, "Float") + y1 := NumGet(buf, 4, "Float") + this.Line(x1, y1, x, y) + } + + Line(x1, y1, x2, y2) { + DllCall( + "gdiplus.dll\GdipAddPathLine", + "Ptr", this, + "Float", x1, + "Float", y1, + "Float", x2, + "Float", y2, + "Int", + ) + } + + Rectangle(x, y, width, height) { + DllCall( + "gdiplus.dll\GdipAddPathRectangle", + "Ptr", this, + "Float", x, + "Float", y, + "Float", width, + "Float", height, + "Int", + ) + } + + Arc(x, y, width, height, startAngle, sweepAngle) { + DllCall( + "gdiplus.dll\GdipAddPathArc", + "Ptr", this, + "Float", x, + "Float", y, + "Float", width, + "Float", height, + "Float", startAngle, + "Float", sweepAngle, + "Int", + ) + } + + Bezier(x1, y1, x2, y2, x3, y3, x4, y4) { + DllCall( + "gdiplus.dll\GdipAddPathBezier", + "Ptr", this, + "Float", x1, + "Float", y1, + "Float", x2, + "Float", y2, + "Float", x3, + "Float", y3, + "Float", x4, + "Float", y4, + "Int", + ) + } + + Text(s, rect, format, font) { + DllCall( + "gdiplus.dll\GdipAddPathString", + "Ptr", this, + "Str", s, + "Int", -1, + "Ptr", font.Family, + "Int", font.Style, + "Float", font.Size, + "Ptr", rect, + "Ptr", format, + "Int", + ) + } +} + +SystemDefault := 0 +SingleBitPerPixelGridFit := 1 +SingleBitPerPixel := 2 +AntiAliasGridFit := 3 +AntiAlias := 4 + +class GdiGraphics { + __New(handle) { + this._handle := handle + } + + __Delete() { + DllCall( + "gdiplus.dll\GdipDeleteGraphics", + "Ptr", this, + "Int", + ) + } + + Ptr => this._handle + + static FromImage(image) { + handle := 0 + DllCall( + "gdiplus.dll\GdipGetImageGraphicsContext", + "Ptr", image, + "UInt*", &handle, + "Int", + ) + return GdiGraphics(handle) + } + + TextRenderingHint { + set { + DllCall( + "gdiplus.dll\GdipSetTextRenderingHint", + "Ptr", this, + "Int", value, + "Int", + ) + } + } + +; _drawRectangle(x, y, width, height, pen) { +; DllCall( +; "gdiplus.dll\GdipDrawRectangle", +; "Ptr", this, +; "Ptr", pen, +; "Float", x, +; "Float", y, +; "Float", width, +; "Float", height, +; "Int", +; ) +; } + +; _fillRectangle(x, y, width, height, brush) { +; DllCall( +; "gdiplus.dll\GdipFillRectangle", +; "Ptr", this, +; "Ptr", brush, +; "Float", x, +; "Float", y, +; "Float", width, +; "Float", height, +; "Int", +; ) +; } + + ; _drawString(s, rect, format, font, brush) { + ; DllCall( + ; "gdiplus.dll\GdipDrawString", + ; "Ptr", this, + ; "Str", s, + ; "Int", -1, + ; "Ptr", font, + ; "Ptr", rect, + ; "Ptr", format, + ; "Ptr", brush, + ; "Int", + ; ) + ; } + + ; _drawBezier(x1, y1, x2, y2, x3, y3, x4, y4, pen) { + ; DllCall( + ; "gdiplus.dll\GdipDrawBezier", + ; "Ptr", this, + ; "Ptr", pen, + ; "Float", x1, + ; "Float", y1, + ; "Float", x2, + ; "Float", y2, + ; "Float", x3, + ; "Float", y3, + ; "Float", x4, + ; "Float", y4, + ; "Int", + ; ) + ; } + + _drawPath(pen, path) { + DllCall( + "gdiplus.dll\GdipDrawPath", + "Ptr", this, + "Ptr", pen, + "Ptr", path, + "Int", + ) + } + + _fillPath(brush, path) { + DllCall( + "gdiplus.dll\GdipFillPath", + "Ptr", this, + "Ptr", brush, + "Ptr", path, + "Int", + ) + } + + RoundedRectangle(x, y, width, height, radius, pen, brush) { +; kappa := 0.5522847498 +; +; xRadius:= 8 +; yRadius:= 8 +; +; path := GdiPath() +; originX := x, originY := y + height +; topLeftX := x, topLeftY := y +; topRightX := x + width, topRightY := y +; bottomRightX := x + width, bottomRightY := y + height +; +; ;; top left +; startX := topLeftX + xRadius, startY := topLeftY +; endX := topLeftX, endY := topLeftY - yRadius +; control1X := startX - kappa * xRadius, control1Y := startY +; control2X := endX, control2Y := endY + kappa * yRadius +; ; this._drawBezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY, pen) +; +; ; path.MoveTo(startX, startY) +; path.Bezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY) +; ; path.LineTo(0, 10) +; ; path.LineTo(10, 10) +; ; path.LineTo(10, 0) +; +; ;; top right +; startX := topRightX, startY := topRightY - yRadius +; endX := topRightX - xRadius, endY := topRightY +; control1X := startX, control1Y := startY + kappa * yRadius +; control2X := endX + kappa * xRadius, control2Y := endY +; ; this._drawBezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY, pen) +; ; path.LineTo(startX, startY) +; ; path.Bezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY) +; +; ;; bottom right +; startX := bottomRightX - xRadius, startY := bottomRightY +; endX := bottomRightX, endY := bottomRightY + yRadius +; control1X := startX + kappa * yRadius, control1Y := startY +; control2X := endX, control2Y := endY - kappa * xRadius +; ; this._drawBezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY, pen) +; ; path.LineTo(startX, startY) +; ; path.Bezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY) +; +; ;; bottom left +; startX := originX, startY := originY + yRadius +; endX := originX + xRadius, endY := originY +; control1X := startX, control1Y := startY - kappa * yRadius +; control2X := endX - kappa * xRadius, control2Y := endY +; ; this._drawBezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY, pen) + ; path.LineTo(startX, startY) + ; path.Bezier(startX, startY, control1X, control1Y, control2X, control2Y, endX, endY) + + ; path.Rectangle(x, y, width, height) + + path := GdiPath() + path.Arc(x, y, radius, radius, 180, 90) + path.Arc(x + width - radius - 1, y, radius, radius, 270, 90) + path.Arc(x + width - radius - 1, y + height - radius - 1, radius, radius, 0, 90) + path.Arc(x, y + height - radius - 1, radius, radius, 90, 90) + path.Close() + this._fillPath(brush, path) + this._drawPath(pen, path) + } + + Text(s, x, y, width, height, font, pen, brush) { + rect := GdiRect(x, y, width, height) + format := GdiStringFormat() + format.Align := StringAlignmentCenter + format.LineAlign := StringAlignmentCenter + + path := GdiPath() + path.Text(s, rect, format, font) + path.Close() + this._fillPath(brush, path) + this._drawPath(pen, path) + } + + Triangle(x, y, width, height, pen, brush, skew := 0) { + path := GdiPath() + path.Rectangle(x, y, width, height) + path.Close() + this._fillPath(brush, path) + this._drawPath(pen, path) + } + + Rectangle(x, y, width, height, pen, brush) { + path := GdiPath() + path.Rectangle(x, y, width, height) + path.Close() + this._fillPath(brush, path) + this._drawPath(pen, path) + } + + Arc(x, y, width, height, startAngle, sweepAngle, pen, brush) { + path := GdiPath() + path.Arc(x, y, width, height, startAngle, sweepAngle) + path.Close() + this._fillPath(brush, path) + this._drawPath(pen, path) + } +} diff --git a/lib/miguru/miguru.ahk b/lib/miguru/miguru.ahk index 8809071..2af948a 100644 --- a/lib/miguru/miguru.ahk +++ b/lib/miguru/miguru.ahk @@ -78,16 +78,8 @@ class MiguruWM extends WMEvents { focusWorkspaceByWindow: true, - showPopup: (*) =>, - focusIndicator: { - Show: (*) =>, - Hide: (*) =>, - Unmanaged: (*) =>, - SetMonitorList: (*) =>, - HideWhenPositioning: false, - ShowOnFocusRequest: false, - UpdateOnRetile: false, - }, + focusIndicator: EmptyFocusIndicator(), + workspaceIndicator: EmptyWorkspaceIndicator(), delays: { retryManage: 100, @@ -104,6 +96,8 @@ class MiguruWM extends WMEvents { this._focusIndicator := this._opts.focusIndicator this._delays := this._opts.delays + this._wsIndicator := this._opts.workspaceIndicator + this._monitors := MonitorList() this._workspaces := WorkspaceList(this._monitors, ObjClone(this._opts)) this._managed := Map() @@ -257,6 +251,7 @@ class MiguruWM extends WMEvents { this.lastMonitor := this.activeMonitor this.activeMonitor := monitor + this._wsIndicator.MonitorChanged(this.activeMonitor.Index) } goto fallthrough @@ -391,10 +386,11 @@ class MiguruWM extends WMEvents { this.lastWsIdx := this.activeWsIdx this.activeWsIdx := args.now - oldWs := this._workspaces[this.activeMonitor, args.was] newWs := this._workspaces[this.activeMonitor, args.now] + this._wsIndicator.WorkspaceChanged(newWs) + oldWs.ActiveWindow := "" if newWs.WindowCount < 1 { this._focusIndicator.Hide() @@ -402,10 +398,6 @@ class MiguruWM extends WMEvents { this._focusIndicator.Show(newWs.ActiveWindow) } - this._opts.showPopup.Call(this.VD.DesktopName(args.now), { - activeMonitor: this.activeMonitor.Index, - }) - ;; Add pinned windows to the newly active workspace or retile. if this._pinned.Count > 0 { for k in this._pinned { @@ -426,12 +418,12 @@ class MiguruWM extends WMEvents { debug(() => ["Created Desktop: {}", this.VD.DesktopName(args.desktop)]) - ;; Do nothing + this._wsIndicator.WorkspaceCountChanged(this.VD.Count()) case EV_DESKTOP_DESTROYED: debug("Destroyed Desktop: #{}", args.desktopId) - ;; Do nothing + this._wsIndicator.WorkspaceCountChanged(this.VD.Count()) default: throw "Unknown desktop event: " event @@ -607,6 +599,8 @@ class MiguruWM extends WMEvents { this.lastMonitor := this.activeMonitor this.activeMonitor := monitor this._focusIndicator.Show(hwnd) + this._wsIndicator.MonitorChanged(this.activeMonitor.Index) + this._wsIndicator.WorkspaceChanged(newWs) } else { ws := this._workspaces[this.activeMonitor, this.activeWsIdx] this._focusWorkspace(ws) @@ -755,9 +749,7 @@ class MiguruWM extends WMEvents { case "set-layout": ws := getWorkspace() ws.Layout := req.value - this._opts.showPopup.Call(ws.Layout.DisplayName, { - activeMonitor: this.activeMonitor.Index, - }) + this._wsIndicator.LayoutChanged(ws) this._delayed.Add( ws.Retile.Bind(ws), this._delays.retile2ndTime, @@ -911,6 +903,7 @@ class MiguruWM extends WMEvents { } this._initWithCurrentDesktopAndWindows() + this._wsIndicator.MonitorCountChanged(this._monitors.Count) } _initWithCurrentDesktopAndWindows() { @@ -920,6 +913,12 @@ class MiguruWM extends WMEvents { this.lastWsIdx := 0 this.activeWsMonitors := Map() + ws := this._workspaces[this.activeMonitor, this.activeWsIdx] + this._wsIndicator.MonitorCountChanged(this._monitors.Count) + this._wsIndicator.MonitorChanged(this.activeMonitor.Index) + this._wsIndicator.WorkspaceCountChanged(this.VD.Count()) + this._wsIndicator.WorkspaceChanged(ws) + old := A_DetectHiddenWindows DetectHiddenWindows(false) windows := WinGetList() @@ -1086,7 +1085,7 @@ class MiguruWM extends WMEvents { ;; Remove a window from its workspace and add it to another. _reassociate(hwnd, window, monitor, workspace) { debug(() => ["Moved: D={} WS={} -> D={} WS={} - {}", - window.monitor.Index, window.workspace.Index, + window.monitor.Index, window.workspace.Index, monitor.Index, workspace.Index, WinInfo(hwnd)]) @@ -1139,7 +1138,6 @@ class MiguruWM extends WMEvents { ws.ActiveWindow := hwnd this._maybeActiveWindow := "" - this._opts.showPopup.Call("", {}) this._focusIndicator.Show(hwnd) ;; If it's an explorer window, focus the content panel. diff --git a/lib/miguru/utils.ahk b/lib/miguru/utils.ahk index f176d82..ff49848 100644 --- a/lib/miguru/utils.ahk +++ b/lib/miguru/utils.ahk @@ -3,6 +3,7 @@ #include utils\HazeOver.ahk #include utils\Logger.ahk #include utils\Timeouts.ahk +#include utils\WhichSpace.ahk DPI_AWARENESS_CONTEXT := 0 DPI_AWARENESS_CONTEXT_UNAWARE := DPI_AWARENESS_CONTEXT - 1 @@ -753,3 +754,58 @@ ResizeWindow(hwnd, delta := 0) { "Int", ) } + +class EmptyFocusIndicator { + __New() { + } + + __Delete() { + } + + Show(hwnd) { + } + + Hide(hwnd) { + } + + Unmanaged(hwnd) { + } + + SetMonitorList(monitors) { + } + + HideWhenPositioning { + get => false + } + + ShowOnFocusRequest { + get => false + } + + UpdateOnRetile { + get => false + } +} + +class EmptyWorkspaceIndicator { + __New() { + } + + __Delete() { + } + + MonitorChanged(idx) { + } + + MonitorCountChanged(count) { + } + + WorkspaceChanged(ws) { + } + + WorkspaceCountChanged(count) { + } + + LayoutChanged(ws) { + } +} diff --git a/lib/miguru/utils/WhichSpace.ahk b/lib/miguru/utils/WhichSpace.ahk new file mode 100644 index 0000000..7857ec3 --- /dev/null +++ b/lib/miguru/utils/WhichSpace.ahk @@ -0,0 +1,107 @@ +#include ..\..\gdi.ahk + +class WhichSpace { + __New(opts := {}) { + opts := ObjMerge({ + color: "0x080808", + }, opts) + + this._monitorCount := 0 + this._monitorIdx := -1 + this._wsCount := 0 + this._wsIdx := -1 + + this._createIcons() + } + + __Delete() { + this._deleteIcons(this._leftIcons) + this._deleteIcons(this._rightIcons) + this._deleteIcons(this._midIcons) + } + + _deleteIcons(icons) { + for icon in icons { + DllCall( + "DestroyIcon", + "Ptr", icon, + "Int", + ) + } + } + + _createIcons() { + this._leftIcons := [] + this._rightIcons := [] + this._midIcons := [] + + digit := 1 + while digit <= 9 { + loop 3 { + bitmap := GdiBitmap(32, 32) + font := GdiFont("Segoe UI", 24) + bitmap.Canvas.RoundedRectangle(0, 0, 32, 32, 12, GdiPen(0xffe7e4f6), GdiBrush(0xffe7e4f6)) + bitmap.Canvas.Text(digit, 0, 0, bitmap.Width, bitmap.Height, font, GdiPen(0xff000000), GdiBrush(0xff000000)) + + switch A_Index { + case 1: + dest := this._leftIcons + bitmap.Canvas.Arc(0, 32/2, 5, 5, 0, 360, GdiPen(0), GdiBrush(0xffff0000)) + ;; triangle + case 2: + dest := this._rightIcons + bitmap.Canvas.Arc(32-5, 32/2, 5, 5, 0, 360, GdiPen(0), GdiBrush(0xffff0000)) + ;; triangle + case 3: + dest := this._midIcons + bitmap.Canvas.Arc(0, bitmap.Height / 2 - 10 / 2 - 1, 10, 10, 0, 360, GdiPen(0xffff0000), GdiBrush(0xffff0000)) + } + + dest.Push(bitmap.ToIcon()) + } + digit++ + } + } + + MonitorChanged(idx) { + this._monitorIdx := idx + this._update() + } + + MonitorCountChanged(count) { + this._monitorCount := count + this._update() + } + + WorkspaceChanged(ws) { + this._wsIdx := ws.Index + this._update() + } + + WorkspaceCountChanged(count) { + this._wsCount := count + this._update() + } + + LayoutChanged(ws) { + ;; Do nothing + } + + _update() { + ; monitorMax := MonitorGetCount() + if this._monitorCount > 1 && this._monitorIdx == 1 { + icon := this._leftIcons[this._wsIdx] + } else if this._monitorCount > 1 && this._monitorIdx == this._monitorCount { + icon := this._rightIcons[this._wsIdx] + } else { + icon := this._midIcons[this._wsIdx] + } + + copy := DllCall( + "CopyIcon", + "Ptr", icon, + "Ptr", + ) + TraySetIcon("HICON:" copy, , true) + } +} diff --git a/mwm.ahk b/mwm.ahk index 8324297..e32dfe2 100644 --- a/mwm.ahk +++ b/mwm.ahk @@ -46,18 +46,43 @@ layouts := [ SpiralLayout(), ] +MiguruWM.SetupTrayMenu() +icon := A_IconFile + mwm := { __Call: (name, params*) => } ; Ignore requests while mwm isn't ready yet mwm := MiguruWM({ layout: layouts[1], - showPopup: (text, opts) => Popup(text, ObjMerge({ - duration: 500, - showIcon: true, - }, opts)), focusIndicator: HazeOver(), + workspaceIndicator: WSIndicator(), ;; …see https://github.com/imawizard/MiguruWM/wiki/Configuration }) -MiguruWM.SetupTrayMenu() +class WSIndicator extends WhichSpace { + __New(opts := {}) { + super.__New(opts) + this.activeMonitor := 1 + } + MonitorChanged(idx) { + super.MonitorChanged(idx) + this.activeMonitor := idx + } + WorkspaceChanged(ws) { + super.WorkspaceChanged(ws) + this._popup(ws.Layout.DisplayName) + } + LayoutChanged(ws) { + super.LayoutChanged(ws) + this._popup(ws.Layout.DisplayName) + } + _popup(text) { + Popup("") ; Close any prior popup + Popup(text, { + duration: 500, + showIcon: icon, + activeMonitor: this.activeMonitor, + }) + } +} ; Use Alt as modifier but disable it if pressed alone mod1 := "Alt"