-
-
Notifications
You must be signed in to change notification settings - Fork 464
Add MacDockToggler to hide/show Dock icon at runtime with focus handling #710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add MacDockToggler to hide/show Dock icon at runtime with focus handling #710
Conversation
Hi @amirroid thank you for this PR. I'm not very familiar with the macOS ecosystem, so I have a few questions:
Thanks! |
Hi, One more thing: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a new MacDockToggler
implementation to hide and show the macOS Dock icon at runtime (with focus handling), replaces the old activator binding with a unified FoundationLibrary
interface, wires the toggler into the UI, and updates the app packaging to run as an LSUIElement app.
- Introduce
FoundationLibrary
interface and remove the oldFoundation
file. - Implement
MacDockToggler
(formerly “Hider”) using JNA’sNativeLibrary
. - Hook
PlatformDockToggler.hide()
/show()
into the Compose UI system tray logic. - Add
LSUIElement
key ininfoPlist
to enable Dock-less execution.
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/utils/mac/FoundationLibrary.kt | New unified JNA interface for Objective-C runtime calls |
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/dock/mac/MacDockHider.kt | Implement MacDockToggler object (file/name mismatch) |
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/activator/mac/MacAppActivator.kt | Swap old Foundation for new FoundationLibrary |
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/activator/mac/Foundation.kt | Removed obsolete Foundation interface file |
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/PlatformDockHider.kt | Define PlatformDockToggler but file is named “Hider” |
desktop/app/src/main/kotlin/com/abdownloadmanager/desktop/ui/Ui.kt | Call PlatformDockToggler.hide() /show() via LaunchedEffect |
desktop/app/build.gradle.kts | Add LSUIElement key in infoPlist for Dock-less mode |
Comments suppressed due to low confidence (3)
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/dock/mac/MacDockHider.kt:1
- The filename
MacDockHider.kt
does not match the objectMacDockToggler
defined inside. Consider renaming the file toMacDockToggler.kt
for consistency.
package ir.amirab.util.desktop.dock.mac
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/PlatformDockHider.kt:1
- This file defines
PlatformDockToggler
but is namedPlatformDockHider.kt
. Rename it toPlatformDockToggler.kt
to avoid confusion.
package ir.amirab.util.desktop
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/dock/mac/MacDockHider.kt:30
- [nitpick] The
hideAndKeepFocus
logic involves OS interop and state changes—consider adding a unit or integration test (with mocking) to verify that it calls the correct selectors.
private fun hideAndKeepFocus() {
import com.sun.jna.NativeLibrary | ||
import com.sun.jna.Pointer | ||
import ir.amirab.util.desktop.PlatformDockToggler | ||
|
||
object MacDockToggler : PlatformDockToggler { | ||
private val objc: NativeLibrary = NativeLibrary.getInstance("objc") | ||
|
||
private val getClass = objc.getFunction("objc_getClass") | ||
private val getSel = objc.getFunction("sel_registerName") | ||
private val msgSend = objc.getFunction("objc_msgSend") | ||
|
||
private val nsAppClass: Pointer = getClass.invokePointer(arrayOf("NSApplication")) | ||
private val sharedAppSel: Pointer = getSel.invokePointer(arrayOf("sharedApplication")) | ||
private val setPolicySel: Pointer = getSel.invokePointer(arrayOf("setActivationPolicy:")) | ||
|
||
private val nsRunningAppClass: Pointer = getClass.invokePointer(arrayOf("NSRunningApplication")) | ||
private val currentAppSel: Pointer = getSel.invokePointer(arrayOf("currentApplication")) | ||
private val activateSel: Pointer = getSel.invokePointer(arrayOf("activateWithOptions:")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] You’re mixing two patterns for JNA (NativeLibrary
in MacDockToggler
vs. Native.load
in MacAppActivator
). Consider standardizing on one approach (e.g., always use FoundationLibrary
) to reduce duplicated interop code.
import com.sun.jna.NativeLibrary | |
import com.sun.jna.Pointer | |
import ir.amirab.util.desktop.PlatformDockToggler | |
object MacDockToggler : PlatformDockToggler { | |
private val objc: NativeLibrary = NativeLibrary.getInstance("objc") | |
private val getClass = objc.getFunction("objc_getClass") | |
private val getSel = objc.getFunction("sel_registerName") | |
private val msgSend = objc.getFunction("objc_msgSend") | |
private val nsAppClass: Pointer = getClass.invokePointer(arrayOf("NSApplication")) | |
private val sharedAppSel: Pointer = getSel.invokePointer(arrayOf("sharedApplication")) | |
private val setPolicySel: Pointer = getSel.invokePointer(arrayOf("setActivationPolicy:")) | |
private val nsRunningAppClass: Pointer = getClass.invokePointer(arrayOf("NSRunningApplication")) | |
private val currentAppSel: Pointer = getSel.invokePointer(arrayOf("currentApplication")) | |
private val activateSel: Pointer = getSel.invokePointer(arrayOf("activateWithOptions:")) | |
import com.sun.jna.Native | |
import com.sun.jna.Pointer | |
import com.sun.jna.Library | |
import ir.amirab.util.desktop.PlatformDockToggler | |
object MacDockToggler : PlatformDockToggler { | |
private val foundation: FoundationLibrary = Native.load("objc", FoundationLibrary::class.java) | |
private val nsAppClass: Pointer = foundation.objc_getClass("NSApplication") | |
private val sharedAppSel: Pointer = foundation.sel_registerName("sharedApplication") | |
private val setPolicySel: Pointer = foundation.sel_registerName("setActivationPolicy:") | |
private val nsAppClass: Pointer = getClass.invokePointer(arrayOf("NSApplication")) | |
private val sharedAppSel: Pointer = getSel.invokePointer(arrayOf("sharedApplication")) | |
private val setPolicySel: Pointer = getSel.invokePointer(arrayOf("setActivationPolicy:")) | |
private val nsRunningAppClass: Pointer = foundation.objc_getClass("NSRunningApplication") | |
private val currentAppSel: Pointer = foundation.sel_registerName("currentApplication") | |
private val activateSel: Pointer = foundation.sel_registerName("activateWithOptions:") |
Copilot uses AI. Check for mistakes.
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/activator/mac/MacAppActivator.kt
Outdated
Show resolved
Hide resolved
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/PlatformDockHider.kt
Outdated
Show resolved
Hide resolved
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/utils/mac/FoundationLibrary.kt
Show resolved
Hide resolved
desktop/shared/src/main/kotlin/ir/amirab/util/desktop/utils/mac/FoundationLibrary.kt
Outdated
Show resolved
Hide resolved
Thanks. |
Hi! To completely hide the icon, if it already exists in the Dock, you need to manually right-click it and select "Remove from Dock".
Related to #592