Author SHA1 Message Date
Acaki 0a2fdbf0c0 refactor: separate metrics calculation for legacy and queue pacing 2025-08-26 03:18:52 +08:00
Acaki 91f95f13ae refactor: remove framerate limiter to avoid false positives 2025-08-26 03:07:30 +08:00
Acaki ebf46042ba feat: use CAPABILITY_PULL_RENDERER for legacy pacing mode 2025-08-26 03:06:19 +08:00
Acaki 8547cee1b9 revert: legacy frame pacing changes for VideoDecoderRenderer 2025-08-26 01:33:19 +08:00
Acaki 56729469bf fix: Metal kIOGPUCommandBufferCallbackErrorSubmissionsIgnored error 2025-08-26 00:09:18 +08:00
TrueZhuanjia e619d31622 ui tweaks for frame pacing & perfGraph setting stacks 2025-08-25 22:45:12 +08:00
TrueZhuanjia 370cc76dbe dynamic label for frameQueSizeStack 2025-08-25 18:08:49 +08:00
TrueZhuanjia 1ac2f9e923 - perfGraph UI & storyboard changes - iPad
- perfGraph info button tip
2025-08-25 18:05:55 +08:00
TrueZhuanjia 8caf360671 perfGraph UI & storyboard changes - iPhone 2025-08-25 18:05:55 +08:00
TrueZhuanjia 59b1dcfd13 feat: add back legacy pacing 2025-08-25 18:05:53 +08:00
Acaki 34a115706e Revert "fix: sync imgui fps to stream fps to avoid it interferes with displaylink timings"
This reverts commit 5d1bed4d25.
2025-08-25 15:29:30 +08:00
Acaki b6c029df38 feat: bring back CAMetalDisplayLink
This reverts commit 6f381362dd.
2025-08-25 15:29:29 +08:00
Acaki 0582655954 fix: occasional crashes when put in foreground 2025-08-25 02:05:00 +08:00
Acaki c8f61b0b87 feat: pause Metal renderer and graph when app is in background 2025-08-25 00:42:01 +08:00
Acaki a2c4d440db fix: memory leak for Metal renderer when repeatedly connect/disconnect stream or toggling HDR on host 2025-08-24 20:54:51 +08:00
Acaki b64ad64524 fix: error handling of nil decompressionSession 2025-08-24 17:01:25 +08:00
Acaki e9e6f1deb9 fix: skip applying EDR metadata when HDR is disabled in client or host settings 2025-08-24 17:01:23 +08:00
Acaki 6f381362dd Revert "fix: sync imgui fps to stream fps to avoid it interferes with displaylink timings"
This reverts commit 5d1bed4d25.

Revert "fix: another attempt to fix occasional video freeze with metal renderer"

This reverts commit 958b391afa.

Revert "fix: attempt to fix occasional video freeze with metal renderer"

This reverts commit bcb4d0a905.

Revert "feat: use CAMetalDisplayLink for metal renderer to fix refresh rate issues"

This reverts commit 9e20efff4b.

Revert "Revert "MetalViewController: run a no-op DisplayLink at the target framerate so iOS won't switch to a slower rate""

This reverts commit 39b2621e30.
2025-08-22 17:39:25 +08:00
Acaki 5d1bed4d25 fix: sync imgui fps to stream fps to avoid it interferes with displaylink timings 2025-08-21 19:13:05 +08:00
TrueZhuanjia 9b9104b502 new bitrate table 2025-08-21 17:26:36 +08:00
TrueZhuanjia facbc01a9c c-module source 2025-08-21 17:26:33 +08:00
Acaki 3ba0e35032 refactor: remove duplicate video format creation 2025-08-21 05:11:12 +08:00
Acaki 958b391afa fix: another attempt to fix occasional video freeze with metal renderer 2025-08-21 03:08:07 +08:00
Acaki 678c8054d7 Revert "fix: frozen image when switching from metal to AVSB"
This reverts commit 2b052fef1e.
2025-08-21 00:07:40 +08:00
Acaki d90657b05a refactor: enable EDR metadata for Metal and add more error handling 2025-08-20 22:21:25 +08:00
Acaki 9e56547435 Revert "fix: remove waitUntilCompleted call as semaphore is used"
This reverts commit 05f6dd86b1.
2025-08-20 21:34:16 +08:00
Acaki bcb4d0a905 fix: attempt to fix occasional video freeze with metal renderer 2025-08-20 01:50:27 +08:00
Acaki 225b66cb06 Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal 2025-08-19 23:26:25 +08:00
Acaki cd1ff0da97 refactor: optimize graph dimensions 2025-08-19 23:14:48 +08:00
Acaki 1d1e553191 refactor: change render mode back to NSDefaultRunLoopMode 2025-08-19 23:14:47 +08:00
TrueZhuanjia e10691e2b7 fix: direction pad on-screen widget performance & indicator clean-up issue 2025-08-19 21:52:14 +08:00
Acaki 9e20efff4b feat: use CAMetalDisplayLink for metal renderer to fix refresh rate issues
This reverts commit 868288e7
Using CAMetalDisplayLink with CAMetalLayer is required for ProMotion to work consistently and make graphs and touch widgets work smoothly with Metal (especially wasdpad)
With CADisplayLink (no-op or not) the widgets and/or graphs can cause fps drops to the stream
2025-08-19 18:47:41 +08:00
Acaki 9e32c1da3d Revert "fix: metal renderer packed format crash on emulator and lower iOS version"
This reverts commit 34eb2ccd
2025-08-19 04:45:53 +08:00
Acaki 39b2621e30 Revert "MetalViewController: run a no-op DisplayLink at the target framerate so iOS won't switch to a slower rate"
This reverts commit 6010182592.
2025-08-19 04:38:07 +08:00
Acaki 4e44136f51 feat: prompt for restart when changing renderer 2025-08-19 04:04:18 +08:00
Acaki 62bd695138 Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink/ViewControllers/MainFrameViewController.m
#	VoidLink/ViewControllers/ToolboxViewController.swift
2025-08-19 02:21:03 +08:00
Acaki 0b9454896c Revert "feat: add proper CADisplayLink handling for iOS15 where commandBuffer presentDrawable:drawable afterMinimumDuration doesn't seem to work well"
This reverts commit ee67ff56c7.
2025-08-19 00:07:23 +08:00
TrueZhuanjia 51c1c6db33 remove session launch tip 2025-08-18 23:33:01 +08:00
TrueZhuanjia 0c1fc66be8 feat: disconnect & quit current app in tool box 2025-08-18 23:28:19 +08:00
Acaki ee67ff56c7 feat: add proper CADisplayLink handling for iOS15 where commandBuffer presentDrawable:drawable afterMinimumDuration doesn't seem to work well 2025-08-18 19:37:14 +08:00
Acaki ea1a2f3cd0 fix: metal renderer no image on ios15.2 simulator 2025-08-18 17:01:09 +08:00
Acaki 25ca131da1 fix: overlapping graphs for ipthone 13 pro max 2025-08-18 16:57:34 +08:00
Acaki 05f6dd86b1 fix: remove waitUntilCompleted call as semaphore is used 2025-08-18 01:30:59 +08:00
Acaki 2b052fef1e fix: frozen image when switching from metal to AVSB 2025-08-16 17:03:36 +08:00
Acaki d6aa64b816 fix: osc, stats, and video layering 2025-08-16 16:17:07 +08:00
Acaki b3664d0e2d refactor: remove unnecessary changes 2025-08-16 04:09:02 +08:00
Acaki 34eb2ccd31 fix: metal renderer packed format crash on emulator and lower iOS version 2025-08-15 04:32:48 +08:00
Acaki 0d32b8f06f fix: availability warnings 2025-08-15 01:18:03 +08:00
Acaki 73316ab5b8 fix: iphone storyboard and add more translations 2025-08-14 23:35:53 +08:00
Acaki 69ed345191 Merge remote-tracking branch 'refs/remotes/upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink/Base.lproj/iPhone.storyboard
2025-08-14 20:40:13 +08:00
Acaki 78188527a9 ci: build ipa for frame pacing metal branch 2025-08-14 20:35:55 +08:00
Acaki 2d57c3f7c1 feat: add translations for new settings 2025-08-14 20:11:59 +08:00
Acaki 4bfa6d270d refactor: remove old frame pacing setting as it's no longer used 2025-08-14 19:53:56 +08:00
TrueZhuanjia 85e6eb9eb6 deployment target: iOS 12.0 2025-08-14 09:11:03 +08:00
TrueZhuanjia 83593a0ea7 iOS 12 widget layout tool trashcan button fix 2 2025-08-14 02:17:55 +08:00
TrueZhuanjia dd6dbdf1aa localization fix 2025-08-14 02:10:55 +08:00
TrueZhuanjia 6a2c3d7b28 settings stack gyro check 2025-08-14 01:28:23 +08:00
TrueZhuanjia 15161ede06 Merge branch 'iOS12_compatibility' into Integration 2025-08-14 01:04:36 +08:00
TrueZhuanjia ee370ec978 iOS 12 fix: docked navigation bar tint color 2025-08-14 01:04:10 +08:00
TrueZhuanjia e844b5df65 disable external display settings stack for iOS12 2025-08-14 00:57:13 +08:00
TrueZhuanjia 087e38c9d7 change default selected resolution index 2025-08-14 00:32:47 +08:00
TrueZhuanjia 2bf39aeb2e waterMark for iOS12 2025-08-14 00:01:53 +08:00
TrueZhuanjia ccc85772d3 iOS 12 fix: UIAppView playIcon 2025-08-13 23:52:48 +08:00
TrueZhuanjia 9d2f7704c7 iOS12 fix: widget layout tool trashcan button 2025-08-13 23:34:29 +08:00
TrueZhuanjia 6e20c126c3 - iOS compatibility: on-screen widget profile tableView 2025-08-13 23:25:02 +08:00
TrueZhuanjia 9b149a1eb5 iOS12 compatibility: toolbox menu 2025-08-13 20:05:13 +08:00
Andy Grundman 6010182592 MetalViewController: run a no-op DisplayLink at the target framerate so iOS won't switch to a slower rate 2025-08-13 15:16:19 +08:00
Acaki 868288e70f Revert "feat: use CAMetalDisplayLink for metal renderer to fix refresh rate issues"
This reverts commit 628ce0f12e.
2025-08-13 15:15:52 +08:00
TrueZhuanjia d73c0ee319 iOS12 compatibility: on-screen widget layout Tool 2025-08-12 13:26:42 +08:00
TrueZhuanjia 5cf9ea74f4 iOS 12 compatibility: settings menu 2025-08-12 01:26:27 +08:00
Acaki 2ab705efdf docs: add copyright info to metal renderer related code 2025-08-12 00:35:10 +08:00
TrueZhuanjia 1f4865c468 - insert videoRenderView for iOS12 2025-08-11 23:32:00 +08:00
Acaki 8388076b71 fix: graph not showing on stream start when it's enabled with metal renderer 2025-08-11 22:04:19 +08:00
Acaki 7a37d11aea Revert "fix: disappearing graph when showing and hiding settings panel in stream"
This reverts commit 76f765ce5f.
2025-08-11 21:47:19 +08:00
Acaki cdd5802a72 refactor: rename rendering backend setting and add to iphone dashboard 2025-08-11 21:34:23 +08:00
Acaki 951b7bcfb0 refactor: disable pip toggle when metal renderer is used as it's not supported 2025-08-11 20:54:16 +08:00
TrueZhuanjia 4cb060f496 - feat: add several on-screen gamepad button widgets 2025-08-11 19:52:08 +08:00
Acaki c5da9e9bf7 Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal 2025-08-11 15:52:01 +08:00
Acaki 386516ade9 fix: ios12 compatibility 2025-08-11 15:46:08 +08:00
Acaki 211f90307f Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink/mul.lproj/iPad.xcstrings
2025-08-11 15:05:21 +08:00
TrueZhuanjia d6558c3619 - fix broken native touch passthrough after launching session without opening settings menu in advance 2025-08-11 14:06:08 +08:00
TrueZhuanjia bb265a51fd minor chage on UI strings 2025-08-11 11:15:50 +08:00
TrueZhuanJia 4bc8934b1b Merge pull request #103 from Acaki/fix/homebar_handling
Fix homebar handling
2025-08-11 08:33:10 +08:00
TrueZhuanjia 1bc15a1ba3 fix: slide & hold on-screen button storyboard update 2025-08-11 01:15:08 +08:00
Acaki 5026a6fbcf fix: better handling of app resize for metal renderer 2025-08-10 20:59:24 +08:00
Acaki 628ce0f12e feat: use CAMetalDisplayLink for metal renderer to fix refresh rate issues
- update imgui to use stream fps instead of hardcoded 60
- hide renderer setting for iOS < 17 as CAMetalDisplayLink only supports later versions
2025-08-10 20:58:56 +08:00
Acaki 8d05e8e670 feat: disable render backend settings during stream 2025-08-10 18:37:42 +08:00
TrueZhuanjia 2b11f12763 DS4Touch on-screen widget 2025-08-10 16:19:26 +08:00
TrueZhuanjia 3536bf06ba - feat: slide & hold mode for slidable buttons 2025-08-10 13:22:30 +08:00
TrueZhuanjia 88c0e358aa - make all buttons slidable by default 2025-08-10 00:45:40 +08:00
TrueZhuanjia 7b5d9cde2e - feat: vibration available for controller & keyboard direction pad sliding 2025-08-09 23:50:28 +08:00
TrueZhuanjia c5fc72cc2c make double tap dragging easier to trigger on iPhone 2025-08-09 22:14:16 +08:00
TrueZhuanjia ab503875cb - feat: implementation of slidable button 2025-08-09 22:02:03 +08:00
Acaki a9c4417434 Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal 2025-08-08 16:34:34 +08:00
TrueZhuanjia 679c0a2b4c normalize on-screen widget size 2025-08-08 12:57:59 +08:00
Acaki 5c1d2de9e5 Revert "fix: start stream with graph enabled and metal doesn't show the graph"
This reverts commit 69026cbcd6.
2025-08-08 02:03:46 +08:00
Acaki 1442495c9e Revert "fix: temporary fix for fps drop issue when starting stream with graph disabled"
This reverts commit d72930877a.
2025-08-08 01:51:09 +08:00
Acaki 22ce2edfa3 fix: prefersHomeIndicatorAutoHidden conflicting with preferredScreenEdgesDeferringSystemGestures 2025-08-07 16:18:06 +08:00
Acaki 69026cbcd6 fix: start stream with graph enabled and metal doesn't show the graph 2025-08-07 04:25:15 +08:00
Acaki 76f765ce5f fix: disappearing graph when showing and hiding settings panel in stream 2025-08-07 04:25:15 +08:00
Acaki b8ba716f34 feat: add and option to toggle stats and graphs temporaily in toolbox 2025-08-07 04:25:15 +08:00
Acaki 131d115761 fix: lift stream view for metal renderer when OSK is invoked 2025-08-07 04:04:54 +08:00
Acaki 9b9f4f7603 fix: allow clicking through statistics overlay 2025-08-07 04:04:54 +08:00
TrueZhuanjia c32f2d3f50 fix broken osw widget profile after switching app to background 2025-08-07 01:50:49 +08:00
TrueZhuanjia c2d3028668 further fix iPhone settings menu safe area issue 2025-08-06 23:45:53 +08:00
TrueZhuanjia 338523e216 - further fix for wake-on-lan button & logic 2025-08-06 23:04:04 +08:00
TrueZhuanjia 54bf701495 minor change 2025-08-06 22:15:49 +08:00
TrueZhuanJia c1bf40d825 Merge pull request #5 from xzzpig/resolution-fix
optimize resolution update handling logic
2025-08-06 22:00:46 +08:00
TrueZhuanjia 905e3eaf73 settings menu safeArea fix for iPhone 2025-08-06 21:01:34 +08:00
TrueZhuanjia 900a4cca14 ignore readme 2025-08-06 15:36:14 +08:00
TrueZhuanjia a44e9c87d7 - make wakeup button available as long as the host has valid mac address 2025-08-06 10:26:38 +08:00
TrueZhuanJia 7b9bbba3fd Merge pull request #8 from xzzpig/wol
feat: Allow WOL during host detection
2025-08-06 10:06:36 +08:00
TrueZhuanjia fc6591f26f Merge commit '245613162a37b4cbc0f40e79f873def8776ce20f' into Integration 2025-08-05 01:08:23 +08:00
TrueZhuanjia f50d1bd488 - Add online documentation link, including "Read Widget Instruction" button. 2025-08-05 00:52:45 +08:00
xzzpig 0c386ef337 feat: Allow WOL during host detection 2025-08-01 22:37:56 +08:00
TrueZhuanJia 245613162a Update README.md 2025-08-01 20:49:17 +08:00
TrueZhuanJia d2eb674bb5 Update README.md 2025-08-01 20:43:43 +08:00
Acaki d5af136da2 Merge remote-tracking branch 'refs/remotes/upstream/Integration' into feat/frame_pacing_metal 2025-07-31 03:33:51 +08:00
TrueZhuanjia 3e48df9756 remove KVC accessed api 2025-07-29 20:37:38 +08:00
xzzpig fb04850ae8 optimize resolution update handling logic 2025-07-29 20:22:32 +08:00
TrueZhuanjia f62a405979 default osw profile for iPhone 2025-07-29 11:19:44 +08:00
TrueZhuanjia 58180ae3fa session connection ui tweak 2025-07-29 09:22:04 +08:00
TrueZhuanjia e416dd5f78 simplify stage label for session connection 2025-07-29 09:06:31 +08:00
TrueZhuanjia e37c22b259 - remove legacy help button 2025-07-29 08:49:50 +08:00
Acaki 238d17020b Merge remote-tracking branch 'refs/remotes/upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink.xcodeproj/project.pbxproj
#	VoidLink/Input/PureNativeTouchHandler.m
#	VoidLink/Input/StreamView.m
#	VoidLink/UIComputerView.m
2025-07-28 23:23:13 +08:00
TrueZhuanjia 96f11a4a69 host view improvements 2025-07-27 02:13:11 +08:00
TrueZhuanjia 5fcfa84766 try to fix aboutView ok button 2025-07-27 01:12:07 +08:00
TrueZhuanjia 04ecd45f7c osw layouTool post orientation change actions 2025-07-27 00:32:44 +08:00
TrueZhuanjia dd9ad9728a fix: widget layout tool disfunction after display orientation change 2025-07-27 00:08:10 +08:00
TrueZhuanjia 6a3e1d8a88 - revert to blacklist edge detection for native touch 2025-07-26 13:55:37 +08:00
TrueZhuanjia d2e0cc779c fix: abnormal native/absolute touch after orientation change & expanding setting menu 2025-07-26 11:42:46 +08:00
TrueZhuanjia ee4c1defa0 fix: having no touch response under default osw profile. 2025-07-26 10:11:18 +08:00
TrueZhuanjia 49417e7bc9 - aboutView fix 2025-07-26 01:32:12 +08:00
TrueZhuanjia e301f2af16 full portrait support for on-screen widgets & legacy on-screen controls 2025-07-26 01:18:48 +08:00
TrueZhuanjia 0df5135f0b fix: widgets disappears after display orientation change & saving profile in osw tool 2025-07-25 20:18:43 +08:00
TrueZhuanjia ab7ccc7732 fix: profile saving issue caused by launching osw tool via tap gesture 2025-07-25 19:56:46 +08:00
TrueZhuanjia 02c6a12a82 - aboutView and tip url fixes for iOS 13
- final fix: osw profile saving issue
- replace document link for add widget action
2025-07-25 15:48:55 +08:00
TrueZhuanjia ed1efdd102 remove codes of deprecated views 2025-07-25 11:53:58 +08:00
TrueZhuanjia e51701ef00 background info update tweak 2025-07-25 11:51:18 +08:00
TrueZhuanjia 2b0fb3844b load default favorite setting stacks if persisted data is not available 2025-07-25 11:21:51 +08:00
TrueZhuanjia 323613e4db minor tweaks on UI 2025-07-25 10:42:11 +08:00
TrueZhuanjia 80f9118c0d fix osw tool outbounds issue again 2025-07-25 09:27:33 +08:00
TrueZhuanjia 074a31606c revert edge detection for touchHandler 2025-07-25 08:19:50 +08:00
Acaki 3bb78db2da Merge remote-tracking branch 'refs/remotes/upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink/Base.lproj/iPad.storyboard
#	VoidLink/ViewControllers/SettingsViewController.m
2025-07-25 01:50:47 +08:00
Andy Grundman e5fc439da2 Misc: fix bugs that were preventing starting a new stream after disconnecting. Known issue: this leaks a bit of memory. This also removes all OSX-specific code from the Apple example I used, as I have no intention of using this on Mac. 2025-07-25 01:46:49 +08:00
TrueZhuanjia 87ad97c00f bump up to 1.0.0 2025-07-25 01:09:03 +08:00
TrueZhuanjia ce21c398b5 - fix on-screen widget tool selectedWidgetView bug
- update & replace new widget template bin file
2025-07-25 00:36:04 +08:00
TrueZhuanjia ed56d26b1c fix unable to open osw profile table 2025-07-24 22:01:42 +08:00
TrueZhuanjia 08a2312099 toolbox ui string update 2025-07-24 21:11:06 +08:00
TrueZhuanjia 619e936c39 pop aboutView upon first app launch 2025-07-24 20:50:19 +08:00
TrueZhuanjia 75e88ee31e tweak aboutView 2025-07-24 20:32:58 +08:00
TrueZhuanjia 2e7abf0eb2 change default app icon 2025-07-24 19:58:13 +08:00
TrueZhuanjia 6941f006b3 tweak lockIcon size 2025-07-24 19:46:41 +08:00
TrueZhuanjia e0d03baf1c osw reshape fix 2025-07-24 19:37:36 +08:00
TrueZhuanjia 08ef392fca save osw profile when temporarily leaving osw layout tool 2025-07-24 19:25:05 +08:00
TrueZhuanjia b7d7b9a058 gyroModeStack tip 2025-07-24 16:24:40 +08:00
TrueZhuanjia a7fc2a65e0 - emulated controller type tip
- HDR switch fixes
2025-07-24 16:02:30 +08:00
TrueZhuanjia a707b21446 - stack tips: external display 2025-07-24 10:30:38 +08:00
TrueZhuanjia 0e8980da95 fill in stack tips 2025-07-23 23:59:20 +08:00
TrueZhuanjia ff991dd617 - PiP stack tip
- settings menu interaction minor fix
2025-07-23 20:10:42 +08:00
TrueZhuanjia a8c1451a28 yuv444Stack info button 2025-07-23 17:14:08 +08:00
TrueZhuanjia 9198960cc7 - pipStack 2025-07-23 13:52:14 +08:00
TrueZhuanjia b8478bfedc HDR info button 2025-07-23 13:10:16 +08:00
TrueZhuanjia 0f443af355 tweak toolbox view 2025-07-22 23:57:13 +08:00
TrueZhuanjia e63ce4d101 re-enable and update first launch tip 2025-07-22 21:41:38 +08:00
Andy Grundman e57f0cd723 Metal: after experimenting with EDR tone-mapping, it's here but disabled. It's unclear how best to render content from Windows. We get the system calibration peak nits in the Mastering Display Color Volume metadata, but this won't apply to most content. 2025-07-22 17:01:57 +08:00
Acaki 4fb3f7fa97 fix: metal renderer stuck when disconnect and resume stream 2025-07-22 16:55:12 +08:00
TrueZhuanjia 5759c122ed update about page 2025-07-22 00:47:00 +08:00
Acaki 55ac533718 Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal 2025-07-21 22:34:15 +08:00
TrueZhuanjia 282b3d6545 appView tweak 2025-07-21 13:14:12 +08:00
TrueZhuanjia eb41536749 disable background hostCard update while streaming 2025-07-21 13:01:19 +08:00
TrueZhuanjia 0728959ebe iOS13 compatibility: audio menu section icon 2025-07-21 00:41:59 +08:00
TrueZhuanjia b118f80b52 touchHandler debug 2025-07-20 23:11:02 +08:00
TrueZhuanjia 25073803ba iOS13 compatibility: apply background for each widget in widget panel 2025-07-20 21:54:06 +08:00
TrueZhuanjia 1046cfe9a0 - iOS 13 compatibility: try to fix iOS transparent osw widgetPanel background 2025-07-20 19:20:10 +08:00
Acaki 2250cba35c feat: apply normalization fix to new shader 2025-07-20 19:06:41 +08:00
Andy Grundman 1e24731b60 MetalVideoRenderer: EDR support, enabling better HDR output and tone-mapping regardless of screen brightness. This commit also includes some fixes around stream shutdown, but restarting the app is still needed if the stream stops. 2025-07-20 19:06:21 +08:00
Andy Grundman 034ee52eec ImGui: avoid garbage at the beginning of graphs 2025-07-20 17:08:53 +08:00
Acaki 13a1a4d97b Merge remote-tracking branch 'refs/remotes/upstream/Integration' into feat/frame_pacing_metal 2025-07-20 17:07:27 +08:00
TrueZhuanjia f22d05a34e - prevent onscreen widget moved out of superView bounds 2025-07-20 16:22:33 +08:00
TrueZhuanjia 0d3149a696 ios 13 compatibility fix: disable auto & controller gyro mode 2025-07-20 14:11:37 +08:00
TrueZhuanjia 11e5028020 - save osw profile when read instruction button tapped
- try to fix iOS 13 widgetPanel background
2025-07-20 12:51:45 +08:00
TrueZhuanjia 2b96bb1c77 appView background update tweak 2025-07-20 11:37:48 +08:00
TrueZhuanjia f843acf12f - hostView & host card fixes: add unpaired offline host to collection 2025-07-20 10:43:27 +08:00
Acaki 05d3567489 fix: missing frametime graph due to previous incorrect merge 2025-07-20 00:56:43 +08:00
TrueZhuanjia 25c4ca01de hostView test 2025-07-19 23:00:20 +08:00
TrueZhuanjia ac66ffe302 iOS 13 compatibility - hostIcon & lockIcon 2025-07-19 22:12:46 +08:00
Acaki f0b4e2e2ad fix: green tinted image for metal 10bit yuv444 2025-07-19 20:05:17 +08:00
Acaki d72930877a fix: temporary fix for fps drop issue when starting stream with graph disabled 2025-07-19 20:05:17 +08:00
Acaki 4d45c538cc Revert "feat: pip for metal renderer"
This reverts commit fa7710ce44.
2025-07-19 20:05:03 +08:00
Acaki f368076aaf Revert "refactor: use separate piplayer for AVSB pip function"
This reverts commit bd9e2e3215.
2025-07-19 17:43:46 +08:00
Acaki 61c5b958f2 feat: enable full range by default 2025-07-19 17:43:46 +08:00
TrueZhuanjia 334788c26f - fix appview background update issue
- relocate chevron view for ipad osw tool
- improve edge slide detection for native touchHandler
2025-07-19 14:47:04 +08:00
TrueZhuanjia 12ad168e13 about page content udpate 2025-07-17 23:41:11 +08:00
TrueZhuanjia 48fbec02e4 Merge branch 'aboutViewTest' into Integration 2025-07-17 22:49:01 +08:00
TrueZhuanjia ba453f9405 aboutView draft 2025-07-17 22:46:56 +08:00
TrueZhuanjia 00491d3bce tweak hostSpinner 2025-07-17 22:08:13 +08:00
TrueZhuanjia 98f73928fa zh-hant, zh-hk localization 2025-07-17 16:34:49 +08:00
TrueZhuanjia 3dccec3739 appView update 2025-07-17 02:05:05 +08:00
TrueZhuanjia 5e40c9f9ad tweak edge slide & touchHandler 2025-07-16 13:55:19 +08:00
Acaki 5bcc452c9f fix: metal renderer black screen 2025-07-16 02:40:51 +08:00
Acaki 5bf218d930 Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink/mul.lproj/iPad.xcstrings
2025-07-16 02:17:31 +08:00
Acaki 5df330f608 fix: stream freeze when turning screen off and on with AVSB 2025-07-16 02:15:28 +08:00
Acaki bd9e2e3215 refactor: use separate piplayer for AVSB pip function 2025-07-16 01:17:28 +08:00
TrueZhuanjia d1ecc894b3 swapAbxySwitch 2025-07-15 23:13:55 +08:00
Acaki 5a2d5af887 MetalVideoRenderer: don't recreate pipeline state every frame 2025-07-15 22:57:06 +08:00
Acaki 45895fa8bf Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal 2025-07-15 22:38:37 +08:00
Acaki fa7710ce44 feat: pip for metal renderer 2025-07-15 22:37:58 +08:00
TrueZhuanjia cd7d560e63 handle old profile sensitivityFactor 2025-07-15 21:44:47 +08:00
TrueZhuanjia fcf0ec98e5 tweak toolbar button order for osw tool 2025-07-15 21:34:33 +08:00
Acaki 7691d2c0eb Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink/Base.lproj/iPad.storyboard
#	VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents
2025-07-15 18:10:07 +08:00
Acaki 686418997b fix: metal renderer teardown and resume 2025-07-15 18:08:54 +08:00
TrueZhuanjia f4b6fd709d fix resolution loading & update issue 2025-07-15 03:08:22 +08:00
Acaki 8126173128 fix: overlay level hanling 2025-07-15 02:30:08 +08:00
Andy Grundman 5e4ba088be Clean up render thread, plot method calls 2025-07-15 02:06:13 +08:00
Acaki 6682e64519 feat: allow toggling graphs in stream with shortcut 2025-07-15 02:06:13 +08:00
TrueZhuanjia 2140cc7820 emulated controller type selection 2025-07-15 00:12:52 +08:00
Acaki 7bafa39960 Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal 2025-07-14 01:33:21 +08:00
Acaki 37379220b4 fix: enableGraphsSwitch doesn't persist 2025-07-14 01:33:01 +08:00
TrueZhuanjia db6906d7a4 hide pipstack in streaming 2025-07-14 00:46:30 +08:00
TrueZhuanjia b262ae47e1 slide to open settings menu distance default value to 0 2025-07-14 00:19:01 +08:00
TrueZhuanjia 797b1a0b71 larger app card for iphone 2025-07-14 00:12:12 +08:00
TrueZhuanjia 77955f6bd6 host card appearance tweak 2025-07-13 23:20:22 +08:00
TrueZhuanjia 17a9fa39f5 - storyboard quick loading
- temporary default app view card image
- select new host button icon
2025-07-13 22:49:16 +08:00
Acaki f6032969c3 fix(AVSB): black screen when turning screen off and on 2025-07-13 22:07:40 +08:00
TrueZhuanjia 686696a6d9 disable pip for iOS lower than iOS15 2025-07-13 18:51:25 +08:00
TrueZhuanjia c629acfa4b fix session disconnection animation 2025-07-13 18:39:19 +08:00
Acaki 02ede90d77 refactor: remove comments added by xcode 2025-07-13 16:25:29 +08:00
Acaki 84cb37f6f3 fix: touch input and settings 2025-07-13 04:22:55 +08:00
Acaki 46526603cc Merge remote-tracking branch 'upstream/Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink/Base.lproj/iPad.storyboard
#	VoidLink/Database/DataManager.h
#	VoidLink/Database/DataManager.m
#	VoidLink/Database/TemporarySettings.m
#	VoidLink/ViewControllers/MainFrameViewController.m
#	VoidLink/ViewControllers/SettingsViewController.h
#	VoidLink/ViewControllers/SettingsViewController.m
#	VoidLink/ViewControllers/StreamFrameViewController.m
2025-07-13 01:01:51 +08:00
Acaki 80c6d488cf fix: attempt to fix background and resume issues with pip and new renderer changes 2025-07-13 00:52:08 +08:00
TrueZhuanjia 6ea23d94cb minor fixes 2025-07-13 00:09:42 +08:00
Acaki 29c8382825 fix: conflicts 2025-07-12 22:05:31 +08:00
TrueZhuanjia b83811cc62 settings item for background session timer 2025-07-12 22:01:10 +08:00
Acaki 47a590e92a Merge branch 'Integration' into feat/frame_pacing_metal
# Conflicts:
#	VoidLink.xcodeproj/project.pbxproj
#	VoidLink/Base.lproj/iPad.storyboard
#	VoidLink/Database/DataManager.m
#	VoidLink/Database/TemporarySettings.m
#	VoidLink/SceneDelegate.h
#	VoidLink/Stream/StreamManager.m
#	VoidLink/Stream/VideoDecoderRenderer.h
#	VoidLink/Stream/VideoDecoderRenderer.m
#	VoidLink/ViewControllers/MainFrameViewController.m
#	VoidLink/ViewControllers/SettingsViewController.m
2025-07-12 20:12:20 +08:00
TrueZhuanjia e2ab307da7 hostDiscovery improvement 2025-07-12 17:51:27 +08:00
TrueZhuanjia a83274ab28 ipad storyboard fix 2025-07-12 16:51:45 +08:00
TrueZhuanjia da8a210fcc 5 second host update period 2025-07-12 16:44:27 +08:00
TrueZhuanjia 0f8ba42975 Merge branch 'piptest' into Integration
- PiP & its toolbox special entry
- host update timer

# Conflicts:
#	VoidLink/ViewControllers/MainFrameViewController.m
#	VoidLink/ViewControllers/StreamFrameViewController.m
2025-07-12 16:41:47 +08:00
TrueZhuanjia 35a2c1111d pip tool box special entry 2025-07-12 16:30:37 +08:00
TrueZhuanjia d263482f7e Merge branch 'PiP2' into PiP1 2025-07-12 16:04:16 +08:00
TrueZhuanjia 19beac795b toolbox pip entry 2025-07-12 15:35:49 +08:00
Acaki e61e96b352 fix: background stream issues 2025-07-12 15:33:14 +08:00
Acaki 4b8691b943 fix: missing pip outlet for iphone 2025-07-12 14:45:51 +08:00
Acaki 487f987c25 Revert "fix: pip video freeze"
This reverts commit 0473233775.
2025-07-12 11:41:57 +08:00
Acaki a69d7eaede build: add new files to pbxproj 2025-07-12 11:25:11 +08:00
Acaki a87969d5b8 refactor: disable verbose logging and remove gesture handlers 2025-07-12 04:42:50 +08:00
Acaki 8f25081bbb fix: cleanup fps and resolution settings 2025-07-12 03:25:17 +08:00
Acaki 6a36e73463 fix: new settings 2025-07-12 02:47:27 +08:00
Acaki d69688678d fix: build 2025-07-12 02:00:33 +08:00
TrueZhuanjia 56c589f042 foreground host update timer 2025-07-11 22:08:14 +08:00
Travis Thieman df2b67ac99 Fix thread sync
Reformat Metal files

Rewrite Metal render loop to not use DisplayLink so we can use the amazing afterMinimumDuration API call

Improve frame drop algorithm

Misc fixes to HDR <-> SDR switching, deprecation warning cleanup

Fix AVSB mode

Fix renderingBackend selected value

Add Rendering Backend setting to iPhone

Let users choose which renderer to use. Refactor metrics into a singleton

Fix tvOS build

Port moonlight-qt's Metal renderer

Bump imgui to latest

Misc tweaks and refactoring

Switch iOS to use Opus float32 audio, in line with other clients.  It was already being sent from the server as float32, so this is simply decoding it with less loss of dynamic range.

Give FrameQueue a faster data structure and full test coverage. Remove pts frame pacing, it's not a fit for iOS.

Don't allow very transparent opacity for middle stats overlay box

Change wording to select button instead of remote button

Add tvOS gesture for double-clicking remote button to show/hide stats

Remove kVTDecodeFrame_1xRealTimePlayback, this was found to seriously impact decode times

Move decode time stat out of async block

Enabe FQ logging

Misc cleanup/fixes

iPad: 26 will require UIRequiresFullScreen to be false, and for all orientations to be supported

Updated iPhone and iPad settings panels

Add setting for graph opacity. Enable stats and graphs to be toggled via two finger swipe

Fix wait time in displaylink, fix settings spacing, stats fixes

Settings for frame queue size, graphs. Misc refactoring and cleanup

Bump moonlight-common-c

Improve logging readability

Fix host frametime and frame queue stats to be recorded during input not output. Show min/max/avg decode time

Add bytes per frame graph

Started on settings changes, both modes working very well

Misc refactoring, move graphs around, both pacing functions working well

Set frameDropTarget to 2 always

Use raw RTP timestamps without converting to floating point for even more accurate playback

Standard frame pacing: remove +3 logic

Switch to NSRunLoopCommonModes for displayLink

Add frame pacing algorithm based on moonlight-qt's Pacer class as a simpler baseline

Switch to os_unfair_lock, misc tweaks

Large refactor that now uses VTDecompressionSession based on PR535 from felipejfc. This now allows more control over queue size and allows dropping arbitrary frames, but introduces a lot of new problems

bump moonlight-common-c to fix 32-bit rollover issue

LiWait timing graph

Add displaylink interval graph

Graphs checkpoint

ImGui graph working checkpoint

ImGui working checkpoint

Add pending frames to stats, add a define for verbose logging

DisplayLink WIP

Add imgui & implot modules

Frame pacing: alternate method based on thieman's pullFrames thread

Frame pacing: simplify displayLink, support passing targetTimestamp down to the sample buffer

Point to my common-c with some timing changes.

Add stats gestures: 2-finger swipe down to show stats, and 2-finger swipe up to hide

Limit refresh rate to 60 when in low power mode

Display screen refresh rate in stats overlay, remove decode time and explain why it can't be measured

Adjust the refresh rate of the display to match the stream framerate and improve battery life

Don't use presentationTimeMs when creating CMSampleTimingInfo

Fix overlayView build for iOS

Refactor CADisplayLink callback for improved frame delivery. Support a user-configurable buffered frame count.

[opt] Use UILabel for stats overlay
2025-07-11 21:02:13 +08:00
Acaki 0473233775 fix: pip video freeze 2025-07-11 19:40:07 +08:00
Acaki 57b12d7a54 Revert "fix: flip UIRequiresFullScreen to true for pip to work properly"
This reverts commit 903312a9f16996a00bf8381dbbd62b8c8c990fd9.
2025-07-11 19:40:07 +08:00
Acaki 6c6d7b73c1 refactor: always create separate render view for stream as we no longer have disabled option 2025-07-11 19:40:07 +08:00
Acaki 10dcdc31d8 refactor: let os handle pip invocation 2025-07-11 19:40:07 +08:00
Acaki 7c725a4d80 fix: flip UIRequiresFullScreen to true for pip to work properly 2025-07-11 19:40:07 +08:00
Acaki 4640ec86ed Revert "fix: pip works only for first invocation"
This reverts commit 3df8f73c25e3ec81c4c90bedd01de7cea7bdda3f.
2025-07-11 19:40:07 +08:00
Acaki 03cabd0506 fix: pip works only for first invocation 2025-07-11 19:40:07 +08:00
Acaki 879c2f368c feat: update storyboard and translations for pip 2025-07-11 19:40:07 +08:00
Acaki fc73e7a3b2 build: dep update 2025-07-11 19:40:06 +08:00
Acaki b9bd1c8c44 fix: SceneDelegate files wrong folder 2025-07-11 03:03:19 +08:00
Acaki b5ce7a8ff7 refactor: adopt UIScene lifecycle
feat: add initial support for pip

