Skip to content

Commit 7b7cbd1

Browse files
Feature: Resizable Window (#202)
* feat: resizable window * Fix: Swiftlint complain on control mode --------- Co-authored-by: José Moreno <[email protected]>
1 parent e40b589 commit 7b7cbd1

File tree

7 files changed

+160
-36
lines changed

7 files changed

+160
-36
lines changed

AKPlugin.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import Foundation
1313
private struct AKAppSettingsData: Codable {
1414
var hideTitleBar: Bool?
1515
var floatingWindow: Bool?
16+
var resolution: Int?
17+
var resizableAspectRatioWidth: Int?
18+
var resizableAspectRatioHeight: Int?
1619
}
1720

1821
class AKPlugin: NSObject, Plugin {
@@ -35,6 +38,11 @@ class AKPlugin: NSObject, Plugin {
3538
if self.floatingWindowSetting == true {
3639
window.level = .floating
3740
}
41+
42+
if let aspectRatio = self.aspectRatioSetting {
43+
window.contentAspectRatio = aspectRatio
44+
}
45+
3846
NSWindow.allowsAutomaticWindowTabbing = true
3947
}
4048

@@ -57,6 +65,10 @@ class AKPlugin: NSObject, Plugin {
5765
if self.floatingWindowSetting == true {
5866
win.level = .floating
5967
}
68+
69+
if let aspectRatio = self.aspectRatioSetting {
70+
win.contentAspectRatio = aspectRatio
71+
}
6072
}
6173
}
6274

@@ -278,6 +290,17 @@ class AKPlugin: NSObject, Plugin {
278290
/// Convenience instance property that exposes the cached static preference.
279291
private var hideTitleBarSetting: Bool { Self.akAppSettingsData?.hideTitleBar ?? false }
280292
private var floatingWindowSetting: Bool { Self.akAppSettingsData?.floatingWindow ?? false }
293+
private var aspectRatioSetting: NSSize? {
294+
guard Self.akAppSettingsData?.resolution == 6 else {
295+
return nil
296+
}
297+
let width = Self.akAppSettingsData?.resizableAspectRatioWidth ?? 0
298+
let height = Self.akAppSettingsData?.resizableAspectRatioHeight ?? 0
299+
guard width > 0 && height > 0 else {
300+
return nil
301+
}
302+
return NSSize(width: width, height: height)
303+
}
281304

282305
fileprivate static var akAppSettingsData: AKAppSettingsData? = {
283306
let bundleIdentifier = Bundle.main.bundleIdentifier ?? ""

PlayTools/Controls/Frontend/ControlMode.swift

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,69 +25,117 @@ public class ControlMode: Equatable {
2525
private var keyboardAdapter: KeyboardEventAdapter!
2626
private var mouseAdapter: MouseEventAdapter!
2727
private var controllerAdapter: ControllerEventAdapter!
28+
private var keyWindowObserver: NSObjectProtocol?
2829

2930
public func cursorHidden() -> Bool {
3031
return mouseAdapter?.cursorHidden() ?? false
3132
}
3233

3334
public func initialize() {
34-
let centre = NotificationCenter.default
35-
let main = OperationQueue.main
3635
if PlaySettings.shared.noKMOnInput {
37-
centre.addObserver(forName: UITextField.textDidEndEditingNotification, object: nil, queue: main) { _ in
38-
ModeAutomaton.onUITextInputEndEdit()
39-
Toucher.writeLog(logMessage: "uitextinput end edit")
40-
}
41-
centre.addObserver(forName: UITextField.textDidBeginEditingNotification, object: nil, queue: main) { _ in
42-
ModeAutomaton.onUITextInputBeginEdit()
43-
Toucher.writeLog(logMessage: "uitextinput begin edit")
44-
}
45-
centre.addObserver(forName: UITextView.textDidEndEditingNotification, object: nil, queue: main) { _ in
46-
ModeAutomaton.onUITextInputEndEdit()
47-
Toucher.writeLog(logMessage: "uitextinput end edit")
48-
}
49-
centre.addObserver(forName: UITextView.textDidBeginEditingNotification, object: nil, queue: main) { _ in
50-
ModeAutomaton.onUITextInputBeginEdit()
51-
Toucher.writeLog(logMessage: "uitextinput begin edit")
52-
}
36+
setupTextInputObservers()
5337
set(.arbitraryClick)
5438
} else {
5539
set(.off)
5640
}
5741

42+
setupGameController()
43+
setupKeyboard()
44+
if PlaySettings.shared.enableScrollWheel {
45+
setupScrollWheel()
46+
}
47+
48+
// Mouse polling rate as high as 1000 causes issue to some games
49+
setupMouseMoved(maxPollingRate: 125)
50+
setupMouseButtons()
51+
52+
if PlaySettings.shared.resizableWindow {
53+
initializeResizableWindowSupport()
54+
}
55+
56+
ActionDispatcher.build()
57+
}
58+
59+
private func setupTextInputObservers() {
60+
let centre = NotificationCenter.default
61+
let main = OperationQueue.main
62+
centre.addObserver(forName: UITextField.textDidEndEditingNotification, object: nil, queue: main) { _ in
63+
ModeAutomaton.onUITextInputEndEdit()
64+
Toucher.writeLog(logMessage: "uitextinput end edit")
65+
}
66+
centre.addObserver(forName: UITextField.textDidBeginEditingNotification, object: nil, queue: main) { _ in
67+
ModeAutomaton.onUITextInputBeginEdit()
68+
Toucher.writeLog(logMessage: "uitextinput begin edit")
69+
}
70+
centre.addObserver(forName: UITextView.textDidEndEditingNotification, object: nil, queue: main) { _ in
71+
ModeAutomaton.onUITextInputEndEdit()
72+
Toucher.writeLog(logMessage: "uitextinput end edit")
73+
}
74+
centre.addObserver(forName: UITextView.textDidBeginEditingNotification, object: nil, queue: main) { _ in
75+
ModeAutomaton.onUITextInputBeginEdit()
76+
Toucher.writeLog(logMessage: "uitextinput begin edit")
77+
}
78+
}
79+
80+
private func setupGameController() {
81+
let centre = NotificationCenter.default
82+
let main = OperationQueue.main
5883
centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in
59-
GCController.current?.extendedGamepad?.valueChangedHandler = {profile, element in
84+
GCController.current?.extendedGamepad?.valueChangedHandler = { profile, element in
6085
self.controllerAdapter.handleValueChanged(profile, element)
6186
}
6287
}
88+
}
6389

64-
AKInterface.shared!.setupKeyboard(keyboard: { keycode, pressed, isRepeat, ctrlModified in
65-
self.keyboardAdapter.handleKey(keycode: keycode, pressed: pressed,
66-
isRepeat: isRepeat, ctrlModified: ctrlModified)},
67-
swapMode: ModeAutomaton.onOption)
68-
69-
if PlaySettings.shared.enableScrollWheel {
70-
AKInterface.shared!.setupScrollWheel({deltaX, deltaY in
71-
self.mouseAdapter.handleScrollWheel(deltaX: deltaX, deltaY: deltaY)
72-
})
73-
}
90+
private func setupKeyboard() {
91+
AKInterface.shared!.setupKeyboard(
92+
keyboard: { keycode, pressed, isRepeat, ctrlModified in
93+
self.keyboardAdapter.handleKey(
94+
keycode: keycode,
95+
pressed: pressed,
96+
isRepeat: isRepeat,
97+
ctrlModified: ctrlModified
98+
)
99+
},
100+
swapMode: ModeAutomaton.onOption
101+
)
102+
}
74103

75-
// Mouse polling rate as high as 1000 causes issue to some games
76-
setupMouseMoved(maxPollingRate: 125)
104+
private func setupScrollWheel() {
105+
AKInterface.shared!.setupScrollWheel({ deltaX, deltaY in
106+
self.mouseAdapter.handleScrollWheel(deltaX: deltaX, deltaY: deltaY)
107+
})
108+
}
77109

78-
AKInterface.shared!.setupMouseButton(left: true, right: false, {_, pressed in
110+
private func setupMouseButtons() {
111+
AKInterface.shared!.setupMouseButton(left: true, right: false, { _, pressed in
79112
self.mouseAdapter.handleLeftButton(pressed: pressed)
80113
})
81114

82-
AKInterface.shared!.setupMouseButton(left: false, right: false, {id, pressed in
115+
AKInterface.shared!.setupMouseButton(left: false, right: false, { id, pressed in
83116
self.mouseAdapter.handleOtherButton(id: id, pressed: pressed)
84117
})
85118

86-
AKInterface.shared!.setupMouseButton(left: false, right: true, {id, pressed in
119+
AKInterface.shared!.setupMouseButton(left: false, right: true, { id, pressed in
87120
self.mouseAdapter.handleOtherButton(id: id, pressed: pressed)
88121
})
122+
}
89123

90-
ActionDispatcher.build()
124+
private func initializeResizableWindowSupport() {
125+
// Reactivate keymapping once the key window is initialized
126+
keyWindowObserver = NotificationCenter.default.addObserver(forName: UIWindow.didBecomeKeyNotification,
127+
object: nil, queue: .main) { _ in
128+
ActionDispatcher.build()
129+
if let observer = self.keyWindowObserver {
130+
NotificationCenter.default.removeObserver(observer)
131+
self.keyWindowObserver = nil
132+
}
133+
}
134+
// Reactivate keymapping once the user finishes resizing the window
135+
NotificationCenter.default.addObserver(forName: Notification.Name("NSWindowDidEndLiveResizeNotification"),
136+
object: nil, queue: .main) { _ in
137+
ActionDispatcher.build()
138+
}
91139
}
92140

93141
private func setupMouseMoved(maxPollingRate: Int) {

PlayTools/Controls/Frontend/EventAdapter/Mouse/Instances/TouchscreenMouseEventAdapter.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ public class TouchscreenMouseEventAdapter: MouseEventAdapter {
1818
if rect.width < 1 || rect.height < 1 {
1919
return nil
2020
}
21+
if screen.resizable && !screen.fullscreen {
22+
// Allow user to resize window by dragging edges
23+
let margin = CGFloat(10)
24+
if point.x < margin || point.x > rect.width - margin ||
25+
point.y < margin || point.y > rect.height - margin {
26+
return nil
27+
}
28+
}
2129
let viewRect: CGRect = screen.screenRect
2230
let widthRate = viewRect.width / rect.width
2331
var rate = viewRect.height / rect.height

PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ - (double) get_default_width {
149149

150150
}
151151

152+
- (CGRect) hook_boundsResizable {
153+
return [PlayScreen boundsResizable:[self hook_boundsResizable]];
154+
}
155+
156+
- (BOOL) hook_requiresFullScreen {
157+
return NO;
158+
}
159+
152160
- (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription {
153161
// do nothing
154162
}
@@ -240,7 +248,13 @@ @implementation PTSwizzleLoader
240248
+ (void)load {
241249
// This might need refactor soon
242250
if(@available(iOS 16.3, *)) {
243-
if ([[PlaySettings shared] adaptiveDisplay]) {
251+
if ([[PlaySettings shared] resizableWindow]) {
252+
[objc_getClass("_UIApplicationInfoParser") swizzleInstanceMethod:NSSelectorFromString(@"requiresFullScreen") withMethod:@selector(hook_requiresFullScreen)];
253+
[objc_getClass("UIScreen") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsResizable)];
254+
[objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)];
255+
[objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)];
256+
}
257+
else if ([[PlaySettings shared] adaptiveDisplay]) {
244258
// This is an experimental fix
245259
if ([[PlaySettings shared] inverseScreenValues]) {
246260
// This lines set External Scene settings and other IOS10 Runtime services by swizzling

PlayTools/PlayCover.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class PlayCover: NSObject {
1414
@objc static public func launch() {
1515
quitWhenClose()
1616
AKInterface.initialize()
17+
PlayScreen.shared.initialize()
1718
PlayInput.shared.initialize()
1819
DiscordIPC.shared.initialize()
1920

PlayTools/PlayScreen.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ extension UIScreen {
8585
public class PlayScreen: NSObject {
8686
@objc public static let shared = PlayScreen()
8787

88+
func initialize() {
89+
if resizable {
90+
// Remove default size restrictions
91+
NotificationCenter.default.addObserver(forName: UIWindow.didBecomeKeyNotification, object: nil,
92+
queue: .main) { notification in
93+
if let window = notification.object as? UIWindow,
94+
let windowScene = window.windowScene {
95+
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 0, height: 0)
96+
windowScene.sizeRestrictions?.maximumSize = CGSize(width: .max, height: .max)
97+
}
98+
}
99+
}
100+
}
101+
88102
@objc public static func frame(_ rect: CGRect) -> CGRect {
89103
return rect.toAspectRatioReversed()
90104
}
@@ -113,6 +127,10 @@ public class PlayScreen: NSObject {
113127
return AKInterface.shared!.isFullscreen
114128
}
115129

130+
var resizable: Bool {
131+
return PlaySettings.shared.resizableWindow
132+
}
133+
116134
@objc public var screenRect: CGRect {
117135
return UIScreen.main.bounds
118136
}
@@ -183,6 +201,13 @@ public class PlayScreen: NSObject {
183201
return rect.toAspectRatioDefault()
184202
}
185203

204+
private static weak var cachedWindow: UIWindow?
205+
@objc public static func boundsResizable(_ rect: CGRect) -> CGRect {
206+
if cachedWindow == nil {
207+
cachedWindow = PlayScreen.shared.keyWindow
208+
}
209+
return cachedWindow?.bounds ?? rect
210+
}
186211
}
187212

188213
extension CGFloat {

PlayTools/PlaySettings.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ let settings = PlaySettings.shared
4141

4242
@objc lazy var adaptiveDisplay = settingsData.resolution == 0 ? false : true
4343

44+
@objc lazy var resizableWindow = settingsData.resolution == 6 ? true : false
45+
4446
@objc lazy var deviceModel = settingsData.iosDeviceModel as NSString
4547

4648
@objc lazy var oemID: NSString = {
@@ -120,4 +122,7 @@ struct AppSettingsData: Codable {
120122
var checkMicPermissionSync = false
121123
var limitMotionUpdateFrequency = false
122124
var disableBuiltinMouse = false
125+
var resizableAspectRatioType = 0
126+
var resizableAspectRatioWidth = 0
127+
var resizableAspectRatioHeight = 0
123128
}

0 commit comments

Comments
 (0)