Skip to content
This repository has been archived by the owner on Mar 6, 2024. It is now read-only.

Auto-adjust docked position to match available screen space #39

Merged
merged 7 commits into from Jul 4, 2019

Conversation

ThatsJustCheesy
Copy link
Collaborator

@ThatsJustCheesy ThatsJustCheesy commented Apr 16, 2019

Fixes #35

Implemented by polling for the size of the "visible frame" every fraction of a second when in docked mode. I don't know of a way to get notified when it changes, but polling allows us to automatically adjust the docked position, fixing the issue described.


IssueHunt Summary

Referenced issues

This pull request has been submitted to:


IssueHunt has been backed by the following sponsors. Become a sponsor

@sindresorhus
Copy link
Owner

How about swizzling NSScreen#visibleFrame? We can then just hook when it's set, emit an event, and then forward the original call.

This is what I've used for swizzling in the past:

func swizzle(type: AnyClass, original originalSelector: Selector, swizzled swizzledSelector: Selector) {
	guard
		let originalMethod = class_getInstanceMethod(type, originalSelector),
		let swizzledMethod = class_getInstanceMethod(type, swizzledSelector)
	else {
		return
	}

	// Check if the original method has been swizzelled
	if class_addMethod(
		type,
		originalSelector,
		method_getImplementation(swizzledMethod),
		method_getTypeEncoding(swizzledMethod)
	) {
		class_replaceMethod(
			type,
			swizzledSelector,
			originalMethod,
			method_getTypeEncoding(originalMethod)
		)
	} else {
		method_exchangeImplementations(
			originalMethod,
			swizzledMethod
		)
	}
}

https://nshipster.com/method-swizzling/

@sindresorhus
Copy link
Owner

I would also like to see the visibleFrame listening logic moved into a NSScreen extension so it can be reused if we need it for other things in the future and for readability reasons.

@sindresorhus
Copy link
Owner

@ThatsJustCheesy Bump :)


@Mortennn Any thoughts on this?

@Mortennn
Copy link
Contributor

Mortennn commented Jun 23, 2019

When the size of the Dock changes a notification is fired which we can then listen for.

func applicationDidFinishLaunching(_ notification: Notification) {
	DistributedNotificationCenter.default().addObserver(self, selector: #selector(dockChangedSize), name: Notification.Name(rawValue: "com.apple.dock.prefchanged"), object: nil)
}

@objc
private func dockSettingsChanged() {
	print("Dock settings changed")
}

@sindresorhus
Copy link
Owner

@Mortennn That only fires when the Dock preferences are changes, not when the Dock is auto-shown/hidden, which is what we want.

@ThatsJustCheesy
Copy link
Collaborator Author

Hey, sorry for leaving this undone!

It looks like there's no setVisibleFrame: method we can easily hook onto:

(lldb) p Array(UnsafeBufferPointer(start: class_copyMethodList(NSScreen.self, nil), count: 48)).map { method_getName($0) }
([Selector]) $R2 = 48 values {
  [0] = "isEqual:"
  [1] = "hash"
  [2] = "visibleFrame"
  [3] = "frame"
  [4] = "dealloc"
  [5] = "_displayID"
  [6] = "_UUIDString"
  [7] = "displayLinkWithTarget:selector:"
  [8] = "colorSpace"
  [9] = "backingScaleFactor"
  [10] = "_currentSpace"
  [11] = "imageInRect:"
  [12] = "backingAlignedRect:options:"
  [13] = "convertRectToBacking:"
  [14] = "convertRectFromBacking:"
  [15] = "deviceDescription"
  [16] = "canRepresentDisplayGamut:"
  [17] = "_screenNumber"
  [18] = "_dockOrientation"
  [19] = "_initWithDisplay:sharedInfo:"
  [20] = "_isZeroScreen"
  [21] = "_menuBarHeight"
  [22] = "_layoutFrame"
  [23] = "_dockRect"
  [24] = "depth"
  [25] = "_resolution"
  [26] = "_updateWithDisplay:sharedInfo:"
  [27] = "_snapshot"
  [28] = "maximumExtendedDynamicRangeColorComponentValue"
  [29] = "devicePixelCounts"
  [30] = "_dockHidden"
  [31] = "_safeVisibleFrame"
  [32] = "supportedWindowDepths"
  [33] = "userSpaceScaleFactor"
  [34] = "imageInRect:underWindow:"
  [35] = "_isActiveScreen"
  [36] = "displayLinkWithHandler:"
  [37] = "_bestSettingWithBitsPerPixel:width:height:refreshRate:exactMatch:"
  [38] = "_capture:"
  [39] = "_isCaptured"
  [40] = "_releaseCapture:"
  [41] = "_currentSetting"
  [42] = "_availableSettings"
  [43] = "_bestSettingWithBitsPerPixel:width:height:exactMatch:"
  [44] = "_bestSettingSimilarToSetting:exactMatch:"
  [45] = "_switchToSetting:error:"
  [46] = "addUpdateHandler:"
  [47] = {
    ptr = 0x0000000000000000
  }
}

So I'll move the current logic into an NSScreen extension, if that's okay.

Previously, the touch bar window would obstruct menubar clicks when the menubar was set to auto-hide. Now, the touch bar stays below a hardcoded distance of 22 pixels from the top of the screen.
@sindresorhus
Copy link
Owner

I found a better way. Turns out the NSApplication.didChangeScreenParametersNotification notification is fired when the Dock size and position changes (as long as it's not set to auto-hide). So we can use that instead of polling.

@@ -72,7 +73,7 @@ extension NSWindow {
}
switch yPositioning {
case .top:
y = visibleFrame.maxY - frame.height
y = min(visibleFrame.maxY - frame.height, screen.frame.maxY - 22 - frame.height)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a short comment that explains this logic (Like in the commit message).

And can you move 22 into a variable with a descriptive name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use NSStatusBar.system.thickness to retrieve the status bar height (22).

@ThatsJustCheesy
Copy link
Collaborator Author

Oh, excellent! API discoverability is fun…

And I initially saw NSMenu#menuBarHeight to get the menubar thickness, but our app doesn't own a menubar, so it didn't work. Thanks @Mortennn!

@ThatsJustCheesy
Copy link
Collaborator Author

Actually, looking back, this solution could've worked:

When the size of the Dock changes a notification is fired which we can then listen for.

func applicationDidFinishLaunching(_ notification: Notification) {
	DistributedNotificationCenter.default().addObserver(self, selector: #selector(dockChangedSize), name: Notification.Name(rawValue: "com.apple.dock.prefchanged"), object: nil)
}

@objc
private func dockSettingsChanged() {
	print("Dock settings changed")
}

Since none of our solutions have accounted for auto-show anyhow. But, now that we've found it, it's probably best to rely on the documented notification.

@ThatsJustCheesy ThatsJustCheesy changed the title Auto-adjust docked position every fraction of a second Auto-adjust docked position to match available screen space Jul 3, 2019
@sindresorhus
Copy link
Owner

Can you fix the merge conflict?

@sindresorhus sindresorhus merged commit c184ba5 into sindresorhus:master Jul 4, 2019
@sindresorhus
Copy link
Owner

🙌

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Maintain fixed distance from dock/menu bar when they move or resize
3 participants