feat: add setting toggle for pip

fix: let os handle invokation of pip which is more reliable

fix: UIScene compatibility with airplay

fix: black screen on external display when on app view and streaming

fix: incorrect window size when launched directly on external display

fix: wrong resolution shown in settings when in stream session

Enable gamemode flag for iOS18+

Explicitly enters pip mode when in background
2025-07-11 01:34:53 +08:00
TrueZhuanjia 51e915b598 disable unlockorientationSelector when it's iPad + requiresFulscreen disabled 2025-07-10 22:29:28 +08:00
TrueZhuanjia 0addca3876 Merge branch 'gyroImprovements' into Integration
- disable gyro mode selector when it's unable to perform in-stream hot switch
- make osc enabled when gyroMode is not off
- make it unable to set oscEnabled to false in streaming, to ensure gyro hot switch keeps working.
2025-07-10 22:19:56 +08:00
TrueZhuanjia 09349ebdef disable gyro mode selector when it's unable to perform in-stream hot switch 2025-07-10 22:10:48 +08:00
TrueZhuanjia e93d031f86 - make oscEnabled when gyroMode is not off
- make it unable to hot switch oscEnabled to false in streaming
2025-07-10 12:52:51 +08:00
Acaki c491132c09 fix: need to touch any buttons on physical controller for it to show up on host 2025-07-09 20:28:13 +08:00
Acaki 0fef5a389d fix: auto gyro mode not working for controllers without gyro 2025-07-09 17:39:55 +08:00
Acaki 0047bfe51f fix: built-in gyro not working when resuming stream 2025-07-09 14:17:29 +08:00
Acaki 994a60c8a4 fix: missing right brace and wrong device type for osc gyro 2025-07-08 22:40:22 +08:00
TrueZhuanjia 50d53cd826 tweak hostSpinner & lockIcon location for hostcard view 2025-07-08 22:36:18 +08:00
Acaki 1d6906e5e7 fix: virtual controller still appears when osc is disabled 2025-07-08 21:12:19 +08:00
Acaki 2c8f2ee408 feat: hide home indicator without needing a controller connected 2025-07-08 21:12:19 +08:00
TrueZhuanjia 4810451e6a tweak storyboard ui strings for zh-hant localization 2025-07-08 20:45:42 +08:00
TrueZhuanJia 60801e3553 Merge pull request #2 from Acaki/fix/translations
Update translations for zhhant and hk
2025-07-08 20:25:21 +08:00
Acaki 9bdd92aeb3 feat: update translations for zhhant and hk 2025-07-08 16:52:57 +08:00
TrueZhuanjia 835c9c1166 surround sound UI, localization tweak & fixes 2025-07-08 14:59:15 +08:00
TrueZhuanJia 9da143ed81 Merge pull request #1 from Acaki/feat/surround_sound
Add config to set audio channels
2025-07-08 11:59:58 +08:00
TrueZhuanjia f7a032a918 fix touchMode - osw - tapGestureForSoftkeyboard settings menu logic 2025-07-08 11:35:28 +08:00
TrueZhuanjia eacdda384a does not force return to host view after session connection 2025-07-07 22:56:44 +08:00
TrueZhuanjia 1f13fd691b fix unable to reorder setting items in favorite menu by long pressing 2025-07-07 22:14:50 +08:00
TrueZhuanjia 73de0877a1 switch to built-in gyro only when external controllers don't have any 2025-07-07 21:36:07 +08:00
Acaki 616c7673a4 Update SDL2 to newest version to fix 7.1 playback
This fixes 7.1 playback. Before this commit, SDL used 7.1 front audio configuration with Center Left and Center Right speakers, but without Rear speakers.

See https://github.com/libsdl-org/SDL/issues/11709 for more details.
2025-07-07 20:14:09 +08:00
Acaki 489e60d2b1 feat: add setting for audio configuration 2025-07-07 20:13:39 +08:00
TrueZhuanjia af2a2728f8 fix legacy osc button drag to delete issue 2025-07-07 11:50:41 +08:00
TrueZhuanjia 91f012ee9f disable user interaction when settings menu expanded in-streaming 2025-07-07 11:32:10 +08:00
TrueZhuanjia c4bc77db05 disable run-time theme switch 2025-07-07 09:46:56 +08:00
TrueZhuanjia 6579ea681e allows 30 min background streaming 2025-07-07 09:31:17 +08:00
TrueZhuanjia 07b6aa099b remove useless code 2025-07-06 23:38:00 +08:00
TrueZhuanjia e10a296305 iOS18 gameMode support 2025-07-06 23:28:56 +08:00
TrueZhuanjia c42fe94b46 reset controller gyro input when switched away 2025-07-06 23:01:35 +08:00
TrueZhuanjia 1d0743cda6 remove tmp annotations 2025-07-06 22:43:44 +08:00
TrueZhuanjia 0f6f105ec1 implementation of auto gyro mode 2025-07-06 22:36:18 +08:00
TrueZhuanjia 578ad81b8a switch between built-in & controller gyros 2025-07-06 21:12:57 +08:00
TrueZhuanjia 3cbdba78d6 fix osw stuck caused by gyro event 2025-07-04 23:38:11 +08:00
TrueZhuanjia 1de7592e86 remove useless codes in settingsViewController 2025-07-04 21:01:28 +08:00
TrueZhuanjia 6a9891adee c-module ready 2025-07-04 21:00:04 +08:00
TrueZhuanjia 29700e6f18 fix crash caused by long pressing blank area in favorite mode 2025-07-03 13:11:50 +08:00
TrueZhuanjia 7426e3e78c exchange trash & undo button for oswTool 2025-07-02 22:14:43 +08:00
TrueZhuanjia 724a11ae4f minor tweak 2025-07-02 21:57:43 +08:00
TrueZhuanjia 03d08a2c65 toolbox local soft keyboard toggle localization 2025-07-02 19:49:23 +08:00
TrueZhuanjia f24e68d0d9 change statsoverlay default setting 2025-07-02 17:09:33 +08:00
TrueZhuanjia a07e94ca74 toolbox local soft keyboard toggle 2025-07-02 17:02:20 +08:00
TrueZhuanjia 14a384436c gcDeviceBatteryState compatibility 2025-07-02 11:17:39 +08:00
TrueZhuanjia 6be5aa6507 current widget name displayed on current profile label 2025-07-02 10:55:35 +08:00
TrueZhuanjia 97d17f505b trackball deceleration rate implementation 2025-07-02 10:15:15 +08:00
TrueZhuanjia c2ed8c5517 minor tweak 2025-07-02 01:51:22 +08:00
TrueZhuanjia ac299287e4 tap to stall trackball scrolling 2025-07-02 01:21:47 +08:00
TrueZhuanjia ec5aec2601 trackball implementation 2025-07-02 00:52:41 +08:00
TrueZhuanjia 59033ff8d7 update 2025-07-02 00:35:39 +08:00
TrueZhuanjia 88edc7f263 smaller widgetPanel spacing for ipad 2025-07-02 00:27:07 +08:00
TrueZhuanjia 19882a9373 osw mousePad improvement: micro move fix 2025-07-02 00:07:47 +08:00
TrueZhuanjia 620385501f relativeTouch improvements:
- fix micro move issue
- fix clumsy move in portrait mode
2025-07-01 23:49:02 +08:00
TrueZhuanjia d0eb864814 move gyro settings to experimental stack 2025-07-01 23:05:12 +08:00
TrueZhuanjia 1920735efa gyro storyboards & localization 2025-07-01 23:00:57 +08:00
TrueZhuanjia 7370fa5c94 gyroSensitivity implementation 2025-07-01 22:25:54 +08:00
TrueZhuanjia a875a65a7f in-stream gyro switch stage 1 2025-07-01 13:00:18 +08:00
TrueZhuanjia 88414cd3db initial build for motion mode 2025-06-29 19:59:56 +08:00
TrueZhuanjia 00aff53e35 iboutlet motionselector ipad 2025-06-29 19:45:46 +08:00
TrueZhuanjia 62561ac423 update 2025-06-29 16:23:41 +08:00
TrueZhuanjia d26cef7ef4 rounded icon 2025-06-29 16:35:06 +08:00
TrueZhuanjia 527fb16428 white bg icon 2025-06-29 11:49:39 +08:00
TrueZhuanjia 0f7ba20e65 update 2025-06-29 11:40:54 +08:00
TrueZhuanjia 94e9bc35b1 icon1 2025-06-29 10:39:09 +08:00
TrueZhuanjia b3fd9e51d7 debug bundle id for tf version 2025-06-28 13:35:10 +08:00
TrueZhuanjia bef2456bea app icon test 2025-06-28 13:17:09 +08:00
TrueZhuanjia 25f65c3596 testflight bundle id 2025-06-28 00:42:32 +08:00
TrueZhuanjia 8260624a41 ignore .github 2025-06-27 14:47:01 +08:00
TrueZhuanjia 7ab1c43588 remove workflow 2025-06-27 01:54:45 +08:00
TrueZhuanjia 7d0477204b new bundle id 2025-06-26 22:59:46 +08:00
TrueZhuanjia 8f1dda44b9 further renaming 2025-06-26 22:37:00 +08:00
TrueZhuanjia ee03f6b866 further renaming 2025-06-26 22:15:18 +08:00
TrueZhuanjia 9e41537554 rebrand project to VoidLink 2025-06-26 21:23:11 +08:00
TrueZhuanjia 8baa2e9067 rename dataModel to VoidLink 2025-06-26 20:59:49 +08:00
TrueZhuanjia 556db88ff7 minor tweak 2025-06-26 02:10:32 +08:00
TrueZhuanjia 9063331d38 - button down action for MOUSEPAD 2025-06-26 01:17:31 +08:00
TrueZhuanjia 594906c121 fix host card resizing issue 2025-06-25 22:34:43 +08:00
TrueZhuanjia 357a907838 stage manager & display orientation lock for iPhone 2025-06-25 20:52:07 +08:00
TrueZhuanjia 6bc142a07b - notification lifecycle fixes - replace some notifications with delegates
- fix osw profile switch issue
2025-06-22 21:25:29 +08:00
TrueZhuanjia cd6e08956c - widget tool improvements
- tool box constraint fixes
2025-06-22 15:57:29 +08:00
TrueZhuanjia 9937b67509 iphone localization fix 2025-06-22 11:48:05 +08:00
TrueZhuanjia c478d641c6 add non-favorite setting stacks to favorites only 2025-06-22 11:21:38 +08:00
TrueZhuanjia 1702f37d3e - localization fixes
- auto hide all hidden menusections
2025-06-22 11:11:52 +08:00
TrueZhuanjia e1920ddb01 audio menuSection 2025-06-22 10:40:31 +08:00
TrueZhuanjia 9b5b93dc31 iphone menu spacing 2025-06-22 10:23:27 +08:00
TrueZhuanjia cd4c5bbc56 - legacy osc vibration style
- vibration style ios13 compatibility
- low ios verision UI fixes
2025-06-22 10:11:11 +08:00
TrueZhuanjia 47bae92dd5 downsized widget panel for iphone 2025-06-20 00:35:02 +08:00
TrueZhuanjia a5bbca55a2 widgetPanelStack coding fix 2025-06-18 20:47:31 +08:00
TrueZhuanjia a4f5c4f211 - create/reload/update onscreenWidget layer index fix
- iphone OSW layoutTool bar storyboard
2025-06-17 22:59:53 +08:00
TrueZhuanjia 9c61e5725f host view & menu size tweak 2025-06-17 21:07:53 +08:00
TrueZhuanjia 8c4f2e46f7 tweak host card 2025-06-17 20:27:00 +08:00
TrueZhuanjia 76253a9583 hostcard localization 2025-06-17 10:14:02 +08:00
TrueZhuanjia 5653215f68 new UI CN localization 2025-06-16 16:54:39 +08:00
TrueZhuanjia 1b9e9575f1 stage manager 2025-06-16 09:09:44 +08:00
TrueZhuangjia dfde770274 minor change 2025-06-15 23:26:22 +08:00
TrueZhuangjia 1737eae39b old iOS compatibility fixes:
- strong settingButton reference
- hidden stackView heightContraint
- navBarAppearance
2025-06-15 23:01:22 +08:00
TrueZhuanjia fb32ef3629 minor tweak 2025-06-14 21:37:28 +08:00
TrueZhuanjia 5734a0a0b7 highlighted background for long press setting stack 2025-06-14 21:34:31 +08:00
TrueZhuanjia 758a888a53 favorite section offset 2025-06-14 19:48:33 +08:00
TrueZhuanjia ed0045afec Other section & experimental section 2025-06-14 19:30:44 +08:00
TrueZhuanjia de5fb7a19b - Dynamic Label & its hiding mechanism
-
2025-06-14 18:09:31 +08:00
TrueZhuanjia e2bebbcc61 peripheral section 2025-06-14 10:52:27 +08:00
TrueZhuanjia 24fd69e7dc softkeyboard & layout osw gesture interaction 2025-06-14 01:12:16 +08:00
TrueZhuanjia 16b1f9164f new touch mode config 2025-06-13 18:12:08 +08:00
TrueZhuanjia 51a62ce1d8 settings menu resize 2025-06-13 12:39:16 +08:00
TrueZhuanjia 23b68b2d15 videoSection 2025-06-12 17:34:53 +08:00
TrueZhuanjia 7ee449c0e4 yuv444switch 2025-06-12 17:22:28 +08:00
TrueZhuanjia 2891beef5f storyBoard fix 2025-06-12 17:09:17 +08:00
TrueZhuanjia da47a6ae46 hdrSwitch 2025-06-12 13:38:54 +08:00
TrueZhuanjia 96145baa7a fix favorite settings relocation issue 2025-06-12 00:19:08 +08:00
TrueZhuanjia ab31ab393a video section - resolution new design & dynamic label for settings stacks 2025-06-11 23:41:37 +08:00
TrueZhuanjia 38ee17a852 better capturedStack locating 2025-06-11 19:11:34 +08:00
TrueZhuanjia 78be18ed89 visibility improvements 2025-06-10 23:42:23 +08:00
TrueZhuanjia 5fd8a7c587 on-screen widget tool icon compatibility 2025-06-10 21:05:34 +08:00
TrueZhuanjia 79a075e338 menuSection icon image compatibility 2025-06-10 20:24:35 +08:00
TrueZhuanjia 5c3a6de2b7 autoScroll improvement for settings menu 2025-06-10 11:56:23 +08:00
TrueZhuanjia 5a77571d6f otherSection 2025-06-10 00:14:08 +08:00
TrueZhuanjia 1e6a380f99 setting menuSections initial layout 2025-06-09 23:43:36 +08:00
TrueZhuanjia 8e00552c61 - new on-screen widget tool UI with movable widget panel
with iOS12 compatibility
2025-06-09 20:28:41 +08:00
TrueZhuanjia fabd01ca9e restore osc profile decode for ios12 2025-06-07 22:30:08 +08:00
TrueZhuanjia ab2efcf05a coding correction & old versoin better switch test 2025-06-07 21:50:28 +08:00
TrueZhuanjia dfaa4005bb - newUI ios12 compatibility
- settings wighet them color
- minor tweaks
2025-06-07 21:28:40 +08:00
TrueZhuanjia a64a29ce07 simulates ios 12 2025-06-07 18:25:45 +08:00
TrueZhuanjia 68f21fa8e5 ios 12 & 13 compatibility for moreButton of settings navBar 2025-06-07 18:18:41 +08:00
TrueZhuanjia d9dbb2bcee add to favorite actionsheet minor fix 2025-06-07 12:42:23 +08:00
TrueZhuanjia 782ac4c362 minor fix 2025-06-07 10:48:37 +08:00
TrueZhuanjia ff68114fe3 settingsView layout , separatorLine, etc 2025-06-07 01:48:57 +08:00
TrueZhuanjia cbf48bdf0a setting stack info button & its interactions 2025-06-06 23:29:35 +08:00
TrueZhuanjia fcb68bc54f settingView close/expand control 2025-06-06 22:44:17 +08:00
TrueZhuanjia a591380d60 close settings view before desktop launching 2025-06-06 22:17:01 +08:00
TrueZhuanjia 822dfc433b moreButton + disconnectButton in Streaming, and related codings 2025-06-06 22:06:47 +08:00
TrueZhuanjia 17ecd52755 dateModel Name change 2025-06-06 20:49:54 +08:00
TrueZhuanjia 6fd35deaa4 rebranding 2 2025-06-06 12:04:54 +08:00
TrueZhuanjia d80c2e35ca rebranding 1 2025-06-06 12:00:23 +08:00
TrueZhuanjia 7ed6c559e0 optimize relocating setting stacks to lowest point 2025-06-05 23:48:53 +08:00
TrueZhuanjia e5b1eda3a1 favorite setting stack removal function 2025-06-05 22:03:26 +08:00
TrueZhuanjia 3120cd785b - auto save favorite settings with order
- auto setup morebutton menu in 2 settings mode
2025-06-05 17:39:23 +08:00
TrueZhuanjia b036e01afc speed up settings menu loading 2025-06-05 16:03:01 +08:00
TrueZhuanjia 01e379eefe _UIConstraintBasedLayoutLogUnsatisfiable test 2025-06-05 02:19:18 +08:00
TrueZhuanjia 17a00a1604 setting stack relocation in favorite mode, with background color indicator 2025-06-05 00:49:06 +08:00
TrueZhuanjia 59660840e4 favorite settings basic fuction 2025-06-04 01:49:59 +08:00
TrueZhuanjia 2775ca09c7 - settings menu nav more bar button and separatorLine
- settings menu hit test
2025-06-03 20:58:04 +08:00
TrueZhuanjia 8c8fea0d4b switch wifi icon to svg 2025-06-01 14:30:02 +08:00
TrueZhuanjia 7f23c4a508 mainframVC coding 2025-06-01 12:38:59 +08:00
TrueZhuanjia fda5c8610c minor fixes 2025-06-01 11:18:09 +08:00
TrueZhuanjia 049034e5a9 - theme
- host card actions including long press
- add host button
2025-06-01 04:06:06 +08:00
TrueZhuanjia 6ae3beb2a5 - hostCardButtonActionDelegate
- better theme color management
2025-05-31 12:00:32 +08:00
TrueZhuanjia 13853449d2 hostCardView implementation 2025-05-29 23:22:22 +08:00
TrueZhuangjia 2679ba75af hide some settings in streaming 2025-05-17 23:07:33 +08:00
TrueZhuangjia 561080568c - modularized settings widgets for iPad
- navBar for settings menu
2025-05-17 22:40:10 +08:00
TrueZhuangjia 4edf1e2cf5 upstream updates 2025-05-15 15:30:00 +08:00
TrueZhuangjia e7998c3559 disable session disconnection in background 2025-05-14 11:22:10 +08:00
TrueZhuangjia 8f674d6a3d mark copyright 2025-05-14 11:01:24 +08:00
TrueZhuangjia f1a9fd2b4f cmd tool special entry visual effect 2025-05-14 01:54:30 +08:00
TrueZhuangjia 46f75f0676 first launcher tips 2025-05-14 01:27:05 +08:00
TrueZhuangjia d3b62e38fc Onscreen Widget Tool:
- edit/duplicate function support
- opened from cmd tool by sliding from screen edge
2025-05-13 23:18:32 +08:00
TrueZhuangjia c8ebdc0a88 disable async touch down&move priorities by GUI 2025-04-09 21:21:23 +08:00
TrueZhuangjia 238eb816d0 update c-module 2 2025-04-09 15:03:26 +08:00
TrueZhuangjia 0f054bb96f update c-module 2025-04-08 22:17:57 +08:00
TrueZhuangjia 711996b478 async touch qos & setting option 2025-04-08 22:15:11 +08:00
TrueZhuangjia a43fe609d3 tweak qos for async native touch 2025-04-04 16:39:00 +08:00
TrueZhuangjia 2712922ce5 setting option for async native touch 2025-04-04 14:29:54 +08:00
TrueZhuangjia 49b685b3ec update preset template profie 2025-04-03 18:15:22 +08:00
TrueZhuangjia 302606564c tweak dPad & L3R3 indicator 2025-04-03 01:04:03 +08:00
TrueZhuangjia 1fc1b5bc60 greyBackground for layout tool opened in settings menu 2025-04-03 00:42:20 +08:00
TrueZhuangjia f260d75372 preset advanced widget template profile 2025-04-03 00:13:52 +08:00
TrueZhuangjia a03a50d238 guide lines optimizations for legacy osc buttons 2025-04-02 20:35:14 +08:00
TrueZhuangjia 928284990b minor fixes 2025-04-02 16:59:35 +08:00
TrueZhuangjia 03046988c7 "airplay" for default external display mode 2025-04-02 10:39:32 +08:00
TrueZhuangjia c860433065 relative touch rollback 2025-04-01 22:22:52 +08:00
TrueZhuangjia 5d66c17bc1 fix LSPAD, RSPAD indicator visual effect glitches 2025-03-30 14:00:25 +08:00
TrueZhuangjia 16b5df7dd1 Fulscreen 2025-03-30 12:18:11 +08:00
TrueZhuangjia 589c025951 tweak on-screen widget resizing 2025-03-30 11:58:34 +08:00
TrueZhuangjia 7fbfe94b71 fix legacy osc button L1 R1 position shift after being duplicated 2025-03-30 10:36:54 +08:00
TrueZhuangjia 27bb8fd737 tweak storyboard 2025-03-29 22:03:18 +08:00
TrueZhuangjia cc285d5488 export profile Data 2025-03-29 21:44:06 +08:00
TrueZhuangjia c8522de476 normalize legacy controller position 2025-03-29 20:22:32 +08:00
TrueZhuangjia 94f2e39367 passing streamViewBounds 2025-03-29 19:43:03 +08:00
TrueZhuangjia bd83af8bf4 change touchMoveEvent range and default setting 2025-03-29 15:20:36 +08:00
TrueZhuangjia 420eebf831 async mouse move event for relative touch handler 2025-03-29 14:47:56 +08:00
TrueZhuangjia e0691dcd97 minStickOffset localization 2025-03-29 13:47:38 +08:00
TrueZhuangjia 24442c29bb touchPad move event async 2025-03-29 13:43:51 +08:00
TrueZhuangjia 871315c2bc - async native touch passthrough
- touchMoveEvent interval tweak
2025-03-29 00:51:05 +08:00
TrueZhuangjia 7b116b936c sensitivity tweak for direction touchpads (wasdpad, arrowpad, dpad) 2025-03-24 01:24:15 +08:00
TrueZhuangjia 9aa84c03d7 - button-touchPad hybrid widgets
- minimum stick offset for stick widgets
- expand controller cmd strings to playStation style
2025-03-24 00:36:24 +08:00
TrueZhuangjia 471325a358 - tweaking genshinImpact RsvPads ("YSB", "YSRT", "YSRB", "YSRSV") 2025-03-23 00:37:58 +08:00
TrueZhuangjia 978ab18cee - timeInterval command for super combo keys
- expand keyboardButtonMappings dict
2025-03-22 21:57:05 +08:00
TrueZhuangjia ea0c16e334 invalid input msgbox for adding on-screen widget 2025-03-22 21:55:05 +08:00
TrueZhuangjia dd0ff298d5 on screen widget shape - Chinese localization 2025-03-22 12:49:35 +08:00
TrueZhuangjia eca0b30728 - special game widget: YSLT for genshinImpact
- rename YSxx1 cmds to YSxx
2025-03-22 01:11:35 +08:00
TrueZhuangjia 9ade4b5ae0 special game widgets: YSRB1 2025-03-21 23:14:16 +08:00
TrueZhuangjia 4f8c5189e0 special game widgets: YSWASD, YSMR 2025-03-21 22:57:18 +08:00
TrueZhuangjia 0f2d54e8c0 - configurable on screen widget shape
- special game widgets: YSB1, YSRT1 for genshinImapct
2025-03-21 21:36:54 +08:00
Qian23333 c0fc1becb7 modify workflow to build Stage Manager version 2025-03-07 17:39:04 +08:00
TrueZhuangjia 70e5b3520c - fix the issue of previous selected on-screen widget get deleted when deleting legacy osc button
- coding improvements for on-screen widget deletion
2025-03-02 21:47:57 +08:00
TrueZhuangjia ebec9877b0 fix some traditional Chinese localization errors 2025-03-02 18:02:10 +08:00
TrueZhuangjia d9a5d8aaac tweak stick indicator border reaching visual feedback 2025-02-28 12:37:58 +08:00
TrueZhuangjia e58d05a85b slide from left to close settings menu 2025-02-26 23:46:39 +08:00
TrueZhuangjia 3efddab070 - vertical stick indicator offset
- smaller stick indicator cross
- animation for stick ball return
- border detection for stick ball
- allow zero alpha for custom on-screen touch pads
2025-02-26 22:48:10 +08:00
TrueZhuangjia a17a27005d fix on screen widget location shift while after quick tapping in layout tool 2025-02-23 20:42:31 +08:00
TrueZhuangjia a8f7f4679b github ipa build test 2025-02-23 19:39:21 +08:00
TrueZhuanJia 11c0119944 Merge pull request #60 from Qian23333/noWaterMark
actions build
2025-02-23 19:36:53 +08:00
TrueZhuangjia 80804ee7ac super combo custom on-screen button connected by "-" in key string 2025-02-23 16:12:23 +08:00
TrueZhuangjia 06e80823b8 try to fix: layoutTool unable to lock screen orientation in some conditions 2025-02-23 10:49:09 +08:00
TrueZhuangjia ecda390dc0 Merge branch 'DevDebug' into noWaterMark: tweak padding for iPad 2025-02-23 02:38:27 +08:00
TrueZhuanJia 481c443211 Merge pull request #57 from Acaki/feat/yuv444
Support YUV 4:4:4 streaming
2025-02-23 02:27:44 +08:00
Qian23333 80b7b8eaec Merge branch 'Moonlight-the-Fried-Fish:noWaterMark' into noWaterMark 2025-02-23 02:18:11 +08:00
TrueZhuangjia 1e33fc7935 tweak padding 2025-02-23 02:09:08 +08:00
Acaki 6b42d271ba refactor: adjust padding 2025-02-20 03:17:26 +08:00
Acaki 7823d1a60b refactor: remove unnecessary line 2025-02-20 01:16:01 +08:00
Acaki d23c4a5795 feat: update translations 2025-02-20 00:09:16 +08:00
Acaki c91c40e79f feat: update supportedVideoFormats according to YUV444 setting 2025-02-20 00:09:16 +08:00
Acaki 5302f418db feat: add YUV444 toggle 2025-02-20 00:09:16 +08:00
Acaki 6370bc187a fix: crypto build error 2025-02-20 00:09:16 +08:00
TrueZhuangjia 3e2dc4989e full screen version 2025-02-13 22:06:27 +08:00
TrueZhuangjia 715cb86ab9 - better alignment & resizing for on-screen widget view
- stage manager
2025-02-13 21:55:45 +08:00
TrueZhuangjia cf375eae84 full screen version 2025-02-10 00:57:33 +08:00
TrueZhuangjia 4d4ab333f4 - make dPad, select, start OSC button on-screen widgets rectangle
- on screen control & widget size factor up to 800%
- configurable borderWidth for on-screen widget views
stage manager version
2025-02-10 00:52:56 +08:00
TrueZhuangjia ca04c62ecd fullScreen version 2025-02-09 13:08:12 +08:00
TrueZhuangjia 34f632e7e4 - fix iPhone onScreen Widget sensitivity in storyboard
- fix the issue of unable to load touch point velocity factor higher than 200%
- 700% sensitivity available for touchPad mode mouse
- change maximum sensitivity for stick & on-screen mouse touchPad widget to 600%
stage manager version
2025-02-09 12:39:48 +08:00
TrueZhuangjia 4f952b5daa simplified statsOverlay, fullscreen version 2025-02-01 19:32:42 +08:00
TrueZhuangjia edcb9cf275 add simplified statsOverlay, stage manager version 2025-02-01 19:29:45 +08:00
TrueZhuangjia ac44efbbbe try to fix iPhone host view landscape lock 2025-01-31 09:14:39 +08:00
TrueZhuangjia e0a81dbd34 fullScreen version 2025-01-29 21:20:19 +08:00
TrueZhuangjia 301c8580c8 - onScreenWidget guidelines alignment with OnScreenControl CALayers
- stage manager version
2025-01-29 21:13:38 +08:00
TrueZhuangjia 9b9b278a14 - OnScreenButtonView refactored to OnScreenWidgetView
- oscLayer guideline alignment with onScreenWidgetViews
2025-01-29 19:37:31 +08:00
Qian23333 31c6569910 Create build.yml 2025-01-29 16:56:02 +08:00
TrueZhuangjia ed3a837200 fullscreen version 2025-01-29 16:11:24 +08:00
TrueZhuangjia 9cfa5f35cb fix touchPad sensitivity slider issue.
- stage manager version
2025-01-29 16:10:01 +08:00
TrueZhuangjia cc13b60411 in mainFrameView, lock landscape orientation for iPhone, lock current orientation for iPad
Fullscreen version
2025-01-28 18:59:18 +08:00
TrueZhuangjia caa4fccabd fullScreen version 2025-01-28 13:29:19 +08:00
TrueZhuangjia 4ba33002dd UI string updates for new onScreen widgets 2025-01-28 13:13:34 +08:00
TrueZhuangjia ef418be2df special overlay button "settings" implementation 2025-01-28 12:09:43 +08:00
TrueZhuangjia b7d8889180 sensitivity & stick indicator settings in touchPad profile config 2025-01-28 10:15:17 +08:00
TrueZhuangjia 18fe545533 UI construction for new TouchPad profile settings 2025-01-27 22:44:45 +08:00
TrueZhuangjia 87208b07a5 UI tweak 2025-01-27 01:55:51 +08:00
TrueZhuangjia c713eafac0 tweak visual effect for touchPads 2025-01-27 00:19:49 +08:00
TrueZhuangjia d493cba990 display "keystring" for touchPads in edit mode 2025-01-27 00:06:05 +08:00
TrueZhuangjia ae188fd999 MOUSEPAD implementation 2025-01-26 23:35:06 +08:00
TrueZhuangjia 5747656be7 implementation of round custom osc buttons in OnscreenButtonView.swift 2025-01-26 15:17:20 +08:00
TrueZhuangjia 4e85d3d079 DPAD implemention 2025-01-26 10:57:10 +08:00
TrueZhuangjia e2bd8b1512 implementation of WASDPAD & ARROWPAD 2025-01-25 22:39:02 +08:00
TrueZhuangjia 422c34f945 L3R3 button visual effect for onScreen touchPad 2025-01-25 22:04:07 +08:00
TrueZhuangjia 8d0b646fbe newTouchPad test - LRUD touchpad visual effect 2025-01-24 20:35:00 +08:00
TrueZhuangjia 2c4a7cc055 newTouchPad test phase3 2025-01-23 21:47:51 +08:00
TrueZhuangjia 54a31689f7 change annotations for unlock device orientation 2025-01-05 19:57:50 +08:00
TrueZhuangjia 6bf967b9d2 stage manager version 2025-01-05 13:55:29 +08:00
TrueZhuangjia b6abd71392 restore waterMark Text 2025-01-05 13:09:47 +08:00
TrueZhuangjia 8506640d7f change allowPortrait to unlockDisplayOrientationInStreaming 2025-01-05 12:54:08 +08:00
TrueZhuangjia 592b7acfc4 add legacy renderView mode 2025-01-05 12:02:15 +08:00
TrueZhuangjia 858843ff9a rename & refactor mouseMode to localMousePointerMode 2025-01-04 20:59:40 +08:00
TrueZhuangjia 9ffeba72fe stop user interaction for streamFrameController->renderView to fix multi-touch issue 2025-01-04 20:41:15 +08:00
TrueZhuangjia 4074823e4e newTouchPad test phase2 2025-01-04 19:48:57 +08:00
TrueZhuangjia b99172d932 Merge branch 'newTouchPad' into noWaterMark 2024-12-03 21:31:10 +08:00
TrueZhuangjia 017b03d200 new touchpad test 2024-12-03 21:30:23 +08:00
TrueZhuangjia ea12dd3c47 remove xcdatamodel v1.11 2024-12-03 21:28:38 +08:00
TrueZhuanJia 2b98543125 Merge pull request #43 from xzzpig/ExternalScreen
Add AirPlay support & Enhanced keyboard and mouse experience
2024-12-03 21:25:03 +08:00
xzzpig a0f13881bf Add key combo support 2024-12-01 18:16:46 +08:00
xzzpig 62ae63d7f6 Allow not capture mouse 2024-11-29 23:00:54 +08:00
xzzpig 589ca19e4f Add AirPlay support 2024-11-29 12:04:08 +08:00
TrueZhuangjia b1785b1fa5 fullscreen version 2024-11-25 20:37:00 +08:00
TrueZhuangjia 3fc26d279a stage manager version 2024-11-24 23:06:49 +08:00
TrueZhuangjia bde477558f - changes on background resolution update.
- disable allowPortrait setting option is force fullscreen is disabled in app bundle
2024-11-24 22:27:18 +08:00
TrueZhuangjia b5afe1fe29 onScreen touchPad control test 2024-11-24 21:30:23 +08:00
TrueZhuangjia 8b2470faf8 Merge branch 'noWaterMark' into DevDebug 2024-11-23 18:30:50 +08:00
TrueZhuangjia 7fcc3d0013 delete duplicated database entries in xcode project 2024-11-23 17:16:56 +08:00
TrueZhuangjia 153b6ce6fe annotations for onscreen controller stick move handling 2024-11-22 22:26:40 +08:00
TrueZhuangjia 777c825cca swift call obj-c test 2024-11-22 22:00:25 +08:00
TrueZhuangjia 33a8e02c5f update moonlight-common-c to 2024.10.21 2024-11-22 21:59:51 +08:00
TrueZhuangjia c797c9db1a restore runtime orientation limitation 2024-11-21 23:53:54 +08:00
TrueZhuangjia 766e30a066 Tips for screen orientation Change (non-FullScreen version) 2024-11-21 23:46:24 +08:00
TrueZhuangjia 3894d92f1b try to fix app crash issue triggered by manually adding host 2024-11-20 21:36:58 +08:00
TrueZhuangjia 2be2ffbcb6 change fullscreen/window resolution label for ipad 2024-11-20 21:02:00 +08:00
TrueZhuangjia 570dff2696 assign default settings for resolutionSelected & slide gesture screen edge 2024-11-20 12:53:28 +08:00
TrueZhuangjia be13295096 refactor app window resolution update to mainFrameView 2024-11-19 23:12:26 +08:00
TrueZhuanJia ea89d0a822 Merge pull request #37 from xzzpig/ExternalScreen
Add support to change app size
2024-11-19 21:02:28 +08:00
xzzpig d7bb9483e4 Update resolution table when mainView loaded 2024-11-19 14:56:33 +08:00
xzzpig 7699e47157 Modify the logic for saving resolution settings 2024-11-19 09:48:23 +08:00
xzzpig 978c50c271 Add support to change app size 2024-11-18 20:46:44 +08:00
TrueZhuanJia 0f5e644041 Merge pull request #26 from xzzpig/noWaterMark
修复某些特殊网络情况(网络掩码非24)时发送组播wol失败的问题 / Fix the issue of multicast WOL failure when sending in certain special network situations (such as network mask not being 24)
2024-11-08 21:11:24 +08:00
xzzpig c5e891eceb Fix the issue of multicast WOL failure when sending in certain special network situations (such as network mask not being 24) 2024-11-04 20:52:02 +08:00
TrueZhuangjia 147b2ce045 tweak watermark position 2024-11-04 20:06:23 +08:00
TrueZhuangjia 9553ec3d96 minor tweak on button down visual effect 2024-11-04 19:56:51 +08:00
TrueZhuangjia 890d3fbfe1 update waterMark 2024-11-04 19:45:36 +08:00
TrueZhuangjia b9ea8cc1a9 change button down effect for onScreenButtonView 2024-11-04 00:53:40 +08:00
TrueZhuangjia 141c9ee2ab change button down effect for onScreenButtonView 2024-11-03 23:31:17 +08:00
TrueZhuangjia f1811ca53d fix on-screen mouse middle button issue 2024-10-31 15:02:07 +08:00
TrueZhuangjia 5bf163f40c Merge branch 'DevDebug' into noWaterMark 2024-10-31 14:59:36 +08:00
TrueZhuangjia 395647cff9 代码注释 - 添加主机crash问题重点关注 2024-10-15 21:37:26 +08:00
TrueZhuangjia 3f3ffdca13 - fix disappearing on-screen sticks 2024-10-15 20:18:25 +08:00
TrueZhuangjia 34659cac70 tweak tap exclusion area size & its default value 2024-10-15 13:00:02 +08:00
TrueZhuangjia d8820df26e button size, height & alpha UISlider for iPhone storyboard 2024-10-15 12:42:06 +08:00
TrueZhuangjia 6495bad6fe button size, height & alpha labels with factors 2024-10-15 11:39:06 +08:00
TrueZhuangjia f918779fc8 adjustable on screen controller layer transparency 2024-10-15 10:48:53 +08:00
TrueZhuangjia 8999fd625d adjustable onScreenButtonView alpha(transparency) 2024-10-14 15:10:53 +08:00
TrueZhuangjia 113aaa1b81 button size & height slider labels 2024-10-13 23:33:22 +08:00
TrueZhuangjia ec29679c02 HD start & select buttons 2024-10-13 22:37:28 +08:00
TrueZhuangjia 962d19f3cb HD dPad buttons 2024-10-13 22:00:49 +08:00
TrueZhuangjia 3bec2d6e65 HD sticks 2024-10-13 19:14:47 +08:00
TrueZhuangjia 571812062f HD OSC round buttons 2024-10-13 17:27:28 +08:00
TrueZhuangjia c4801f9889 deal with invalid sizefactor 2024-10-13 09:15:25 +08:00
TrueZhuangjia ada2e6f480 resizable dPad buttons 2024-10-12 23:56:21 +08:00
TrueZhuangjia fadf023563 resizable on-screen sticks & single layer on-screen controller buttons 2024-10-12 19:32:58 +08:00
TrueZhuangjia 5db6aa0695 Merge branch 'noWaterMark' into buttonSizeTweak 2024-09-24 10:33:24 +08:00
TrueZhuangjia 1ca71bbc11 - Handles tap exclusion area for dPad 2024-09-24 10:32:11 +08:00
TrueZhuangjia 39b8429998 - on-screen button & on-screen controller resize test 2024-09-21 22:48:16 +08:00
TrueZhuangjia e05b626d51 9.0.263 build 63 2024-09-17 12:22:54 +08:00
TrueZhuangjia 146ca2353d - cancel mouseLeftButtonHeldDown flag 2024-09-17 11:11:27 +08:00
TrueZhuangjia 6d5c5fb0c2 - Fixed app crash caused by 2-finger mouse wheel event 2024-09-14 16:56:05 +08:00
TrueZhuangjia 3d5885f5bd - update UI string in settings 2024-09-14 16:20:54 +08:00
TrueZhuangjia 888ecad4d3 - implements upper screen edge touch detection for absolute touch mode 2024-09-14 16:03:43 +08:00
TrueZhuangjia b3a64fdf0f - minor fix 2024-09-14 14:14:43 +08:00
TrueZhuangjia dc8e361b6b - implements upper screen edge touch detection for relative touch(touchpad) mode 2024-09-14 14:02:04 +08:00
TrueZhuangjia 15bc488f71 - Completely refactor relative touch mode: implements quick double tap for dragging, simulates a real note book computer touchpad.
- Onscreen controllers, buttons & touchpad mouse able to work independently without blocking each other
- Tap exclusion area size upto 1000%
2024-09-14 12:29:31 +08:00
TrueZhuangjia 22b4cc6222 - add option to reverse physical mouse wheel direction
- add slider widget to adjust the size of tap exclusion area for on-screen buttons
2024-09-12 23:43:32 +08:00
TrueZhuangjia f5c03f8045 make on-screen buttons available for absolute touch 2024-09-12 15:20:55 +08:00
TrueZhuangjia 0a47becdc6 change default touch mode to native touch 2024-09-12 10:48:30 +08:00
TrueZhuangjia e4d264cc83 Change frame pacing zh-hans translation 2024-09-12 10:35:52 +08:00
True Zhuanjia 7c455b0daa 9.0.256 2024-09-04 14:19:10 +08:00
True Zhuanjia d2a1add105 remove invalid IP rule 2024-09-02 22:46:24 +08:00
True Zhuanjia dcdfbc7394 excludes on-screen controller & button taps for mouse scroller gesture in relative touch mode 2024-09-02 22:01:30 +08:00
True Zhuanjia 2b5a1c0551 Simplify border detection in native touch modes 2024-08-29 01:15:37 +08:00
True Zhuanjia cb034b96a4 Build 47: make bitrate stepping to 1Mbps under 70Mbps 2024-08-28 20:26:32 +08:00
True Zhuanjia e741f83c06 urgent fix: native touch pointer sensitivity tweak not working 2024-08-26 21:29:10 +08:00
True Zhuanjia b192360ef3 change watermark 2024-08-26 19:28:40 +08:00
True Zhuanjia a028191c82 make keyboard command delay 200ms 2024-08-24 23:46:04 +08:00
True Zhuanjia c6b27ad0e0 resolve conflicts between on-screen buttons & tap gestures 2024-08-24 23:29:18 +08:00
True Zhuanjia 1d1d4cbddf add mouse button function to commandManager & OnScreenButton 2024-08-24 16:48:17 +08:00
True Zhuanjia 747997f46c - Adjust stick style.
- Add + button for iphone
2024-08-23 21:41:29 +08:00
True Zhuanjia 805cf517bb adjust on screen keyboard button style 2024-08-22 23:20:50 +08:00
True Zhuanjia ea78b434d5 adjust UI 2024-08-22 22:38:53 +08:00
True Zhuanjia 69f5fbef31 - Resolving app crash which happens occasionally after slide from screen edge
- Slide from upper 40% of left & right screen edge to open in-stream menus will  no longer sends touch event to remote PC
2024-08-22 19:49:09 +08:00
True Zhuanjia db75cac9bc Build 31 touches spawned at the upper 40% screen edge (left or right) will not be sent to the remote PC 2024-08-20 23:16:26 +08:00
True Zhuanjia ba4af74a9d make on-screen L1/R1 & left & right stick larger by setting option 2024-08-20 21:18:06 +08:00
True Zhuanjia fb474237ee Build 29 oscPofileManViewController UI Improvement 2024-08-19 23:46:55 +08:00
True Zhuanjia d476f5d75b build 28, change on screen button visual effect color 2024-08-19 01:35:40 +08:00
True Zhuanjia 8720847f54 build 27, adjust font size for onscreen key label 2024-08-19 00:32:21 +08:00
True Zhuanjia a12bd80cc1 build 26, custom on screen keyboard localization 2024-08-18 23:50:32 +08:00
True Zhuanjia ab3c551357 implements custom on screen keyboard button 2024-08-18 23:29:00 +08:00
True Zhuanjia a1a7047370 make osdLayoutToolFingers configurable by settings menu 2024-08-17 21:29:44 +08:00
True Zhuanjia 29e43979a7 make OSC + native touch works 2024-08-17 18:59:54 +08:00
True Zhuanjia 6b25de1361 minor change 2024-08-17 01:18:34 +08:00
True Zhuanjia 53e68258f3 2nd time - completely resolving conflict between custom tap gesture & onscreen control taps 2024-08-16 21:45:50 +08:00
True Zhuanjia 6e3c982a22 Merge branch 'noWaterMark' into OSCDebug
# Conflicts:
#	Limelight/Input/StreamView.m
#	Limelight/ViewControllers/StreamFrameViewController.m
2024-08-15 22:29:55 +08:00
True Zhuanjia f0e812e9e2 iterate all customTapGestureRecognizer in OnScreenControls implementation to resolve conflict between tap gesture & osc touch 2024-08-15 21:55:01 +08:00
True Zhuanjia 9ff10135a8 - optimize _areVirtualControllerTap mechanism, fix onscreen game controller button stuck
- change keyboard toggle gesture finger number logic
2024-08-15 09:34:43 +08:00
True Zhuanjia 9618b7bb94 optimize _areVirtualControllerTap mechanism, fix onscreen game controller button stuck 2024-08-14 22:51:51 +08:00
True Zhuanjia fd768db70b onscreen keyboard profile test 2024-08-14 22:49:35 +08:00
True Zhuanjia 3c5b6ba795 udpate 2024-08-14 22:44:12 +08:00
True Zhuanjia add09210ba update 2024-08-11 21:59:21 +08:00
True Zhuanjia 6065b4f742 keyboard button profile test 2024-08-11 21:55:29 +08:00
True Zhuanjia d90bf747fc add OSC visual feedback & corresponding menu option.
adjust menu option layout for iPhone
2024-08-10 21:02:35 +08:00
True Zhuanjia ec8095ea9d fix Custom OSC abnormal touch (no response) after editing OSC profile 2024-08-10 11:52:24 +08:00
True Zhuanjia b11849e45b Merge branch 'onScreenKey test' into noWaterMark
# Conflicts:
#	Limelight/Input/ControllerSupport.m
2024-08-09 18:54:38 +08:00
True Zhuanjia e2f09d711d on screen key test 2024-08-09 18:51:12 +08:00
True Zhuanjia 04b68e3a61 test 2024-08-09 18:02:26 +08:00
True Zhuanjia b21b93ff71 cancel some restrictions for iOS 13.3 & lower 2024-08-09 11:40:41 +08:00
True Zhuanjia 9f9df09c61 test flight build (No.12) 2024-08-06 23:03:22 +08:00
True Zhuanjia c7700904e0 handle public ip 2024-08-03 20:57:32 +08:00
True Zhuanjia a8b45851cc rm waterMark 2024-08-03 18:43:33 +08:00
True Zhuanjia bf1a30505f try to fix invalid MAC issue that happens in some cases 2024-08-03 18:42:35 +08:00
True Zhuanjia 7a55fee258 fill sapces for all UI strings in settings view storyboard 2024-08-01 19:08:12 +08:00
True Zhuanjia abc7805aab add high bitrate warning to the settings view 2024-07-29 12:27:47 +08:00
True Zhuanjia 98db99b3ff minor improvement for command tool 2024-07-28 10:03:38 +08:00
True Zhuanjia 07d08918a3 improve entry selection after add / deleting entries in the command tool 2024-07-27 22:51:24 +08:00
True Zhuanjia 8371c9fedd fix unable to remove on-line paired host 2024-07-27 21:45:04 +08:00
True Zhuanjia b106e60880 add switch for attachWatermark 2024-07-27 12:45:08 +08:00
True Zhuanjia 81c04bc2b2 add watermark back to the host view in mainframe 2024-07-27 12:43:04 +08:00
True Zhuanjia 945a4c67e4 improve add / duplicate button 2024-07-26 22:24:28 +08:00
True Zhuanjia 95644075ec improve entry tapping flash effect for cmd tool tableview cells 2024-07-26 21:57:38 +08:00
True Zhuanjia 19adb5e106 update annotation 2024-07-26 21:44:49 +08:00
True Zhuanjia ff789200b9 add more default entries for cmd tool 2024-07-26 21:35:46 +08:00
True Zhuanjia b53b0784aa implement default entries for command tool. change add button to add/duplicate 2024-07-26 21:05:10 +08:00
True Zhuanjia 69056332d3 change command tool view constraint & transparency 2024-07-26 20:15:07 +08:00
True Zhuanjia f5342caca6 update placeholder for alias input field 2024-07-26 19:53:36 +08:00
True Zhuanjia 039bf549a9 refactor coding & settings menu for command sending tool 2024-07-26 00:19:39 +08:00
True Zhuanjia ea9e335a36 refactor Cancel button in osc layout tool to Exit button 2024-07-25 21:57:31 +08:00
True Zhuanjia 46e3f85903 localize send command view 2024-07-25 21:31:06 +08:00
True Zhuanjia b00d836c13 add pin button for the sending cmd view 2024-07-25 21:02:05 +08:00
True Zhuanjia b587279450 update 2024-07-25 01:07:17 +08:00
True Zhuanjia 279ef707d5 update 2024-07-25 01:06:52 +08:00
True Zhuanjia 7e8be94530 implement async keyboard event sending 2024-07-25 00:30:02 +08:00
True Zhuanjia c8262da5d7 sending keyboard cmd 2024-07-24 23:50:16 +08:00
True Zhuanjia 81beb35809 keyboard command test 2024-07-24 22:56:46 +08:00
True Zhuanjia eb302e5d19 correct CTRL & ALT keycode for toolbar 2024-07-24 22:15:36 +08:00
True Zhuanjia 9c54168173 cancel filling selected alias for the input field for new keyboard cmd entries 2024-07-24 20:23:30 +08:00
True Zhuanjia 3eb233ffe2 handling empty alias for keyboardCmdSring 2024-07-24 20:18:10 +08:00
True Zhuanjia cf21ca9503 conforming RemoteCommand class to NSSecureCoding 2024-07-24 20:09:16 +08:00
True Zhuanjia b6ecdcd0ea make key mapping static 2024-07-24 19:29:52 +08:00
True Zhuanjia a975b0e24a keep entry of send cmd manager selected 2024-07-24 19:20:54 +08:00
True Zhuanjia 84b198ed29 implement regular expression check 2024-07-24 18:56:41 +08:00
True Zhuanjia 4cff75c531 tweak view contrain 2024-07-24 13:02:13 +08:00
True Zhuanjia 916e9b65a6 implementation of alias for the send special command entries 2024-07-24 12:36:01 +08:00
True Zhuanjia eb6adea47c UI & Data persistence for Send Special Keys menu 2024-07-23 23:52:20 +08:00
True Zhuanjia 458daddf38 disable exitSwipeRecognizer for settings view 2024-07-22 12:49:26 +08:00
True Zhuanjia ea98fe1fbf minor changes 2024-07-21 10:39:18 +08:00
True Zhuanjia c3b7814542 update provisioning 2024-07-21 01:50:31 +08:00
True Zhuanjia abe414765e try to fix LAN host waking failure. 2024-07-21 01:14:17 +08:00
True Zhuanjia 9a93022478 update storyboard UI string for "allow portrait" setting 2024-07-20 20:04:03 +08:00
True Zhuanjia 033265d58e disallow orientation change in mainframe host view (need iOS16+) 2024-07-20 19:40:49 +08:00
True Zhuanjia 2bcdd25cc2 fix onscreen control not working after enabled from realtime game back menu, if the stream view is launched without onscreen control in the first time 2024-07-20 16:39:22 +08:00
True Zhuanjia c38dfc2c20 exclude bottom right corner for coord reset in high touch pointer velocity condition 2024-07-16 20:56:06 +08:00
True Zhuanjia ef5c77d8df fix touch interruption on the left side of screen under high pointer velocity (sensitivity) 2024-07-15 13:01:18 +08:00
True Zhuanjia b05c3c7e4a on iPhone, force close expanded settings view if target device orientation will be portrait. 2024-07-14 18:44:02 +08:00
True Zhuanjia 606ec51108 update NSLog for velocFactor 2024-07-14 18:24:14 +08:00
True Zhuanjia 3dbc97701d change pointer reset coordinate, change max pointer velocity 2024-07-14 18:19:16 +08:00
True Zhuanjia 31b3df08cc coord reset for extremely high pointer velocity, core implementation 2024-07-14 16:41:26 +08:00
True Zhuanjia 01718282ff single Host view refresh bug fix 2024-07-14 01:05:40 +08:00
True Zhuanjia 1b5f414c89 starRail pointer veloc tweak enchancement 2024-07-13 22:52:17 +08:00
True Zhuanjia a441670591 temporally enable allowPortrait selctor 2024-07-13 18:57:20 +08:00
True Zhuanjia 7c0f9b65f1 update annotation 2024-07-13 18:28:57 +08:00
True Zhuanjia 837a75c477 get isIPhonePortrait flag ready for release 2024-07-13 18:18:19 +08:00
True Zhuanjia 0a449c25eb Test: disable settings view in portrait mode for iphone (prevents buggy settings view), flag not ready for release 2024-07-13 17:51:19 +08:00
True Zhuanjia 88d5038f94 make allowportrait selector available on iOS16+ only 2024-07-13 14:32:50 +08:00
True Zhuanjia 2de3197ca9 runtime orientation limitation, tested on iOS17.2 (probably need iOS16+, not working with iOS14) 2024-07-13 13:55:10 +08:00
True Zhuanjia c72accb3cb scroll view update upon orientation change 2024-07-12 23:30:08 +08:00
True Zhuanjia 00d89f12e8 correct landscape host view well device sensor is portrait 2024-07-12 22:48:53 +08:00
True Zhuanjia 565e31983c landscape limitation test 2024-07-12 22:08:34 +08:00
True Zhuanjia 06aa772a02 orientation change test 2024-07-12 21:13:19 +08:00
True Zhuanjia a5b1cd6c99 revert to both rotatable 2024-07-12 20:33:55 +08:00
True Zhuanjia d84be68f4c udpate 2024-07-12 20:31:26 +08:00
True Zhuanjia e01b51bf37 back to landscape only 2024-07-12 00:12:38 +08:00
True Zhuanjia 9c6bf5c16f correct method name 2024-07-12 00:09:37 +08:00
True Zhuanjia 9bf4ba9af9 adjust widgets alignment 2024-07-12 00:00:22 +08:00
True Zhuanjia a37c7ed8f8 go back to stream view button for iphone 2024-07-11 23:50:32 +08:00
True Zhuanjia 36ba82dd16 go back to stream view button on ipad 2024-07-11 23:32:17 +08:00
True Zhuanjia 802f8714ef cancel immediate opening of osc layout tool in setting view during streaming 2024-07-11 03:13:49 +08:00
True Zhuanjia bf6cf21084 resolve absolute touch gesture 2024-07-11 02:54:36 +08:00
True Zhuanjia 30df8aec8a full auto screen resolution update in background 2024-07-11 01:34:16 +08:00
True Zhuanjia 84bffdd287 cancel auto osc mode 2024-07-11 01:14:24 +08:00
True Zhuanjia 6c4b5321e1 update iphone storyboard cn localization 2024-07-11 00:38:25 +08:00
True Zhuanjia 14ef07f072 fix gesture reconfig in real time streamview 2024-07-11 00:28:27 +08:00
True Zhuanjia 52c2a831d9 update statsOverlay localization 2024-07-10 02:22:25 +08:00
True Zhuanjia d427270699 localize statsOverlay 2024-07-09 23:39:17 +08:00
True Zhuanjia b25f2d0916 add statsOverlay realtime setting config 2024-07-09 23:12:04 +08:00
True Zhuanjia 56fc18c378 add exit button by storyboard, config widget disabling in streamview settingview 2024-07-09 22:42:01 +08:00
True Zhuanjia 38343c20cb get stream real time setting menu working 2024-07-08 23:59:42 +08:00
True Zhuanjia 6f1773d512 Real Time setting menu in streamView beta 2024-07-08 20:45:48 +08:00
True Zhuanjia d9fc1fc5c9 fix swipeToExitDistance loading problem 2024-07-08 19:57:51 +08:00
True Zhuanjia 3ad8b0db71 backmenutmp 2024-07-08 19:43:23 +08:00
True Zhuanjia a021784d48 replace force expand & close of settings view with background resolution width & height swap 2024-07-08 19:06:59 +08:00
True Zhuanjia 072f7bdffb force reload setting after quitting remote session 2024-07-07 13:38:11 +08:00
True Zhuanjia b8a3aa5d8c fix physical mouse wheel direction 2024-07-07 13:09:58 +08:00
True Zhuanjia ee91177ca6 fix OSCLayoutTool multi-reload bug 2024-07-07 12:34:59 +08:00
True Zhuanjia 3f02a76c3d update iphone storyboard 2024-07-07 12:34:04 +08:00
True Zhuanjia c88753c7e9 HybridNativeTouchMode 2024-07-07 12:19:03 +08:00
True Zhuanjia 94addc69f7 res auto load improvement 2024-07-07 03:48:53 +08:00
True Zhuanjia b4cfe21074 fix setting view won't expand after quitting remote session 2024-07-05 19:35:07 +08:00
True Zhuanjia 8437c8ada1 update default touchMode setting in database 2024-07-05 17:49:40 +08:00
True Zhuanjia 0a5109823c add resolution auto update for orientation change 2024-07-05 17:41:45 +08:00
True Zhuanjia e863a5df6a update annotation 2024-07-04 23:13:58 +08:00
True Zhuanjia 547eb1c85d auto res table update on orientation change 2024-07-04 23:04:45 +08:00
True Zhuanjia 63c853d4ce add portrait mode 2024-07-04 20:57:57 +08:00
True Zhuanjia 329c9b7b92 add touch mode label description 2024-07-04 20:47:21 +08:00
True Zhuanjia 0789a2f27e native only & native touch mode 2024-07-04 01:04:00 +08:00
True Zhuanjia 382116ab21 add waterMark 2024-07-03 20:50:00 +08:00
True Zhuanjia 7290bff594 display zh-hans for 2 zh-hant localizations 2024-07-03 12:59:14 +08:00
True Zhuanjia 8b57c448ef fix mini5 menu widget visibility update problem.
update Chinese Localizatioin
2024-07-02 21:55:51 +08:00
True Zhuanjia e5cf54e92f update localization 2024-07-01 18:06:08 +08:00
True Zhuanjia 34d9875ff3 host discovery Chinese localization. Cancel LAN restriction 2024-06-30 23:46:21 +08:00
True Zhuanjia 070fbc8b52 Chinese Localization 2024-06-30 22:26:52 +08:00
True Zhuanjia 9d57c22757 Fix weird setting & select new host disabling after closing OSC layout tool 2024-06-29 20:21:45 +08:00
True Zhuanjia 93f1a2b4fb prevent default layout from being changed, fix fake layout change bug 2024-06-29 09:27:24 +08:00
True Zhuanjia a156962259 current profile UILabel 2024-06-28 19:32:15 +08:00
True Zhuanjia 9d8fbf789f iphone OSC profile manager transparency improvements 2024-06-28 14:22:35 +08:00
True Zhuanjia 0d91be217b iphone transparency + exit icon 2024-06-28 12:56:19 +08:00
True Zhuanjia 91a8fdc392 OSCLayoutTool Improvements 2024-06-28 10:27:06 +08:00
True Zhuanjia b79a762ce1 enhanced CustomOSC, allow users to rebase OSC in stream view 2024-06-26 03:16:41 +08:00
True Zhuanjia 46770f4588 Merge branch 'onScreenControlStuckFix' into CustomOSC-Integration 2024-06-25 02:11:12 +08:00
True Zhuanjia aae87d1684 fix onscreenControl Stuck 2024-06-25 02:08:43 +08:00
True Zhuanjia 0bac5f1ae9 CustomOSC first integration 2024-06-24 23:15:55 +08:00
True Zhuanjia 457a4e8725 Merge commit 'CustomOSCRebase' into CustomOSC-Integration
# Conflicts:
#	Limelight/ViewControllers/SettingsViewController.h
#	Limelight/ViewControllers/SettingsViewController.m
#	Limelight/iPad.storyboard
#	Limelight/iPhone.storyboard
2024-06-24 18:55:20 +08:00
True Zhuanjia a5c8224023 add mouse pointer velocity tweak for trackpad (relative) mode 2024-06-23 21:31:18 +08:00
True Zhuanjia 001deeee66 update annotation 2024-06-21 20:16:23 +08:00
True Zhuanjia 7847299c77 minior change 2024-06-21 01:04:50 +08:00
True Zhuanjia ac77447066 minor change 2024-06-20 23:32:48 +08:00
True Zhuanjia f09c7be18d NativeTouchHandler improvement, general coding improvements. 2024-06-20 22:52:38 +08:00
True Zhuanjia ee4eb35fe3 improved coding for native touch 2024-06-20 11:51:46 +08:00
True Zhuanjia 8834f492b4 tweak RIGHTCLICK_TAP_DOWN_TIME_THRESHOLD_S for relative touch 2024-06-18 13:03:01 +08:00
True Zhuanjia f2cf788d86 minor change 2024-06-18 02:25:08 +08:00
True Zhuanjia c3c7e670fc resolve gesture competition in non-native touch modes 2024-06-18 02:12:35 +08:00
True Zhuanjia c3d44647f9 CustomTapGestureRecognizer improvement 2024-06-17 22:56:55 +08:00
True Zhuanjia 115d540b7d UI string improvement 2024-06-17 22:19:58 +08:00
True Zhuanjia ec4299fd88 Right click gesture & scroll gesture improvement in relative touch (trackpad) mode;
CustomTapGestureRecognizer improvement.
2024-06-17 21:45:11 +08:00
True Zhuanjia c6785e3728 minor improvement 2024-06-17 01:10:15 +08:00
True Zhuanjia b2e4d45025 minor change 2024-06-17 00:33:26 +08:00
True Zhuanjia 4170376945 del unnecessary files 2024-06-17 00:26:29 +08:00
True Zhuanjia 01d2a937ca update annotation 2024-06-17 00:20:39 +08:00
True Zhuanjia 4e3be8513d recover absolute touch related code. StreamFrameViewController gesture recovered 2024-06-17 00:03:46 +08:00
True Zhuanjia c8f0d8bf0b recover absolute touch related codes 2024-06-16 23:42:07 +08:00
True Zhuanjia bfe02ed6a3 minior change 2024-06-16 22:56:53 +08:00
True Zhuanjia 3442df0ca4 remove deprecated codes in StreamView.m 2024-06-16 22:15:58 +08:00
True Zhuanjia b85a1e9c07 minor change 2024-06-16 21:01:28 +08:00
True Zhuanjia e7fd911fb8 Refactor NativeTouchHandler, restore absolute touch, Bug fixes 2024-06-16 15:48:55 +08:00
True Zhuanjia d673c4900c more & higher bit rate options. 2024-06-15 21:19:46 +08:00
True Zhuanjia 49fb6dbbd6 pointerVelocityMD_defaultChange 2024-06-14 19:08:26 +08:00
True Zhuanjia 5c88787401 fix null relative coordinate when pointerVelocityfactor is 100% 2024-06-12 09:16:17 +08:00
True Zhuanjia 873269e410 Native touch oriented improvements 2024-06-01 16:37:04 +08:00
Long Le 6740a7130b includes on screen controls 2023-06-27 14:13:05 -07:00
1009 changed files with 53709 additions and 143043 deletions
Vendored
BIN
View File
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
BasedOnStyle: Chromium
AlignAfterOpenBracket: true
AlignOperands: false
AllowAllParametersOfDeclarationOnNextLine: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeTernaryOperators: true
ColumnLimit: 160
ContinuationIndentWidth: 4
DerivePointerAlignment: false
IndentWidth: 4
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PointerBindsToType: false
PointerAlignment: Right
SpacesInContainerLiterals: true
TabWidth: 4
UseTab: Never
+30
View File
@@ -0,0 +1,30 @@
name: Build VoidLink IPA
on:
push:
branches: [ "feat/frame_pacing_metal" ]
jobs:
build:
name: Build VoidLink IPA
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Checkout submodules
run: "git submodule update --init --recursive"
- name: Build VoidLink
run: |
xcodebuild -verbose -scheme VoidLink -sdk iphoneos -configuration Debug archive -archivePath $PWD/build/VoidLink.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
mkdir -p ./build/IPA && mkdir -p ./build/IPA/Payload
cp -r ./build/VoidLink.xcarchive/Products/Applications/VoidLink.app ./build/IPA/Payload/VoidLink.app
cd ./build/IPA && zip -r ../../build/VoidLink.ipa ./Payload/* && cd ../..
- name: Upload VoidLink IPA to Artifacts
uses: actions/upload-artifact@v4
with:
name: VoidLink.ipa
path: build/VoidLink.ipa
+3
View File
@@ -1,6 +1,9 @@
**/xcuserdata/
**/Moonlight.xcscmblueprint
**/xcschemes/*.xcscheme
.DS_Store
Build
DerivedData
*.xccheckout
.github
README.md
+4 -1
View File
@@ -1,6 +1,9 @@
[submodule "moonlight-common/moonlight-common-c"]
path = moonlight-common/moonlight-common-c
url = https://github.com/moonlight-stream/moonlight-common-c.git
url=https://github.com/TrueZhuangJia/voidlink-c.git
[submodule "X1Kit"]
path = X1Kit
url = https://github.com/cgutman/X1Kit.git
[submodule "third_party/imgui"]
path = third_party/imgui
url = https://github.com/ocornut/imgui.git
+78
View File
@@ -0,0 +1,78 @@
// dear imgui: Renderer Backend for Metal
// This needs to be used along with a Platform Backend (e.g. OSX)
// Implemented features:
// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
//-----------------------------------------------------------------------------
// ObjC API
//-----------------------------------------------------------------------------
#ifdef __OBJC__
@class MTLRenderPassDescriptor;
@protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder;
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id<MTLDevice> device);
IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor);
IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData,
id<MTLCommandBuffer> commandBuffer,
id<MTLRenderCommandEncoder> commandEncoder);
// Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device);
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex);
#endif
//-----------------------------------------------------------------------------
// C++ API
//-----------------------------------------------------------------------------
// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file
// More info about using Metal from C++: https://developer.apple.com/metal/cpp/
#ifdef IMGUI_IMPL_METAL_CPP
#include <Metal/Metal.hpp>
#ifndef __OBJC__
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device);
IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor);
IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
MTL::CommandBuffer* commandBuffer,
MTL::RenderCommandEncoder* commandEncoder);
// Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device);
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex);
#endif
#endif
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE
+657
View File
@@ -0,0 +1,657 @@
// dear imgui: Renderer Backend for Metal
// This needs to be used along with a Platform Backend (e.g. OSX)
// Implemented features:
// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture().
// 2025-02-03: Metal: Crash fix. (#8367)
// 2024-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419).
// 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'.
// 2022-07-05: Metal: Add dispatch synchronization.
// 2022-06-30: Metal: Use __bridge for ARC based systems.
// 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler.
// 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
// 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code).
// 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file.
// 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464)
// 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer.
// 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst.
// 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
// 2018-07-05: Metal: Added new Metal backend implementation.
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_metal.h"
#import <time.h>
#import <Metal/Metal.h>
#pragma mark - Support classes
// A wrapper around a MTLBuffer object that knows the last time it was reused
@interface MetalBuffer : NSObject
@property (nonatomic, strong) id<MTLBuffer> buffer;
@property (nonatomic, assign) double lastReuseTime;
- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer;
@end
// An object that encapsulates the data necessary to uniquely identify a
// render pipeline state. These are used as cache keys.
@interface FramebufferDescriptor : NSObject<NSCopying>
@property (nonatomic, assign) unsigned long sampleCount;
@property (nonatomic, assign) MTLPixelFormat colorPixelFormat;
@property (nonatomic, assign) MTLPixelFormat depthPixelFormat;
@property (nonatomic, assign) MTLPixelFormat stencilPixelFormat;
- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor;
@end
@interface MetalTexture : NSObject
@property (nonatomic, strong) id<MTLTexture> metalTexture;
- (instancetype)initWithTexture:(id<MTLTexture>)metalTexture;
@end
// A singleton that stores long-lived objects that are needed by the Metal
// renderer backend. Stores the render pipeline state cache and the default
// font texture, and manages the reusable buffer cache.
@interface MetalContext : NSObject
@property (nonatomic, strong) id<MTLDevice> device;
@property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState;
@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient
@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
@property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache;
@property (nonatomic, assign) double lastBufferCachePurge;
- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device;
- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device;
@end
struct ImGui_ImplMetal_Data
{
MetalContext* SharedMetalContext;
ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); }
};
static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; }
static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); }
// XXX Work around missing clock symbols on iOS. The NSDate version is the method this function used previously.
#if defined(CLOCK_UPTIME_RAW)
static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); }
#else
static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)[NSDate date].timeIntervalSince1970; }
#endif
#ifdef IMGUI_IMPL_METAL_CPP
#pragma mark - Dear ImGui Metal C++ Backend API
bool ImGui_ImplMetal_Init(MTL::Device* device)
{
return ImGui_ImplMetal_Init((__bridge id<MTLDevice>)(device));
}
void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor)
{
ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor));
}
void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
MTL::CommandBuffer* commandBuffer,
MTL::RenderCommandEncoder* commandEncoder)
{
ImGui_ImplMetal_RenderDrawData(draw_data,
(__bridge id<MTLCommandBuffer>)(commandBuffer),
(__bridge id<MTLRenderCommandEncoder>)(commandEncoder));
}
bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device)
{
return ImGui_ImplMetal_CreateDeviceObjects((__bridge id<MTLDevice>)(device));
}
#endif // #ifdef IMGUI_IMPL_METAL_CPP
#pragma mark - Dear ImGui Metal Backend API
bool ImGui_ImplMetal_Init(id<MTLDevice> device)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_metal";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
bd->SharedMetalContext = [[MetalContext alloc] init];
bd->SharedMetalContext.device = device;
return true;
}
void ImGui_ImplMetal_Shutdown()
{
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
IM_UNUSED(bd);
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGui_ImplMetal_DestroyDeviceObjects();
ImGui_ImplMetal_DestroyBackendData();
ImGuiIO& io = ImGui::GetIO();
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
}
void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor)
{
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?");
#ifdef IMGUI_IMPL_METAL_CPP
bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease];
#else
bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor];
#endif
if (bd->SharedMetalContext.depthStencilState == nil)
ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device);
}
static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer,
id<MTLRenderCommandEncoder> commandEncoder, id<MTLRenderPipelineState> renderPipelineState,
MetalBuffer* vertexBuffer, size_t vertexBufferOffset)
{
IM_UNUSED(commandBuffer);
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
[commandEncoder setCullMode:MTLCullModeNone];
[commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState];
// Setup viewport, orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to
// draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
MTLViewport viewport =
{
.originX = 0.0,
.originY = 0.0,
.width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x),
.height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y),
.znear = 0.0,
.zfar = 1.0
};
[commandEncoder setViewport:viewport];
float L = draw_data->DisplayPos.x;
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
float T = draw_data->DisplayPos.y;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
float N = (float)viewport.znear;
float F = (float)viewport.zfar;
const float ortho_projection[4][4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
{ 0.0f, 0.0f, 1/(F-N), 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f },
};
[commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1];
[commandEncoder setRenderPipelineState:renderPipelineState];
[commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0];
[commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0];
}
// Metal Render function.
void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
{
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
MetalContext* ctx = bd->SharedMetalContext;
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0)
return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplMetal_UpdateTexture(tex);
// Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame
// The hit rate for this cache should be very near 100%.
id<MTLRenderPipelineState> renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor];
if (renderPipelineState == nil)
{
// No luck; make a new render pipeline state
renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device];
// Cache render pipeline state for later reuse
ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState;
}
size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert);
size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx);
MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device];
MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device];
ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0);
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
// Render command lists
size_t vertexBufferOffset = 0;
size_t indexBufferOffset = 0;
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* draw_list = draw_data->CmdLists[n];
memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset);
else
pcmd->UserCallback(draw_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
// Clamp to viewport as setScissorRect() won't accept values that are off bounds
if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; }
if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; }
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this
continue;
// Apply scissor/clipping rectangle
MTLScissorRect scissorRect =
{
.x = NSUInteger(clip_min.x),
.y = NSUInteger(clip_min.y),
.width = NSUInteger(clip_max.x - clip_min.x),
.height = NSUInteger(clip_max.y - clip_min.y)
};
[commandEncoder setScissorRect:scissorRect];
// Bind texture, Draw
if (ImTextureID tex_id = pcmd->GetTexID())
[commandEncoder setFragmentTexture:(__bridge id<MTLTexture>)(void*)(intptr_t)(tex_id) atIndex:0];
[commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0];
[commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
indexCount:pcmd->ElemCount
indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
indexBuffer:indexBuffer.buffer
indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)];
}
}
vertexBufferOffset += (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert);
indexBufferOffset += (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx);
}
MetalContext* sharedMetalContext = bd->SharedMetalContext;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>)
{
dispatch_async(dispatch_get_main_queue(), ^{
@synchronized(sharedMetalContext.bufferCache)
{
[sharedMetalContext.bufferCache addObject:vertexBuffer];
[sharedMetalContext.bufferCache addObject:indexBuffer];
}
});
}];
}
static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex)
{
MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData);
if (backend_tex == nullptr)
return;
IM_ASSERT(backend_tex.metalTexture == (__bridge id<MTLTexture>)(void*)(intptr_t)tex->TexID);
backend_tex.metalTexture = nil;
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex)
{
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// We are retrieving and uploading the font atlas as a 4-channels RGBA texture here.
// In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth.
// However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures.
// You can make that change in your implementation.
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
width:(NSUInteger)tex->Width
height:(NSUInteger)tex->Height
mipmapped:NO];
textureDescriptor.usage = MTLTextureUsageShaderRead;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
textureDescriptor.storageMode = MTLStorageModeManaged;
#else
textureDescriptor.storageMode = MTLStorageModeShared;
#endif
id <MTLTexture> texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor];
[texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4];
MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture];
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)texture);
tex->SetStatus(ImTextureStatus_OK);
tex->BackendUserData = (__bridge_retained void*)(backend_tex);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData);
for (ImTextureRect& r : tex->Updates)
{
[backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h)
mipmapLevel:0
withBytes:tex->GetPixelsAt(r.x, r.y)
bytesPerRow:(NSUInteger)tex->Width * 4];
}
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
{
ImGui_ImplMetal_DestroyTexture(tex);
}
}
bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
{
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
depthStencilDescriptor.depthWriteEnabled = NO;
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
#ifdef IMGUI_IMPL_METAL_CPP
[depthStencilDescriptor release];
#endif
return true;
}
void ImGui_ImplMetal_DestroyDeviceObjects()
{
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplMetal_DestroyTexture(tex);
[bd->SharedMetalContext.renderPipelineStateCache removeAllObjects];
}
#pragma mark - MetalBuffer implementation
@implementation MetalBuffer
- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer
{
if ((self = [super init]))
{
_buffer = buffer;
_lastReuseTime = GetMachAbsoluteTimeInSeconds();
}
return self;
}
@end
#pragma mark - FramebufferDescriptor implementation
@implementation FramebufferDescriptor
- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor
{
if ((self = [super init]))
{
_sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount;
_colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat;
_depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat;
_stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat;
}
return self;
}
- (nonnull id)copyWithZone:(nullable NSZone*)zone
{
FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init];
copy.sampleCount = self.sampleCount;
copy.colorPixelFormat = self.colorPixelFormat;
copy.depthPixelFormat = self.depthPixelFormat;
copy.stencilPixelFormat = self.stencilPixelFormat;
return copy;
}
- (NSUInteger)hash
{
NSUInteger sc = _sampleCount & 0x3;
NSUInteger cf = _colorPixelFormat & 0x3FF;
NSUInteger df = _depthPixelFormat & 0x3FF;
NSUInteger sf = _stencilPixelFormat & 0x3FF;
NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc;
return hash;
}
- (BOOL)isEqual:(id)object
{
FramebufferDescriptor* other = object;
if (![other isKindOfClass:[FramebufferDescriptor class]])
return NO;
return other.sampleCount == self.sampleCount &&
other.colorPixelFormat == self.colorPixelFormat &&
other.depthPixelFormat == self.depthPixelFormat &&
other.stencilPixelFormat == self.stencilPixelFormat;
}
@end
#pragma mark - MetalTexture implementation
@implementation MetalTexture
- (instancetype)initWithTexture:(id<MTLTexture>)metalTexture
{
if ((self = [super init]))
self.metalTexture = metalTexture;
return self;
}
@end
#pragma mark - MetalContext implementation
@implementation MetalContext
- (instancetype)init
{
if ((self = [super init]))
{
self.renderPipelineStateCache = [NSMutableDictionary dictionary];
self.bufferCache = [NSMutableArray array];
_lastBufferCachePurge = GetMachAbsoluteTimeInSeconds();
}
return self;
}
- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device
{
uint64_t now = GetMachAbsoluteTimeInSeconds();
@synchronized(self.bufferCache)
{
// Purge old buffers that haven't been useful for a while
if (now - self.lastBufferCachePurge > 1.0)
{
NSMutableArray* survivors = [NSMutableArray array];
for (MetalBuffer* candidate in self.bufferCache)
if (candidate.lastReuseTime > self.lastBufferCachePurge)
[survivors addObject:candidate];
self.bufferCache = [survivors mutableCopy];
self.lastBufferCachePurge = now;
}
// See if we have a buffer we can reuse
MetalBuffer* bestCandidate = nil;
for (MetalBuffer* candidate in self.bufferCache)
if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime))
bestCandidate = candidate;
if (bestCandidate != nil)
{
[self.bufferCache removeObject:bestCandidate];
bestCandidate.lastReuseTime = now;
return bestCandidate;
}
}
// No luck; make a new buffer
id<MTLBuffer> backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared];
return [[MetalBuffer alloc] initWithBuffer:backing];
}
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device
{
NSError* error = nil;
NSString* shaderSource = @""
"#include <metal_stdlib>\n"
"using namespace metal;\n"
"\n"
"struct Uniforms {\n"
" float4x4 projectionMatrix;\n"
"};\n"
"\n"
"struct VertexIn {\n"
" float2 position [[attribute(0)]];\n"
" float2 texCoords [[attribute(1)]];\n"
" uchar4 color [[attribute(2)]];\n"
"};\n"
"\n"
"struct VertexOut {\n"
" float4 position [[position]];\n"
" float2 texCoords;\n"
" float4 color;\n"
"};\n"
"\n"
"vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n"
" constant Uniforms &uniforms [[buffer(1)]]) {\n"
" VertexOut out;\n"
" out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n"
" out.texCoords = in.texCoords;\n"
" out.color = float4(in.color) / float4(255.0);\n"
" return out;\n"
"}\n"
"\n"
"fragment half4 fragment_main(VertexOut in [[stage_in]],\n"
" texture2d<half, access::sample> texture [[texture(0)]]) {\n"
" constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n"
" half4 texColor = texture.sample(linearSampler, in.texCoords);\n"
" return half4(in.color) * texColor;\n"
"}\n";
id<MTLLibrary> library = [device newLibraryWithSource:shaderSource options:nil error:&error];
if (library == nil)
{
NSLog(@"Error: failed to create Metal library: %@", error);
return nil;
}
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
if (vertexFunction == nil || fragmentFunction == nil)
{
NSLog(@"Error: failed to find Metal shader functions in library: %@", error);
return nil;
}
MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos);
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv);
vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords
vertexDescriptor.attributes[1].bufferIndex = 0;
vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col);
vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color
vertexDescriptor.attributes[2].bufferIndex = 0;
vertexDescriptor.layouts[0].stepRate = 1;
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert);
MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineDescriptor.vertexFunction = vertexFunction;
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.vertexDescriptor = vertexDescriptor;
pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount;
pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat;
pipelineDescriptor.colorAttachments[0].blendingEnabled = YES;
pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat;
pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat;
id<MTLRenderPipelineState> renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
if (error != nil)
NSLog(@"Error: failed to create Metal pipeline state: %@", error);
return renderPipelineState;
}
@end
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-13
View File
@@ -1,13 +0,0 @@
//
// ComputerScrollView.h
// Moonlight
//
// Created by Diego Waxemberg on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ComputerScrollView : UIScrollView
@end
-28
View File
@@ -1,28 +0,0 @@
//
// ComputerScrollView.m
// Moonlight
//
// Created by Diego Waxemberg on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "ComputerScrollView.h"
@implementation ComputerScrollView
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:[UIButton class]]) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
@end
-51
View File
@@ -1,51 +0,0 @@
//
// DataManager.h
// Moonlight
//
// Created by Diego Waxemberg on 10/28/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "AppDelegate.h"
#import "TemporaryHost.h"
#import "TemporaryApp.h"
#import "TemporarySettings.h"
@interface DataManager : NSObject
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
framerate:(NSInteger)framerate
height:(NSInteger)height
width:(NSInteger)width
audioConfig:(NSInteger)audioConfig
onscreenControls:(NSInteger)onscreenControls
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
swipeExitScreenEdge:(NSInteger)swipeExitScreenEdge
swipeToExitDistance:(CGFloat)swipeToExitDistance
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
showKeyboardToolbar:(BOOL)showKeyboardToolbar
optimizeGames:(BOOL)optimizeGames
multiController:(BOOL)multiController
swapABXYButtons:(BOOL)swapABXYButtons
audioOnPC:(BOOL)audioOnPC
preferredCodec:(uint32_t)preferredCodec
useFramePacing:(BOOL)useFramePacing
enableHdr:(BOOL)enableHdr
btMouseSupport:(BOOL)btMouseSupport
absoluteTouchMode:(BOOL)absoluteTouchMode
statsOverlay:(BOOL)statsOverlay;
- (NSArray*) getHosts;
- (void) updateHost:(TemporaryHost*)host;
- (void) updateAppsForExistingHost:(TemporaryHost *)host;
- (void) removeHost:(TemporaryHost*)host;
- (void) removeApp:(TemporaryApp*)app;
- (TemporarySettings*) getSettings;
- (void) updateUniqueId:(NSString*)uniqueId;
- (NSString*) getUniqueId;
@end
-48
View File
@@ -1,48 +0,0 @@
//
// TemporarySettings.h
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "Settings+CoreDataClass.h"
@interface TemporarySettings : NSObject
@property (nonatomic, retain) Settings * parent;
@property (nonatomic, retain) NSNumber * bitrate;
@property (nonatomic, retain) NSNumber * framerate;
@property (nonatomic, retain) NSNumber * height;
@property (nonatomic, retain) NSNumber * width;
@property (nonatomic, retain) NSNumber * audioConfig;
@property (nonatomic, retain) NSNumber * onscreenControls;
@property (nonatomic, retain) NSNumber * keyboardToggleFingers;
@property (nonatomic, retain) NSNumber * swipeExitScreenEdge;
@property (nonatomic, retain) NSNumber * swipeToExitDistance;
@property (nonatomic, retain) NSNumber * touchPointerVelocityFactor;
@property (nonatomic, retain) NSNumber * pointerVelocityModeDivider;
@property (nonatomic, retain) NSString * uniqueId;
@property (nonatomic) enum {
CODEC_PREF_AUTO,
CODEC_PREF_H264,
CODEC_PREF_HEVC,
CODEC_PREF_AV1,
} preferredCodec;
@property (nonatomic) BOOL useFramePacing;
@property (nonatomic) BOOL multiController;
@property (nonatomic) BOOL swapABXYButtons;
@property (nonatomic) BOOL playAudioOnPC;
@property (nonatomic) BOOL optimizeGames;
@property (nonatomic) BOOL enableHdr;
@property (nonatomic) BOOL btMouseSupport;
@property (nonatomic) BOOL absoluteTouchMode;
@property (nonatomic) BOOL statsOverlay;
@property (nonatomic) BOOL liftStreamViewForKeyboard;
@property (nonatomic) BOOL showKeyboardToolbar;
- (id) initFromSettings:(Settings*)settings;
@end
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

-122
View File
@@ -1,122 +0,0 @@
//
// AbsoluteTouchHandler.m
// Moonlight
//
// Created by Cameron Gutman on 11/1/20.
// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved.
//
#import "AbsoluteTouchHandler.h"
#include <Limelight.h>
// How long the fingers must be stationary to start a right click
#define LONG_PRESS_ACTIVATION_DELAY 0.650f
// How far the finger can move before it cancels a right click
#define LONG_PRESS_ACTIVATION_DELTA 0.01f
// How long the double tap deadzone stays in effect between touch up and touch down
#define DOUBLE_TAP_DEAD_ZONE_DELAY 0.250f
// How far the finger can move before it can override the double tap deadzone
#define DOUBLE_TAP_DEAD_ZONE_DELTA 0.025f
@implementation AbsoluteTouchHandler {
StreamView* view;
NSTimer* longPressTimer;
UITouch* lastTouchDown;
CGPoint lastTouchDownLocation;
UITouch* lastTouchUp;
CGPoint lastTouchUpLocation;
}
- (id)initWithView:(StreamView*)view {
self = [self init];
self->view = view;
return self;
}
//- (void)onLongPressStart:(NSTimer*)timer {
// // Raise the left click and start a right click
// LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
// LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
//}
//
//- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// // Ignore touch down events with more than one finger
// return;
// if ([[event allTouches] count] > 1) {
// return;
// }
//
// UITouch* touch = [touches anyObject];
// CGPoint touchLocation = [touch locationInView:view];
//
// // Don't reposition for finger down events within the deadzone. This makes double-clicking easier.
// if (touch.timestamp - lastTouchUp.timestamp > DOUBLE_TAP_DEAD_ZONE_DELAY ||
// sqrt(pow((touchLocation.x / view.bounds.size.width) - (lastTouchUpLocation.x / view.bounds.size.width), 2) +
// pow((touchLocation.y / view.bounds.size.height) - (lastTouchUpLocation.y / view.bounds.size.height), 2)) > DOUBLE_TAP_DEAD_ZONE_DELTA) {
// [view updateCursorLocation:touchLocation isMouse:NO];
// }
//
// // Press the left button down
// LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
//
// // Start the long press timer
// longPressTimer = [NSTimer scheduledTimerWithTimeInterval:LONG_PRESS_ACTIVATION_DELAY
// target:self
// selector:@selector(onLongPressStart:)
// userInfo:nil
// repeats:NO];
//
// lastTouchDown = touch;
// lastTouchDownLocation = touchLocation;
//}
//
//- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// // Ignore touch move events with more than one finger
// return;
// if ([[event allTouches] count] > 1) {
// return;
// }
//
// UITouch* touch = [touches anyObject];
// CGPoint touchLocation = [touch locationInView:view];
//
// if (sqrt(pow((touchLocation.x / view.bounds.size.width) - (lastTouchDownLocation.x / view.bounds.size.width), 2) +
// pow((touchLocation.y / view.bounds.size.height) - (lastTouchDownLocation.y / view.bounds.size.height), 2)) > LONG_PRESS_ACTIVATION_DELTA) {
// // Moved too far since touch down. Cancel the long press timer.
// [longPressTimer invalidate];
// longPressTimer = nil;
// }
//
// [view updateCursorLocation:[[touches anyObject] locationInView:view] isMouse:NO];
//}
//
//- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// // Only fire this logic if all touches have ended
// if ([[event allTouches] count] == [touches count]) {
// // Cancel the long press timer
// [longPressTimer invalidate];
// longPressTimer = nil;
//
// // Left button up on finger up
// LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
//
// // Raise right button too in case we triggered a long press gesture
// LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
//
// // Remember this last touch for touch-down deadzoning
// lastTouchUp = [touches anyObject];
// lastTouchUpLocation = [lastTouchUp locationInView:view];
// }
//}
//
//- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
// // Treat this as a normal touchesEnded event
// [self touchesEnded:touches withEvent:event];
//}
@end
@@ -1,22 +0,0 @@
//
// CustomTapGestureRecognizer.h
// Moonlight
//
// Created by ZWM on 2024/5/15.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#ifndef CustomTapGestureRecognizer_h
#define CustomTapGestureRecognizer_h
@interface CustomTapGestureRecognizer : UIGestureRecognizer
@property (nonatomic, assign) uint8_t numberOfTouchesRequired;
@property (nonatomic, assign) double tapDownTimeThreshold;
@property (atomic, readonly) CGFloat lowestTouchPointHeight;
@end
#endif /* CustomTapGestureRecognizer_h */
@@ -1,57 +0,0 @@
//
// CustomTapGestureRecognizer.m
// Moonlight
//
// Created by ZWM on 2024/5/15.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "CustomTapGestureRecognizer.h"
@implementation CustomTapGestureRecognizer
static NSTimeInterval multiFingerDownTime;
static bool multiFingerDown;
static CGFloat screenHeightInPoints;
static CGFloat lowestTouchPointYCoord = 0.0;
- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action {
self = [super initWithTarget:target action:action];
screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]);
return self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// Check if the number of touches and taps meets the required criteria
if ([[event allTouches] count] == _numberOfTouchesRequired) {
multiFingerDownTime = CACurrentMediaTime();
multiFingerDown = true;
for(UITouch *touch in [event allTouches]){
if(lowestTouchPointYCoord < [touch locationInView:self.view].y) lowestTouchPointYCoord = [touch locationInView:self.view].y;
}
_lowestTouchPointHeight = screenHeightInPoints - lowestTouchPointYCoord;
self.state = UIGestureRecognizerStatePossible;
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// [super touchesEnded:touches withEvent:event];
if(multiFingerDown && [[event allTouches] count] == [touches count]){
multiFingerDown = false;
if((CACurrentMediaTime() - multiFingerDownTime) < _tapDownTimeThreshold / 1000){
lowestTouchPointYCoord = 0.0; //reset for next recognition
self.state = UIGestureRecognizerStateRecognized;
}
}
}
+ (CGFloat)lowestTouchPointHeight{
@synchronized (self){
return self.lowestTouchPointHeight;
}
}
@end
@@ -1,4 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
-34
View File
@@ -1,34 +0,0 @@
//
// NativeTouchHandler.h
// Moonlight
//
// Created by ZWM on 2024/5/14.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import "StreamView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TouchPointer : NSObject
+ (void)initContextWith:(StreamView *)view;
+ (void)populatePointerId:(UITouch*)touch;
+ (void)removePointerId:(UITouch*)touch;
+ (uint32_t) retrievePointerIdFromDict:(UITouch*)touch;
+ (void)setPointerVelocityDivider:(CGFloat)dividerLocation;
+ (void)setPointerVelocityFactor:(CGFloat)velocityFactor;
+ (void)populatePointerObjIntoDict:(UITouch*)touch;
+ (void)removePointerObjFromDict:(UITouch*)touch;
+ (void)updatePointerObjInDict:(UITouch *)touch;
+ (CGPoint)selectCoordsFor:(UITouch *)touch;
- (instancetype)initWith:(UITouch *)touch;
@end
NS_ASSUME_NONNULL_END
-139
View File
@@ -1,139 +0,0 @@
//
// NativeTouchHandler.m
// Moonlight
//
// Created by ZWM on 2024/5/14.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "NativeTouchHandler.h"
#include <Limelight.h>
// Use a Dictionary to store UITouch object's memory address as key, and pointerId as value,字典存放UITouch对象地址和pointerId映射关系
// pointerId will be generated from electronic noise, by arc4_random, pointerId,由随机噪声生成
// Use a NSSet store pointerId, for quick repeition inquiry, NSSet存放活跃的pointerId合集,用于快速查找,以防重复.
static NSMutableDictionary *pointerIdDict; //pointerId Dict for native touch.
static NSMutableSet<NSNumber *> *pointerIdSet; //pointerIdSet for native touch.
static NSMutableDictionary *pointerDict;
static CGFloat pointerVelocityFactor;
static CGFloat pointerVelocityDivider;
static CGFloat pointerVelocityDividerLocation;
StreamView *streamView;
@implementation TouchPointer{
CGPoint initialPoint;
CGPoint latestPoint;
CGPoint previousPoint;
CGPoint latestRelativePoint;
CGPoint previousRelativePoint;
CGFloat velocityX;
CGFloat velocityY;
}
+ (void)setPointerVelocityDivider:(CGFloat)dividerLocation{
pointerVelocityDivider = dividerLocation;
}
+ (void)setPointerVelocityFactor:(CGFloat)velocityFactor{
pointerVelocityFactor = velocityFactor;
}
- (instancetype)initWith:(UITouch *)touch{
self = [self init];
self->initialPoint = [touch locationInView:streamView];
self->latestPoint = self->initialPoint;
self->latestRelativePoint = self->initialPoint;
return self;
}
- (void)updatePointerCoords:(UITouch *)touch{
previousPoint = latestPoint;
latestPoint = [touch locationInView:streamView];
if (pointerVelocityFactor != 1.0f){
velocityX = latestPoint.x - previousPoint.x;
velocityY = latestPoint.y - previousPoint.y;
previousRelativePoint = latestRelativePoint;
latestRelativePoint.x = previousRelativePoint.x + pointerVelocityFactor * velocityX;
latestRelativePoint.y = previousRelativePoint.y + pointerVelocityFactor * velocityY;
}
}
+ (void)initContextWith:(StreamView *)view{
streamView = view;
pointerIdDict = [NSMutableDictionary dictionary];
pointerIdSet = [NSMutableSet set];
pointerDict = [NSMutableDictionary dictionary];
pointerVelocityDividerLocation = CGRectGetWidth([[UIScreen mainScreen] bounds]) * pointerVelocityDivider;
NSLog(@"pointerVelocityDivider: %.2f", pointerVelocityDivider);
NSLog(@"pointerVelocityDividerLocation: %.2f", pointerVelocityDividerLocation);
}
+ (void)populatePointerObjIntoDict:(UITouch*)touch{
[pointerDict setObject:[[TouchPointer alloc] initWith:touch] forKey:@((uint64_t)touch)];
}
+ (void)removePointerObjFromDict:(UITouch*)touch{
uint64_t eventAddrValue = (uint64_t)touch;
TouchPointer* pointer = [pointerDict objectForKey:@(eventAddrValue)];
if(pointer != nil){
[pointerDict removeObjectForKey:@(eventAddrValue)];
}
}
+ (void)updatePointerObjInDict:(UITouch *)touch{
[[pointerDict objectForKey:@((uint64_t)touch)] updatePointerCoords:touch];
}
+ (CGPoint)selectCoordsFor:(UITouch *)touch{
TouchPointer *pointer = [pointerDict objectForKey:@((uint64_t)touch)];
if((pointer -> initialPoint).x > pointerVelocityDividerLocation){ //if first contact coords locates on the right side of divider.
return pointer->latestRelativePoint;
}
return [touch locationInView:streamView];
}
// 随机生成pointerId并填入NSDict和NSSet
// generate & populate pointerId into NSDict & NSSet, called in UITouchPhaseBegan
+ (void)populatePointerId:(UITouch*)touch{
uint64_t eventAddrValue = (uint64_t)touch;
uint32_t randomPointerId = arc4random_uniform(UINT32_MAX); // generate pointerId from eletronic noise.
for(;;){
if([pointerIdSet containsObject:@(randomPointerId)]) randomPointerId = arc4random_uniform(UINT32_MAX); // in case of new pointerId collides with existing ones, generate again.
else{ // populate pointerId into NSDict & NSSet.
[pointerIdDict setObject:@(randomPointerId) forKey:@(eventAddrValue)];
[pointerIdSet addObject:@(randomPointerId)];
return;
}
}
}
// remove pointerId in UITouchPhaseEnded condition
+ (void)removePointerId:(UITouch*)touch{
uint64_t eventAddrValue = (uint64_t)touch;
NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(eventAddrValue)];
if(pointerIdObj != nil){
[pointerIdSet removeObject:pointerIdObj];
[pointerIdDict removeObjectForKey:@(eventAddrValue)];
}
}
// 从字典中获取UITouch事件对应的pointerId
// call this only when NSDcit & NSSet is up-to-date.
+ (uint32_t) retrievePointerIdFromDict:(UITouch*)touch{
return [[pointerIdDict objectForKey:@((uint64_t)touch)] unsignedIntValue];
}
@end
-36
View File
@@ -1,36 +0,0 @@
//
// OnScreenControls.h
// Moonlight
//
// Created by Diego Waxemberg on 12/28/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
@class ControllerSupport;
@class StreamConfiguration;
@interface OnScreenControls : NSObject
typedef NS_ENUM(NSInteger, OnScreenControlsLevel) {
OnScreenControlsLevelOff,
OnScreenControlsLevelAuto,
OnScreenControlsLevelSimple,
OnScreenControlsLevelFull,
// Internal levels selected by ControllerSupport
OnScreenControlsLevelAutoGCGamepad,
OnScreenControlsLevelAutoGCExtendedGamepad,
OnScreenControlsLevelAutoGCExtendedGamepadWithStickButtons
};
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport streamConfig:(StreamConfiguration*)streamConfig;
- (BOOL) handleTouchDownEvent:(NSSet*)touches;
- (BOOL) handleTouchUpEvent:(NSSet*)touches;
- (BOOL) handleTouchMovedEvent:(NSSet*)touches;
- (void) setLevel:(OnScreenControlsLevel)level;
- (OnScreenControlsLevel) getLevel;
- (void) show;
@end
-985
View File
@@ -1,985 +0,0 @@
//
// OnScreenControls.m
// Moonlight
//
// Created by Diego Waxemberg on 12/28/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "OnScreenControls.h"
#import "StreamView.h"
#import "ControllerSupport.h"
#import "Controller.h"
#include "Limelight.h"
#define UPDATE_BUTTON(x, y) (buttonFlags = \
(y) ? (buttonFlags | (x)) : (buttonFlags & ~(x)))
@implementation OnScreenControls {
CALayer* _aButton;
CALayer* _bButton;
CALayer* _xButton;
CALayer* _yButton;
CALayer* _upButton;
CALayer* _downButton;
CALayer* _leftButton;
CALayer* _rightButton;
CALayer* _leftStickBackground;
CALayer* _leftStick;
CALayer* _rightStickBackground;
CALayer* _rightStick;
CALayer* _startButton;
CALayer* _selectButton;
CALayer* _r1Button;
CALayer* _r2Button;
CALayer* _r3Button;
CALayer* _l1Button;
CALayer* _l2Button;
CALayer* _l3Button;
UITouch* _aTouch;
UITouch* _bTouch;
UITouch* _xTouch;
UITouch* _yTouch;
UITouch* _dpadTouch;
UITouch* _lsTouch;
UITouch* _rsTouch;
UITouch* _startTouch;
UITouch* _selectTouch;
UITouch* _r1Touch;
UITouch* _r2Touch;
UITouch* _r3Touch;
UITouch* _l1Touch;
UITouch* _l2Touch;
UITouch* _l3Touch;
NSDate* l3TouchStart;
NSDate* r3TouchStart;
BOOL l3Set;
BOOL r3Set;
BOOL _iPad;
CGRect _controlArea;
UIView* _view;
OnScreenControlsLevel _level;
BOOL _visible;
ControllerSupport *_controllerSupport;
Controller *_controller;
NSMutableArray* _deadTouches;
BOOL _swapABXY;
}
static const float EDGE_WIDTH = .05;
//static const float BUTTON_SIZE = 50;
static const float BUTTON_DIST = 20;
static float BUTTON_CENTER_X;
static float BUTTON_CENTER_Y;
static const float D_PAD_DIST = 10;
static float D_PAD_CENTER_X;
static float D_PAD_CENTER_Y;
static const float DEAD_ZONE_PADDING = 15;
static const double STICK_CLICK_RATE = 100;
static const float STICK_DEAD_ZONE = .1;
static float STICK_INNER_SIZE;
static float STICK_OUTER_SIZE;
static float LS_CENTER_X;
static float LS_CENTER_Y;
static float RS_CENTER_X;
static float RS_CENTER_Y;
static float START_X;
static float START_Y;
static float SELECT_X;
static float SELECT_Y;
static float R1_X;
static float R1_Y;
static float R2_X;
static float R2_Y;
static float R3_X;
static float R3_Y;
static float L1_X;
static float L1_Y;
static float L2_X;
static float L2_Y;
static float L3_X;
static float L3_Y;
- (id) initWithView:(UIView*)view controllerSup:(ControllerSupport*)controllerSupport streamConfig:(StreamConfiguration*)streamConfig {
self = [self init];
_view = view;
_controllerSupport = controllerSupport;
_controller = [controllerSupport getOscController];
_deadTouches = [[NSMutableArray alloc] init];
_swapABXY = streamConfig.swapABXYButtons;
_iPad = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad);
_controlArea = CGRectMake(0, 0, _view.frame.size.width, _view.frame.size.height);
if (_iPad)
{
// Cut down the control area on an iPad so the controls are more reachable
_controlArea.size.height = _view.frame.size.height / 2.0;
_controlArea.origin.y = _view.frame.size.height - _controlArea.size.height;
}
else
{
_controlArea.origin.x = _controlArea.size.width * EDGE_WIDTH;
_controlArea.size.width -= _controlArea.origin.x * 2;
}
_aButton = [CALayer layer];
_bButton = [CALayer layer];
_xButton = [CALayer layer];
_yButton = [CALayer layer];
_upButton = [CALayer layer];
_downButton = [CALayer layer];
_leftButton = [CALayer layer];
_rightButton = [CALayer layer];
_l1Button = [CALayer layer];
_r1Button = [CALayer layer];
_l2Button = [CALayer layer];
_r2Button = [CALayer layer];
_l3Button = [CALayer layer];
_r3Button = [CALayer layer];
_startButton = [CALayer layer];
_selectButton = [CALayer layer];
_leftStickBackground = [CALayer layer];
_rightStickBackground = [CALayer layer];
_leftStick = [CALayer layer];
_rightStick = [CALayer layer];
return self;
}
- (void) show {
_visible = YES;
[self updateControls];
}
- (void) setLevel:(OnScreenControlsLevel)level {
_level = level;
// Only update controls if we're showing, otherwise
// show will do it for us.
if (_visible) {
[self updateControls];
}
}
- (OnScreenControlsLevel) getLevel {
return _level;
}
- (void) updateControls {
switch (_level) {
case OnScreenControlsLevelOff:
[self hideButtons];
[self hideBumpers];
[self hideTriggers];
[self hideStartSelect];
[self hideSticks];
[self hideL3R3];
break;
case OnScreenControlsLevelAutoGCGamepad:
// GCGamepad is missing triggers, both analog sticks,
// and the select button
[self setupGamepadControls];
[self hideButtons];
[self hideBumpers];
[self hideL3R3];
[self drawTriggers];
[self drawStartSelect];
[self drawSticks];
break;
case OnScreenControlsLevelAutoGCExtendedGamepad:
// GCExtendedGamepad is missing R3, L3, and select
[self setupExtendedGamepadControls];
[self hideButtons];
[self hideBumpers];
[self hideTriggers];
[self drawStartSelect];
[self hideSticks];
[self drawL3R3];
break;
case OnScreenControlsLevelAutoGCExtendedGamepadWithStickButtons:
// This variant of GCExtendedGamepad has L3 and R3 but
// is still missing Select
[self setupExtendedGamepadControls];
[self hideButtons];
[self hideBumpers];
[self hideTriggers];
[self hideL3R3];
[self drawStartSelect];
[self hideSticks];
break;
case OnScreenControlsLevelSimple:
[self setupSimpleControls];
[self hideTriggers];
[self hideL3R3];
[self hideBumpers];
[self hideSticks];
[self drawStartSelect];
[self drawButtons];
break;
case OnScreenControlsLevelFull:
[self setupComplexControls];
[self drawButtons];
[self drawStartSelect];
[self drawBumpers];
[self drawTriggers];
[self drawSticks];
[self hideL3R3]; // Full controls don't need these they have the sticks
break;
default:
Log(LOG_W, @"Unknown on-screen controls level: %d", (int)_level);
break;
}
}
// For GCExtendedGamepad controls we move start, select, L3, and R3 to the button
- (void) setupExtendedGamepadControls {
// Start with the default complex layout
[self setupComplexControls];
START_X = _controlArea.size.width * .95 + _controlArea.origin.x;
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_X = _controlArea.size.width * .05 + _controlArea.origin.x;
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L3_Y = _controlArea.size.height * .85 + _controlArea.origin.y;
R3_Y = _controlArea.size.height * .85 + _controlArea.origin.y;
if (_iPad) {
L3_X = _controlArea.size.width * .15 + _controlArea.origin.x;
R3_X = _controlArea.size.width * .85 + _controlArea.origin.x;
}
else {
L3_X = _controlArea.size.width * .25 + _controlArea.origin.x;
R3_X = _controlArea.size.width * .75 + _controlArea.origin.x;
}
}
// For GCGamepad controls we move triggers, start, and select
// to sit right above the analog sticks
- (void) setupGamepadControls {
// Start with the default complex layout
[self setupComplexControls];
L2_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
L2_X = _controlArea.size.width * .05 + _controlArea.origin.x;
R2_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
R2_X = _controlArea.size.width * .95 + _controlArea.origin.x;
START_X = _controlArea.size.width * .95 + _controlArea.origin.x;
START_Y = _controlArea.size.height * .95 + _controlArea.origin.y;
SELECT_X = _controlArea.size.width * .05 + _controlArea.origin.x;
SELECT_Y = _controlArea.size.height * .95 + _controlArea.origin.y;
if (_iPad) {
// The analog sticks are kept closer to the sides on iPad
LS_CENTER_X = _controlArea.size.width * .15 + _controlArea.origin.x;
RS_CENTER_X = _controlArea.size.width * .85 + _controlArea.origin.x;
}
}
// For simple controls we move the triggers and buttons to the bottom
- (void) setupSimpleControls {
// Start with the default complex layout
[self setupComplexControls];
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
R2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
if (_iPad) {
// Lower the D-pad and buttons on iPad
D_PAD_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
BUTTON_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
// Move Start and Select closer to sides
SELECT_X = _controlArea.size.width * .2 + _controlArea.origin.x;
START_X = _controlArea.size.width * .8 + _controlArea.origin.x;
}
else {
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
}
}
- (void) setupComplexControls
{
D_PAD_CENTER_X = _controlArea.size.width * .1 + _controlArea.origin.x;
D_PAD_CENTER_Y = _controlArea.size.height * .60 + _controlArea.origin.y;
BUTTON_CENTER_X = _controlArea.size.width * .9 + _controlArea.origin.x;
BUTTON_CENTER_Y = _controlArea.size.height * .60 + _controlArea.origin.y;
if (_iPad)
{
// The analog sticks are kept closer to the sides on iPad
LS_CENTER_X = _controlArea.size.width * .22 + _controlArea.origin.x;
LS_CENTER_Y = _controlArea.size.height * .80 + _controlArea.origin.y;
RS_CENTER_X = _controlArea.size.width * .77 + _controlArea.origin.x;
RS_CENTER_Y = _controlArea.size.height * .80 + _controlArea.origin.y;
}
else
{
LS_CENTER_X = _controlArea.size.width * .35 + _controlArea.origin.x;
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
RS_CENTER_X = _controlArea.size.width * .65 + _controlArea.origin.x;
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
}
START_X = _controlArea.size.width * .9 + _controlArea.origin.x;
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_X = _controlArea.size.width * .1 + _controlArea.origin.x;
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
L2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
R1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
R2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
if (_iPad) {
// Move L/R buttons closer to the side on iPad
L1_X = _controlArea.size.width * .05 + _controlArea.origin.x;
L2_X = _controlArea.size.width * .05 + _controlArea.origin.x;
R1_X = _controlArea.size.width * .95 + _controlArea.origin.x;
R2_X = _controlArea.size.width * .95 + _controlArea.origin.x;
}
else {
L1_X = _controlArea.size.width * .1 + _controlArea.origin.x;
L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
R1_X = _controlArea.size.width * .9 + _controlArea.origin.x;
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
}
}
- (void) drawButtons {
UIImage* aButtonImage = [UIImage imageNamed:@"AButton"];
UIImage* bButtonImage = [UIImage imageNamed:@"BButton"];
UIImage* xButtonImage = [UIImage imageNamed:@"XButton"];
UIImage* yButtonImage = [UIImage imageNamed:@"YButton"];
CGRect aButtonFrame = CGRectMake(BUTTON_CENTER_X - aButtonImage.size.width / 2, BUTTON_CENTER_Y + BUTTON_DIST, aButtonImage.size.width, aButtonImage.size.height);
CGRect bButtonFrame = CGRectMake(BUTTON_CENTER_X + BUTTON_DIST, BUTTON_CENTER_Y - bButtonImage.size.height / 2, bButtonImage.size.width, bButtonImage.size.height);
CGRect xButtonFrame = CGRectMake(BUTTON_CENTER_X - BUTTON_DIST - xButtonImage.size.width, BUTTON_CENTER_Y - xButtonImage.size.height/ 2, xButtonImage.size.width, xButtonImage.size.height);
CGRect yButtonFrame = CGRectMake(BUTTON_CENTER_X - yButtonImage.size.width / 2, BUTTON_CENTER_Y - BUTTON_DIST - yButtonImage.size.height, yButtonImage.size.width, yButtonImage.size.height);
// create A button
_aButton.contents = (id) aButtonImage.CGImage;
_aButton.frame = _swapABXY ? bButtonFrame : aButtonFrame;
[_view.layer addSublayer:_aButton];
// create B button
_bButton.frame = _swapABXY ? aButtonFrame : bButtonFrame;
_bButton.contents = (id) bButtonImage.CGImage;
[_view.layer addSublayer:_bButton];
// create X Button
_xButton.frame = _swapABXY ? yButtonFrame : xButtonFrame;
_xButton.contents = (id) xButtonImage.CGImage;
[_view.layer addSublayer:_xButton];
// create Y Button
_yButton.frame = _swapABXY ? xButtonFrame : yButtonFrame;
_yButton.contents = (id) yButtonImage.CGImage;
[_view.layer addSublayer:_yButton];
// create Down button
UIImage* downButtonImage = [UIImage imageNamed:@"DownButton"];
_downButton.frame = CGRectMake(D_PAD_CENTER_X - downButtonImage.size.width / 2, D_PAD_CENTER_Y + D_PAD_DIST, downButtonImage.size.width, downButtonImage.size.height);
_downButton.contents = (id) downButtonImage.CGImage;
[_view.layer addSublayer:_downButton];
// create Right button
UIImage* rightButtonImage = [UIImage imageNamed:@"RightButton"];
_rightButton.frame = CGRectMake(D_PAD_CENTER_X + D_PAD_DIST, D_PAD_CENTER_Y - rightButtonImage.size.height / 2, rightButtonImage.size.width, rightButtonImage.size.height);
_rightButton.contents = (id) rightButtonImage.CGImage;
[_view.layer addSublayer:_rightButton];
// create Up button
UIImage* upButtonImage = [UIImage imageNamed:@"UpButton"];
_upButton.frame = CGRectMake(D_PAD_CENTER_X - upButtonImage.size.width / 2, D_PAD_CENTER_Y - D_PAD_DIST - upButtonImage.size.height, upButtonImage.size.width, upButtonImage.size.height);
_upButton.contents = (id) upButtonImage.CGImage;
[_view.layer addSublayer:_upButton];
// create Left button
UIImage* leftButtonImage = [UIImage imageNamed:@"LeftButton"];
_leftButton.frame = CGRectMake(D_PAD_CENTER_X - D_PAD_DIST - leftButtonImage.size.width, D_PAD_CENTER_Y - leftButtonImage.size.height / 2, leftButtonImage.size.width, leftButtonImage.size.height);
_leftButton.contents = (id) leftButtonImage.CGImage;
[_view.layer addSublayer:_leftButton];
}
- (void) drawStartSelect {
// create Start button
UIImage* startButtonImage = [UIImage imageNamed:@"StartButton"];
_startButton.frame = CGRectMake(START_X - startButtonImage.size.width / 2, START_Y - startButtonImage.size.height / 2, startButtonImage.size.width, startButtonImage.size.height);
_startButton.contents = (id) startButtonImage.CGImage;
[_view.layer addSublayer:_startButton];
// create Select button
UIImage* selectButtonImage = [UIImage imageNamed:@"SelectButton"];
_selectButton.frame = CGRectMake(SELECT_X - selectButtonImage.size.width / 2, SELECT_Y - selectButtonImage.size.height / 2, selectButtonImage.size.width, selectButtonImage.size.height);
_selectButton.contents = (id) selectButtonImage.CGImage;
[_view.layer addSublayer:_selectButton];
}
- (void) drawBumpers {
// create L1 button
UIImage* l1ButtonImage = [UIImage imageNamed:@"L1"];
_l1Button.frame = CGRectMake(L1_X - l1ButtonImage.size.width / 2, L1_Y - l1ButtonImage.size.height / 2, l1ButtonImage.size.width, l1ButtonImage.size.height);
_l1Button.contents = (id) l1ButtonImage.CGImage;
[_view.layer addSublayer:_l1Button];
// create R1 button
UIImage* r1ButtonImage = [UIImage imageNamed:@"R1"];
_r1Button.frame = CGRectMake(R1_X - r1ButtonImage.size.width / 2, R1_Y - r1ButtonImage.size.height / 2, r1ButtonImage.size.width, r1ButtonImage.size.height);
_r1Button.contents = (id) r1ButtonImage.CGImage;
[_view.layer addSublayer:_r1Button];
}
- (void) drawTriggers {
// create L2 button
UIImage* l2ButtonImage = [UIImage imageNamed:@"L2"];
_l2Button.frame = CGRectMake(L2_X - l2ButtonImage.size.width / 2, L2_Y - l2ButtonImage.size.height / 2, l2ButtonImage.size.width, l2ButtonImage.size.height);
_l2Button.contents = (id) l2ButtonImage.CGImage;
[_view.layer addSublayer:_l2Button];
// create R2 button
UIImage* r2ButtonImage = [UIImage imageNamed:@"R2"];
_r2Button.frame = CGRectMake(R2_X - r2ButtonImage.size.width / 2, R2_Y - r2ButtonImage.size.height / 2, r2ButtonImage.size.width, r2ButtonImage.size.height);
_r2Button.contents = (id) r2ButtonImage.CGImage;
[_view.layer addSublayer:_r2Button];
}
- (void) drawSticks {
// create left analog stick
UIImage* leftStickBgImage = [UIImage imageNamed:@"StickOuter"];
_leftStickBackground.frame = CGRectMake(LS_CENTER_X - leftStickBgImage.size.width / 2, LS_CENTER_Y - leftStickBgImage.size.height / 2, leftStickBgImage.size.width, leftStickBgImage.size.height);
_leftStickBackground.contents = (id) leftStickBgImage.CGImage;
[_view.layer addSublayer:_leftStickBackground];
UIImage* leftStickImage = [UIImage imageNamed:@"StickInner"];
_leftStick.frame = CGRectMake(LS_CENTER_X - leftStickImage.size.width / 2, LS_CENTER_Y - leftStickImage.size.height / 2, leftStickImage.size.width, leftStickImage.size.height);
_leftStick.contents = (id) leftStickImage.CGImage;
[_view.layer addSublayer:_leftStick];
// create right analog stick
UIImage* rightStickBgImage = [UIImage imageNamed:@"StickOuter"];
_rightStickBackground.frame = CGRectMake(RS_CENTER_X - rightStickBgImage.size.width / 2, RS_CENTER_Y - rightStickBgImage.size.height / 2, rightStickBgImage.size.width, rightStickBgImage.size.height);
_rightStickBackground.contents = (id) rightStickBgImage.CGImage;
[_view.layer addSublayer:_rightStickBackground];
UIImage* rightStickImage = [UIImage imageNamed:@"StickInner"];
_rightStick.frame = CGRectMake(RS_CENTER_X - rightStickImage.size.width / 2, RS_CENTER_Y - rightStickImage.size.height / 2, rightStickImage.size.width, rightStickImage.size.height);
_rightStick.contents = (id) rightStickImage.CGImage;
[_view.layer addSublayer:_rightStick];
STICK_INNER_SIZE = rightStickImage.size.width;
STICK_OUTER_SIZE = rightStickBgImage.size.width;
}
- (void) drawL3R3 {
UIImage* l3ButtonImage = [UIImage imageNamed:@"L3"];
_l3Button.frame = CGRectMake(L3_X - l3ButtonImage.size.width / 2, L3_Y - l3ButtonImage.size.height / 2, l3ButtonImage.size.width, l3ButtonImage.size.height);
_l3Button.contents = (id) l3ButtonImage.CGImage;
_l3Button.cornerRadius = l3ButtonImage.size.width / 2;
_l3Button.borderColor = [UIColor colorWithRed:15.f/255 green:160.f/255 blue:40.f/255 alpha:1.f].CGColor;
[_view.layer addSublayer:_l3Button];
UIImage* r3ButtonImage = [UIImage imageNamed:@"R3"];
_r3Button.frame = CGRectMake(R3_X - r3ButtonImage.size.width / 2, R3_Y - r3ButtonImage.size.height / 2, r3ButtonImage.size.width, r3ButtonImage.size.height);
_r3Button.contents = (id) r3ButtonImage.CGImage;
_r3Button.cornerRadius = r3ButtonImage.size.width / 2;
_r3Button.borderColor = [UIColor colorWithRed:15.f/255 green:160.f/255 blue:40.f/255 alpha:1.f].CGColor;
[_view.layer addSublayer:_r3Button];
}
- (void) hideButtons {
[_aButton removeFromSuperlayer];
[_bButton removeFromSuperlayer];
[_xButton removeFromSuperlayer];
[_yButton removeFromSuperlayer];
[_upButton removeFromSuperlayer];
[_downButton removeFromSuperlayer];
[_leftButton removeFromSuperlayer];
[_rightButton removeFromSuperlayer];
}
- (void) hideStartSelect {
[_startButton removeFromSuperlayer];
[_selectButton removeFromSuperlayer];
}
- (void) hideBumpers {
[_l1Button removeFromSuperlayer];
[_r1Button removeFromSuperlayer];
}
- (void) hideTriggers {
[_l2Button removeFromSuperlayer];
[_r2Button removeFromSuperlayer];
}
- (void) hideSticks {
[_leftStickBackground removeFromSuperlayer];
[_rightStickBackground removeFromSuperlayer];
[_leftStick removeFromSuperlayer];
[_rightStick removeFromSuperlayer];
}
- (void) hideL3R3 {
[_l3Button removeFromSuperlayer];
[_r3Button removeFromSuperlayer];
}
- (BOOL) handleTouchMovedEvent:touches {
BOOL updated = false;
BOOL buttonTouch = false;
float rsMaxX = RS_CENTER_X + STICK_OUTER_SIZE / 2;
float rsMaxY = RS_CENTER_Y + STICK_OUTER_SIZE / 2;
float rsMinX = RS_CENTER_X - STICK_OUTER_SIZE / 2;
float rsMinY = RS_CENTER_Y - STICK_OUTER_SIZE / 2;
float lsMaxX = LS_CENTER_X + STICK_OUTER_SIZE / 2;
float lsMaxY = LS_CENTER_Y + STICK_OUTER_SIZE / 2;
float lsMinX = LS_CENTER_X - STICK_OUTER_SIZE / 2;
float lsMinY = LS_CENTER_Y - STICK_OUTER_SIZE / 2;
for (UITouch* touch in touches) {
CGPoint touchLocation = [touch locationInView:_view];
float xLoc = touchLocation.x;
float yLoc = touchLocation.y;
if (touch == _lsTouch) {
if (xLoc > lsMaxX) xLoc = lsMaxX;
if (xLoc < lsMinX) xLoc = lsMinX;
if (yLoc > lsMaxY) yLoc = lsMaxY;
if (yLoc < lsMinY) yLoc = lsMinY;
_leftStick.frame = CGRectMake(xLoc - STICK_INNER_SIZE / 2, yLoc - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE);
float xStickVal = (xLoc - LS_CENTER_X) / (lsMaxX - LS_CENTER_X);
float yStickVal = (yLoc - LS_CENTER_Y) / (lsMaxY - LS_CENTER_Y);
if (fabsf(xStickVal) < STICK_DEAD_ZONE) xStickVal = 0;
if (fabsf(yStickVal) < STICK_DEAD_ZONE) yStickVal = 0;
[_controllerSupport updateLeftStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal];
updated = true;
} else if (touch == _rsTouch) {
if (xLoc > rsMaxX) xLoc = rsMaxX;
if (xLoc < rsMinX) xLoc = rsMinX;
if (yLoc > rsMaxY) yLoc = rsMaxY;
if (yLoc < rsMinY) yLoc = rsMinY;
_rightStick.frame = CGRectMake(xLoc - STICK_INNER_SIZE / 2, yLoc - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE);
float xStickVal = (xLoc - RS_CENTER_X) / (rsMaxX - RS_CENTER_X);
float yStickVal = (yLoc - RS_CENTER_Y) / (rsMaxY - RS_CENTER_Y);
if (fabsf(xStickVal) < STICK_DEAD_ZONE) xStickVal = 0;
if (fabsf(yStickVal) < STICK_DEAD_ZONE) yStickVal = 0;
[_controllerSupport updateRightStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal];
updated = true;
} else if (touch == _dpadTouch) {
[_controllerSupport clearButtonFlag:_controller
flags:UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG];
// Allow the user to slide their finger to another d-pad button
if ([_upButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:UP_FLAG];
updated = true;
} else if ([_downButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:DOWN_FLAG];
updated = true;
} else if ([_leftButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:LEFT_FLAG];
updated = true;
} else if ([_rightButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:RIGHT_FLAG];
updated = true;
}
buttonTouch = true;
} else if (touch == _aTouch) {
buttonTouch = true;
} else if (touch == _bTouch) {
buttonTouch = true;
} else if (touch == _xTouch) {
buttonTouch = true;
} else if (touch == _yTouch) {
buttonTouch = true;
} else if (touch == _startTouch) {
buttonTouch = true;
} else if (touch == _selectTouch) {
buttonTouch = true;
} else if (touch == _l1Touch) {
buttonTouch = true;
} else if (touch == _r1Touch) {
buttonTouch = true;
} else if (touch == _l2Touch) {
buttonTouch = true;
} else if (touch == _r2Touch) {
buttonTouch = true;
} else if (touch == _l3Touch) {
buttonTouch = true;
} else if (touch == _r3Touch) {
buttonTouch = true;
}
if ([_deadTouches containsObject:touch]) {
updated = true;
}
}
if (updated) {
[_controllerSupport updateFinished:_controller];
}
return updated || buttonTouch;
}
- (BOOL)handleTouchDownEvent:touches {
BOOL updated = false;
BOOL stickTouch = false;
for (UITouch* touch in touches) {
CGPoint touchLocation = [touch locationInView:_view];
if (_aButton.superlayer != nil && [_aButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:A_FLAG];
_aTouch = touch;
updated = true;
} else if (_bButton.superlayer != nil && [_bButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:B_FLAG];
_bTouch = touch;
updated = true;
} else if (_xButton.superlayer != nil && [_xButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:X_FLAG];
_xTouch = touch;
updated = true;
} else if (_yButton.superlayer != nil && [_yButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:Y_FLAG];
_yTouch = touch;
updated = true;
} else if (_upButton.superlayer != nil && [_upButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:UP_FLAG];
_dpadTouch = touch;
updated = true;
} else if (_downButton.superlayer != nil && [_downButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:DOWN_FLAG];
_dpadTouch = touch;
updated = true;
} else if (_leftButton.superlayer != nil && [_leftButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:LEFT_FLAG];
_dpadTouch = touch;
updated = true;
} else if (_rightButton.superlayer != nil && [_rightButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:RIGHT_FLAG];
_dpadTouch = touch;
updated = true;
} else if (_startButton.superlayer != nil && [_startButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:PLAY_FLAG];
_startTouch = touch;
updated = true;
} else if (_selectButton.superlayer != nil && [_selectButton.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:BACK_FLAG];
_selectTouch = touch;
updated = true;
} else if (_l1Button.superlayer != nil && [_l1Button.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:LB_FLAG];
_l1Touch = touch;
updated = true;
} else if (_r1Button.superlayer != nil && [_r1Button.presentationLayer hitTest:touchLocation]) {
[_controllerSupport setButtonFlag:_controller flags:RB_FLAG];
_r1Touch = touch;
updated = true;
} else if (_l2Button.superlayer != nil && [_l2Button.presentationLayer hitTest:touchLocation]) {
[_controllerSupport updateLeftTrigger:_controller left:0xFF];
_l2Touch = touch;
updated = true;
} else if (_r2Button.superlayer != nil && [_r2Button.presentationLayer hitTest:touchLocation]) {
[_controllerSupport updateRightTrigger:_controller right:0xFF];
_r2Touch = touch;
updated = true;
} else if (_l3Button.superlayer != nil && [_l3Button.presentationLayer hitTest:touchLocation]) {
if (l3Set) {
[_controllerSupport clearButtonFlag:_controller flags:LS_CLK_FLAG];
_l3Button.borderWidth = 0.0f;
} else {
[_controllerSupport setButtonFlag:_controller flags:LS_CLK_FLAG];
_l3Button.borderWidth = 2.0f;
}
l3Set = !l3Set;
_l3Touch = touch;
updated = true;
} else if (_r3Button.superlayer != nil && [_r3Button.presentationLayer hitTest:touchLocation]) {
if (r3Set) {
[_controllerSupport clearButtonFlag:_controller flags:RS_CLK_FLAG];
_r3Button.borderWidth = 0.0f;
} else {
[_controllerSupport setButtonFlag:_controller flags:RS_CLK_FLAG];
_r3Button.borderWidth = 2.0f;
}
r3Set = !r3Set;
_r3Touch = touch;
updated = true;
} else if (_leftStick.superlayer != nil && [_leftStick.presentationLayer hitTest:touchLocation]) {
if (l3TouchStart != nil) {
// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
double l3TouchTime = [l3TouchStart timeIntervalSinceNow] * -1000.0;
if (l3TouchTime < STICK_CLICK_RATE) {
[_controllerSupport setButtonFlag:_controller flags:LS_CLK_FLAG];
updated = true;
}
}
_lsTouch = touch;
stickTouch = true;
} else if (_rightStick.superlayer != nil && [_rightStick.presentationLayer hitTest:touchLocation]) {
if (r3TouchStart != nil) {
// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
double r3TouchTime = [r3TouchStart timeIntervalSinceNow] * -1000.0;
if (r3TouchTime < STICK_CLICK_RATE) {
[_controllerSupport setButtonFlag:_controller flags:RS_CLK_FLAG];
updated = true;
}
}
_rsTouch = touch;
stickTouch = true;
}
if (!updated && !stickTouch && [self isInDeadZone:touch]) {
[_deadTouches addObject:touch];
updated = true;
}
}
if (updated) {
[_controllerSupport updateFinished:_controller];
}
return updated || stickTouch;
}
- (BOOL)handleTouchUpEvent:touches {
BOOL updated = false;
BOOL touched = false;
for (UITouch* touch in touches) {
if (touch == _aTouch) {
[_controllerSupport clearButtonFlag:_controller flags:A_FLAG];
_aTouch = nil;
updated = true;
} else if (touch == _bTouch) {
[_controllerSupport clearButtonFlag:_controller flags:B_FLAG];
_bTouch = nil;
updated = true;
} else if (touch == _xTouch) {
[_controllerSupport clearButtonFlag:_controller flags:X_FLAG];
_xTouch = nil;
updated = true;
} else if (touch == _yTouch) {
[_controllerSupport clearButtonFlag:_controller flags:Y_FLAG];
_yTouch = nil;
updated = true;
} else if (touch == _dpadTouch) {
[_controllerSupport clearButtonFlag:_controller
flags:UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG];
_dpadTouch = nil;
updated = true;
} else if (touch == _startTouch) {
[_controllerSupport clearButtonFlag:_controller flags:PLAY_FLAG];
_startTouch = nil;
updated = true;
} else if (touch == _selectTouch) {
[_controllerSupport clearButtonFlag:_controller flags:BACK_FLAG];
_selectTouch = nil;
updated = true;
} else if (touch == _l1Touch) {
[_controllerSupport clearButtonFlag:_controller flags:LB_FLAG];
_l1Touch = nil;
updated = true;
} else if (touch == _r1Touch) {
[_controllerSupport clearButtonFlag:_controller flags:RB_FLAG];
_r1Touch = nil;
updated = true;
} else if (touch == _l2Touch) {
[_controllerSupport updateLeftTrigger:_controller left:0];
_l2Touch = nil;
updated = true;
} else if (touch == _r2Touch) {
[_controllerSupport updateRightTrigger:_controller right:0];
_r2Touch = nil;
updated = true;
} else if (touch == _lsTouch) {
_leftStick.frame = CGRectMake(LS_CENTER_X - STICK_INNER_SIZE / 2, LS_CENTER_Y - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE);
[_controllerSupport updateLeftStick:_controller x:0 y:0];
[_controllerSupport clearButtonFlag:_controller flags:LS_CLK_FLAG];
l3TouchStart = [NSDate date];
_lsTouch = nil;
updated = true;
} else if (touch == _rsTouch) {
_rightStick.frame = CGRectMake(RS_CENTER_X - STICK_INNER_SIZE / 2, RS_CENTER_Y - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE);
[_controllerSupport updateRightStick:_controller x:0 y:0];
[_controllerSupport clearButtonFlag:_controller flags:RS_CLK_FLAG];
r3TouchStart = [NSDate date];
_rsTouch = nil;
updated = true;
}
else if (touch == _l3Touch) {
_l3Touch = nil;
touched = true;
}
else if (touch == _r3Touch) {
_r3Touch = nil;
touched = true;
}
if ([_deadTouches containsObject:touch]) {
[_deadTouches removeObject:touch];
updated = true;
}
}
if (updated) {
[_controllerSupport updateFinished:_controller];
}
return updated || touched;
}
- (BOOL) isInDeadZone:(UITouch*) touch {
// Dynamically evaluate deadzones based on the controls
// on screen at the time
if (_leftButton.superlayer != nil && [self isDpadDeadZone:touch]) {
return true;
}
else if (_aButton.superlayer != nil && [self isAbxyDeadZone:touch]) {
return true;
}
else if (_l2Button.superlayer != nil && [self isTriggerDeadZone:touch]) {
return true;
}
else if (_l1Button.superlayer != nil && [self isBumperDeadZone:touch]) {
return true;
}
else if (_startButton.superlayer != nil && [self isStartSelectDeadZone:touch]) {
return true;
}
else if (_l3Button.superlayer != nil && [self isL3R3DeadZone:touch]) {
return true;
}
else if (_leftStickBackground.superlayer != nil && [self isStickDeadZone:touch]) {
return true;
}
return false;
}
- (BOOL) isDpadDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_upButton.frame.origin.y
endX:_rightButton.frame.origin.x + _rightButton.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isAbxyDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_xButton.frame.origin.x
startY:_yButton.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isBumperDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_l2Button.frame.origin.y + _l2Button.frame.size.height
endX:_l1Button.frame.origin.x + _l1Button.frame.size.width
endY:_upButton.frame.origin.y]
|| [self isDeadZone:touch
startX:_r2Button.frame.origin.x
startY:_r2Button.frame.origin.y + _r2Button.frame.size.height
endX:_view.frame.origin.x + _view.frame.size.width
endY:_yButton.frame.origin.y];
}
- (BOOL) isTriggerDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_l2Button.frame.origin.y
endX:_l2Button.frame.origin.x + _l2Button.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height]
|| [self isDeadZone:touch
startX:_r2Button.frame.origin.x
startY:_r2Button.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isL3R3DeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_l3Button.frame.origin.y
endX:_view.frame.origin.x
endY:_view.frame.origin.y + _view.frame.size.height]
|| [self isDeadZone:touch
startX:_r3Button.frame.origin.x
startY:_r3Button.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isStartSelectDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_startButton.frame.origin.x
startY:_startButton.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height]
|| [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_selectButton.frame.origin.y
endX:_selectButton.frame.origin.x + _selectButton.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isStickDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_leftStickBackground.frame.origin.x - 15
startY:_leftStickBackground.frame.origin.y - 15
endX:_leftStickBackground.frame.origin.x + _leftStickBackground.frame.size.width + 15
endY:_view.frame.origin.y + _view.frame.size.height]
|| [self isDeadZone:touch
startX:_rightStickBackground.frame.origin.x - 15
startY:_rightStickBackground.frame.origin.y - 15
endX:_rightStickBackground.frame.origin.x + _rightStickBackground.frame.size.width + 15
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isDeadZone:(UITouch*) touch startX:(float)deadZoneStartX startY:(float)deadZoneStartY endX:(float)deadZoneEndX endY:(float)deadZoneEndY {
deadZoneStartX -= DEAD_ZONE_PADDING;
deadZoneStartY -= DEAD_ZONE_PADDING;
deadZoneEndX += DEAD_ZONE_PADDING;
deadZoneEndY += DEAD_ZONE_PADDING;
CGPoint touchLocation = [touch locationInView:_view];
return (touchLocation.x > deadZoneStartX && touchLocation.x < deadZoneEndX
&& touchLocation.y > deadZoneStartY && touchLocation.y < deadZoneEndY);
}
@end
-19
View File
@@ -1,19 +0,0 @@
//
// RelativeTouchHandler.h
// Moonlight
//
// Created by Cameron Gutman on 11/1/20.
// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved.
//
#import "StreamView.h"
NS_ASSUME_NONNULL_BEGIN
@interface RelativeTouchHandler : UIResponder
-(id)initWithView:(StreamView*)view;
@end
NS_ASSUME_NONNULL_END
-207
View File
@@ -1,207 +0,0 @@
//
// RelativeTouchHandler.m
// Moonlight
//
// Created by Cameron Gutman on 11/1/20.
// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved.
//
#import "RelativeTouchHandler.h"
#include <Limelight.h>
static const int REFERENCE_WIDTH = 1280;
static const int REFERENCE_HEIGHT = 720;
@implementation RelativeTouchHandler {
CGPoint touchLocation, originalLocation;
BOOL touchMoved;
BOOL isDragging;
NSTimer* dragTimer;
NSUInteger peakTouchCount;
#if TARGET_OS_TV
UIGestureRecognizer* remotePressRecognizer;
UIGestureRecognizer* remoteLongPressRecognizer;
#endif
UIView* view;
}
- (id)initWithView:(StreamView*)view {
self = [self init];
self->view = view;
#if TARGET_OS_TV
remotePressRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(remoteButtonPressed:)];
remotePressRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
remoteLongPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(remoteButtonLongPressed:)];
remoteLongPressRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
[self->view addGestureRecognizer:remotePressRecognizer];
[self->view addGestureRecognizer:remoteLongPressRecognizer];
#endif
return self;
}
- (BOOL)isConfirmedMove:(CGPoint)currentPoint from:(CGPoint)originalPoint {
// Movements of greater than 5 pixels are considered confirmed
return hypotf(originalPoint.x - currentPoint.x, originalPoint.y - currentPoint.y) >= 5;
}
- (void)onDragStart:(NSTimer*)timer {
if (!touchMoved && !isDragging){
isDragging = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
touchMoved = false;
peakTouchCount = [[event allTouches] count];
if ([[event allTouches] count] == 1) {
UITouch *touch = [[event allTouches] anyObject];
originalLocation = touchLocation = [touch locationInView:view];
if (!isDragging) {
dragTimer = [NSTimer scheduledTimerWithTimeInterval:0.650
target:self
selector:@selector(onDragStart:)
userInfo:nil
repeats:NO];
}
}
else if ([[event allTouches] count] == 2) {
CGPoint firstLocation = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:view];
CGPoint secondLocation = [[[[event allTouches] allObjects] objectAtIndex:1] locationInView:view];
originalLocation = touchLocation = CGPointMake((firstLocation.x + secondLocation.x) / 2, (firstLocation.y + secondLocation.y) / 2);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if ([[event allTouches] count] == 1) {
UITouch *touch = [[event allTouches] anyObject];
CGPoint currentLocation = [touch locationInView:view];
if (touchLocation.x != currentLocation.x ||
touchLocation.y != currentLocation.y)
{
int deltaX = (currentLocation.x - touchLocation.x) * (REFERENCE_WIDTH / view.bounds.size.width);
int deltaY = (currentLocation.y - touchLocation.y) * (REFERENCE_HEIGHT / view.bounds.size.height);
if (deltaX != 0 || deltaY != 0) {
LiSendMouseMoveEvent(deltaX, deltaY);
touchLocation = currentLocation;
// If we've moved far enough to confirm this wasn't just human/machine error,
// mark it as such.
if ([self isConfirmedMove:touchLocation from:originalLocation]) {
touchMoved = true;
}
}
}
} else if ([[event allTouches] count] == 2) {
CGPoint firstLocation = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:view];
CGPoint secondLocation = [[[[event allTouches] allObjects] objectAtIndex:1] locationInView:view];
CGPoint avgLocation = CGPointMake((firstLocation.x + secondLocation.x) / 2, (firstLocation.y + secondLocation.y) / 2);
if (touchLocation.y != avgLocation.y) {
LiSendHighResScrollEvent((avgLocation.y - touchLocation.y) * 10);
}
// If we've moved far enough to confirm this wasn't just human/machine error,
// mark it as such.
if ([self isConfirmedMove:firstLocation from:originalLocation]) {
touchMoved = true;
}
touchLocation = avgLocation;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[dragTimer invalidate];
dragTimer = nil;
if (isDragging) {
isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
} else if (!touchMoved) {
if (peakTouchCount == 2) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
Log(LOG_D, @"Sending right mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
});
} else if (peakTouchCount == 1) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (!self->isDragging){
Log(LOG_D, @"Sending left mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
}
self->isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
});
}
}
// We we're moving from 2+ touches to 1. Synchronize the current position
// of the active finger so we don't jump unexpectedly on the next touchesMoved
// callback when finger 1 switches on us.
if ([[event allTouches] count] - [touches count] == 1) {
NSMutableSet *activeSet = [[NSMutableSet alloc] initWithCapacity:[[event allTouches] count]];
[activeSet unionSet:[event allTouches]];
[activeSet minusSet:touches];
touchLocation = [[activeSet anyObject] locationInView:view];
// Mark this touch as moved so we don't send a left mouse click if the user
// right clicks without moving their other finger.
touchMoved = true;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[dragTimer invalidate];
dragTimer = nil;
if (isDragging) {
isDragging = false;
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
peakTouchCount = 0;
}
#if TARGET_OS_TV
- (void)remoteButtonPressed:(id)sender {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
Log(LOG_D, @"Sending left mouse button press");
// Mark this as touchMoved to avoid a duplicate press on touch up
self->touchMoved = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press
usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
});
}
- (void)remoteButtonLongPressed:(id)sender {
Log(LOG_D, @"Holding left mouse button");
isDragging = true;
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
#endif
@end
-37
View File
@@ -1,37 +0,0 @@
//
// StreamView.h
// Moonlight
//
// Created by Cameron Gutman on 10/19/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "ControllerSupport.h"
#import "OnScreenControls.h"
#import "Moonlight-Swift.h"
#import "StreamConfiguration.h"
@protocol UserInteractionDelegate <NSObject>
- (void) userInteractionBegan;
- (void) userInteractionEnded;
@end
#if TARGET_OS_TV
@interface StreamView : UIView <X1KitMouseDelegate, UITextFieldDelegate>
#else
@interface StreamView : UIView <X1KitMouseDelegate, UITextFieldDelegate, UIPointerInteractionDelegate>
#endif
- (void) setupStreamView:(ControllerSupport*)controllerSupport
interactionDelegate:(id<UserInteractionDelegate>)interactionDelegate
config:(StreamConfiguration*)streamConfig;
- (void) showOnScreenControls;
- (OnScreenControlsLevel) getCurrentOscState;
#if !TARGET_OS_TV
- (void) updateCursorLocation:(CGPoint)location isMouse:(BOOL)isMouse;
#endif
@end
File diff suppressed because it is too large Load Diff
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C81f" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="Host" representedClassName="Host" syncable="YES">
<attribute name="address" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="2" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
</entity>
<elements>
<element name="Host" positionX="0" positionY="0" width="128" height="148"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="120"/>
</elements>
</model>
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C81f" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="Host" representedClassName="Host" syncable="YES">
<attribute name="address" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
</entity>
<elements>
<element name="Host" positionX="0" positionY="0" width="128" height="148"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="120"/>
</elements>
</model>
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="17D102" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES">
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES">
<attribute name="address" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="streamingRemotely" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="163"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="150"/>
</elements>
</model>
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Automatic">
<entity name="App" representedClassName="App" syncable="YES">
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES">
<attribute name="address" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
<attribute name="uniqueId" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="163"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="135"/>
</elements>
</model>
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14135" systemVersion="17F77" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="165"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
</elements>
</model>
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14135" systemVersion="17G65" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="165"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
</elements>
</model>
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="18C54" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="195"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
</elements>
</model>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14490.99" systemVersion="18F132" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
</elements>
</model>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19E266" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="238"/>
</elements>
</model>
@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17192" systemVersion="19H2" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="118"/>
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="268"/>
</elements>
</model>
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="20G224" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="118"/>
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="254"/>
</elements>
</model>
@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21G72" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="118"/>
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="298"/>
</elements>
</model>
@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21G72" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="statsOverlay" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="useHevc2" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="118"/>
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="298"/>
</elements>
</model>
-26
View File
@@ -1,26 +0,0 @@
//
// VideoDecoderRenderer.h
// Moonlight
//
// Created by Cameron Gutman on 10/18/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
@import AVFoundation;
#import "ConnectionCallbacks.h"
#include "Limelight.h"
@interface VideoDecoderRenderer : NSObject
- (id)initWithView:(UIView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing;
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate;
- (void)start;
- (void)stop;
- (void)setHdrMode:(BOOL)enabled;
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType decodeUnit:(PDECODE_UNIT)du;
@end
-684
View File
@@ -1,684 +0,0 @@
//
// VideoDecoderRenderer.m
// Moonlight
//
// Created by Cameron Gutman on 10/18/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "VideoDecoderRenderer.h"
#import "StreamView.h"
#include <libavcodec/avcodec.h>
#include <libavcodec/cbs.h>
#include <libavcodec/cbs_av1.h>
#include <libavformat/avio.h>
#include <libavutil/mem.h>
// Private libavformat API for writing the AV1 Codec Configuration Box
extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
int write_seq_header);
@implementation VideoDecoderRenderer {
StreamView* _view;
id<ConnectionCallbacks> _callbacks;
float _streamAspectRatio;
AVSampleBufferDisplayLayer* displayLayer;
int videoFormat;
int frameRate;
NSMutableArray *parameterSetBuffers;
NSData *masteringDisplayColorVolume;
NSData *contentLightLevelInfo;
CMVideoFormatDescriptionRef formatDesc;
CADisplayLink* _displayLink;
BOOL framePacing;
}
- (void)reinitializeDisplayLayer
{
CALayer *oldLayer = displayLayer;
displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
displayLayer.backgroundColor = [UIColor blackColor].CGColor;
// Ensure the AVSampleBufferDisplayLayer is sized to preserve the aspect ratio
// of the video stream. We used to use AVLayerVideoGravityResizeAspect, but that
// respects the PAR encoded in the SPS which causes our computed video-relative
// touch location to be wrong in StreamView if the aspect ratio of the host
// desktop doesn't match the aspect ratio of the stream.
CGSize videoSize;
if (_view.bounds.size.width > _view.bounds.size.height * _streamAspectRatio) {
videoSize = CGSizeMake(_view.bounds.size.height * _streamAspectRatio, _view.bounds.size.height);
} else {
videoSize = CGSizeMake(_view.bounds.size.width, _view.bounds.size.width / _streamAspectRatio);
}
displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
displayLayer.videoGravity = AVLayerVideoGravityResize;
// Hide the layer until we get an IDR frame. This ensures we
// can see the loading progress label as the stream is starting.
displayLayer.hidden = YES;
if (oldLayer != nil) {
// Switch out the old display layer with the new one
[_view.layer replaceSublayer:oldLayer with:displayLayer];
}
else {
[_view.layer addSublayer:displayLayer];
}
if (formatDesc != nil) {
CFRelease(formatDesc);
formatDesc = nil;
}
}
- (id)initWithView:(StreamView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing
{
self = [super init];
_view = view;
_callbacks = callbacks;
_streamAspectRatio = aspectRatio;
framePacing = useFramePacing;
parameterSetBuffers = [[NSMutableArray alloc] init];
[self reinitializeDisplayLayer];
return self;
}
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate
{
self->videoFormat = videoFormat;
self->frameRate = frameRate;
}
- (void)start
{
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)];
if (@available(iOS 15.0, tvOS 15.0, *)) {
_displayLink.preferredFrameRateRange = CAFrameRateRangeMake(self->frameRate, self->frameRate, self->frameRate);
}
else {
_displayLink.preferredFramesPerSecond = self->frameRate;
}
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
// TODO: Refactor this
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
- (void)displayLinkCallback:(CADisplayLink *)sender
{
VIDEO_FRAME_HANDLE handle;
PDECODE_UNIT du;
while (LiPollNextVideoFrame(&handle, &du)) {
LiCompleteVideoFrame(handle, DrSubmitDecodeUnit(du));
if (framePacing) {
// Calculate the actual display refresh rate
double displayRefreshRate = 1 / (_displayLink.targetTimestamp - _displayLink.timestamp);
// Only pace frames if the display refresh rate is >= 90% of our stream frame rate.
// Battery saver, accessibility settings, or device thermals can cause the actual
// refresh rate of the display to drop below the physical maximum.
if (displayRefreshRate >= frameRate * 0.9f) {
// Keep one pending frame to smooth out gaps due to
// network jitter at the cost of 1 frame of latency
if (LiGetPendingVideoFrames() == 1) {
break;
}
}
}
}
}
- (void)stop
{
[_displayLink invalidate];
}
#define NALU_START_PREFIX_SIZE 3
#define NAL_LENGTH_PREFIX_SIZE 4
- (void)updateAnnexBBufferForRange:(CMBlockBufferRef)frameBuffer dataBlock:(CMBlockBufferRef)dataBuffer offset:(int)offset length:(int)nalLength
{
OSStatus status;
size_t oldOffset = CMBlockBufferGetDataLength(frameBuffer);
// Append a 4 byte buffer to the frame block for the length prefix
status = CMBlockBufferAppendMemoryBlock(frameBuffer, NULL,
NAL_LENGTH_PREFIX_SIZE,
kCFAllocatorDefault, NULL, 0,
NAL_LENGTH_PREFIX_SIZE, 0);
if (status != noErr) {
Log(LOG_E, @"CMBlockBufferAppendMemoryBlock failed: %d", (int)status);
return;
}
// Write the length prefix to the new buffer
const int dataLength = nalLength - NALU_START_PREFIX_SIZE;
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
status = CMBlockBufferReplaceDataBytes(lengthBytes, frameBuffer,
oldOffset, NAL_LENGTH_PREFIX_SIZE);
if (status != noErr) {
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
return;
}
// Attach the data buffer to the frame buffer by reference
status = CMBlockBufferAppendBufferReference(frameBuffer, dataBuffer, offset + NALU_START_PREFIX_SIZE, dataLength, 0);
if (status != noErr) {
Log(LOG_E, @"CMBlockBufferAppendBufferReference failed: %d", (int)status);
return;
}
}
- (NSData*)getAv1CodecConfigurationBox:(NSData*)frameData {
AVIOContext* ioctx = NULL;
int err;
err = avio_open_dyn_buf(&ioctx);
if (err < 0) {
Log(LOG_E, @"avio_open_dyn_buf() failed: %d", err);
return nil;
}
// Submit the IDR frame to write the av1C blob
err = ff_isom_write_av1c(ioctx, (uint8_t*)frameData.bytes, (int)frameData.length, 1);
if (err < 0) {
Log(LOG_E, @"ff_isom_write_av1c() failed: %d", err);
// Fall-through to close and free buffer
}
// Close the dynbuf and get the underlying buffer back (which we must free)
uint8_t* av1cBuf = NULL;
int av1cBufLen = avio_close_dyn_buf(ioctx, &av1cBuf);
Log(LOG_I, @"av1C block is %d bytes", av1cBufLen);
// Only return data if ff_isom_write_av1c() was successful
NSData* data = nil;
if (err >= 0 && av1cBufLen > 0) {
data = [NSData dataWithBytes:av1cBuf length:av1cBufLen];
}
else {
data = nil;
}
av_free(av1cBuf);
return data;
}
// Much of this logic comes from Chrome
- (CMVideoFormatDescriptionRef)createAV1FormatDescriptionForIDRFrame:(NSData*)frameData {
NSMutableDictionary* extensions = [[NSMutableDictionary alloc] init];
CodedBitstreamContext* cbsCtx = NULL;
int err = ff_cbs_init(&cbsCtx, AV_CODEC_ID_AV1, NULL);
if (err < 0) {
Log(LOG_E, @"ff_cbs_init() failed: %d", err);
return nil;
}
AVPacket avPacket = {};
avPacket.data = (uint8_t*)frameData.bytes;
avPacket.size = (int)frameData.length;
// Read the sequence header OBU
CodedBitstreamFragment cbsFrag = {};
err = ff_cbs_read_packet(cbsCtx, &cbsFrag, &avPacket);
if (err < 0) {
Log(LOG_E, @"ff_cbs_read_packet() failed: %d", err);
ff_cbs_close(&cbsCtx);
return nil;
}
#define SET_CFSTR_EXTENSION(key, value) extensions[(__bridge NSString*)key] = (__bridge NSString*)(value)
#define SET_EXTENSION(key, value) extensions[(__bridge NSString*)key] = (value)
SET_EXTENSION(kCMFormatDescriptionExtension_FormatName, @"av01");
// We use the value for YUV without alpha, same as Chrome
// https://developer.apple.com/library/archive/qa/qa1183/_index.html
SET_EXTENSION(kCMFormatDescriptionExtension_Depth, @24);
CodedBitstreamAV1Context* bitstreamCtx = (CodedBitstreamAV1Context*)cbsCtx->priv_data;
AV1RawSequenceHeader* seqHeader = bitstreamCtx->sequence_header;
if (seqHeader == NULL) {
Log(LOG_E, @"AV1 sequence header not found in IDR frame!");
ff_cbs_fragment_free(&cbsFrag);
ff_cbs_close(&cbsCtx);
return nil;
}
switch (seqHeader->color_config.color_primaries) {
case 1: // CP_BT_709
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
kCMFormatDescriptionColorPrimaries_ITU_R_709_2);
break;
case 6: // CP_BT_601
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
kCMFormatDescriptionColorPrimaries_SMPTE_C);
break;
case 9: // CP_BT_2020
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
kCMFormatDescriptionColorPrimaries_ITU_R_2020);
break;
default:
Log(LOG_W, @"Unsupported color_primaries value: %d", seqHeader->color_config.color_primaries);
break;
}
switch (seqHeader->color_config.transfer_characteristics) {
case 1: // TC_BT_709
case 6: // TC_BT_601
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
kCMFormatDescriptionTransferFunction_ITU_R_709_2);
break;
case 7: // TC_SMPTE_240
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
kCMFormatDescriptionTransferFunction_SMPTE_240M_1995);
break;
case 8: // TC_LINEAR
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
kCMFormatDescriptionTransferFunction_Linear);
break;
case 14: // TC_BT_2020_10_BIT
case 15: // TC_BT_2020_12_BIT
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
kCMFormatDescriptionTransferFunction_ITU_R_2020);
break;
case 16: // TC_SMPTE_2084
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ);
break;
case 17: // TC_HLG
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG);
break;
default:
Log(LOG_W, @"Unsupported transfer_characteristics value: %d", seqHeader->color_config.transfer_characteristics);
break;
}
switch (seqHeader->color_config.matrix_coefficients) {
case 1: // MC_BT_709
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2);
break;
case 6: // MC_BT_601
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4);
break;
case 7: // MC_SMPTE_240
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
kCMFormatDescriptionYCbCrMatrix_SMPTE_240M_1995);
break;
case 9: // MC_BT_2020_NCL
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
kCMFormatDescriptionYCbCrMatrix_ITU_R_2020);
break;
default:
Log(LOG_W, @"Unsupported matrix_coefficients value: %d", seqHeader->color_config.matrix_coefficients);
break;
}
SET_EXTENSION(kCMFormatDescriptionExtension_FullRangeVideo, @(seqHeader->color_config.color_range == 1));
// Progressive content
SET_EXTENSION(kCMFormatDescriptionExtension_FieldCount, @(1));
switch (seqHeader->color_config.chroma_sample_position) {
case 1: // CSP_VERTICAL
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ChromaLocationTopField,
kCMFormatDescriptionChromaLocation_Left);
break;
case 2: // CSP_COLOCATED
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ChromaLocationTopField,
kCMFormatDescriptionChromaLocation_TopLeft);
break;
default:
Log(LOG_W, @"Unsupported chroma_sample_position value: %d", seqHeader->color_config.chroma_sample_position);
break;
}
if (contentLightLevelInfo) {
SET_EXTENSION(kCMFormatDescriptionExtension_ContentLightLevelInfo, contentLightLevelInfo);
}
if (masteringDisplayColorVolume) {
SET_EXTENSION(kCMFormatDescriptionExtension_MasteringDisplayColorVolume, masteringDisplayColorVolume);
}
// Referenced the VP9 code in Chrome that performs a similar function
// https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/mac/vt_config_util.mm;drc=977dc02c431b4979e34c7792bc3d646f649dacb4;l=155
extensions[(__bridge NSString*)kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms] =
@{
@"av1C" : [self getAv1CodecConfigurationBox:frameData],
};
extensions[@"BitsPerComponent"] = @(bitstreamCtx->bit_depth);
#undef SET_EXTENSION
#undef SET_CFSTR_EXTENSION
// AV1 doesn't have a special format description function like H.264 and HEVC have, so we just use the generic one
CMVideoFormatDescriptionRef formatDesc = NULL;
OSStatus status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_AV1,
bitstreamCtx->frame_width, bitstreamCtx->frame_height,
(__bridge CFDictionaryRef)extensions,
&formatDesc);
if (status != noErr) {
Log(LOG_E, @"Failed to create AV1 format description: %d", (int)status);
formatDesc = NULL;
}
ff_cbs_fragment_free(&cbsFrag);
ff_cbs_close(&cbsCtx);
return formatDesc;
}
// This function must free data for bufferType == BUFFER_TYPE_PICDATA
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType decodeUnit:(PDECODE_UNIT)du
{
OSStatus status;
// Construct a new format description object each time we receive an IDR frame
if (du->frameType == FRAME_TYPE_IDR) {
if (bufferType != BUFFER_TYPE_PICDATA) {
if (bufferType == BUFFER_TYPE_VPS || bufferType == BUFFER_TYPE_SPS || bufferType == BUFFER_TYPE_PPS) {
// Add new parameter set into the parameter set array
int startLen = data[2] == 0x01 ? 3 : 4;
[parameterSetBuffers addObject:[NSData dataWithBytes:&data[startLen] length:length - startLen]];
}
// Data is NOT to be freed here. It's a direct usage of the caller's buffer.
// No frame data to submit for these NALUs
return DR_OK;
}
// Create the new format description when we get the first picture data buffer of an IDR frame.
// This is the only way we know that there is no more CSD for this frame.
//
// NB: This logic depends on the fact that we submit all picture data in one buffer!
// Free the old format description
if (formatDesc != NULL) {
CFRelease(formatDesc);
formatDesc = NULL;
}
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
// Construct parameter set arrays for the format description
size_t parameterSetCount = [parameterSetBuffers count];
const uint8_t* parameterSetPointers[parameterSetCount];
size_t parameterSetSizes[parameterSetCount];
for (int i = 0; i < parameterSetCount; i++) {
NSData* parameterSet = parameterSetBuffers[i];
parameterSetPointers[i] = parameterSet.bytes;
parameterSetSizes[i] = parameterSet.length;
}
Log(LOG_I, @"Constructing new H264 format description");
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
parameterSetCount,
parameterSetPointers,
parameterSetSizes,
NAL_LENGTH_PREFIX_SIZE,
&formatDesc);
if (status != noErr) {
Log(LOG_E, @"Failed to create H264 format description: %d", (int)status);
formatDesc = NULL;
}
// Free parameter set buffers after submission
[parameterSetBuffers removeAllObjects];
}
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
// Construct parameter set arrays for the format description
size_t parameterSetCount = [parameterSetBuffers count];
const uint8_t* parameterSetPointers[parameterSetCount];
size_t parameterSetSizes[parameterSetCount];
for (int i = 0; i < parameterSetCount; i++) {
NSData* parameterSet = parameterSetBuffers[i];
parameterSetPointers[i] = parameterSet.bytes;
parameterSetSizes[i] = parameterSet.length;
}
Log(LOG_I, @"Constructing new HEVC format description");
NSMutableDictionary* videoFormatParams = [[NSMutableDictionary alloc] init];
if (contentLightLevelInfo) {
[videoFormatParams setObject:contentLightLevelInfo forKey:(__bridge NSString*)kCMFormatDescriptionExtension_ContentLightLevelInfo];
}
if (masteringDisplayColorVolume) {
[videoFormatParams setObject:masteringDisplayColorVolume forKey:(__bridge NSString*)kCMFormatDescriptionExtension_MasteringDisplayColorVolume];
}
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
parameterSetCount,
parameterSetPointers,
parameterSetSizes,
NAL_LENGTH_PREFIX_SIZE,
(__bridge CFDictionaryRef)videoFormatParams,
&formatDesc);
if (status != noErr) {
Log(LOG_E, @"Failed to create HEVC format description: %d", (int)status);
formatDesc = NULL;
}
// Free parameter set buffers after submission
[parameterSetBuffers removeAllObjects];
}
else if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
NSData* fullFrameData = [NSData dataWithBytesNoCopy:data length:length freeWhenDone:NO];
Log(LOG_I, @"Constructing new AV1 format description");
formatDesc = [self createAV1FormatDescriptionForIDRFrame:fullFrameData];
}
else {
// Unsupported codec!
abort();
}
}
if (formatDesc == NULL) {
// Can't decode if we haven't gotten our parameter sets yet
free(data);
return DR_NEED_IDR;
}
// Check for previous decoder errors before doing anything
if (displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
Log(LOG_E, @"Display layer rendering failed: %@", displayLayer.error);
// Recreate the display layer. We are already on the main thread,
// so this is safe to do right here.
[self reinitializeDisplayLayer];
// Request an IDR frame to initialize the new decoder
free(data);
return DR_NEED_IDR;
}
// Now we're decoding actual frame data here
CMBlockBufferRef frameBlockBuffer;
CMBlockBufferRef dataBlockBuffer;
status = CMBlockBufferCreateWithMemoryBlock(NULL, data, length, kCFAllocatorDefault, NULL, 0, length, 0, &dataBlockBuffer);
if (status != noErr) {
Log(LOG_E, @"CMBlockBufferCreateWithMemoryBlock failed: %d", (int)status);
free(data);
return DR_NEED_IDR;
}
// From now on, CMBlockBuffer owns the data pointer and will free it when it's dereferenced
status = CMBlockBufferCreateEmpty(NULL, 0, 0, &frameBlockBuffer);
if (status != noErr) {
Log(LOG_E, @"CMBlockBufferCreateEmpty failed: %d", (int)status);
CFRelease(dataBlockBuffer);
return DR_NEED_IDR;
}
// H.264 and HEVC formats require NAL prefix fixups from Annex B to length-delimited
if (videoFormat & (VIDEO_FORMAT_MASK_H264 | VIDEO_FORMAT_MASK_H265)) {
int lastOffset = -1;
for (int i = 0; i < length - NALU_START_PREFIX_SIZE; i++) {
// Search for a NALU
if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1) {
// It's the start of a new NALU
if (lastOffset != -1) {
// We've seen a start before this so enqueue that NALU
[self updateAnnexBBufferForRange:frameBlockBuffer dataBlock:dataBlockBuffer offset:lastOffset length:i - lastOffset];
}
lastOffset = i;
}
}
if (lastOffset != -1) {
// Enqueue the remaining data
[self updateAnnexBBufferForRange:frameBlockBuffer dataBlock:dataBlockBuffer offset:lastOffset length:length - lastOffset];
}
}
else {
// For formats that require no length-changing fixups, just append a reference to the raw data block
status = CMBlockBufferAppendBufferReference(frameBlockBuffer, dataBlockBuffer, 0, length, 0);
if (status != noErr) {
Log(LOG_E, @"CMBlockBufferAppendBufferReference failed: %d", (int)status);
return DR_NEED_IDR;
}
}
CMSampleBufferRef sampleBuffer;
CMSampleTimingInfo sampleTiming = {kCMTimeInvalid, CMTimeMake(du->presentationTimeMs, 1000), kCMTimeInvalid};
status = CMSampleBufferCreateReady(kCFAllocatorDefault,
frameBlockBuffer,
formatDesc, 1, 1,
&sampleTiming, 0, NULL,
&sampleBuffer);
if (status != noErr) {
Log(LOG_E, @"CMSampleBufferCreate failed: %d", (int)status);
CFRelease(dataBlockBuffer);
CFRelease(frameBlockBuffer);
return DR_NEED_IDR;
}
// Enqueue the next frame
[self->displayLayer enqueueSampleBuffer:sampleBuffer];
if (du->frameType == FRAME_TYPE_IDR) {
// Ensure the layer is visible now
self->displayLayer.hidden = NO;
// Tell our parent VC to hide the progress indicator
[self->_callbacks videoContentShown];
}
// Dereference the buffers
CFRelease(dataBlockBuffer);
CFRelease(frameBlockBuffer);
CFRelease(sampleBuffer);
return DR_OK;
}
- (void)setHdrMode:(BOOL)enabled {
SS_HDR_METADATA hdrMetadata;
BOOL hasMetadata = enabled && LiGetHdrMetadata(&hdrMetadata);
BOOL metadataChanged = NO;
if (hasMetadata && hdrMetadata.displayPrimaries[0].x != 0 && hdrMetadata.maxDisplayLuminance != 0) {
// This data is all in big-endian
struct {
vector_ushort2 primaries[3];
vector_ushort2 white_point;
uint32_t luminance_max;
uint32_t luminance_min;
} __attribute__((packed, aligned(4))) mdcv;
// mdcv is in GBR order while SS_HDR_METADATA is in RGB order
mdcv.primaries[0].x = __builtin_bswap16(hdrMetadata.displayPrimaries[1].x);
mdcv.primaries[0].y = __builtin_bswap16(hdrMetadata.displayPrimaries[1].y);
mdcv.primaries[1].x = __builtin_bswap16(hdrMetadata.displayPrimaries[2].x);
mdcv.primaries[1].y = __builtin_bswap16(hdrMetadata.displayPrimaries[2].y);
mdcv.primaries[2].x = __builtin_bswap16(hdrMetadata.displayPrimaries[0].x);
mdcv.primaries[2].y = __builtin_bswap16(hdrMetadata.displayPrimaries[0].y);
mdcv.white_point.x = __builtin_bswap16(hdrMetadata.whitePoint.x);
mdcv.white_point.y = __builtin_bswap16(hdrMetadata.whitePoint.y);
// These luminance values are in 10000ths of a nit
mdcv.luminance_max = __builtin_bswap32((uint32_t)hdrMetadata.maxDisplayLuminance * 10000);
mdcv.luminance_min = __builtin_bswap32(hdrMetadata.minDisplayLuminance);
NSData* newMdcv = [NSData dataWithBytes:&mdcv length:sizeof(mdcv)];
if (masteringDisplayColorVolume == nil || ![newMdcv isEqualToData:masteringDisplayColorVolume]) {
masteringDisplayColorVolume = newMdcv;
metadataChanged = YES;
}
}
else if (masteringDisplayColorVolume != nil) {
masteringDisplayColorVolume = nil;
metadataChanged = YES;
}
if (hasMetadata && hdrMetadata.maxContentLightLevel != 0 && hdrMetadata.maxFrameAverageLightLevel != 0) {
// This data is all in big-endian
struct {
uint16_t max_content_light_level;
uint16_t max_frame_average_light_level;
} __attribute__((packed, aligned(2))) cll;
cll.max_content_light_level = __builtin_bswap16(hdrMetadata.maxContentLightLevel);
cll.max_frame_average_light_level = __builtin_bswap16(hdrMetadata.maxFrameAverageLightLevel);
NSData* newCll = [NSData dataWithBytes:&cll length:sizeof(cll)];
if (contentLightLevelInfo == nil || ![newCll isEqualToData:contentLightLevelInfo]) {
contentLightLevelInfo = newCll;
metadataChanged = YES;
}
}
else if (contentLightLevelInfo != nil) {
contentLightLevelInfo = nil;
metadataChanged = YES;
}
// If the metadata changed, request an IDR frame to re-create the CMVideoFormatDescription
if (metadataChanged) {
LiRequestIdrFrame();
}
}
@end
-29
View File
@@ -1,29 +0,0 @@
//
// UIComputerView.h
// Moonlight
//
// Created by Diego Waxemberg on 10/22/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TemporaryHost.h"
@protocol HostCallback <NSObject>
- (void) hostClicked:(TemporaryHost*)host view:(UIView*)view;
- (void) hostLongClicked:(TemporaryHost*)host view:(UIView*)view;
- (void) addHostClicked;
@end
#if !TARGET_OS_TV
@interface UIComputerView : UIButton <UIContextMenuInteractionDelegate>
#else
@interface UIComputerView : UIButton
#endif
- (id) initWithComputer:(TemporaryHost*)host andCallback:(id<HostCallback>)callback;
- (id) initForAddWithCallback:(id<HostCallback>)callback;
@end
-239
View File
@@ -1,239 +0,0 @@
//
// UIComputerView.m
// Moonlight
//
// Created by Diego Waxemberg on 10/22/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "UIComputerView.h"
@implementation UIComputerView {
TemporaryHost* _host;
UIImageView* _hostIcon;
UILabel* _hostLabel;
UIImageView* _hostOverlay;
UIActivityIndicatorView* _hostSpinner;
id<HostCallback> _callback;
CGSize _labelSize;
}
static const float REFRESH_CYCLE = 2.0f;
#if TARGET_OS_TV
static const int ITEM_PADDING = 50;
static const int LABEL_DY = 40;
#else
static const int ITEM_PADDING = 0;
static const int LABEL_DY = 20;
#endif
- (id) init {
self = [super init];
#if TARGET_OS_TV
self.frame = CGRectMake(0, 0, 400, 400);
#else
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
self.frame = CGRectMake(0, 0, 200, 200);
} else {
self.frame = CGRectMake(0, 0, 100, 100);
}
#endif
_hostIcon = [[UIImageView alloc] initWithFrame:self.frame];
[_hostIcon setImage:[UIImage imageNamed:@"Computer"]];
self.layer.shadowColor = [[UIColor blackColor] CGColor];
self.layer.shadowOffset = CGSizeMake(5,8);
self.layer.shadowOpacity = 0.3;
[self addTarget:self action:@selector(hostButtonSelected:) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:@selector(hostButtonDeselected:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit];
_hostLabel = [[UILabel alloc] init];
_hostLabel.textColor = [UIColor whiteColor];
_hostOverlay = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.size.width / 3, _hostIcon.frame.size.height / 4, _hostIcon.frame.size.width / 3, self.frame.size.height / 3)];
_hostSpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[_hostSpinner setFrame:_hostOverlay.frame];
_hostSpinner.userInteractionEnabled = NO;
_hostSpinner.hidesWhenStopped = YES;
[self addSubview:_hostLabel];
[self addSubview:_hostIcon];
#if TARGET_OS_TV
_hostIcon.clipsToBounds = NO;
_hostIcon.adjustsImageWhenAncestorFocused = YES;
_hostIcon.masksFocusEffectToContents = YES;
self.adjustsImageWhenHighlighted = NO;
_hostOverlay.masksFocusEffectToContents = YES;
_hostOverlay.adjustsImageWhenAncestorFocused = NO;
[_hostIcon.overlayContentView addSubview:_hostOverlay];
[_hostIcon.overlayContentView addSubview:_hostSpinner];
#else
[self addSubview:_hostOverlay];
[self addSubview:_hostSpinner];
if (@available(iOS 13.4.1, *)) {
// Allow the button style to change when moused over
self.pointerInteractionEnabled = YES;
}
#endif
return self;
}
- (void) hostButtonSelected:(id)sender {
_hostIcon.layer.opacity = 0.5f;
_hostSpinner.layer.opacity = 0.5f;
_hostOverlay.layer.opacity = 0.5f;
}
- (void) hostButtonDeselected:(id)sender {
_hostIcon.layer.opacity = 1.0f;
_hostSpinner.layer.opacity = 1.0f;
_hostOverlay.layer.opacity = 1.0f;
}
- (id) initForAddWithCallback:(id<HostCallback>)callback {
self = [self init];
_callback = callback;
[self addTarget:self action:@selector(addClicked) forControlEvents:UIControlEventPrimaryActionTriggered];
[_hostLabel setText:@"Add Host Manually"];
[_hostLabel sizeToFit];
[_hostOverlay setImage:[UIImage imageNamed:@"AddOverlayIcon"]];
[self updateBounds];
return self;
}
- (id) initWithComputer:(TemporaryHost*)host andCallback:(id<HostCallback>)callback {
self = [self init];
_host = host;
_callback = callback;
// Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer
// for tvOS devices and iOS prior to 13.0.
#if !TARGET_OS_TV
if (@available(iOS 13.0, *)) {
UIContextMenuInteraction* rightClickInteraction = [[UIContextMenuInteraction alloc] initWithDelegate:self];
[self addInteraction:rightClickInteraction];
}
else
#endif
{
UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(hostLongClicked:)];
[self addGestureRecognizer:longPressRecognizer];
}
[self addTarget:self action:@selector(hostClicked) forControlEvents:UIControlEventPrimaryActionTriggered];
[self updateContentsForHost:host];
return self;
}
- (void)didMoveToSuperview {
// Start our update loop when we are added to our cell
if (self.superview != nil && _host != nil) {
[self updateLoop];
}
}
- (void) updateBounds {
float x = FLT_MAX;
float y = FLT_MAX;
float width = 0;
float height;
float iconX = _hostIcon.frame.origin.x + _hostIcon.frame.size.width / 2;
_hostLabel.center = CGPointMake(iconX, _hostIcon.frame.origin.y + _hostIcon.frame.size.height + LABEL_DY);
x = MIN(x, _hostIcon.frame.origin.x);
x = MIN(x, _hostLabel.frame.origin.x);
y = MIN(y, _hostIcon.frame.origin.y);
y = MIN(y, _hostLabel.frame.origin.y);
width = MAX(width, _hostIcon.frame.size.width);
width = MAX(width, _hostLabel.frame.size.width);
height = _hostIcon.frame.size.height +
_hostLabel.frame.size.height +
LABEL_DY / 2;
self.bounds = CGRectMake(x - ITEM_PADDING, y - ITEM_PADDING, width + 2 * ITEM_PADDING, height + 2 * ITEM_PADDING);
}
- (void) updateContentsForHost:(TemporaryHost*)host {
_hostLabel.text = _host.name;
[_hostLabel sizeToFit];
if (host.state == StateOnline) {
[_hostSpinner stopAnimating];
if (host.pairState == PairStateUnpaired) {
[_hostOverlay setImage:[UIImage imageNamed:@"LockedOverlayIcon"]];
}
else {
[_hostOverlay setImage:nil];
}
}
else if (host.state == StateOffline) {
[_hostSpinner stopAnimating];
[_hostOverlay setImage:[UIImage imageNamed:@"ErrorOverlayIcon"]];
}
else {
[_hostSpinner startAnimating];
}
[self updateBounds];
}
- (void) updateLoop {
// Stop immediately if the view has been detached
if (self.superview == nil) {
return;
}
[self updateContentsForHost:_host];
// Queue the next refresh cycle
[self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE];
}
- (void) hostLongClicked:(UILongPressGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
[_callback hostLongClicked:_host view:self];
}
}
#if !TARGET_OS_TV
- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
configurationForMenuAtLocation:(CGPoint)location {
// We don't want to trigger the primary action at this point, so cancel
// tracking touch on this view now. This will also have the (intended)
// effect of removing the touch highlight on this view.
[self cancelTrackingWithEvent:nil];
[_callback hostLongClicked:_host view:self];
return nil;
}
#endif
- (void) hostClicked {
[_callback hostClicked:_host view:self];
}
- (void) addClicked {
[_callback addHostClicked];
}
@end
-29
View File
@@ -1,29 +0,0 @@
//
// Logger.h
// Moonlight
//
// Created by Diego Waxemberg on 2/10/15.
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
//
#ifndef Limelight_Logger_h
#define Limelight_Logger_h
#import <stdarg.h>
typedef enum {
LOG_D,
LOG_I,
LOG_W,
LOG_E
} LogLevel;
#define PRFX_DEBUG @"<DEBUG>"
#define PRFX_INFO @"<INFO>"
#define PRFX_WARN @"<WARN>"
#define PRFX_ERROR @"<ERROR>"
void Log(LogLevel level, NSString* fmt, ...);
void LogTag(LogLevel level, NSString* tag, NSString* fmt, ...);
#endif
@@ -1,20 +0,0 @@
//
// CustomEdgeSwipeGestureRecognizer.h
// Moonlight-ZWM
//
// Created by ZWM on 2024/4/30.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#ifndef CustomEdgeSwipeGestureRecognizer_h
#define CustomEdgeSwipeGestureRecognizer_h
// CustomEdgeSwipeGestureRecognizer.h
#import <UIKit/UIKit.h>
@interface CustomEdgeSwipeGestureRecognizer : UIGestureRecognizer
@property (nonatomic, assign) UIRectEdge edges; // Specify the edge(s) you want to recognize the swipe gesture on
@property (nonatomic, assign) CGFloat normalizedThresholdDistance; // Distance from the edge to start recognizing the gesture
@end
#endif /* CustomEdgeSwipeGestureRecognizer_h */
@@ -1,62 +0,0 @@
//
// CustomEdgeSwipeGestureRecognizer.m
// Moonlight-ZWM
//
// Created by ZWM on 2024/4/30.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
// #import <Foundation/Foundation.h>
#import "CustomEdgeSwipeGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
@implementation CustomEdgeSwipeGestureRecognizer
UITouch* capturedUITouch;
CGFloat _startPointX;
static CGFloat screenWidthInPoints;
static CGFloat EDGE_TOLERANCE_POINTS = 50.0f;
- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action {
self = [super initWithTarget:target action:action];
screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]); // Get the screen's bounds (in points)
return self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// [super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
capturedUITouch = touch;
_startPointX = [capturedUITouch locationInView:self.view].x;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// [super touchesEnded:touches withEvent:event];
if([touches containsObject:capturedUITouch]){
CGFloat _endPointX = [capturedUITouch locationInView:self.view].x;
CGFloat normalizedGestureDistance = fabs(_endPointX - _startPointX)/screenWidthInPoints;
if(self.edges & UIRectEdgeLeft){
if(_startPointX < EDGE_TOLERANCE_POINTS && normalizedGestureDistance > _normalizedThresholdDistance){
self.state = UIGestureRecognizerStateBegan;
self.state = UIGestureRecognizerStateEnded;
}
// NSLog(@"_startPointX %f , normalizedGestureDeltaX %f", _startPointX, normalizedGestureDistance);
}
if(self.edges & UIRectEdgeRight){
if((_startPointX > (screenWidthInPoints - EDGE_TOLERANCE_POINTS)) && normalizedGestureDistance > _normalizedThresholdDistance){
self.state = UIGestureRecognizerStateBegan;
self.state = UIGestureRecognizerStateEnded;
}
// NSLog(@"_startPointX %f , normalizedGestureDeltaX %f", _startPointX, normalizedGestureDistance);
}
}
}
@end
@@ -1,25 +0,0 @@
//
// MainFrameViewController.h
// Moonlight
//
// Created by Diego Waxemberg on 1/17/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "DiscoveryManager.h"
#import "PairManager.h"
#import "StreamConfiguration.h"
#import "UIComputerView.h"
#import "UIAppView.h"
#import "AppAssetManager.h"
#import "SWRevealViewController.h"
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, HostCallback, AppCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UIBarButtonItem *settingsButton;
#if !TARGET_OS_TV
@property (weak, nonatomic) IBOutlet UIBarButtonItem *upButton;
#endif
@end
File diff suppressed because it is too large Load Diff
@@ -8,6 +8,9 @@
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CustomOSCViewControl/LayoutOnScreenControlsViewController.h"
#import "MainFrameViewController.h"
#import "CustomEdgeSlideGestureRecognizer.h"
@interface SettingsViewController : UIViewController
@property (strong, nonatomic) IBOutlet UILabel *bitrateLabel;
@@ -15,31 +18,50 @@
@property (strong, nonatomic) IBOutlet UISegmentedControl *framerateSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *resolutionSelector;
@property (strong, nonatomic) IBOutlet UIView *resolutionDisplayView;
@property (strong, nonatomic) IBOutlet UILabel *touchModeLabel;
@property (strong, nonatomic) IBOutlet UISegmentedControl *touchModeSelector;
@property (strong, nonatomic) IBOutlet UILabel *onscreenControllerLabel;
@property (strong, nonatomic) IBOutlet UISegmentedControl *onscreenControlSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *motionModeSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *largerStickLR1Selector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *optimizeSettingsSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *multiControllerSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *swapABXYButtonsSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *pipSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *reverseMouseWheelDirectionSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *statsOverlaySelector;
@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) IBOutlet UILabel *keyboardToggleFingerNumLabel;
@property (strong, nonatomic) IBOutlet UISlider *keyboardToggleFingerNumSlider;
@property (strong, nonatomic) IBOutlet UISegmentedControl *liftStreamViewForKeyboardSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *showKeyboardToolbarSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *swipeExitScreenEdgeSelector;
@property (strong, nonatomic) IBOutlet UILabel *swipeToExitDistanceUILabel;
@property (strong, nonatomic) IBOutlet UISlider *swipeToExitDistanceSlider;
@property (strong, nonatomic) IBOutlet UISegmentedControl *slideToSettingsScreenEdgeSelector;
@property (strong, nonatomic) IBOutlet UILabel *oscTapExlusionAreaSizeLabel;
@property (strong, nonatomic) IBOutlet UISlider *oscTapExlusionAreaSizeSlider;
@property (strong, nonatomic) IBOutlet UILabel *slideToSettingsScreenEdgeUILabel;
@property (strong, nonatomic) IBOutlet UISegmentedControl *cmdToolScreenEdgeSelector;
@property (strong, nonatomic) IBOutlet UILabel *slideToSettingsDistanceUILabel;
@property (strong, nonatomic) IBOutlet UISlider *slideToMenuDistanceSlider;
@property (strong, nonatomic) IBOutlet UISlider *pointerVelocityModeDividerSlider;
@property (strong, nonatomic) IBOutlet UILabel *pointerVelocityModeDividerUILabel;
@property (strong, nonatomic) IBOutlet UISlider *touchPointerVelocityFactorSlider;
@property (strong, nonatomic) IBOutlet UILabel *touchPointerVelocityFactorUILabel;
@property (strong, nonatomic) IBOutlet UISlider *mousePointerVelocityFactorSlider;
@property (strong, nonatomic) IBOutlet UILabel *mousePointerVelocityFactorUILabel;
@property (strong, nonatomic) IBOutlet UISegmentedControl *unlockDisplayOrientationSelector;
@property (strong, nonatomic) IBOutlet UIButton *backToStreamingButton;
@property (strong, nonatomic) LayoutOnScreenControlsViewController *layoutOnScreenControlsVC;
@property (nonatomic, strong) MainFrameViewController *mainFrameViewController;
@property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *localMousePointerModeSelector;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
@@ -50,5 +72,8 @@
#pragma clang diagnostic pop
- (void) saveSettings;
+ (bool) isLandscapeNow;
- (void)updateResolutionTable;
- (void) widget:(UISlider*)widget setEnabled:(bool)enabled;
@end
@@ -14,14 +14,19 @@
#import <VideoToolbox/VideoToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import "LocalizationHelper.h"
@implementation SettingsViewController {
NSInteger _bitrate;
NSInteger _lastSelectedResolutionIndex;
bool justEnteredSettingsViewDoNotOpenOscLayoutTool;
uint16_t oscLayoutFingers;
CustomEdgeSlideGestureRecognizer *slideToCloseSettingsViewRecognizer;
}
@dynamic overrideUserInterfaceStyle;
static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
//static NSString* bitrateFormat;
static const int bitrateTable[] = {
500,
1000,
@@ -36,19 +41,94 @@ static const int bitrateTable[] = {
8000,
9000,
10000,
11000,
12000,
13000,
14000,
15000,
16000,
17000,
18000,
19000,
20000,
21000,
22000,
23000,
24000,
25000,
26000,
27000,
28000,
29000,
30000,
31000,
32000,
33000,
34000,
35000,
36000,
37000,
38000,
39000,
40000,
41000,
42000,
43000,
44000,
45000,
46000,
47000,
48000,
49000,
50000,
50000,
51000,
52000,
53000,
54000,
55000,
56000,
57000,
58000,
59000,
60000,
61000,
62000,
63000,
64000,
65000,
66000,
67000,
68000,
69000,
70000,
80000,
90000,
100000,
110000,
120000,
130000,
140000,
150000,
160000,
170000,
180000,
200000,
220000,
240000,
260000,
280000,
300000,
320000,
340000,
360000,
380000,
400000,
420000,
440000,
460000,
480000,
500000,
};
const int RESOLUTION_TABLE_SIZE = 7;
@@ -119,7 +199,8 @@ BOOL isCustomResolution(CGSize res) {
}
for (int i = 0; i < RESOLUTION_TABLE_CUSTOM_INDEX; i++) {
if (res.width == resolutionTable[i].width && res.height == resolutionTable[i].height) {
if ((res.width == resolutionTable[i].width && res.height == resolutionTable[i].height) || (res.height == resolutionTable[i].width && res.width == resolutionTable[i].height)) {
return NO;
}
}
@@ -127,9 +208,154 @@ BOOL isCustomResolution(CGSize res) {
return YES;
}
- (void)viewDidLoad {
[super viewDidLoad];
+ (bool)isLandscapeNow {
return CGRectGetWidth([[UIScreen mainScreen]bounds]) > CGRectGetHeight([[UIScreen mainScreen]bounds]);
}
- (bool)isFullScreenRequired {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSNumber *requiresFullScreen = infoDictionary[@"UIRequiresFullScreen"];
if (requiresFullScreen != nil) {
return [requiresFullScreen boolValue];
}
// Default behavior if the key is not set
return true;
}
- (bool)isAirPlayEnabled{
return [self.externalDisplayModeSelector selectedSegmentIndex] == 1;
}
- (void)updateResolutionTable{
UIWindow *window = self.view.window;
CGFloat screenScale = window.screen.scale;
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
CGFloat appWindowWidth = window.frame.size.width * screenScale;
CGFloat appWindowHeight = window.frame.size.height * screenScale;
if([self isAirPlayEnabled] && UIScreen.screens.count > 1){
CGRect bounds = [UIScreen.screens.lastObject bounds];
screenScale = [UIScreen.screens.lastObject scale];
appWindowWidth = bounds.size.width * screenScale;
appWindowHeight = bounds.size.height * screenScale;
}
bool needSwapWidthAndHeight = appWindowWidth > appWindowHeight;
resolutionTable[4] = CGSizeMake(safeAreaWidth, appWindowHeight);
for(uint8_t i=0;i<7;i++){
CGFloat longSideLen = resolutionTable[i].height > resolutionTable[i].width ? resolutionTable[i].height : resolutionTable[i].width;
CGFloat shortSideLen = resolutionTable[i].height < resolutionTable[i].width ? resolutionTable[i].height : resolutionTable[i].width;
if(needSwapWidthAndHeight) resolutionTable[i] = CGSizeMake(longSideLen, shortSideLen);
else resolutionTable[i] = CGSizeMake(shortSideLen, longSideLen);
}
resolutionTable[5] = CGSizeMake(appWindowWidth, appWindowHeight);
[self updateResolutionDisplayViewText];
}
// this will also be called back when device orientation changes
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
double delayInSeconds = 0.2;
// Convert the delay into a dispatch_time_t value
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
// Perform some task after the delay
dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay
[self updateResolutionTable];
});
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self updateResolutionTable];
NSLog(@"Resolution table updated");
}
- (void)deviceOrientationDidChange{
double delayInSeconds = 1.0;
// Convert the delay into a dispatch_time_t value
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
// Perform some task after the delay
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
// Code to execute after the delay
// [self updateResolutionTable];
// since we have viewWillTransitionToSize being called back both when orientation changed & app window size changed, resoltion update is dprecated here
});
}
- (void)simulateSettingsButtonPress{
[self.mainFrameViewController simulateSettingsButtonPress];
}
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:@"SettingsViewClosedNotification" object:self]; // notify other view that settings view just closed
}
- (void)pushDownExistingWidgets{
// Calculate the height needed for the exit button
CGFloat exitButtonHeight = 50.0; // Adjust as needed
CGFloat topOffset = exitButtonHeight; // Space needed for the exit button
// Iterate through subviews of self.view and adjust their frames
for (UIView *subview in self.view.subviews) {
CGRect frame = subview.frame;
frame.origin.y += topOffset;
subview.frame = frame;
}
}
- (IBAction)exitButtonTapped:(id)sender {
[self->_mainFrameViewController simulateSettingsButtonPress];
[[NSNotificationCenter defaultCenter] postNotificationName:@"SessionDisconnectedBySettingsViewNotification" object:self];
}
- (IBAction)backToStreamingButtonTapped:(id)sender {
[self simulateSettingsButtonPress];
}
/*
- (void)addExitButtonOnTop{
UIButton *exitButton = [UIButton buttonWithType:UIButtonTypeSystem];
[exitButton setTitle:@"Exit" forState:UIControlStateNormal];
[exitButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; // Set font color
exitButton.backgroundColor = [UIColor clearColor]; // Set background color
exitButton.titleLabel.font = [UIFont boldSystemFontOfSize:16.0]; // Set font size and style
exitButton.layer.cornerRadius = 8.0; // Optional: Round corners if desired
exitButton.clipsToBounds = YES; // Ensure rounded corners are applied properly
[exitButton addTarget:self action:@selector(exitButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:exitButton];
exitButton.frame = CGRectMake(0, 20, 200, 50); // Adjust Y and height as needed
}*/
- (void)edgeSwiped {
[self simulateSettingsButtonPress];
}
- (void)viewDidLoad {
//[self pushDownExistingWidgets];
//[self addExitButtonOnTop];
self->slideToCloseSettingsViewRecognizer = [[CustomEdgeSlideGestureRecognizer alloc] initWithTarget:self action:@selector(edgeSwiped)];
slideToCloseSettingsViewRecognizer.edges = UIRectEdgeLeft;
slideToCloseSettingsViewRecognizer.normalizedThresholdDistance = 0.0;
slideToCloseSettingsViewRecognizer.EDGE_TOLERANCE = 10;
slideToCloseSettingsViewRecognizer.immediateTriggering = true;
slideToCloseSettingsViewRecognizer.delaysTouchesBegan = NO;
slideToCloseSettingsViewRecognizer.delaysTouchesEnded = NO;
[self.view addGestureRecognizer:slideToCloseSettingsViewRecognizer];
justEnteredSettingsViewDoNotOpenOscLayoutTool = true;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceOrientationDidChange) // handle orientation change since i made portrait mode available
name:UIDeviceOrientationDidChangeNotification
object:nil];
// Always run settings in dark mode because we want the light fonts
if (@available(iOS 13.0, tvOS 13.0, *)) {
self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
@@ -160,6 +386,7 @@ BOOL isCustomResolution(CGSize res) {
resolutionTable[4] = CGSizeMake(safeAreaWidth, fullScreenHeight);
resolutionTable[5] = CGSizeMake(fullScreenWidth, fullScreenHeight);
resolutionTable[6] = CGSizeMake([currentSettings.width integerValue], [currentSettings.height integerValue]); // custom initial value
[self updateResolutionTable];
// Don't populate the custom entry unless we have a custom resolution
if (!isCustomResolution(resolutionTable[6])) {
@@ -180,13 +407,9 @@ BOOL isCustomResolution(CGSize res) {
break;
}
NSInteger resolution = 1;
for (int i = 0; i < RESOLUTION_TABLE_SIZE; i++) {
if ((int) resolutionTable[i].height == [currentSettings.height intValue]
&& (int) resolutionTable[i].width == [currentSettings.width intValue]) {
resolution = i;
break;
}
NSInteger resolution = currentSettings.resolutionSelected.integerValue;
if(resolution >= RESOLUTION_TABLE_SIZE){
resolution = 0;
}
// Only show the 120 FPS option if we have a > 60-ish Hz display
@@ -234,30 +457,29 @@ BOOL isCustomResolution(CGSize res) {
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC) || !(AVPlayer.availableHDRModes & AVPlayerHDRModeHDR10)) {
[self.hdrSelector removeAllSegments];
[self.hdrSelector insertSegmentWithTitle:@"Unsupported on this device" atIndex:0 animated:NO];
[self.hdrSelector insertSegmentWithTitle:[LocalizationHelper localizedStringForKey:@"Unsupported on this device"] atIndex:0 animated:NO];
[self.hdrSelector setEnabled:NO];
}
else {
[self.hdrSelector setSelectedSegmentIndex:currentSettings.enableHdr ? 1 : 0];
}
[self.touchModeSelector setSelectedSegmentIndex:currentSettings.absoluteTouchMode ? 1 : 0];
[self.touchModeSelector addTarget:self action:@selector(touchModeChanged) forControlEvents:UIControlEventValueChanged];
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlay ? 1 : 0];
[self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0];
[self.pipSelector setSelectedSegmentIndex:currentSettings.enablePIP ? 1 : 0];
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0];
[self.framePacingSelector setSelectedSegmentIndex:currentSettings.useFramePacing ? 1 : 0];
[self.multiControllerSelector setSelectedSegmentIndex:currentSettings.multiController ? 1 : 0];
[self.swapABXYButtonsSelector setSelectedSegmentIndex:currentSettings.swapABXYButtons ? 1 : 0];
[self.audioOnPCSelector setSelectedSegmentIndex:currentSettings.playAudioOnPC ? 1 : 0];
NSInteger onscreenControls = [currentSettings.onscreenControls integerValue];
_lastSelectedResolutionIndex = resolution;
[self.resolutionSelector setSelectedSegmentIndex:resolution];
[self.resolutionSelector addTarget:self action:@selector(newResolutionChosen) forControlEvents:UIControlEventValueChanged];
[self.framerateSelector setSelectedSegmentIndex:framerate];
[self.framerateSelector addTarget:self action:@selector(updateBitrate) forControlEvents:UIControlEventValueChanged];
[self.onscreenControlSelector setSelectedSegmentIndex:onscreenControls];
[self.onscreenControlSelector setEnabled:!currentSettings.absoluteTouchMode];
NSInteger motionMode = [currentSettings.motionMode integerValue];
[self.motionModeSelector setSelectedSegmentIndex:motionMode];
[self.bitrateSlider setMinimumValue:0];
[self.bitrateSlider setMaximumValue:(sizeof(bitrateTable) / sizeof(*bitrateTable)) - 1];
[self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES];
@@ -265,36 +487,236 @@ BOOL isCustomResolution(CGSize res) {
[self updateBitrateText];
[self updateResolutionDisplayViewText];
[self.keyboardToggleFingerNumSlider setValue:(CGFloat)currentSettings.keyboardToggleFingers.intValue animated:YES]; // Load old setting. old setting was converted to uint16_t before saving.
if (self.keyboardToggleFingerNumSlider.value > 10.5f) [self.keyboardToggleFingerNumLabel setText:[NSString stringWithFormat:@"Tap-Toggle Local Keyboard Disabled"]]; // Initiate label display. Allow higher required finger number to completely disable keyboard toggle
else [self.keyboardToggleFingerNumLabel setText:[NSString stringWithFormat:@"Fingers to Tap-Toggle Local Keyboard: %d", (uint16_t)self.keyboardToggleFingerNumSlider.value]]; // Initiate label display
[self.keyboardToggleFingerNumSlider addTarget:self action:@selector(keyboardToggleFingerNumSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
// Unlock Display Orientation setting
bool unlockDisplayOrientationSelectorEnabled = [self isFullScreenRequired];//need "requires fullscreen" enabled in the app bunddle to make runtime orientation limitation woring
if(unlockDisplayOrientationSelectorEnabled) [self.unlockDisplayOrientationSelector setSelectedSegmentIndex:currentSettings.unlockDisplayOrientation ? 1 : 0];
else [self.unlockDisplayOrientationSelector setSelectedSegmentIndex:1]; // can't lock screen orientation in this mode = Display Orientation always unlocked
[self.unlockDisplayOrientationSelector setEnabled:unlockDisplayOrientationSelectorEnabled];
// lift streamview setting
[self.liftStreamViewForKeyboardSelector setSelectedSegmentIndex:currentSettings.liftStreamViewForKeyboard ? 1 : 0];// Load old setting
// showkeyboard toolbar setting
[self.showKeyboardToolbarSelector setSelectedSegmentIndex:currentSettings.showKeyboardToolbar ? 1 : 0];// Load old setting
[self.swipeExitScreenEdgeSelector setSelectedSegmentIndex:[self getSelectorIndexFromScreenEdge:(uint32_t)currentSettings.swipeExitScreenEdge.integerValue]]; // Load old setting
[self.swipeToExitDistanceSlider setValue:(CGFloat)currentSettings.swipeToExitDistance.floatValue animated:YES]; // Load old setting.
[self.swipeToExitDistanceUILabel setText:[NSString stringWithFormat:@"Swipe & Exit Distance: %.2f * screen-width", self.swipeToExitDistanceSlider.value]]; // Initiate label display
[self.swipeToExitDistanceSlider addTarget:self action:@selector(swipeToExitDistanceSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self.pointerVelocityModeDividerSlider setValue:currentSettings.pointerVelocityModeDivider.floatValue * 100 animated:YES]; // Load old setting.
[self.pointerVelocityModeDividerUILabel setText:[NSString stringWithFormat:@"Touch Pointer Velocity: Scaled on %d%% of Right Screen", 100 - (uint8_t)self.pointerVelocityModeDividerSlider.value]]; // Initiate label display
// reverse mouse wheel direction setting
[self.reverseMouseWheelDirectionSelector setSelectedSegmentIndex:currentSettings.reverseMouseWheelDirection ? 1 : 0];// Load old setting
// slide to menu settings
[self.slideToSettingsScreenEdgeSelector setSelectedSegmentIndex:[self getSelectorIndexFromScreenEdge:(uint32_t)currentSettings.slideToSettingsScreenEdge.integerValue]];
// Load old setting
[self.cmdToolScreenEdgeSelector setEnabled:false];
[self.slideToSettingsScreenEdgeSelector addTarget:self action:@selector(slideToSettingsScreenEdgeChanged) forControlEvents:UIControlEventValueChanged];
[self slideToSettingsScreenEdgeChanged];
[self.slideToMenuDistanceSlider setValue:currentSettings.slideToSettingsDistance.floatValue];
[self.slideToMenuDistanceSlider addTarget:self action:@selector(slideToMenuDistanceSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self slideToMenuDistanceSliderMoved];
//TouchMode & OSC Related Settings:
// pointer veloc setting, will be enable/disabled by touchMode
[self.pointerVelocityModeDividerSlider setValue: (uint8_t)(currentSettings.pointerVelocityModeDivider.floatValue * 100) animated:YES]; // Load old setting.
[self.pointerVelocityModeDividerSlider addTarget:self action:@selector(pointerVelocityModeDividerSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self.touchPointerVelocityFactorSlider setValue:currentSettings.touchPointerVelocityFactor.floatValue * 100 animated:YES]; // Load old setting.
[self.touchPointerVelocityFactorUILabel setText:[NSString stringWithFormat:@"Touch Pointer Velocity Factor: %d%%", (uint16_t)self.touchPointerVelocityFactorSlider.value]]; // Initiate label display
[self pointerVelocityModeDividerSliderMoved];
// init pointer veloc setting, will be enable/disabled by touchMode
[self.touchPointerVelocityFactorSlider setValue: [self map_SliderValue_fromVelocFactor: currentSettings.touchPointerVelocityFactor.floatValue] animated:YES]; // Load old setting.
[self.touchPointerVelocityFactorSlider addTarget:self action:@selector(touchPointerVelocityFactorSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self touchPointerVelocityFactorSliderMoved];
// init relative touch mouse pointer veloc setting, will be enable/disabled by touchMode
[self.mousePointerVelocityFactorSlider setValue:[self map_SliderValue_fromVelocFactor: currentSettings.mousePointerVelocityFactor.floatValue] animated:YES]; // Load old setting.
[self.mousePointerVelocityFactorSlider addTarget:self action:@selector(mousePointerVelocityFactorSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self mousePointerVelocityFactorSliderMoved];
// these settings will be affected by onscreenControl & touchMode, must be loaded before them.
// NSLog(@"osc tool fingers setting test: %d", currentSettings.oscLayoutToolFingers.intValue);
self->oscLayoutFingers = (uint16_t)currentSettings.oscLayoutToolFingers.intValue; // load old setting of oscLayoutFingers
[self.keyboardToggleFingerNumSlider setValue:(CGFloat)currentSettings.keyboardToggleFingers.intValue animated:YES]; // Load old setting. old setting was converted to uint16_t before saving.
[self.keyboardToggleFingerNumSlider addTarget:self action:@selector(keyboardToggleFingerNumSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self keyboardToggleFingerNumSliderMoved];
// this setting will be affected by touchMode, must be loaded before them.
NSInteger onscreenControlsLevel = [currentSettings.onscreenControls integerValue];
[self.onscreenControlSelector setSelectedSegmentIndex:onscreenControlsLevel];
[self.onscreenControlSelector addTarget:self action:@selector(onscreenControlChanged) forControlEvents:UIControlEventValueChanged];
[self onscreenControlChanged];
[self.largerStickLR1Selector setSelectedSegmentIndex:currentSettings.largerStickLR1 ? 1 : 0]; // load old setting of largerStickLR1
// tap exclusion area size for custom OSC, must be loaded before touchMode & osc selector.
[self.oscTapExlusionAreaSizeSlider setValue:currentSettings.oscTapExlusionAreaSize.floatValue * 100 animated:YES]; // Load old setting.
[self.oscTapExlusionAreaSizeSlider addTarget:self action:@selector(oscTapExlusionAreaSizeSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self oscTapExlusionAreaSizeSliderMoved];
// [self.touchModeSelector setSelectedSegmentIndex:currentSettings.absoluteTouchMode ? 1 : 0];
// this part will enable/disable oscSelector & the largerStickLR1 selector
[self.touchModeSelector setSelectedSegmentIndex:currentSettings.touchMode.intValue]; //Load old touchMode setting
[self.touchModeSelector addTarget:self action:@selector(touchModeChanged) forControlEvents:UIControlEventValueChanged];
[self touchModeChanged];
// init CustomOSC stuff
/* sets a reference to the correct 'LayoutOnScreenControlsViewController' depending on whether the user is on an iPhone or iPad */
self.layoutOnScreenControlsVC = [[LayoutOnScreenControlsViewController alloc] init];
BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
if (isIPhone) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"iPhone" bundle:nil];
self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"];
}
else {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"iPad" bundle:nil];
self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"];
self.layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationFullScreen;
}
[self.externalDisplayModeSelector setSelectedSegmentIndex:currentSettings.externalDisplayMode.integerValue];
[self.localMousePointerModeSelector setSelectedSegmentIndex:currentSettings.localMousePointerMode.integerValue];
}
- (void)slideToSettingsScreenEdgeChanged{
if([self.slideToSettingsScreenEdgeSelector selectedSegmentIndex] == 0) [self.cmdToolScreenEdgeSelector setSelectedSegmentIndex:1];
else [self.cmdToolScreenEdgeSelector setSelectedSegmentIndex:0];
}
- (void)updateTouchModeLabel{
NSString* labelText;
switch([self.touchModeSelector selectedSegmentIndex]){
case RELATIVE_TOUCH:
labelText = [LocalizationHelper localizedStringForKey:@"Touch Mode - Double Tap to Drag, OSC Available"];break;
case REGULAR_NATIVE_TOUCH:
labelText = [LocalizationHelper localizedStringForKey:@"Touch Mode - With OSC & Mouse Support"];break;
case PURE_NATIVE_TOUCH:
labelText = [LocalizationHelper localizedStringForKey:@"Touch Mode - No OSC & Mouse Support"];break;
case ABSOLUTE_TOUCH:
labelText = [LocalizationHelper localizedStringForKey:@"Touch Mode - For MacOS Direct Touch"];break;
}
[self.touchModeLabel setText:labelText];
}
- (void)showCustomOSCTip {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Rebase in Stream View"]
message:[LocalizationHelper localizedStringForKey:@"Tap %d fingers to change layout in stream view, or change the number of fingers required to:", self->oscLayoutFingers]
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = [LocalizationHelper localizedStringForKey:@"%d", self->oscLayoutFingers];
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"]
style:UIAlertActionStyleCancel
handler:nil];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
UITextField *textField = alertController.textFields.firstObject;
NSString *inputText = textField.text;
NSInteger fingers = [inputText integerValue];
if (inputText.length > 0 && fingers >= 4) {
self->oscLayoutFingers = (uint16_t) fingers;
NSLog(@"OK button tapped with %d fingers", (uint16_t)fingers);
} else {
NSLog(@"OK button tapped with no change");
}
// Continue execution after the alert is dismissed
if (!self->_mainFrameViewController.settingsExpandedInStreamView) {
[self invokeOscLayout]; // Don't open osc layout tool immediately during streaming
}
[self.onscreenControllerLabel setText:[LocalizationHelper localizedStringForKey: @"Tap %d Fingers to Change OSC Layout in Stream View", self->oscLayoutFingers]]; //update the osc label
[self keyboardToggleFingerNumSliderMoved]; //update keyboard toggle number;
}];
[alertController addAction:cancelAction];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
- (bool) isOnScreenControllerOrButtonEnabled{
return ([self.touchModeSelector selectedSegmentIndex] == RELATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == REGULAR_NATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == ABSOLUTE_TOUCH) && [self.onscreenControlSelector selectedSegmentIndex] != OnScreenControlsLevelOff;
}
- (void)onscreenControlChanged{
BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
if (isIPhone) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"iPhone" bundle:nil];
self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"];
}
else {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"iPad" bundle:nil];
self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"];
self.layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationFullScreen;
}
bool customOscEnabled = [self isOnScreenControllerOrButtonEnabled] && [self.onscreenControlSelector selectedSegmentIndex] == OnScreenControlsLevelCustom;
// [self widget:self.keyboardToggleFingerNumSlider setEnabled:!customOscEnabled];
[self widget:self.oscTapExlusionAreaSizeSlider setEnabled:customOscEnabled];
if(customOscEnabled && !justEnteredSettingsViewDoNotOpenOscLayoutTool) {
// [self.keyboardToggleFingerNumSlider setValue:3.0];
// [self keyboardToggleFingerNumSliderMoved];
[self keyboardToggleFingerNumSliderMoved]; // go to exclude 5 fingers
[self.onscreenControllerLabel setText:[LocalizationHelper localizedStringForKey: @"Tap %d Fingers to Change OSC Layout in Stream View", self->oscLayoutFingers]];
[self showCustomOSCTip];
justEnteredSettingsViewDoNotOpenOscLayoutTool = false;
//if (self.layoutOnScreenControlsVC.isBeingPresented == NO)
}
else{
[self.onscreenControllerLabel setText:[LocalizationHelper localizedStringForKey: @"On-Screen Controls & Widgets"]];
}
justEnteredSettingsViewDoNotOpenOscLayoutTool = false;
}
- (void)invokeOscLayout{
[self presentViewController:self.layoutOnScreenControlsVC animated:YES completion:nil];
}
- (void) pointerVelocityModeDividerSliderMoved {
[self.pointerVelocityModeDividerUILabel setText:[NSString stringWithFormat:@"Touch Pointer Velocity: Scaled on %d%% of Right Screen", 100 - (uint8_t)self.pointerVelocityModeDividerSlider.value]];
[self.pointerVelocityModeDividerUILabel setText:[LocalizationHelper localizedStringForKey:@"Touch Pointer Velocity: Scaled on %d%% of Right Screen", 100 - (uint8_t)self.pointerVelocityModeDividerSlider.value]];
}
- (void) touchPointerVelocityFactorSliderMoved {
[self.touchPointerVelocityFactorUILabel setText:[NSString stringWithFormat:@"Touch Pointer Velocity Factor: %d%%", (uint16_t)self.touchPointerVelocityFactorSlider.value]]; // Initiate label display
[self.touchPointerVelocityFactorUILabel setText:[LocalizationHelper localizedStringForKey: @"Touch Pointer Velocity: %d%%", [self map_velocFactorDisplay_fromSliderValue: self.touchPointerVelocityFactorSlider.value]]]; // Update label display
}
// veloc factor upto 700%
- (uint16_t) map_velocFactorDisplay_fromSliderValue:(CGFloat)sliderValue{
uint16_t velocFactorDisplay = 0;
if(sliderValue > 200) velocFactorDisplay = 200 + ((uint16_t)sliderValue % 200) * 5;
else velocFactorDisplay = (uint16_t) sliderValue;
return velocFactorDisplay;
}
// veloc factor upto 700%
- (CGFloat) map_SliderValue_fromVelocFactor:(CGFloat)velocFactor{
CGFloat sliderValue = 0.0f;
if(velocFactor < 2.0f) sliderValue = velocFactor * 100;
else sliderValue = (velocFactor - 2.0) * 100 / 5 + 200;
return sliderValue;
}
- (void) mousePointerVelocityFactorSliderMoved {
[self.mousePointerVelocityFactorUILabel setText:[LocalizationHelper localizedStringForKey: @"Mouse Pointer Velocity: %d%%", [self map_velocFactorDisplay_fromSliderValue: self.mousePointerVelocityFactorSlider.value]]]; // Update label display
}
- (uint32_t) getScreenEdgeFromSelector {
switch (self.swipeExitScreenEdgeSelector.selectedSegmentIndex) {
switch (self.slideToSettingsScreenEdgeSelector.selectedSegmentIndex) {
case 0: return UIRectEdgeLeft;
case 1: return UIRectEdgeRight;
case 2: return UIRectEdgeLeft|UIRectEdgeRight;
@@ -312,12 +734,43 @@ BOOL isCustomResolution(CGSize res) {
return 0;
}
- (void) widget:(UISlider*)widget setEnabled:(bool)enabled{
[widget setEnabled:enabled];
if(enabled){
widget.alpha = 1.0;
[widget setValue:widget.value + 0.0001]; // this is for low iOS version (like iOS14), only setting this minor value change is able to make widget visibility clear
}
else widget.alpha = 0.5; // this is for updating widget visibility on low iOS version like mini5 ios14
}
- (void) touchModeChanged {
// Disable on-screen controls in absolute touch mode
[self.onscreenControlSelector setEnabled:[self.touchModeSelector selectedSegmentIndex] == 0];
[self.pointerVelocityModeDividerSlider setEnabled:[self.touchModeSelector selectedSegmentIndex] == 1]; // pointer velocity scaling works only in native touch mode.
[self.touchPointerVelocityFactorSlider setEnabled:[self.touchModeSelector selectedSegmentIndex] == 1]; // pointer velocity scaling works only in native touch mode.
// Disable On-Screen Controls & Widgets in non-relative touch mode
bool oscSelectorEnabled = [self.touchModeSelector selectedSegmentIndex] == RELATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == REGULAR_NATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == ABSOLUTE_TOUCH;
bool customOscEnabled = [self isOnScreenControllerOrButtonEnabled] && [self.onscreenControlSelector selectedSegmentIndex] == OnScreenControlsLevelCustom;
bool isNativeTouch = [self.touchModeSelector selectedSegmentIndex] == PURE_NATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == REGULAR_NATIVE_TOUCH;
[self.onscreenControlSelector setEnabled:oscSelectorEnabled];
[self.largerStickLR1Selector setEnabled:oscSelectorEnabled]; // this selector stay aligned with oscSelector
[self widget:self.pointerVelocityModeDividerSlider setEnabled:isNativeTouch]; // pointer velocity scaling works only in native touch mode.
[self widget:self.touchPointerVelocityFactorSlider setEnabled:isNativeTouch]; // pointer velocity scaling works only in native touch mode.
[self widget:self.mousePointerVelocityFactorSlider setEnabled:[self.touchModeSelector selectedSegmentIndex] == RELATIVE_TOUCH]; // mouse velocity scaling works only in relative touch mode.
[self widget:self.oscTapExlusionAreaSizeSlider setEnabled:customOscEnabled];
// number of touches required to toggle keyboard will be fixed to 3 when OSC is enabled.
// [self widget:self.keyboardToggleFingerNumSlider setEnabled: !customOscEnabled]; // cancel OSC limitation for regular native touch,
// when CustomOSC is enabled:
if(customOscEnabled) {
// [self.keyboardToggleFingerNumSlider setValue:3.0];
// [self.keyboardToggleFingerNumLabel setText:[LocalizationHelper localizedStringForKey:@"To Toggle Local Keyboard: Tap %d Fingers", (uint16_t)self.keyboardToggleFingerNumSlider.value]];
[self.onscreenControllerLabel setText:[LocalizationHelper localizedStringForKey: @"Tap %d Fingers to Change OSC Layout in Stream View", self->oscLayoutFingers]];
[self keyboardToggleFingerNumSliderMoved]; // go exclude 5 fingers
//if (self.layoutOnScreenControlsVC.isBeingPresented == NO)
}
else{
[self.onscreenControllerLabel setText:[LocalizationHelper localizedStringForKey: @"On-Screen Controls & Widgets"]];
}
[self updateTouchModeLabel];
}
- (void) updateBitrate {
@@ -392,13 +845,14 @@ BOOL isCustomResolution(CGSize res) {
[self updateResolutionDisplayViewText];
_lastSelectedResolutionIndex = [self.resolutionSelector selectedSegmentIndex];
}
[self updateResolutionTable];
}
- (void) promptCustomResolutionDialog {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Enter Custom Resolution" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey: @"Enter Custom Resolution"] message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = @"Video Width";
textField.placeholder = [LocalizationHelper localizedStringForKey:@"Video Width"];
textField.clearButtonMode = UITextFieldViewModeAlways;
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.keyboardType = UIKeyboardTypeNumberPad;
@@ -412,7 +866,7 @@ BOOL isCustomResolution(CGSize res) {
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = @"Video Height";
textField.placeholder = [LocalizationHelper localizedStringForKey:@"Video Height"];
textField.clearButtonMode = UITextFieldViewModeAlways;
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.keyboardType = UIKeyboardTypeNumberPad;
@@ -425,7 +879,7 @@ BOOL isCustomResolution(CGSize res) {
}
}];
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSArray * textfields = alertController.textFields;
UITextField *widthField = textfields[0];
UITextField *heightField = textfields[1];
@@ -460,12 +914,12 @@ BOOL isCustomResolution(CGSize res) {
[self updateResolutionDisplayViewText];
self->_lastSelectedResolutionIndex = [self.resolutionSelector selectedSegmentIndex];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Custom Resolution Selected" message: @"Custom resolutions are not officially supported by GeForce Experience, so it will not set your host display resolution. You will need to set it manually while in game.\n\nResolutions that are not supported by your client or host PC may cause streaming errors." preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Custom Resolution Selected"] message: [LocalizationHelper localizedStringForKey:@"Custom resolutions are not officially supported by GeForce Experience, so it will not set your host display resolution. You will need to set it manually while in game.\n\nResolutions that are not supported by your client or host PC may cause streaming errors."] preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
// Restore the previous selection
[self.resolutionSelector setSelectedSegmentIndex:self->_lastSelectedResolutionIndex];
}]];
@@ -492,7 +946,7 @@ BOOL isCustomResolution(CGSize res) {
[subview removeFromSuperview];
}
UILabel *label1 = [[UILabel alloc] init];
label1.text = @"Set PC/Game resolution: ";
label1.text = [LocalizationHelper localizedStringForKey:@"Set PC/Game resolution:"];
label1.font = [UIFont systemFontOfSize:fontSize];
[label1 sizeToFit];
label1.frame = CGRectMake(padding, (viewFrameHeight - label1.frame.size.height) / 2, label1.frame.size.width, label1.frame.size.height);
@@ -506,13 +960,28 @@ BOOL isCustomResolution(CGSize res) {
[self.resolutionDisplayView addSubview:label2];
}
- (void) keyboardToggleFingerNumSliderMoved{
if (self.keyboardToggleFingerNumSlider.value > 10.5f) [self.keyboardToggleFingerNumLabel setText:[NSString stringWithFormat:@"Tap-Toggle Local Keyboard Disabled"]];
else [self.keyboardToggleFingerNumLabel setText:[NSString stringWithFormat:@"Fingers to Tap-Toggle Local Keyboard: %d", (uint16_t)self.keyboardToggleFingerNumSlider.value]]; // Initiate label display
- (void) oscTapExlusionAreaSizeSliderMoved{
[self.oscTapExlusionAreaSizeLabel setText:[LocalizationHelper localizedStringForKey:@"Tap Exclusion Area Size for Custom OSC: %d%%", (uint16_t)self.oscTapExlusionAreaSizeSlider.value]];
}
- (void) swipeToExitDistanceSliderMoved{
[self.swipeToExitDistanceUILabel setText:[NSString stringWithFormat:@"Swipe & Exit Distance: %.2f * screen-width", self.swipeToExitDistanceSlider.value]];
- (void) keyboardToggleFingerNumSliderMoved{
// bool oscEnabled = [self isOnScreenControllerOrButtonEnabled];
bool customOscEnabled = [self isOnScreenControllerOrButtonEnabled] && [self.onscreenControlSelector selectedSegmentIndex] == OnScreenControlsLevelCustom;
CGFloat sliderValue = self.keyboardToggleFingerNumSlider.value;
if(customOscEnabled){
// exclude self->oscLayoutFingers when custom osc is enabled
if(sliderValue > self->oscLayoutFingers - 1 && sliderValue < self->oscLayoutFingers) [self.keyboardToggleFingerNumSlider setValue: self->oscLayoutFingers - 1];
if(sliderValue >= self->oscLayoutFingers && sliderValue < self->oscLayoutFingers + 1) [self.keyboardToggleFingerNumSlider setValue: self->oscLayoutFingers + 1];
}
sliderValue = self.keyboardToggleFingerNumSlider.value;
if(sliderValue > 10.5f) [self.keyboardToggleFingerNumLabel setText:[LocalizationHelper localizedStringForKey:@"Local Keyboard Toggle Disabled"]];
else [self.keyboardToggleFingerNumLabel setText:[LocalizationHelper localizedStringForKey:@"To Toggle Local Keyboard: Tap %d Fingers", (uint16_t)sliderValue]]; // Initiate label display, exclude 5 fingers.
}
- (void) slideToMenuDistanceSliderMoved{
[self.slideToSettingsDistanceUILabel setText:[LocalizationHelper localizedStringForKey:@"Slide Distance for in-Stream Menu: %.2f * screen-width", self.slideToMenuDistanceSlider.value]];
}
- (void) bitrateSliderMoved {
@@ -523,7 +992,8 @@ BOOL isCustomResolution(CGSize res) {
- (void) updateBitrateText {
// Display bitrate in Mbps
[self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, _bitrate / 1000.]];
if(_bitrate / 1000. > 50.0) [self.bitrateLabel setText:[LocalizationHelper localizedStringForKey:@"Bitrate: %.1f Mbps - Use High Bitrates with Caution!", _bitrate / 1000.]];
else [self.bitrateLabel setText:[LocalizationHelper localizedStringForKey:@"Bitrate: %.1f Mbps", _bitrate / 1000.]];
}
- (NSInteger) getChosenFrameRate {
@@ -587,12 +1057,21 @@ BOOL isCustomResolution(CGSize res) {
NSInteger height = [self getChosenStreamHeight];
NSInteger width = [self getChosenStreamWidth];
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
NSInteger motionMode = [self.motionModeSelector selectedSegmentIndex];
NSInteger keyboardToggleFingers = (uint16_t)self.keyboardToggleFingerNumSlider.value;
NSInteger oscLayoutToolFingers = (uint16_t)self->oscLayoutFingers;
// NSLog(@"saveSettings keyboardToggleFingers %d", (uint16_t)keyboardToggleFingers);
CGFloat swipeToExitDistance = self.swipeToExitDistanceSlider.value;
uint32_t swipeExitScreenEdge = [self getScreenEdgeFromSelector];
CGFloat slideToSettingsDistance = self.slideToMenuDistanceSlider.value;
uint32_t slideToSettingsScreenEdge = [self getScreenEdgeFromSelector];
CGFloat pointerVelocityModeDivider = (CGFloat)(uint8_t)self.pointerVelocityModeDividerSlider.value/100;
CGFloat touchPointerVelocityFactor =(CGFloat)(uint16_t)self.touchPointerVelocityFactorSlider.value/100;
CGFloat touchPointerVelocityFactor = (CGFloat)(uint16_t)[self map_velocFactorDisplay_fromSliderValue:self.touchPointerVelocityFactorSlider.value]/100;
CGFloat mousePointerVelocityFactor = (CGFloat)(uint16_t)[self map_velocFactorDisplay_fromSliderValue:self.mousePointerVelocityFactorSlider.value]/100;
// CGFloat mousePointerVelocityFactor =(CGFloat)(uint16_t)self.mousePointerVelocityFactorSlider.value/100;
CGFloat oscTapExlusionAreaSize =(CGFloat)(uint16_t)self.oscTapExlusionAreaSizeSlider.value/100;
BOOL reverseMouseWheelDirection = [self.reverseMouseWheelDirectionSelector selectedSegmentIndex] == 1;
BOOL largerStickLR1 = [self.largerStickLR1Selector selectedSegmentIndex] == 1;
BOOL liftStreamViewForKeyboard = [self.liftStreamViewForKeyboardSelector selectedSegmentIndex] == 1;
BOOL showKeyboardToolbar = [self.showKeyboardToolbarSelector selectedSegmentIndex] == 1;
BOOL optimizeGames = [self.optimizeSettingsSelector selectedSegmentIndex] == 1;
@@ -600,22 +1079,37 @@ BOOL isCustomResolution(CGSize res) {
BOOL swapABXYButtons = [self.swapABXYButtonsSelector selectedSegmentIndex] == 1;
BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1;
uint32_t preferredCodec = [self getChosenCodecPreference];
BOOL enableYUV444 = [self.yuv444Selector selectedSegmentIndex] == 1;
BOOL enablePIP = [self.pipSelector selectedSegmentIndex] == 1;
BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1;
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
BOOL statsOverlay = [self.statsOverlaySelector selectedSegmentIndex] == 1;
// BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
NSInteger touchMode = [self.touchModeSelector selectedSegmentIndex];
NSInteger statsOverlayLevel = [self.statsOverlaySelector selectedSegmentIndex];
BOOL statsOverlayEnabled = statsOverlayLevel != 0;
BOOL enableHdr = [self.hdrSelector selectedSegmentIndex] == 1;
BOOL unlockDisplayOrientation = [self.unlockDisplayOrientationSelector selectedSegmentIndex] == 1;
NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex];
NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex];
NSInteger localMousePointerMode = [self.localMousePointerModeSelector selectedSegmentIndex];
NSLog(@"saving motionMode: %ld", motionMode);
[dataMan saveSettingsWithBitrate:_bitrate
framerate:framerate
height:height
width:width
audioConfig:2 // Stereo
onscreenControls:onscreenControls
motionMode:motionMode
keyboardToggleFingers:keyboardToggleFingers
swipeExitScreenEdge:swipeExitScreenEdge
swipeToExitDistance:swipeToExitDistance
oscLayoutToolFingers:oscLayoutToolFingers
slideToSettingsScreenEdge:slideToSettingsScreenEdge
slideToSettingsDistance:slideToSettingsDistance
pointerVelocityModeDivider:pointerVelocityModeDivider
touchPointerVelocityFactor:touchPointerVelocityFactor
mousePointerVelocityFactor:mousePointerVelocityFactor
oscTapExlusionAreaSize:oscTapExlusionAreaSize
reverseMouseWheelDirection:reverseMouseWheelDirection
largerStickLR1:largerStickLR1
liftStreamViewForKeyboard:liftStreamViewForKeyboard
showKeyboardToolbar:showKeyboardToolbar
optimizeGames:optimizeGames
@@ -623,11 +1117,19 @@ BOOL isCustomResolution(CGSize res) {
swapABXYButtons:swapABXYButtons
audioOnPC:audioOnPC
preferredCodec:preferredCodec
enableYUV444:enableYUV444
enablePIP:enablePIP
useFramePacing:useFramePacing
enableHdr:enableHdr
btMouseSupport:btMouseSupport
absoluteTouchMode:absoluteTouchMode
statsOverlay:statsOverlay];
// absoluteTouchMode:absoluteTouchMode
touchMode:touchMode
statsOverlayLevel:statsOverlayLevel
statsOverlayEnabled:statsOverlayEnabled
unlockDisplayOrientation:unlockDisplayOrientation
resolutionSelected:resolutionSelected
externalDisplayMode:externalDisplayMode
localMousePointerMode:localMousePointerMode];
}
- (void)didReceiveMemoryWarning {
@@ -641,5 +1143,4 @@ BOOL isCustomResolution(CGSize res) {
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
}
@end
@@ -1,26 +0,0 @@
//
// StreamFrameViewController.h
// Moonlight
//
// Created by Diego Waxemberg on 1/18/14.
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
//
#import "Connection.h"
#import "StreamConfiguration.h"
#import "StreamView.h"
#import <UIKit/UIKit.h>
#if TARGET_OS_TV
@import GameController;
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate>
#else
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate>
#endif
@property (nonatomic) StreamConfiguration* streamConfig;
-(void)updatePreferredDisplayMode:(BOOL)streamActive;
@end
@@ -1,719 +0,0 @@
//
// StreamFrameViewController.m
// Moonlight
//
// Created by Diego Waxemberg on 1/18/14.
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
//
#import "StreamFrameViewController.h"
#import "MainFrameViewController.h"
#import "VideoDecoderRenderer.h"
#import "StreamManager.h"
#import "ControllerSupport.h"
#import "DataManager.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <Limelight.h>
#import "CustomEdgeSwipeGestureRecognizer.h"
#if TARGET_OS_TV
#import <AVFoundation/AVDisplayCriteria.h>
#import <AVKit/AVDisplayManager.h>
#import <AVKit/UIWindow.h>
#endif
@interface AVDisplayCriteria()
@property(readonly) int videoDynamicRange;
@property(readonly, nonatomic) float refreshRate;
- (id)initWithRefreshRate:(float)arg1 videoDynamicRange:(int)arg2;
@end
@implementation StreamFrameViewController {
ControllerSupport *_controllerSupport;
StreamManager *_streamMan;
TemporarySettings *_settings;
NSTimer *_inactivityTimer;
NSTimer *_statsUpdateTimer;
UITapGestureRecognizer *_menuTapGestureRecognizer;
UITapGestureRecognizer *_menuDoubleTapGestureRecognizer;
UITapGestureRecognizer *_playPauseTapGestureRecognizer;
UITextView *_overlayView;
UILabel *_stageLabel;
UILabel *_tipLabel;
UIActivityIndicatorView *_spinner;
StreamView *_streamView;
UIScrollView *_scrollView;
BOOL _userIsInteracting;
CGSize _keyboardSize;
#if !TARGET_OS_TV
CustomEdgeSwipeGestureRecognizer *_exitSwipeRecognizer;
#endif
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
#if !TARGET_OS_TV
[[self revealViewController] setPrimaryViewController:self];
#endif
}
#if TARGET_OS_TV
- (void)controllerPauseButtonPressed:(id)sender { }
- (void)controllerPauseButtonDoublePressed:(id)sender {
Log(LOG_I, @"Menu double-pressed -- backing out of stream");
[self returnToMainFrame];
}
- (void)controllerPlayPauseButtonPressed:(id)sender {
Log(LOG_I, @"Play/Pause button pressed -- backing out of stream");
[self returnToMainFrame];
}
#endif
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES animated:YES];
[UIApplication sharedApplication].idleTimerDisabled = YES;
_settings = [[[DataManager alloc] init] getSettings]; //StreamFrameViewController retrieve the settings here.
_stageLabel = [[UILabel alloc] init];
[_stageLabel setUserInteractionEnabled:NO];
[_stageLabel setText:[NSString stringWithFormat:@"Starting %@...", self.streamConfig.appName]];
[_stageLabel sizeToFit];
_stageLabel.textAlignment = NSTextAlignmentCenter;
_stageLabel.textColor = [UIColor whiteColor];
_stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
_spinner = [[UIActivityIndicatorView alloc] init];
[_spinner setUserInteractionEnabled:NO];
#if TARGET_OS_TV
[_spinner setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge];
#else
[_spinner setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhite];
#endif
[_spinner sizeToFit];
[_spinner startAnimating];
_spinner.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2 - _stageLabel.frame.size.height - _spinner.frame.size.height);
_controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig delegate:self];
_inactivityTimer = nil;
_streamView = [[StreamView alloc] initWithFrame:self.view.frame];
[_streamView setupStreamView:_controllerSupport interactionDelegate:self config:self.streamConfig];
#if TARGET_OS_TV
if (!_menuTapGestureRecognizer || !_menuDoubleTapGestureRecognizer || !_playPauseTapGestureRecognizer) {
_menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(controllerPauseButtonPressed:)];
_menuTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
_playPauseTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(controllerPlayPauseButtonPressed:)];
_playPauseTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypePlayPause)];
_menuDoubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(controllerPauseButtonDoublePressed:)];
_menuDoubleTapGestureRecognizer.numberOfTapsRequired = 2;
[_menuTapGestureRecognizer requireGestureRecognizerToFail:_menuDoubleTapGestureRecognizer];
_menuDoubleTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
}
[self.view addGestureRecognizer:_menuTapGestureRecognizer];
[self.view addGestureRecognizer:_menuDoubleTapGestureRecognizer];
[self.view addGestureRecognizer:_playPauseTapGestureRecognizer];
#else
_exitSwipeRecognizer = [[CustomEdgeSwipeGestureRecognizer alloc] initWithTarget:self action:@selector(edgeSwiped)];
_exitSwipeRecognizer.edges = _settings.swipeExitScreenEdge.intValue;
_exitSwipeRecognizer.normalizedThresholdDistance = _settings.swipeToExitDistance.floatValue;
_exitSwipeRecognizer.delaysTouchesBegan = NO;
_exitSwipeRecognizer.delaysTouchesEnded = NO;
[self.view addGestureRecognizer:_exitSwipeRecognizer];
#endif
_tipLabel = [[UILabel alloc] init];
[_tipLabel setUserInteractionEnabled:NO];
#if TARGET_OS_TV
[_tipLabel setText:@"Tip: Tap the Play/Pause button on the Apple TV Remote to disconnect from your PC"];
#else
[_tipLabel setText:@"Tip: Swipe from the left edge to the middle of your screen to disconnect from your PC"];
#endif
[_tipLabel sizeToFit];
_tipLabel.textColor = [UIColor whiteColor];
_tipLabel.textAlignment = NSTextAlignmentCenter;
_tipLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height * 0.9);
_streamMan = [[StreamManager alloc] initWithConfig:self.streamConfig
renderView:_streamView
connectionCallbacks:self];
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperation:_streamMan];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(applicationDidBecomeActive:)
name: UIApplicationDidBecomeActiveNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(applicationDidEnterBackground:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
#if 0
// FIXME: This doesn't work reliably on iPad for some reason. Showing and hiding the keyboard
// several times in a row will not correctly restore the state of the UIScrollView.
// I add this notification to the StreamView, works well on ipad mini6.
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardWillShow:)
name: UIKeyboardWillShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(keyboardWillHide:)
name: UIKeyboardWillHideNotification
object: nil];
#endif
// Only enable scroll and zoom in absolute touch mode
// if (_settings.absoluteTouchMode) {
// _scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
//#if !TARGET_OS_TV
// [_scrollView.panGestureRecognizer setMinimumNumberOfTouches:2];
//#endif
// [_scrollView setShowsHorizontalScrollIndicator:NO];
// [_scrollView setShowsVerticalScrollIndicator:NO];
// [_scrollView setDelegate:self];
// [_scrollView setMaximumZoomScale:10.0f];
//
// // Add StreamView inside a UIScrollView for absolute mode
// [_scrollView addSubview:_streamView];
// [self.view addSubview:_scrollView];
// }
// else {
// // Add StreamView directly in relative mode
// [self.view addSubview:_streamView];
// }
[self.view addSubview:_streamView];
[self.view addSubview:_stageLabel];
[self.view addSubview:_spinner];
[self.view addSubview:_tipLabel];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return _streamView;
}
- (void)willMoveToParentViewController:(UIViewController *)parent {
// Only cleanup when we're being destroyed
if (parent == nil) {
[_controllerSupport cleanup];
[UIApplication sharedApplication].idleTimerDisabled = NO;
[_streamMan stopStream];
if (_inactivityTimer != nil) {
[_inactivityTimer invalidate];
_inactivityTimer = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
}
#if 0
- (void)keyboardWillShow:(NSNotification *)notification {
_keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
[UIView animateWithDuration:0.3 animations:^{
CGRect frame = self->_scrollView.frame;
frame.size.height -= self->_keyboardSize.height;
self->_scrollView.frame = frame;
}];
}
-(void)keyboardWillHide:(NSNotification *)notification {
// NOTE: UIKeyboardFrameEndUserInfoKey returns a different keyboard size
// than UIKeyboardFrameBeginUserInfoKey, so it's unsuitable for use here
// to undo the changes made by keyboardWillShow.
[UIView animateWithDuration:0.3 animations:^{
CGRect frame = self->_scrollView.frame;
frame.size.height += self->_keyboardSize.height;
self->_scrollView.frame = frame;
}];
}
#endif
- (void)updateStatsOverlay {
NSString* overlayText = [self->_streamMan getStatsOverlayText];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateOverlayText:overlayText];
});
}
- (void)updateOverlayText:(NSString*)text {
if (_overlayView == nil) {
_overlayView = [[UITextView alloc] init];
#if !TARGET_OS_TV
[_overlayView setEditable:NO];
#endif
[_overlayView setUserInteractionEnabled:NO];
[_overlayView setSelectable:NO];
[_overlayView setScrollEnabled:NO];
// HACK: If not using stats overlay, center the text
if (_statsUpdateTimer == nil) {
[_overlayView setTextAlignment:NSTextAlignmentCenter];
}
[_overlayView setTextColor:[UIColor lightGrayColor]];
[_overlayView setBackgroundColor:[UIColor blackColor]];
#if TARGET_OS_TV
[_overlayView setFont:[UIFont systemFontOfSize:24]];
#else
[_overlayView setFont:[UIFont systemFontOfSize:12]];
#endif
[_overlayView setAlpha:0.5];
[self.view addSubview:_overlayView];
}
if (text != nil) {
// We set our bounds to the maximum width in order to work around a bug where
// sizeToFit interacts badly with the UITextView's line breaks, causing the
// width to get smaller and smaller each time as more line breaks are inserted.
[_overlayView setBounds:CGRectMake(self.view.frame.origin.x,
_overlayView.frame.origin.y,
self.view.frame.size.width,
_overlayView.frame.size.height)];
[_overlayView setText:text];
[_overlayView sizeToFit];
[_overlayView setCenter:CGPointMake(self.view.frame.size.width / 2, _overlayView.frame.size.height / 2)];
[_overlayView setHidden:NO];
}
else {
[_overlayView setHidden:YES];
}
}
- (void) returnToMainFrame {
// Reset display mode back to default
[self updatePreferredDisplayMode:NO];
[_statsUpdateTimer invalidate];
_statsUpdateTimer = nil;
[self.navigationController popToRootViewControllerAnimated:YES];
}
// This will fire if the user opens control center or gets a low battery message
- (void)applicationWillResignActive:(NSNotification *)notification {
if (_inactivityTimer != nil) {
[_inactivityTimer invalidate];
}
#if !TARGET_OS_TV
// Terminate the stream if the app is inactive for 60 seconds
Log(LOG_I, @"Starting inactivity termination timer");
_inactivityTimer = [NSTimer scheduledTimerWithTimeInterval:60
target:self
selector:@selector(inactiveTimerExpired:)
userInfo:nil
repeats:NO];
#endif
}
- (void)inactiveTimerExpired:(NSTimer*)timer {
Log(LOG_I, @"Terminating stream after inactivity");
[self returnToMainFrame];
_inactivityTimer = nil;
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
// Stop the background timer, since we're foregrounded again
if (_inactivityTimer != nil) {
Log(LOG_I, @"Stopping inactivity timer after becoming active again");
[_inactivityTimer invalidate];
_inactivityTimer = nil;
}
}
// This fires when the home button is pressed
- (void)applicationDidEnterBackground:(UIApplication *)application {
Log(LOG_I, @"Terminating stream immediately for backgrounding");
if (_inactivityTimer != nil) {
[_inactivityTimer invalidate];
_inactivityTimer = nil;
}
[self returnToMainFrame];
}
- (void)edgeSwiped {
Log(LOG_I, @"User swiped to end stream");
[self returnToMainFrame];
}
- (void) connectionStarted {
Log(LOG_I, @"Connection started");
dispatch_async(dispatch_get_main_queue(), ^{
// Leave the spinner spinning until it's obscured by
// the first frame of video.
self->_stageLabel.hidden = YES;
self->_tipLabel.hidden = YES;
[self->_streamView showOnScreenControls];
[self->_controllerSupport connectionEstablished];
if (self->_settings.statsOverlay) {
self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:@selector(updateStatsOverlay)
userInfo:nil
repeats:YES];
}
});
}
- (void)connectionTerminated:(int)errorCode {
Log(LOG_I, @"Connection terminated: %d", errorCode);
unsigned int portFlags = LiGetPortFlagsFromTerminationErrorCode(errorCode);
unsigned int portTestResults = LiTestClientConnectivity(CONN_TEST_SERVER, 443, portFlags);
dispatch_async(dispatch_get_main_queue(), ^{
// Allow the display to go to sleep now
[UIApplication sharedApplication].idleTimerDisabled = NO;
NSString* title;
NSString* message;
if (portTestResults != ML_TEST_RESULT_INCONCLUSIVE && portTestResults != 0) {
title = @"Connection Error";
message = @"Your device's network connection is blocking Moonlight. Streaming may not work while connected to this network.";
}
else {
switch (errorCode) {
case ML_ERROR_GRACEFUL_TERMINATION:
[self returnToMainFrame];
return;
case ML_ERROR_NO_VIDEO_TRAFFIC:
title = @"Connection Error";
message = @"No video received from host.";
if (portFlags != 0) {
char failingPorts[256];
LiStringifyPortFlags(portFlags, "\n", failingPorts, sizeof(failingPorts));
message = [message stringByAppendingString:[NSString stringWithFormat:@"\n\nCheck your firewall and port forwarding rules for port(s):\n%s", failingPorts]];
}
break;
case ML_ERROR_NO_VIDEO_FRAME:
title = @"Connection Error";
message = @"Your network connection isn't performing well. Reduce your video bitrate setting or try a faster connection.";
break;
case ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
case ML_ERROR_PROTECTED_CONTENT:
title = @"Connection Error";
message = @"Something went wrong on your host PC when starting the stream.\n\nMake sure you don't have any DRM-protected content open on your host PC. You can also try restarting your host PC.\n\nIf the issue persists, try reinstalling your GPU drivers and GeForce Experience.";
break;
case ML_ERROR_FRAME_CONVERSION:
title = @"Connection Error";
message = @"The host PC reported a fatal video encoding error.\n\nTry disabling HDR mode, changing the streaming resolution, or changing your host PC's display resolution.";
break;
default:
{
NSString* errorString;
if (abs(errorCode) > 1000) {
// We'll assume large errors are hex values
errorString = [NSString stringWithFormat:@"%08X", (uint32_t)errorCode];
}
else {
// Smaller values will just be printed as decimal (probably errno.h values)
errorString = [NSString stringWithFormat:@"%d", errorCode];
}
title = @"Connection Terminated";
message = [NSString stringWithFormat: @"The connection was terminated\n\nError code: %@", errorString];
break;
}
}
}
UIAlertController* conTermAlert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
[Utils addHelpOptionToDialog:conTermAlert];
[conTermAlert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
[self returnToMainFrame];
}]];
[self presentViewController:conTermAlert animated:YES completion:nil];
});
[_streamMan stopStream];
}
- (void) stageStarting:(const char*)stageName {
Log(LOG_I, @"Starting %s", stageName);
dispatch_async(dispatch_get_main_queue(), ^{
NSString* lowerCase = [NSString stringWithFormat:@"%s in progress...", stageName];
NSString* titleCase = [[[lowerCase substringToIndex:1] uppercaseString] stringByAppendingString:[lowerCase substringFromIndex:1]];
[self->_stageLabel setText:titleCase];
[self->_stageLabel sizeToFit];
self->_stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self->_stageLabel.center.y);
});
}
- (void) stageComplete:(const char*)stageName {
}
- (void) stageFailed:(const char*)stageName withError:(int)errorCode portTestFlags:(int)portTestFlags {
Log(LOG_I, @"Stage %s failed: %d", stageName, errorCode);
unsigned int portTestResults = LiTestClientConnectivity(CONN_TEST_SERVER, 443, portTestFlags);
dispatch_async(dispatch_get_main_queue(), ^{
// Allow the display to go to sleep now
[UIApplication sharedApplication].idleTimerDisabled = NO;
NSString* message = [NSString stringWithFormat:@"%s failed with error %d", stageName, errorCode];
if (portTestFlags != 0) {
char failingPorts[256];
LiStringifyPortFlags(portTestFlags, "\n", failingPorts, sizeof(failingPorts));
message = [message stringByAppendingString:[NSString stringWithFormat:@"\n\nCheck your firewall and port forwarding rules for port(s):\n%s", failingPorts]];
}
if (portTestResults != ML_TEST_RESULT_INCONCLUSIVE && portTestResults != 0) {
message = [message stringByAppendingString:@"\n\nYour device's network connection is blocking Moonlight. Streaming may not work while connected to this network."];
}
UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Connection Failed"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[Utils addHelpOptionToDialog:alert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
[self returnToMainFrame];
}]];
[self presentViewController:alert animated:YES completion:nil];
});
[_streamMan stopStream];
}
- (void) launchFailed:(NSString*)message {
Log(LOG_I, @"Launch failed: %@", message);
dispatch_async(dispatch_get_main_queue(), ^{
// Allow the display to go to sleep now
[UIApplication sharedApplication].idleTimerDisabled = NO;
UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Connection Error"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[Utils addHelpOptionToDialog:alert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
[self returnToMainFrame];
}]];
[self presentViewController:alert animated:YES completion:nil];
});
}
- (void)rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor {
Log(LOG_I, @"Rumble on gamepad %d: %04x %04x", controllerNumber, lowFreqMotor, highFreqMotor);
[_controllerSupport rumble:controllerNumber lowFreqMotor:lowFreqMotor highFreqMotor:highFreqMotor];
}
- (void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger {
Log(LOG_I, @"Trigger rumble on gamepad %d: %04x %04x", controllerNumber, leftTrigger, rightTrigger);
[_controllerSupport rumbleTriggers:controllerNumber leftTrigger:leftTrigger rightTrigger:rightTrigger];
}
- (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz {
Log(LOG_I, @"Set motion state on gamepad %d: %02x %u Hz", controllerNumber, motionType, reportRateHz);
[_controllerSupport setMotionEventState:controllerNumber motionType:motionType reportRateHz:reportRateHz];
}
- (void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b {
Log(LOG_I, @"Set controller LED on gamepad %d: l%02x%02x%02x", controllerNumber, r, g, b);
[_controllerSupport setControllerLed:controllerNumber r:r g:g b:b];
}
- (void)connectionStatusUpdate:(int)status {
Log(LOG_W, @"Connection status update: %d", status);
// The stats overlay takes precedence over these warnings
if (_statsUpdateTimer != nil) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
switch (status) {
case CONN_STATUS_OKAY:
[self updateOverlayText:nil];
break;
case CONN_STATUS_POOR:
if (self->_streamConfig.bitRate > 5000) {
[self updateOverlayText:@"Slow connection to PC\nReduce your bitrate"];
}
else {
[self updateOverlayText:@"Poor connection to PC"];
}
break;
}
});
}
- (void) updatePreferredDisplayMode:(BOOL)streamActive {
#if TARGET_OS_TV
if (@available(tvOS 11.2, *)) {
UIWindow* window = [[[UIApplication sharedApplication] delegate] window];
AVDisplayManager* displayManager = [window avDisplayManager];
// This logic comes from Kodi and MrMC
if (streamActive) {
int dynamicRange;
if (LiGetCurrentHostDisplayHdrMode()) {
dynamicRange = 2; // HDR10
}
else {
dynamicRange = 0; // SDR
}
AVDisplayCriteria* displayCriteria = [[AVDisplayCriteria alloc] initWithRefreshRate:[_settings.framerate floatValue]
videoDynamicRange:dynamicRange];
displayManager.preferredDisplayCriteria = displayCriteria;
}
else {
// Switch back to the default display mode
displayManager.preferredDisplayCriteria = nil;
}
}
#endif
}
- (void) setHdrMode:(bool)enabled {
Log(LOG_I, @"HDR is now: %s", enabled ? "active" : "inactive");
dispatch_async(dispatch_get_main_queue(), ^{
[self updatePreferredDisplayMode:YES];
});
}
- (void) videoContentShown {
[_spinner stopAnimating];
[self.view setBackgroundColor:[UIColor blackColor]];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)gamepadPresenceChanged {
#if !TARGET_OS_TV
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
}
#endif
}
- (void)mousePresenceChanged {
#if !TARGET_OS_TV
if (@available(iOS 14.0, *)) {
[self setNeedsUpdateOfPrefersPointerLocked];
}
#endif
}
- (void) streamExitRequested {
Log(LOG_I, @"Gamepad combo requested stream exit");
[self returnToMainFrame];
}
- (void)userInteractionBegan {
// Disable hiding home bar when user is interacting.
// iOS will force it to be shown anyway, but it will
// also discard our edges deferring system gestures unless
// we willingly give up home bar hiding preference.
_userIsInteracting = YES;
#if !TARGET_OS_TV
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
}
#endif
}
- (void)userInteractionEnded {
// Enable home bar hiding again if conditions allow
_userIsInteracting = NO;
#if !TARGET_OS_TV
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
}
#endif
}
#if !TARGET_OS_TV
// Require a confirmation when streaming to activate a system gesture
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)prefersHomeIndicatorAutoHidden {
if ([_controllerSupport getConnectedGamepadCount] > 0 &&
[_streamView getCurrentOscState] == OnScreenControlsLevelOff &&
_userIsInteracting == NO) {
// Autohide the home bar when a gamepad is connected
// and the on-screen controls are disabled. We can't
// do this all the time because any touch on the display
// will cause the home indicator to reappear, and our
// preferredScreenEdgesDeferringSystemGestures will also
// be suppressed (leading to possible errant exits of the
// stream).
return YES;
}
return NO;
}
- (BOOL)shouldAutorotate {
return YES;
}
- (BOOL)prefersPointerLocked {
// Pointer lock breaks the UIKit mouse APIs, which is a problem because
// GCMouse is horribly broken on iOS 14.0 for certain mice. Only lock
// the cursor if there is a GCMouse present.
return [GCMouse mice].count > 0;
}
#endif
@end
+1 -1
View File
@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Moonlight-ZWM</string>
<string>VoidLink</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
+3 -34
View File
@@ -1,14 +1,9 @@
# VoidLink 已上架App Store.
# VoidLink is available on App Store
## VoidLink 已上架分类为 "游戏" 的版本,可在iOS18+设备激活游戏模式:
## VoidLink is categorized as "game" on App Store now to enable game mode on iOS18+:
### https://apps.apple.com/app/voidlink-extreme/id6755103808
## VoidLink 中国大陆、韩国、越南 App Store 保留分类为工具的版本:
## Original VoidLink for Mainland China, South Korea & Vietnam App Stores:
### [https://apps.apple.com/app/voidlink/id6747717070](https://apps.apple.com/cn/app/voidlink/id6747717070)
<br>
# 代码 Coding
- 代码提交在`Integration`分支。
- For latest coding commits, go to branch `Integration`.
@@ -33,41 +28,15 @@
如果你在用Bilibili, 请关注`True砖家`,了解该fork的最新消息。 <br>
If you are on Bilibili, subscribe `True砖家` to get the latest news of this fork: <br>
https://b23.tv/A0F9v7n
## 中文用户捐赠渠道
爱发电:<br>
https://ifdian.net/a/zhuanjia <br>
或:<br>
<img width="198" height="244" alt="截屏2025-10-25 20 53 06" src="https://github.com/user-attachments/assets/27153225-5651-4d7e-8976-0961739b9639" />
<br>
# 主要贡献者 Main Contributors
# 贡献者 Contributors
[@TrueZhuangJia](https://github.com/TrueZhuangJia) <br>
[@All contributors from moonlight-iOS](https://github.com/moonlight-stream/moonlight-ios/graphs/contributors) <br>
[@stefanilijev97](https://github.com/stefanilijev97/stefanilijev97) <br>
[@Acaki](https://github.com/Acaki) <br>
[@andygrundman](https://github.com/andygrundman) <br>
[@imneway](https://github.com/imneway) <br>
[@seastwood](https://github.com/seastwood) <br>
[@Danos0100](https://github.com/Danos0100) <br>
[@xzzpig](https://github.com/xzzpig) <br>
[@qiin2333](https://github.com/qiin2333) <br>
[@King0fSpace](https://github.com/King0fSpace) <br>
完整贡献者名单: <br>
All contributors: <br>
[中文](https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/nlttrqmdgdzhcwak) <br>
[English](https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/cuqwnht9w7t1o95v) <br>
# 应用截图 ScreenShots
## Host view
<img width="2752" height="2064" alt="1" src="https://github.com/user-attachments/assets/9c39246b-797b-4d45-8d3b-490b9664886c" /> <br>
## Add most used setting items to favorite
<img width="2752" height="2064" alt="2" src="https://github.com/user-attachments/assets/51481cb0-c156-4877-a42a-dd0c672fe9f3" /> <br>
## App view
<img width="2752" height="2064" alt="3-appView" src="https://github.com/user-attachments/assets/c3683e86-c647-4aa9-9c66-5874e9484370" /> <br>
## Settings menu expanded during streaming
<img width="2752" height="2064" alt="4" src="https://github.com/user-attachments/assets/822250f1-22b7-4d28-aa4d-dc6bc7f69d82" /> <br>
## Rich & powerful on-screen widgets
<img width="2752" height="2064" alt="5-stProfilePad" src="https://github.com/user-attachments/assets/4b9bc28b-a99b-4126-a9db-8277e8d98b1e" />
<img width="2752" height="2064" alt="6" src="https://github.com/user-attachments/assets/fe3e18a7-6813-4ac8-8fbe-ec424634ae44" />
<img width="2752" height="2064" alt="7" src="https://github.com/user-attachments/assets/dbc28764-d20d-4a2c-8ce9-c63d61ccd569" />
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,15 @@
{
"originHash" : "f17bf6f1131c7255a75ffd6a989b69573cb49c332e612dffb429905d8d7afe72",
"pins" : [
{
"identity" : "openssl-package",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/OpenSSL-Package.git",
"state" : {
"revision" : "b9eb055fdf73e595cb4b9f665d13dc85975bf80a",
"version" : "3.3.2000"
}
}
],
"version" : 3
}
BIN
View File
Binary file not shown.
+113
View File
@@ -0,0 +1,113 @@
//
// AboutView.swift
// VoidLink
//
// Created by True砖家 on 5/18/25.
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
//
import SwiftUI
@available(iOS 13.0, *)
public struct AboutView: View {
@Environment(\.presentationMode) var presentationMode
public var aboutVC:UIViewController
public var body: some View {
VStack(spacing: 10) {
// App 图标
Image(uiImage: UIImage(named: "AppIconMedium") ?? UIImage())
// .resizable()
.resizable(capInsets:EdgeInsets(top: 3.5, leading: 3.5, bottom: 3.5, trailing: 3.5))
.frame(width: 100, height: 100)
.clipShape(RoundedRectangle(cornerRadius: 28))
// App 名称
Text(Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "App Name")
.font(.title)
.bold()
// 版本号
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
Text("Version \(version)")
.font(.subheadline)
.foregroundColor(.gray)
}
// 说明文字
Text(SwiftLocalizationHelper.localizedString(forKey: "From the player community, to the player community."))
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
if Locale.preferredLanguages.first!.hasPrefix("zh-Hans") {
if #available(iOS 14.0, *) {
Text("闽ICP备17012590号-3A\n主办单位:福州创图信息技术有限公司").font(.caption2)
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
.padding()
} else {
Text("闽ICP备17012590号-3A\n主办单位:福州创图信息技术有限公司").font(.caption)
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
.padding()
}
}
// 链接按钮
if #available(iOS 14.0, *) {
Link(SwiftLocalizationHelper.localizedString(forKey: "Join us"), destination: URL(string: SwiftLocalizationHelper.localizedString(forKey: "supportLink"))!)
.padding(.top, 10)
Spacer()
// OK 按钮
Button(SwiftLocalizationHelper.localizedString(forKey: "OK")) {
aboutVC.dismiss(animated:true)
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.frame(height: 46)
.cornerRadius(12)
} else {
HStack(spacing: 20) {
Button(SwiftLocalizationHelper.localizedString(forKey: "Join us")) {
// 打开链接
if let url = URL(string: SwiftLocalizationHelper.localizedString(forKey: "supportLink")) {
UIApplication.shared.open(url)
}
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.frame(height: 33)
.frame(minWidth: 100)
/*
Button(SwiftLocalizationHelper.localizedString(forKey: "OK")) {
presentationMode.wrappedValue.dismiss()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.frame(height: 33)
.frame(minWidth: 100)*/
Button(SwiftLocalizationHelper.localizedString(forKey: "OK")) {
aboutVC.dismiss(animated:true)
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.frame(height: 33)
}
.padding(.top, 10)
}
}
.padding()
}
}
+41
View File
@@ -0,0 +1,41 @@
//
// AboutViewController.swift
// VoidLink
//
// Created by Weimin on 2025/7/14.
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
//
import SwiftUI
@available(iOS 13.0, *)
@objc class AboutViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let hostingVC = UIHostingController(rootView: AboutView( aboutVC: self))
addChild(hostingVC)
hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingVC.view)
hostingVC.didMove(toParent: self)
NSLayoutConstraint.activate([
hostingVC.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
// 🎯 设置弹窗大小
preferredContentSize = CGSize(width: 530, height: 450)
// ⚠️ 一定要设置以下样式才会生效 preferredContentSize
modalPresentationStyle = .formSheet
}
}
func isIPhone() -> Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
@@ -21,5 +21,6 @@
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
- (NSURL*) getStoreURL;
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0));
@end
@@ -5,8 +5,13 @@
// Created by Diego Waxemberg on 1/17/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
// Modified by True砖家 since 2024.7.12
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
//
#import "AppDelegate.h"
#import "MainFrameViewController.h"
#import "VoidLink-Swift.h"
@implementation AppDelegate
@@ -22,17 +27,64 @@ static NSString* DB_NAME = @"Moonlight_tvOS.bin";
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
#endif
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)){
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)){
}
#if !TARGET_OS_TV
UIApplicationShortcutItem* shortcut = [launchOptions valueForKey:UIApplicationLaunchOptionsShortcutItemKey];
if (shortcut != nil) {
_pcUuidToLoad = (NSString*)[shortcut.userInfo objectForKey:@"UUID"];
/*
// orietation limitatioin test
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
UIViewController *topController = window.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
#endif
return [topController supportedInterfaceOrientations];
}
*/
/*
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
NSLog(@"orientation limit");
return UIInterfaceOrientationMaskLandscape;
}
*/
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for command tool customization after application launch (works only when user default is nil)
[CommandManager presetDefaultCommands];
// For iOS 12 and below, we need to manually create the window
if (@available(iOS 13.0, *)) {
// Scene delegate will handle window creation
} else {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
NSString *storyboardName;
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
storyboardName = @"iPad";
} else {
storyboardName = @"iPhone";
}
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
UIViewController *initialViewController = [storyboard instantiateInitialViewController];
self.window.rootViewController = initialViewController;
[self.window makeKeyAndVisible];
}
return YES;
}
#if !TARGET_OS_TV
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
_pcUuidToLoad = (NSString*)[shortcutItem.userInfo objectForKey:@"UUID"];
_shortcutCompletionHandler = completionHandler;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -96,6 +96,7 @@ static NSData* p12 = nil;
bio = BIO_new(BIO_s_mem());
i2d_X509_bio(bio, x509);
X509_free(x509);
BUF_MEM* mem;
BIO_get_mem_ptr(bio, &mem);
@@ -222,6 +223,7 @@ static NSData* p12 = nil;
+ (NSData *)getSignatureFromCert:(NSData *)cert {
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
BIO_free(bio);
if (!x509) {
Log(LOG_E, @"Unable to parse certificate in memory!");
@@ -4,10 +4,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <OpenSSL/provider.h>
#include <OpenSSL/rsa.h>
#include <openssl/x509.h>
#include <openssl/rand.h>
#include <OpenSSL/rand.h>
static const int NUM_BITS = 2048;
static const int SERIAL = 0;
@@ -61,22 +63,30 @@ void mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
}
struct CertKeyPair generateCertKeyPair(void) {
BIO *bio_err;
X509 *x509 = NULL;
EVP_PKEY *pkey = NULL;
PKCS12 *p12 = NULL;
bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS);
p12 = PKCS12_create("limelight", "GameStream", pkey, x509, NULL, 0, 0, 0, 0, 0);
char* pass = "limelight";
p12 = PKCS12_create(pass,
"GameStream",
pkey,
x509,
NULL,
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
-1, // disable certificate encryption
2048,
-1, // disable the automatic MAC
0);
// MAC it ourselves with SHA1 since iOS refuses to load anything else.
PKCS12_set_mac(p12, pass, -1, NULL, 0, 1, EVP_sha1());
if (p12 == NULL) {
printf("Error generating a valid PKCS12 certificate.\n");
}
BIO_free(bio_err);
return (CertKeyPair){x509, pkey, p12};
}
@@ -9,6 +9,7 @@
#ifndef Limelight_mkcert_h
#define Limelight_mkcert_h
#include <sys/_types/_time_t.h>
#include <openssl/x509v3.h>
#include <openssl/pkcs12.h>
BIN
View File
Binary file not shown.
+113
View File
@@ -0,0 +1,113 @@
//
// DataManager.h
// Moonlight
//
// Created by Diego Waxemberg on 10/28/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
// Modified by True砖家 since 2024.6.1
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
//
#import "AppDelegate.h"
#import "TemporaryHost.h"
#import "TemporaryApp.h"
#import "TemporarySettings.h"
@interface DataManager : NSObject
typedef NS_ENUM(NSUInteger, UINavigationBarHeight) {
UINavigationBarHeightIPad = 50,
UINavigationBarHeightIPhone = 44
};
typedef NS_ENUM(NSInteger, TouchMode) {
RelativeTouch,
NativeTouch,
AbsoluteTouch,
NativeTouchOnly
};
typedef NS_ENUM(NSInteger, TouchEventPriorityEnum) {
AsyncNativeTouchOff,
EqualPriority,
TouchDownPriority,// deprecated by GUI
TouchMovePriority// deprecated by GUI
};
typedef NS_ENUM(NSInteger, GyroMode) {
GyroModeOff,
GyroModeAuto,
AlwaysDevice,
AlwaysController
};
typedef NS_ENUM(NSInteger, FramePacingMode) {
FramePacingModeLegacy,
FramePacingModeQueue
};
typedef NS_ENUM(NSInteger, SettingsMenuMode) {
AllSettings,
FavoriteSettings,
RemoveSettingItem,
};
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
framerate:(NSInteger)framerate
height:(NSInteger)height
width:(NSInteger)width
audioConfig:(NSInteger)audioConfig
onscreenControls:(NSInteger)onscreenControls
gyroMode:(NSInteger)gyroMode
emulatedControllerType:(NSInteger)emulatedControllerType
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
gyroSensitivity:(CGFloat)gyroSensitivity
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
showKeyboardToolbar:(BOOL)showKeyboardToolbar
optimizeGames:(BOOL)optimizeGames
multiController:(BOOL)multiController
swapABXYButtons:(BOOL)swapABXYButtons
audioOnPC:(BOOL)audioOnPC
preferredCodec:(uint32_t)preferredCodec
enableYUV444:(BOOL)enableYUV444
enablePIP:(BOOL)enablePIP
enableHdr:(BOOL)enableHdr
btMouseSupport:(BOOL)btMouseSupport
// absoluteTouchMode:(BOOL)absoluteTouchMode
touchMode:(NSInteger)touchMode
statsOverlayLevel:(NSInteger)statsOverlayLevel
statsOverlayEnabled:(BOOL)statsOverlayEnabled
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
resolutionSelected:(NSInteger)resolutionSelected
externalDisplayMode:(NSInteger)externalDisplayMode
localMousePointerMode:(NSInteger)localMousePointerMode
frameQueueSize:(NSInteger)frameQueueSize
enableGraphs:(BOOL)enableGraphs
graphOpacity:(NSInteger)graphOpacity
renderingBackend:(NSInteger)renderingBackend
framePacingMode:(NSInteger)framePacingMode
backgroundSessionTimer:(NSInteger)backgroundSessionTimer;
- (NSArray*) getHosts;
- (void) updateHost:(TemporaryHost*)host;
- (void) updateAppsForExistingHost:(TemporaryHost *)host;
- (void) removeHost:(TemporaryHost*)host;
- (void) removeApp:(TemporaryApp*)app;
- (Settings*) retrieveSettings;
- (void) saveData;
- (TemporarySettings*) getSettings;
- (void) updateUniqueId:(NSString*)uniqueId;
- (NSString*) getUniqueId;
@end
@@ -5,6 +5,9 @@
// Created by Diego Waxemberg on 10/28/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
// Modified by True砖家 since 2024.6.1
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
//
#import "DataManager.h"
#import "TemporaryApp.h"
@@ -58,11 +61,19 @@
width:(NSInteger)width
audioConfig:(NSInteger)audioConfig
onscreenControls:(NSInteger)onscreenControls
gyroMode:(NSInteger)gyroMode
emulatedControllerType:(NSInteger)emulatedControllerType
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
swipeExitScreenEdge:(NSInteger)swipeExitScreenEdge
swipeToExitDistance:(CGFloat)swipeToExitDistance
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
gyroSensitivity:(CGFloat)gyroSensitivity
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
showKeyboardToolbar:(BOOL)showKeyboardToolbar
optimizeGames:(BOOL)optimizeGames
@@ -70,12 +81,25 @@
swapABXYButtons:(BOOL)swapABXYButtons
audioOnPC:(BOOL)audioOnPC
preferredCodec:(uint32_t)preferredCodec
useFramePacing:(BOOL)useFramePacing
enableYUV444:(BOOL)enableYUV444
enablePIP:(BOOL)enablePIP
enableHdr:(BOOL)enableHdr
btMouseSupport:(BOOL)btMouseSupport
absoluteTouchMode:(BOOL)absoluteTouchMode
statsOverlay:(BOOL)statsOverlay {
// absoluteTouchMode:(BOOL)absoluteTouchMode
touchMode:(NSInteger)touchMode
statsOverlayLevel:(NSInteger)statsOverlayLevel
statsOverlayEnabled:(BOOL)statsOverlayEnabled
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
resolutionSelected:(NSInteger)resolutionSelected
externalDisplayMode:(NSInteger)externalDisplayMode
localMousePointerMode:(NSInteger)localMousePointerMode
frameQueueSize:(NSInteger)frameQueueSize
enableGraphs:(BOOL)enableGraphs
graphOpacity:(NSInteger)graphOpacity
renderingBackend:(NSInteger)renderingBackend
framePacingMode:(NSInteger)framePacingMode
backgroundSessionTimer:(NSInteger)backgroundSessionTimer{
[_managedObjectContext performBlockAndWait:^{
Settings* settingsToSave = [self retrieveSettings];
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
@@ -84,11 +108,19 @@
settingsToSave.width = [NSNumber numberWithInteger:width];
settingsToSave.audioConfig = [NSNumber numberWithInteger:audioConfig];
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
settingsToSave.gyroMode = [NSNumber numberWithInteger:gyroMode];
settingsToSave.emulatedControllerType = [NSNumber numberWithInteger:emulatedControllerType];
settingsToSave.keyboardToggleFingers = [NSNumber numberWithInteger:(uint16_t)keyboardToggleFingers];
settingsToSave.swipeExitScreenEdge = [NSNumber numberWithInteger:(uint32_t)swipeExitScreenEdge];
settingsToSave.swipeToExitDistance = [NSNumber numberWithFloat:swipeToExitDistance];
settingsToSave.oscLayoutToolFingers = [NSNumber numberWithInteger:(uint16_t)oscLayoutToolFingers];
settingsToSave.slideToSettingsScreenEdge = [NSNumber numberWithInteger:(uint32_t)slideToSettingsScreenEdge];
settingsToSave.slideToSettingsDistance = [NSNumber numberWithFloat:slideToSettingsDistance];
settingsToSave.pointerVelocityModeDivider = [NSNumber numberWithFloat:pointerVelocityModeDivider];
settingsToSave.touchPointerVelocityFactor = [NSNumber numberWithFloat:touchPointerVelocityFactor];
settingsToSave.mousePointerVelocityFactor = [NSNumber numberWithFloat:mousePointerVelocityFactor];
settingsToSave.gyroSensitivity = [NSNumber numberWithFloat:gyroSensitivity];
settingsToSave.touchMoveEventInterval = [NSNumber numberWithInteger:touchMoveEventInterval];
settingsToSave.reverseMouseWheelDirection = reverseMouseWheelDirection;
settingsToSave.asyncNativeTouchPriority = [NSNumber numberWithInteger:asyncNativeTouchPriority];
settingsToSave.liftStreamViewForKeyboard = liftStreamViewForKeyboard;
settingsToSave.showKeyboardToolbar = showKeyboardToolbar;
settingsToSave.optimizeGames = optimizeGames;
@@ -96,12 +128,25 @@
settingsToSave.swapABXYButtons = swapABXYButtons;
settingsToSave.playAudioOnPC = audioOnPC;
settingsToSave.preferredCodec = preferredCodec;
settingsToSave.useFramePacing = useFramePacing;
settingsToSave.enableYUV444 = enableYUV444;
settingsToSave.enablePIP = enablePIP;
settingsToSave.enableHdr = enableHdr;
settingsToSave.btMouseSupport = btMouseSupport;
settingsToSave.absoluteTouchMode = absoluteTouchMode;
settingsToSave.statsOverlay = statsOverlay;
// settingsToSave.absoluteTouchMode = absoluteTouchMode;
settingsToSave.touchMode = [NSNumber numberWithInteger:(uint16_t)touchMode];
settingsToSave.statsOverlayEnabled = statsOverlayEnabled;
settingsToSave.statsOverlayLevel = [NSNumber numberWithInteger:(uint16_t)statsOverlayLevel];
settingsToSave.unlockDisplayOrientation = unlockDisplayOrientation;
settingsToSave.resolutionSelected = [NSNumber numberWithInteger:resolutionSelected];
settingsToSave.externalDisplayMode = [NSNumber numberWithInteger:externalDisplayMode];
settingsToSave.localMousePointerMode = [NSNumber numberWithInteger:localMousePointerMode];
settingsToSave.backroundSessionTimer = [NSNumber numberWithInteger:backgroundSessionTimer];
settingsToSave.frameQueueSize = [NSNumber numberWithInteger:frameQueueSize];
settingsToSave.enableGraphs = enableGraphs;
settingsToSave.graphOpacity = [NSNumber numberWithInteger:graphOpacity];
settingsToSave.renderingBackend = [NSNumber numberWithInteger:renderingBackend];
settingsToSave.framePacingMode = [NSNumber numberWithInteger:framePacingMode];
[self saveData];
}];
}
@@ -5,6 +5,9 @@
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
// Modified by True砖家 since 2024.8.3
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
//
#import "DataManager.h"
#import "TemporaryHost.h"
@@ -28,7 +31,7 @@
self.externalAddress = host.externalAddress;
self.localAddress = host.localAddress;
self.ipv6Address = host.ipv6Address;
self.mac = host.mac;
if(!(host.mac == nil || [host.mac isEqualToString:@"00:00:00:00:00:00"])) self.mac = host.mac; // try to fix invalid mac happens in some cases
self.name = host.name;
self.uuid = host.uuid;
self.serverCodecModeSupport = host.serverCodecModeSupport;
@@ -70,9 +73,9 @@
if (self.ipv6Address != nil) {
parentHost.ipv6Address = self.ipv6Address;
}
if (self.mac != nil) {
parentHost.mac = self.mac;
}
// try to fix invalid mac happens in some cases
// 添加主机crash问题重点关注
if (!(self.mac == nil || [self.mac isEqualToString:@"00:00:00:00:00:00"])) parentHost.mac = self.mac;
if (self.serverCert != nil) {
parentHost.serverCert = self.serverCert;
}
+75
View File
@@ -0,0 +1,75 @@
//
// TemporarySettings.h
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
// Modified by True砖家 since 2024.6.1
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
//
#import "Settings+CoreDataClass.h"
#define OSC_TOOL_FINGERS 4
@interface TemporarySettings : NSObject
@property (nonatomic, retain) Settings * parent;
@property (nonatomic, retain) NSNumber * settingsMenuMode;
@property (nonatomic, retain) NSNumber * settingsMenuWidth;
@property (nonatomic, retain) NSNumber * bitrate;
@property (nonatomic, retain) NSNumber * framerate;
@property (nonatomic, retain) NSNumber * height;
@property (nonatomic, retain) NSNumber * width;
@property (nonatomic, retain) NSNumber * audioConfig;
@property (nonatomic, retain) NSNumber * onscreenControls;
@property (nonatomic, retain) NSNumber * gyroMode;
@property (nonatomic, retain) NSNumber * keyboardToggleFingers;
@property (nonatomic, retain) NSNumber * oscLayoutToolFingers;
@property (nonatomic, retain) NSNumber * slideToSettingsScreenEdge;
@property (nonatomic, retain) NSNumber * slideToSettingsDistance;
@property (nonatomic, retain) NSNumber * touchMoveEventInterval;
@property (nonatomic, retain) NSNumber * touchPointerVelocityFactor;
@property (nonatomic, retain) NSNumber * gyroSensitivity;
@property (nonatomic, retain) NSNumber * emulatedControllerType;
@property (nonatomic, retain) NSNumber * mousePointerVelocityFactor;
@property (nonatomic, retain) NSNumber * pointerVelocityModeDivider;
@property (nonatomic, retain) NSString * uniqueId;
@property (nonatomic, retain) NSNumber * resolutionSelected;
@property (nonatomic, retain) NSNumber * externalDisplayMode;
@property (nonatomic, retain) NSNumber * localMousePointerMode;
@property (nonatomic, retain) NSNumber * backgroundSessionTimer;
@property (nonatomic) enum {
CODEC_PREF_AUTO,
CODEC_PREF_H264,
CODEC_PREF_HEVC,
CODEC_PREF_AV1,
} preferredCodec;
@property (nonatomic) BOOL enableYUV444;
@property (nonatomic) BOOL enablePIP;
@property (nonatomic) BOOL reverseMouseWheelDirection;
@property (nonatomic, retain) NSNumber * asyncNativeTouchPriority;
@property (nonatomic) BOOL multiController;
@property (nonatomic) BOOL swapABXYButtons;
@property (nonatomic) BOOL playAudioOnPC;
@property (nonatomic) BOOL optimizeGames;
@property (nonatomic) BOOL enableHdr;
@property (nonatomic) BOOL btMouseSupport;
// @property (nonatomic) BOOL absoluteTouchMode;
@property (nonatomic, retain) NSNumber * touchMode;
@property (nonatomic, retain) NSNumber * statsOverlayLevel;
@property (nonatomic) BOOL statsOverlayEnabled;
@property (nonatomic) BOOL liftStreamViewForKeyboard;
@property (nonatomic) BOOL showKeyboardToolbar;
@property (nonatomic) BOOL unlockDisplayOrientation;
@property (nonatomic) BOOL enableGraphs;
@property (nonatomic, retain) NSNumber * frameQueueSize;
@property (nonatomic, retain) NSNumber * graphOpacity;
@property (nonatomic, retain) NSNumber * renderingBackend;
@property (nonatomic, retain) NSNumber * framePacingMode;
- (id) initFromSettings:(Settings*)settings;
@end

Some files were not shown because too many files have changed in this diff Show More