800 Commits
Author SHA1 Message Date
True Zhuanjia 691aa9c899 liquid glass for soft keyboard toolbar 2026-03-15 11:43:20 +08:00
True Zhuanjia 0dd419a5a3 tweak toolbox view 2026-03-14 14:15:16 +08:00
True Zhuanjia 1269c46f56 UI improvements: widget profile selector table 2026-03-14 11:50:07 +08:00
True Zhuanjia f8badd51c5 deal with osw profile selector table navigation bar 2026-03-12 18:10:39 +08:00
True Zhuanjia ed8bf5af9e deal with separator color 2026-03-12 16:45:50 +08:00
True Zhuanjia b2521de84e refactor themeManager 2026-03-12 16:15:52 +08:00
True Zhuanjia 165b5e6638 tweak settings menu navigation bar height 2026-03-12 10:19:08 +08:00
True Zhuanjia e742466409 app view and app view navigation bar 2026-03-12 09:54:11 +08:00
True Zhuanjia 201bf5b447 liquid glass: settings menu navigation bar 2026-03-11 22:26:50 +08:00
True Zhuanjia 7bed913a1e Merge branch 'Integration' into iOS26UI 2026-03-11 15:36:44 +08:00
True Zhuanjia e067fd8fa2 tweak menu section header 2026-03-09 12:09:06 +08:00
True Zhuanjia ba7d5feb81 enable liquid glass 2026-03-07 10:12:14 +08:00
True Zhuanjia 4a9e7ad2a1 iOS26 UI adaption: host view navigation bar 2026-03-07 10:08:17 +08:00
True Zhuanjia 4b56a44a47 UI improvements 2026-03-06 17:29:38 +08:00
True Zhuanjia bc98b43de2 secure folder unfolding fix at the end of animation 2026-03-05 19:39:28 +08:00
True Zhuanjia 16b3df5279 fixed: unexpected triggering of buttons collected by folder 2026-03-05 15:11:15 +08:00
True Zhuanjia d202f2f6e3 - remove auto codec option
- set default codec to HEVC
2026-03-05 11:10:46 +08:00
True Zhuanjia 142474eac8 - fixed: widget layout broken by windowing, entering background, orientationChange+PiP 2026-03-04 22:58:20 +08:00
True Zhuanjia acc89dba39 enable track point for onscreen widget edit mode 2026-03-04 13:06:22 +08:00
True Zhuanjia 8cd366d2cd update UI string 2026-02-28 18:44:24 +08:00
True Zhuanjia 2d67c1b22b update UI string 2026-02-28 12:30:32 +08:00
True Zhuanjia 77b0ef55c9 UI string update 2026-02-28 10:44:49 +08:00
True Zhuanjia d76f404a4c UI tip update 2026-02-27 00:48:46 +08:00
True Zhuanjia d0b45a019f minor fixes 2026-02-27 00:08:28 +08:00
True Zhuanjia 0728e999eb - add necessary UI tips
- restore "view all apps" for host card
- minor fixes
2026-02-26 22:32:37 +08:00
True Zhuanjia f325e9a206 default setting update 2026-02-26 18:39:16 +08:00
True Zhuanjia 49b03e65b2 feat: hover mode selector 2026-02-26 17:18:21 +08:00
True Zhuanjia 2bc18e2f02 add disable hovering switch 2026-02-26 00:29:57 +08:00
True Zhuanjia dadb292676 handle hovering automatically 2026-02-25 22:06:09 +08:00
True Zhuanjia 2c9902ce77 feat: auto hover 2026-02-25 00:38:24 +08:00
True Zhuanjia 6d36219e88 - fixed: unable to setup updated pressure curve mapping after switching widget profile during streaming 2026-02-24 19:47:20 +08:00
True Zhuanjia 470862dbe0 - fixed: unable to setup updated pressure curve mapping during streaming
- flexible pressure curve start point
- larger pressure curve view
2026-02-24 16:49:31 +08:00
True Zhuanjia f6017824ee default settings update 2026-02-24 00:32:35 +08:00
True Zhuanjia adeb77cf65 fixed cursor flickering on host 2026-02-23 22:57:46 +08:00
True Zhuanjia 9bc080815a update copyright 2026-02-23 20:48:25 +08:00
True Zhuanjia d03eebb5ba default settings update 2026-02-23 20:24:17 +08:00
True Zhuanjia ca453d8059 - tweak setting stack sequence
- auto set fullColorRangeSwitch
2026-02-23 19:37:17 +08:00
True Zhuanjia cb8126d201 UI fixes 2026-02-19 14:51:10 +08:00
True Zhuanjia 1626f30bb6 lowest latency option of queueSize slider 2026-02-19 13:23:30 +08:00
True Zhuanjia 47b5ec2678 default setting update flag 2026-02-19 01:39:18 +08:00
True Zhuanjia ccd032834d - restore asyncDequeue switch
- tweak frame buffering
2026-02-19 01:31:55 +08:00
True Zhuanjia c7ffd51cec pencil pro widget template import changes 2026-02-18 13:01:08 +08:00
True Zhuanjia 38f8c50d7f native touch track point fix 2026-02-18 08:44:56 +08:00
True Zhuanjia fd653b87af allow sdr workaround on av1 2026-02-18 07:59:52 +08:00
True Zhuanjia 9bf02e477b UI string update 2026-02-14 19:59:55 +08:00
True Zhuanjia 4440c668f1 revert pencil force tweak 2026-02-14 13:55:53 +08:00
True Zhuanjia 63d5417a2c widget profile accessibility 2026-02-14 12:43:31 +08:00
True Zhuanjia 28d9a0faf2 update aboutView 2026-02-14 02:07:28 +08:00
True Zhuanjia 04845df09c enable onscreen widget for pencil pro pack 2026-02-14 01:01:48 +08:00
True Zhuanjia 85df511a23 pencil pro widget profile 2026-02-14 00:14:25 +08:00
True Zhuanjia 4ecb8bfede - fixed: onscreen widget level
- flush frames when entering background
2026-02-13 23:51:31 +08:00
True Zhuanjia 6008865a41 functional button rule update 2026-02-13 23:19:10 +08:00
True Zhuanjia 02926b2c6b widget profile localization 2026-02-13 22:55:00 +08:00
True Zhuanjia 49c5fd308e button sliding fix 2026-02-13 21:31:05 +08:00
True Zhuanjia 7eeff3bc35 hide queueSize stack during streaming 2026-02-13 14:30:25 +08:00
True Zhuanjia 6e99889681 update profile template binaries 2026-02-13 14:19:47 +08:00
True Zhuanjia 8cbd09fe9a update default profile localization 2026-02-12 23:42:36 +08:00
True Zhuanjia edc703c534 allow default profile overwritten by stream view changes 2026-02-12 23:24:35 +08:00
True Zhuanjia f85d545c17 slid&Hold folder stream view guideline 2026-02-12 23:12:23 +08:00
True Zhuanjia 0830173e59 - double tap to make slid&Hold folder movable
- folder state restore improvements
- default profile location ready
2026-02-12 22:54:20 +08:00
True Zhuanjia 0e21e3a9ed Merge commit 'a9ede7a904b1fed1984c28803cf179b41ed257f6' into Integration 2026-02-12 16:03:27 +08:00
True Zhuanjia 6356427454 enable track point for all on screen widgets 2026-02-12 15:51:02 +08:00
True Zhuanjia 4dc6220f28 touch track point improvements 2026-02-12 15:33:43 +08:00
True Zhuanjia 03fcfaff28 sdr performance workaround fix 2026-02-12 14:04:42 +08:00
True Zhuanjia d5c96f77cb highlight touchpad widget while being folded & unfolded 2026-02-11 23:24:17 +08:00
True Zhuanjia bb6f2f0c0d feat: track point for native touch 2026-02-11 22:56:50 +08:00
True Zhuanjia 8d54ed9316 feat: on screen widget touch track point 2026-02-11 21:43:33 +08:00
True Zhuanjia 44123c74d9 crash fixes 2026-02-11 18:54:45 +08:00
True Zhuanjia fdf2548fe3 remove simple & complete legacy on screen controls 2026-02-11 17:04:00 +08:00
True Zhuanjia bee72bd854 - better widget profile update strategy
- feat: restore widget templates
2026-02-11 16:41:46 +08:00
True Zhuanjia 8ec8f32935 crash fixes 2026-02-11 15:00:23 +08:00
True Zhuanjia 1d33feec8b sdr performance workaround fix 2026-02-11 11:43:00 +08:00
True Zhuanjia 80af6a9636 movable button stream view guideline displayed without dragging & moving 2026-02-11 11:26:02 +08:00
True Zhuanjia 94eb4dd8a0 try to fix again:
fixed: squeeze shortcut keys can't be held down
2026-02-11 10:51:47 +08:00
True Zhuanjia d3ce55cbd6 hide sdr performance workaround stack during streaming 2026-02-10 20:16:59 +08:00
True Zhuanjia 0b942caa98 fixed: squeeze shortcut keys can't be held down 2026-02-10 20:12:15 +08:00
True Zhuanjia afc6289a56 - disable sdr performance workaround for av1
- UI string changes
2026-02-10 19:54:53 +08:00
True Zhuanjia 79684598ab learn more action for rendering backend tip 2026-02-10 17:17:35 +08:00
True Zhuanjia b901f2f506 UI string updates 2026-02-10 16:52:35 +08:00
True Zhuanjia 14294009f0 feat: pencil squeeze shortcut 2026-02-10 15:55:36 +08:00
True Zhuanjia adbebd23c8 temporarily disable pencil in-app-purchase for iOS14 and lower OS 2026-02-09 22:43:02 +08:00
True Zhuanjia 3973e13706 feat: disablePencilSlideGestureSwitch 2026-02-09 20:59:41 +08:00
True Zhuanjia e56e68dd6f external display mode settings menu UI string 2026-02-09 17:28:56 +08:00
True Zhuanjia c3fcff8d29 functional button fixes 2026-02-09 16:41:48 +08:00
True Zhuanjia 0152f48886 add mouseButton support for pencil side tap & toolbox menu 2026-02-09 16:19:42 +08:00
True Zhuanjia bbd2e37067 - rename:
balanced mode to standard,
performance mode to metal.

- renderer message text update & menu action fix
2026-02-09 14:50:59 +08:00
True Zhuanjia 8377b5062a default settings update 2026-02-09 09:50:59 +08:00
True Zhuanjia 086f1af974 runtime default setting update changes 2026-02-09 00:16:39 +08:00
True Zhuanjia 652e989927 frame queue tweaks 2026-02-08 23:52:07 +08:00
True Zhuanjia 5480e15595 resume video rendering when app returns from background in legacy pacing mode 2026-02-07 18:05:03 +08:00
True Zhuanjia 375670beb4 sdr performance workaround 2026-02-07 17:00:25 +08:00
True Zhuanjia fa164f0e02 - reduce pressure curve tool tips
- fixed crash in pressure curve tool drawing stage
- initial pencil move force tweak
- frame queue tweaks demo
2026-02-06 15:27:29 +08:00
True Zhuanjia a9ede7a904 fixed: touchPad double tap button not released 2026-02-03 16:13:00 +08:00
True Zhuanjia fa32d157ca load pencil section again 2026-02-02 15:52:48 +08:00
True Zhuanjia 10839f88d8 hide pencil section for 3.2.9 2026-02-02 15:06:51 +08:00
True Zhuanjia 8fa909dea8 tell apart regular & pencil TF 2026-02-02 12:54:59 +08:00
True Zhuanjia 94250624a8 fix renamed var 2026-02-02 11:59:16 +08:00
True Zhuanjia 6f264441ab Merge branch 'Integration' into stylus3
# Conflicts:
#	VoidLink/ViewControllers/StreamFrameViewController.m
2026-02-02 11:56:47 +08:00
True Zhuanjia 03cf463182 dummy event fix 2026-02-02 11:53:50 +08:00
True Zhuanjia 3e29501b23 safetimer for oc 2026-02-02 10:38:38 +08:00
True Zhuanjia 600b882929 restoring fold/unfold states 2026-02-01 16:02:57 +08:00
True Zhuanjia be69af925a folder button minor improvements 2026-02-01 11:26:39 +08:00
True Zhuanjia d14ee640c5 - exclusive folder fix
- other minor fixes
2026-02-01 10:20:12 +08:00
True Zhuanjia ac6c688054 feat: "folder" onscreen button 2026-01-31 20:59:24 +08:00
True Zhuanjia 8f8e86ab4f - on screen button “folder” prototype
- overlapped buttons triggered at touch began
- memory management fixes
- reduce toolbox view & widget layout tool animation
- fast widget profile loading
- toolbox & widget profile selector quick dismiss
2026-01-29 10:06:04 +08:00
True Zhuanjia ab1d6bfa90 storekit2 in app purchase implementation 2026-01-14 20:47:29 +08:00
True Zhuanjia e038601b37 host info updated tip 2026-01-14 10:41:20 +08:00
TrueZhuanjia 008beb9444 Merge branch 'Integration' into stylus3 2026-01-09 11:21:25 +08:00
TrueZhuanjia 835d9c9ef6 fixed: unable to display ipv6 address correctly by long pressing host card view 2026-01-09 11:19:32 +08:00
TrueZhuanjia e29e5bd650 fixed: app crash caused by detecting hosts with alternative IPs 2026-01-09 10:48:24 +08:00
TrueZhuanjia fecdbec31c fixed: unable to save pressure curve to oscProfile in certain cases 2026-01-08 20:28:18 +08:00
TrueZhuanjia 66fdb607d8 Merge branch 'Integration' into stylus3 2026-01-08 18:26:21 +08:00
TrueZhuanjia 6d4f192cbe pressure curve implementation 2026-01-08 16:51:41 +08:00
TrueZhuanjia 4a59af7c20 fixed: app crash caused by controller button string utility 2026-01-07 10:50:12 +08:00
TrueZhuanjia 156b363049 Merge branch 'Integration' into stylus3 2025-12-31 10:55:51 +08:00
TrueZhuanjia c68540c169 allow touchpad widget border width to be zero 2025-12-31 10:08:32 +08:00
TrueZhuanjia f89372e265 Merge branch 'Integration' into stylus3 2025-12-29 17:26:59 +08:00
TrueZhuanjia c134dffec2 turn off unnecessary logs 2025-12-29 16:43:28 +08:00
TrueZhuanjia c2d4527d7e separate X & Y decelerationRate slider 2025-12-29 16:41:07 +08:00
TrueZhuanjia 2071b89d18 new preset on-screen widget profiles 20251229 2025-12-29 16:34:29 +08:00
TrueZhuanjia 46902afdc4 tweak wasdPad center icon 2025-12-29 14:48:33 +08:00
TrueZhuanjia d23e640977 minor fixes 2025-12-28 22:30:40 +08:00
TrueZhuanjia 9c18ab4142 prewarm soft keyboard on app launch 2025-12-28 22:19:30 +08:00
TrueZhuanjia 22e270c4df improve on-screen touchPad stroke 2025-12-28 22:09:15 +08:00
TrueZhuanjia 16bd288e70 preset profile editing 2025-12-28 20:50:21 +08:00
TrueZhuanjia 39643153df Merge branch 'Integration' into stylus3 2025-12-27 20:44:19 +08:00
TrueZhuanjia 2cf9fdfd40 fixed: unable to send touchPad widget to bottom in streamview 2025-12-27 20:43:10 +08:00
TrueZhuanjia f5226562ae Merge branch 'Integration' into stylus3 2025-12-27 14:52:19 +08:00
TrueZhuanjia 32ad5d2f8a highlight border when moving on-screen widgets in layout tool 2025-12-27 14:41:23 +08:00
TrueZhuanjia 245f47f660 always send touchPad widgets to bottom 2025-12-27 14:32:41 +08:00
TrueZhuanjia 17e3fd07f9 quick double tap button for lsWheel/rsWheel 2025-12-27 14:15:51 +08:00
TrueZhuanjia cdbe480863 - directionPad improvements
- feat: highlight alpha tweak
2025-12-27 12:54:46 +08:00
TrueZhuanjia 189ad76f38 - change stroke style, use rounded font style for on-screen widget label 2025-12-26 20:33:21 +08:00
TrueZhuanjia d8528fdc81 - feat: new on-screen widget lsWheel/rsWheel, direction wheel for RPG games.
- feat: direct walkmode test for lsWheel/rsWheel
- feat: direction minimum offset test for all on-screen widgets, gyro->stick mapping & physical controller.
2025-12-26 16:17:55 +08:00
TrueZhuanjia c257a8cab0 Merge branch 'Integration' into stylus3 2025-12-23 01:35:46 +08:00
TrueZhuanjia 2912a3a5cb feat: multi-IP storage for host (tested with zeroTier) 2025-12-23 01:15:37 +08:00
TrueZhuanjia 75f1d4d392 restore content offset after settings menu being loaded for the first time 2025-12-22 15:35:46 +08:00
TrueZhuanjia a6159cc785 Merge branch 'Integration' into stylus3 2025-12-22 10:53:14 +08:00
TrueZhuanjia 38affb4981 add stereo(SDL) audio option 2025-12-22 10:51:33 +08:00
TrueZhuanjia e64090d88f feat: ability to resume mic redirection after a phone call 2025-12-20 20:19:08 +08:00
TrueZhuanjia ef60aeeead fixed: in stereo mode, audio playback can't be resumed after a phone call is ended 2025-12-19 22:27:05 +08:00
TrueZhuanjia 25112b2c27 Merge branch 'Integration' into stylus3 2025-12-13 20:40:57 +08:00
TrueZhuanjia c0b1326dbd fixed: unable to turn on-screen widget on/off during streaming 2025-12-13 20:39:43 +08:00
TrueZhuanjia f77e533dd6 Merge branch 'Integration' into stylus3 2025-12-12 23:47:47 +08:00
TrueZhuanjia 4b001f9366 vertical decelerationRate for inertial touchPads (trackball/LsvPad/RsvPad) 2025-12-12 23:28:19 +08:00
TrueZhuanjia 41eadce89a synthesize rsvPad inertia + gyro input convergently 2025-12-12 21:31:12 +08:00
TrueZhuanjia 46194a5aa0 fixed: tap to toggle button not release after being cleared
reload on-screen widgets only when necessary
2025-12-11 16:40:47 +08:00
TrueZhuanjia 3630880454 - fixed: mic redirection was enabled by default
- fixed: micHandler timer memory leak
- tweak touchPad scroll inertia
2025-12-10 10:38:42 +08:00
TrueZhuanjia a59ffacd3a Merge branch 'Integration' into stylus3 2025-12-09 22:50:45 +08:00
TrueZhuanjia 03817bcfd7 inertial scroll for touchpad gesture 2025-12-09 22:49:34 +08:00
TrueZhuanjia 2c4751bef9 Merge branch 'Integration' into stylus3 2025-12-09 16:40:58 +08:00
TrueZhuanjia 7caa778ff8 - fixed memory leaks caused by on-screen button auto-tap timer
- feat: Inertial RsvPad/LsvPad
2025-12-09 16:38:28 +08:00
TrueZhuanjia 3977d342ba disable pencil tick based on timestamp 2025-12-07 10:13:25 +08:00
TrueZhuanjia 4e83421104 on-screen button manual hover for low-end iPads 2025-12-06 11:22:19 +08:00
TrueZhuanjia adafacbf81 - feat: on-screen button "oscHome" 2025-12-05 10:28:08 +08:00
TrueZhuanjia 053893005b rename varialbles 2025-12-05 10:15:53 +08:00
TrueZhuanjia 838af88d64 minor change 2025-12-04 17:56:14 +08:00
TrueZhuanjia 573ed07b4f Merge branch 'Integration' into stylus3
full color range switch
2025-12-03 13:24:22 +08:00
TrueZhuanjia aad8673ff0 UI tweaks for full color range switch 2025-12-03 13:21:55 +08:00
TrueZhuanJia b6a58ea8ab Merge pull request #160 from Acaki/fix/Metal_AV1_SDR
Fix Metal AV1 SDR color range issue
2025-12-03 08:29:24 +08:00
TrueZhuanjia 8c7bf18b65 tweak touchUp delay 2025-12-02 22:09:00 +08:00
Acaki 3823c95582 feat: add toggle for full range and disable toggles for unsupported codecs 2025-12-02 21:26:09 +08:00
TrueZhuanjia ad0f3dca30 TF bundle ID 2025-12-02 20:36:21 +08:00
TrueZhuanjia 5248bbe9c3 default tickInterval to 1500 2025-12-02 17:39:52 +08:00
TrueZhuanjia 1262a7b41f advancedPencilSupport-demo1 2025-12-02 17:15:56 +08:00
Acaki 85611bcab5 refactor: request limited range for connection 2025-12-02 15:04:15 +08:00
Acaki 46b5d9522d refactor: force limited range for AV1 only 2025-12-02 02:40:03 +08:00
TrueZhuanjia 6dc9ee569b improve widget relocation 2025-12-01 19:16:25 +08:00
TrueZhuanjia 9916f64065 fixed: unable to move mouse cursor under touchpad mode or on mousepad widget when finger moves slowly 2025-12-01 18:51:58 +08:00
Acaki 0ad6b13fae feat: add keyboard shortcut to disconnect & quit app 2025-12-01 17:12:41 +08:00
Acaki 5bbdb76d21 fix: workaround for Metal AV1 darker image 2025-12-01 16:38:47 +08:00
TrueZhuanjia cbf42b1e52 - rounded shape stick boost compensation for physical, gyro mapped & onscreen stick inputs
- add setting stack tips
2025-11-27 22:35:58 +08:00
TrueZhuanjia 72867d855d - minimum offset slider for stick touchpads (rspad rsvpad lspad lsvpad)
- prevents edit button to update onscreen widgets to a different widget type (e.g. touchpad -> button)
2025-11-27 15:06:41 +08:00
TrueZhuanjia 7d5b984157 controller mouse stack info tag crash fix 2025-11-27 10:38:25 +08:00
TrueZhuanjia a3d5771b14 - unlcok motion control section
- controller gyro button stack refactored to motion control section, saved with osc (widget) profile
- feat: reverse gyro-hold button switch
- DS4 gyro activated by default if controller gyro button is disabled
-  synth physical stick input switch
2025-11-27 00:26:53 +08:00
TrueZhuanjia 1338b4fb85 synthesize physical stick input switch for built-in gyro motion control 2025-11-26 16:41:08 +08:00
TrueZhuanjia 379d965769 - feat: controller gyro button (toggle & hold)
- feat: synthesize physical stick input with gyro
2025-11-26 11:48:34 +08:00
TrueZhuanjia d7bc73250d localization fix 2025-11-24 13:07:35 +08:00
TrueZhuanjia 349daef47f UITouch vector util 2025-11-24 08:57:07 +08:00
TrueZhuanjia 138658c635 feat: map controller to mouse with self-defined sticks, buttons, curve expo, pointer velocity. 2025-11-23 13:57:52 +08:00
TrueZhuanjia e058839faf separated controller section in settings menu 2025-11-19 14:50:39 +08:00
TrueZhuanjia d4d5146f66 controller->mouse demo 2025-11-19 14:35:56 +08:00
TrueZhuanjia 85f4838067 fixed: gyro minimum stick offset slider doesn't work on iPad 2025-11-17 00:20:43 +08:00
TrueZhuanjia f23108a3ba fixed some app theme issues 2025-11-15 19:30:53 +08:00
TrueZhuanjia 1623fc34cf fixed: settings menu motion control section some times can't be expanded 2025-11-09 11:18:24 +08:00
TrueZhuanjia d244c9bd1f minor tweak for abs touch 2025-11-08 14:58:42 +08:00
TrueZhuanjia e53e997cd4 tweak favorite setting parentStack spacing 2025-11-08 12:04:30 +08:00
TrueZhuanjia 5c646ef3d2 - touchpad gesture tweaks and improvements
- cancel realtime switch for single point touch gesture passthrough
- cancel remembering settings menu content offset
2025-11-08 11:34:54 +08:00
TrueZhuanjia f01d36d4a0 minor changes 2025-11-08 03:18:09 +08:00
TrueZhuanjia 37fc7e2cb4 fix setting stack long press crash 2025-11-08 03:01:42 +08:00
TrueZhuanjia ea68353328 - optimize touch pad gesture
- relative touch minor fix
2025-11-08 00:42:26 +08:00
TrueZhuanjia da4bfb851c remove test code 2025-11-07 23:45:20 +08:00
TrueZhuanjia 6a1f85975f tweak touch pad gesture sensitivity 2025-11-07 23:41:50 +08:00
TrueZhuanjia 700552bd75 - fixes some UI memory leaks
- clear some obsolete codes
2025-11-07 23:21:53 +08:00
TrueZhuanjia 5750b99af3 - fixed: memory leak and UI stuck caused by MenuSenctionView
- feat: memorize content offset for settings menu
- optimize gesture passthrough for relative touch & absolute touch
- feat: passthroughGesturesSwitch, ctrlDownForPinchSwitch, leftClickDelaySlider, touchModeSelector2 in experimental section
2025-11-07 17:34:19 +08:00
TrueZhuanjia cc35e6b3a4 touchmode indicator for exprerimental section 2025-11-06 21:05:34 +08:00
TrueZhuanjia 3b4800eb78 - touchPad mode optimization 2025-11-06 20:36:09 +08:00
TrueZhuanjia a2855ef522 feat: touchPad gesture sensitivities 2025-11-06 20:19:06 +08:00
TrueZhuanjia 935406b75b - slide distance threshold slider for relative touch & "mousePad", that fixes mouse cursor jump 2025-11-06 16:36:48 +08:00
TrueZhuanjia e933c5de60 - enhanced relative touch (touchpad mode & "mousePad" widget) pointer accuracy
- touchpad gesture simulation for touchPad & single point mode
2025-11-06 11:52:13 +08:00
TrueZhuanjia a23a5491ae feat: functional button "AbsTchDrag" 2025-11-05 11:51:29 +08:00
TrueZhuanjia 1ad8f51b7a feat: widget "ABSMOUSE" 2025-11-05 08:59:39 +08:00
TrueZhuanjia 4d1619346d update c module for "ABSMOUSE" widget 2025-11-04 21:56:24 +08:00
TrueZhuanjia df8c273cb9 - feed integral for roll->left stick motion control
- feat: roll->left stick motion control multi-level sensitivity
2025-11-04 16:01:21 +08:00
TrueZhuanjia 6eca97b4aa fixed: default on-screen widget borderAlpha offset 2025-11-04 11:03:36 +08:00
TrueZhuanjia 39f8398b10 feat: on screen widget "discreteWheel" 2025-11-04 10:48:20 +08:00
TrueZhuanjia e32a356cb5 feat: optimized single point (absolute touch) for OSRS & regular use cases 2025-11-03 23:58:49 +08:00
TrueZhuanjia e0c556da10 feat: mute in background switch 2025-11-03 16:18:21 +08:00
TrueZhuanjia a3f9e07cf7 feat: duck other apps switch in audio section 2025-11-03 11:25:57 +08:00
TrueZhuanjia ff40e55cd1 feat: append mouse left click after touch up as an option for OSRS (Old School RuneScape) or similar games that requires double tap on touchscreen 2025-10-31 15:45:55 +08:00
TrueZhuanjia e548bda73a stereo to system audio engine, multi-channel audio to SDL 2025-10-30 21:35:27 +08:00
TrueZhuanjia cf11b06f3e multi-audioChannel test 2025-10-30 15:41:10 +08:00
TrueZhuanjia a799c39aff set "useBuiltinMic" to true by default 2025-10-29 09:59:06 +08:00
TrueZhuanjia ff230b3433 refactor audio engine init to Connection.m 2025-10-29 09:36:16 +08:00
TrueZhuanjia a9f4db52b5 audio session config test 2025-10-29 00:16:24 +08:00
TrueZhuanjia 071e2a764a feat: audio engine selector 2025-10-28 23:33:42 +08:00
TrueZhuanjia e050c914bb Merge branch 'systemAudioEngineTest' into Integration 2025-10-28 21:55:57 +08:00
TrueZhuanjia cd9ef74f3f add multi-channel audio support 2025-10-28 21:54:16 +08:00
TrueZhuanjia 644687a50c optimize direction pad haptic feedback 2025-10-28 20:24:48 +08:00
TrueZhuanjia e7132e3371 - feat: highlight size tweak
- fixed: legacy onscreen controls unavailable in touch disabled mode
2025-10-28 16:41:39 +08:00
TrueZhuanjia 7223ab3be5 cancel haptic feedback for legacy on-screen control direction pad 2025-10-28 08:47:04 +08:00
TrueZhuanjia 99952281cb fixed: legacy on-screen control layers pass through native touch events 2025-10-28 08:37:26 +08:00
TrueZhuanjia 9c518dfeb6 fixed: direction pad on-screen widgets losing haptic feedback on iPhone 2025-10-28 08:18:31 +08:00
TrueZhuanjia 28e2f13e87 test: replace SDL with system audio engine to support audio playing when screen is dimmed out 2025-10-27 17:12:45 +08:00
TrueZhuanjia 4a9e109de5 stop video render when app enters background without pip enabled for balanced mode 2025-10-25 13:57:36 +08:00
TrueZhuanjia f5b56ca67f Fixed: widget profile, widget tool entry doesn't work for single point touch 2025-10-22 11:43:26 +08:00
TrueZhuanjia b7ad4d3562 Fixed: on-screen widget default border alpha issue 2025-10-20 19:45:33 +08:00
TrueZhuanjia 94a8caa5df fixed: unable to typing non-ascii characters by default iOS soft keyboards 2025-10-20 15:56:45 +08:00
TrueZhuanjia b650ac0ca3 show soft keyboard toolbar by default 2025-10-20 11:50:05 +08:00
TrueZhuanjia 9dda594519 feat: on-screen widget border alpha tweak 2025-10-19 11:04:56 +08:00
TrueZhuanjia 0ea6248773 minor fix for settings menu 2025-10-19 00:14:45 +08:00
TrueZhuanjia fee003b824 feat: app theme selector 2025-10-18 23:30:40 +08:00
TrueZhuanjia cd415cc639 feat: setup autotap timer by text input instead of slider. 2025-10-18 19:14:21 +08:00
TrueZhuanjia 10ceac2ce3 feat: label alpha tweak for on-screen widgets 2025-10-17 20:39:06 +08:00
TrueZhuanjia 86a0ff46ee - dynamic update theme fix for safeAreaPadding on iPhone 2025-10-17 16:27:55 +08:00
TrueZhuanjia 3887ad0c4c - iOS 14 UI minor fix for settings menu 2025-10-17 15:02:02 +08:00
TrueZhuanjia 58f534fcc8 minor fix 2025-10-15 22:15:07 +08:00
TrueZhuanjia f8e14006c1 try to reduce on-screen widget performance overheads 2025-10-15 21:43:55 +08:00
TrueZhuanjia a959a346b0 fixed: motion control configs not loaded correctly from oscProfile 2025-10-15 09:53:28 +08:00
TrueZhuanjia 65459b3f4e update preset widget profiles 2025-10-15 09:35:40 +08:00
TrueZhuanjia 2216899204 minor tweak 2025-10-14 23:10:25 +08:00
TrueZhuanjia 5fb4887c67 fixed: settings menu safeArea adaption some times not working on iPhone 2025-10-14 23:05:25 +08:00
TrueZhuanjia 6ff2add9dd - feat: edge sliding sensitivity for edge sliding gestures
- update flag for new preset widget profiles
2025-10-14 22:34:28 +08:00
TrueZhuanjia c963c1254d update preset widget profiles 2025-10-14 16:43:36 +08:00
TrueZhuanjia 8afce40e81 - feat: functional buttons "toolbox" "widgetTool" "widgetProfiles" "softKeyboard" 2025-10-14 15:49:06 +08:00
TrueZhuanjia e44504bc48 - feat: hybrid form for mousePad & LT/RT Pad 2025-10-14 14:22:44 +08:00
TrueZhuanjia 11b79dda5b - feat: yaw & pitch factor for each on-screen "gyro" button for seamlessly toggling among different level of gyro sensitivities. 2025-10-14 01:37:22 +08:00
TrueZhuanjia 8cee8b679c new preset on-screen widget templates with gyro & settings button 2025-10-12 23:21:54 +08:00
TrueZhuanjia 1cb9360d83 refactor "gyroOff" to "gyroPause" 2025-10-12 22:01:38 +08:00
TrueZhuanjia 297f5ad7e6 fixed: direction pad on-screen widget (Wasdpad, dPad ...) not responsive on first touch move 2025-10-12 21:28:21 +08:00
TrueZhuanjia 3f2a011d7b - feat: gyro-off on-screen button, the opposite of "gyro" button
- fixed: auto tap slider disappeared for regular on-screen buttons
2025-10-12 20:41:33 +08:00
TrueZhuanjia dd0c36bb20 feat: haptic engine selector 2025-10-12 16:49:10 +08:00
TrueZhuanjia aff1bdfe89 - feat: on screen widget "LTAPD" & "RTPAD" (left trigger pad & right trigger pad)
- try to fix single-tap not responsive on some iPhone by "single tap sensitivity option" in settings menu
2025-10-11 15:31:41 +08:00
TrueZhuanJia 00533f5d3b Merge pull request #132 from twilightgod/iphone-haptics
Fall back to phone haptics engine if controller doesn't support it
2025-10-09 17:12:19 +08:00
TrueZhuanjia eff682bc05 feat: minimum stick offset for gyro -> stick mapping 2025-10-09 09:58:57 +08:00
TrueZhuanjia 16a210efa2 feat: motion control - direct mapping from gyro to mouse or controller sticks, with on-screen button "gyro" for toggling 2025-10-07 23:09:06 +08:00
TrueZhuanjia 99a2a4ca1a minor fixes 2025-10-04 17:22:32 +08:00
TrueZhuanjia 67af68fcf1 - motion control demo1
- feat: movable on-screen functional buttons
2025-10-04 16:44:43 +08:00
TrueZhuanjia b96e2d1bdf fixed: built-in gyro mapping doesn't work on iOS12 & iOS13 2025-10-02 12:25:31 +08:00
TrueZhuanjia 2bb0ce086f - feat: tap to toggle on-screen button triggering
- fixed: special overlay button relocation
2025-09-29 17:27:49 +08:00
TrueZhuanjia 9bc518dc68 set button-type widget default properties 2025-09-29 10:44:26 +08:00
TrueZhuanjia f60433ff3f try to fix: built-in gyro doesn't work on iOS12 & iOS13 2025-09-28 10:59:01 +08:00
TrueZhuanjia b4493707ca Merge branch 'menuSectionFoldStateMemorize' into Integration 2025-09-28 10:35:34 +08:00
TrueZhuanjia 0fe10d8654 feat: remember menu section fold state 2025-09-28 10:34:40 +08:00
Rex Xiong 87d9590f3b Update HapticContext.m 2025-09-27 13:35:19 -07:00
Rex Xiong eeff9b74da Fallback to phone haptics engine if controller doesn't support it 2025-09-27 13:13:26 -07:00
TrueZhuanjia ceebd19c0e Merge branch 'aliasKeyboardType' into Integration 2025-09-26 09:18:00 +08:00
TrueZhuanjia 00e0566dd4 story board UI string fix 2025-09-25 20:00:21 +08:00
TrueZhuanjia acd4a04076 fixed: settings menu accidentally resized on iPad 2025-09-25 16:22:27 +08:00
TrueZhuanjia ac0aa29578 feat: mic volume 2025-09-25 13:30:30 +08:00
TrueZhuanjia 9a053d9ed8 fixed: mic redirection not working on iOS 12 2025-09-24 22:43:14 +08:00
TrueZhuanjia 9124a1fee5 use Deque for mic audio ringBuffering 2025-09-24 16:50:14 +08:00
TrueZhuanjia 858e7c7440 feat: use built-in mic 2025-09-24 12:49:25 +08:00
TrueZhuanjia e5669e2a30 feat: local volume 2025-09-23 23:51:21 +08:00
TrueZhuanjia 431b32b680 try to fix broken native touch & broken on screen widget layout when orientation is locked on iPhone iOS 16/17 control center 2025-09-23 20:55:15 +08:00
TrueZhuanjia 7dce11b4c4 add libupos-sim.a file reference 2025-09-22 23:04:58 +08:00
TrueZhuanjia 9fca3a5984 storyboard & settings menu UI fixes 2025-09-22 21:42:02 +08:00
TrueZhuanjia 733d5c38f3 - fixed: non-AWDL stuttering fix doesn't work when settings menu expands during streaming 2025-09-22 15:49:40 +08:00
HsunLu 3834b5a604 Fix: allow default keyboard for alias input (support Zhuyin) 2025-09-22 14:40:11 +08:00
TrueZhuanjia 07e36029d0 tweak relative touch handler 2025-09-22 13:18:25 +08:00
TrueZhuanjia 689221f10e send dummy event to prevent stuttering 2025-09-21 18:35:56 +08:00
TrueZhuanjia 60dacdf05f feat: dynamic theme update 2025-09-18 16:53:17 +08:00
TrueZhuanjia 1614bb0609 Merge branch 'dynamicTheme' into Integration
# Conflicts:
#	VoidLink/ViewControllers/SettingsViewController.m
2025-09-18 11:02:15 +08:00
TrueZhuanjia a386867dd0 fixed: stream view interrupted by mic stream 2025-09-16 08:44:38 +08:00
TrueZhuanjia e177d6eaea perfGraph tips 2025-09-15 13:15:04 +08:00
TrueZhuanjia 464492529e fixed possible crash in micHandler 2025-09-15 11:37:50 +08:00
TrueZhuanjia b950882846 tweak stats overlay 2025-09-15 10:54:53 +08:00
TrueZhuanjia 63808c9e9c use safeTimer for micHandler 2025-09-15 10:39:20 +08:00
TrueZhuanjia 76d138d64d minor change on storyboard 2025-09-15 09:33:47 +08:00
TrueZhuanjia 1232644268 minor fixes 2025-09-14 10:07:50 +08:00
TrueZhuanjia 2e776baec9 fixed potential crash in mousePad widget 2025-09-14 03:09:11 +08:00
TrueZhuanjia 8fb4829aef further fix widget layout with orientation change issue 2025-09-13 22:57:20 +08:00
TrueZhuanjia fcbc844587 switch to default profile after importing widget templates 2025-09-13 22:33:34 +08:00
TrueZhuanjia aecb1163e3 minor UI fixes & tweaks 2025-09-13 21:55:02 +08:00
TrueZhuanjia 0e51a49898 tweak statsOverlay 2025-09-13 21:21:57 +08:00
TrueZhuanjia adce543c45 hostView settings menu gesture improvement 2025-09-13 18:34:49 +08:00
TrueZhuanjia 83a32f96e6 update preset on screen widget templates 2025-09-13 18:01:18 +08:00
TrueZhuanjia 6c72f0331b feat: implements button visual feedback switch for legacy osc 2025-09-13 17:33:40 +08:00
TrueZhuanjia 92f1fbf33b fixed: unpredictable on screen widget size after orientation change in layout tool. now widget size stays consistent. 2025-09-13 17:13:11 +08:00
TrueZhuanjia 424fe2c2f0 fixed: allow widget profiles with identical names and shows the checkmark at the correct profile 2025-09-12 22:47:10 +08:00
TrueZhuanjia f5d629c62c replace widget templates 2025-09-12 22:16:19 +08:00
TrueZhuanjia 19fa975b6d - fixed: widget height adjusting bug 2025-09-12 21:58:25 +08:00
TrueZhuanjia a4951309d3 widget layout tool minor fixes 2025-09-12 17:03:40 +08:00
TrueZhuanjia 3ec53c29d4 - minor UI fix for touch mode selector 2025-09-12 13:14:38 +08:00
TrueZhuanjia 3fcaafc42b iOS 14 settings menu UI bug fix 2025-09-12 12:58:06 +08:00
TrueZhuanjia 5318acc427 minor fix for setting menu loading speed 2025-09-12 10:13:48 +08:00
TrueZhuanjia daa4fc80eb fixed: broken touch input caused by Absolute Touch 2025-09-11 23:14:58 +08:00
TrueZhuanjia c094d74d8e - feat: allow touchPad label to be display if it's different from touchPad cmdString 2025-09-11 21:39:55 +08:00
TrueZhuanjia 5418936816 - feat: mouseWheel on-screen widget 2025-09-11 21:33:15 +08:00
TrueZhuanjia c3b7679268 - fixed: widget height changes unexpectedly after being tapped 2025-09-11 19:49:36 +08:00
TrueZhuanjia d12b83d1ef - Fixed: mousepad unable to perform 2-finger right click in hovering mode
- Minor changes & improvements for mousePad widget
2025-09-11 17:42:17 +08:00
TrueZhuanjia 32b78d1a4c - move performance graph setting stack to experimental section 2025-09-11 15:02:55 +08:00
TrueZhuanjia 71f66fe8c0 - UI: highlighting emerging setting stack 2025-09-11 14:51:39 +08:00
TrueZhuanjia d054bbe35f - feat: on-screen button visual feedback switch
- feat: touch input disabled mode (while allowing on-screen widget input)
- fixed: swapAbxy stack dynamic UI
2025-09-11 11:37:14 +08:00
TrueZhuanjia bd1c4e3cc9 - update mic permission tip 2025-09-10 21:27:23 +08:00
TrueZhuanjia 928b765700 compatibility fix from c-module 2025-09-10 20:47:35 +08:00
TrueZhuanjia 45acc28384 Tap gesture fix 2025-09-10 12:39:26 +08:00
TrueZhuanjia 4e65abf6b1 - feat: auto tap for on-screen widget.
- fixed: on-screen widget button stuck.
2025-09-10 03:02:28 +08:00
TrueZhuanjia d818f8a7ce allows on-screen widget vibration during mic redirection 2025-09-08 20:24:11 +08:00
TrueZhuanjia c0850ce9d2 Merge branch 'micDemo' into Integration 2025-09-08 16:52:45 +08:00
TrueZhuanjia c3941df7e9 - feat: mic redirection (with Sunshine-Foundation) 2025-09-08 16:47:27 +08:00
TrueZhuanjia b1b46b3ec0 Merge commit 'metal renderer crash fix' into Integration 2025-09-02 12:36:50 +08:00
TrueZhuanjia dff6679a90 lib opus file reference 2025-09-02 08:54:53 +08:00
Acaki 4ab21aed0e fix: crash on background timer expired with Metal renderer 2025-09-01 22:51:41 +08:00
TrueZhuanjia d67a40f155 c-module source file reference update 2025-09-01 22:04:55 +08:00
TrueZhuanjia b506917382 update c-module for mic implementation 2025-09-01 10:41:51 +08:00
TrueZhuanjia 944f7422b3 set maximum bitrate to 800Mbps 2025-08-31 11:12:48 +08:00
TrueZhuanjia 4843c046d7 fixed: bitrate slider disabled when sunshine server does not support dynamic bitrate 2025-08-31 09:14:52 +08:00
TrueZhuanjia 61df4e7e61 a few ui string tweaks 2025-08-30 19:22:09 +08:00
TrueZhuanjia 48558316d1 Merge branch 'perfMode' into Integration 2025-08-30 18:57:59 +08:00
TrueZhuanjia 5903b358ee - unify pixel format to MTLPixelFormatRGBA16Float
- move renderingBackEnd to experimental section, add a few tips for performance mode
2025-08-30 18:54:30 +08:00
TrueZhuanjia 6881098ca2 Fixed: failed to launch app on iPhone when there're specific number of hosts presented no the host view. 2025-08-30 11:34:29 +08:00
TrueZhuanjia 4d783649f5 cancel iOS13 availability check for metal renderer 2025-08-28 23:34:26 +08:00
Acaki 2b40224b2b refactor: replace MTLPixelFormatBGR10A2Unorm with MTLPixelFormatBGRA10_XR for older devices 2025-08-28 21:31:14 +08:00
Acaki e4d6be3bb3 Revert "fix: crash when host is on hdr but client is not on Metal lower iOS versions"
This reverts commit 6c0b1550fd.
2025-08-28 21:29:51 +08:00
Acaki 6c0b1550fd fix: crash when host is on hdr but client is not on Metal lower iOS versions 2025-08-28 13:28:28 +08:00
TrueZhuanjia 967fbe8072 feat: no click option for mousePad on-screen widget 2025-08-28 13:23:35 +08:00
Acaki c7dd7a2c21 fix: metal renderer packed format crash on emulator and lower iOS version 2025-08-28 00:07:56 +08:00
TrueZhuanjia 49ed0e43e9 reduce on-screen widget visual effect performance cost 2025-08-27 22:35:47 +08:00
TrueZhuanjia 6bae985e2c ui & string tweaks 2025-08-27 20:20:48 +08:00
Acaki 39d7d9e213 feat: add back lowest latency mode for AVSampleBuffer 2025-08-27 15:53:00 +08:00
Acaki eb3501e090 Revert "feat: bring back CAMetalDisplayLink"
This reverts commit b6c029df
2025-08-27 15:16:24 +08:00
TrueZhuanjia 83566ad4e1 try to reduce directionPad on-screen widget performance cost 2025-08-27 14:04:45 +08:00
TrueZhuanjia 19e78d5eb6 Merge branch 'dynamicBitrate' into Integration 2025-08-26 14:45:04 +08:00
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
TrueZhuanjia 31ab333f5b realtime bitrate adjustment with Sunshine-Foundation (experimental) 2025-08-25 10:07:38 +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
xzzpig ff8cb20cae add support for dynamic theme update 2025-07-30 01:20:36 +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
1033 changed files with 66608 additions and 152516 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
+11 -26
View File
@@ -1,12 +1,12 @@
name: Build IPAs
name: Build VoidLink IPA
on:
push:
branches: [ "noWaterMark" ]
branches: [ "feat/frame_pacing_metal" ]
jobs:
build:
name: Build IPAs
name: Build VoidLink IPA
runs-on: macos-latest
steps:
@@ -16,30 +16,15 @@ jobs:
- name: Checkout submodules
run: "git submodule update --init --recursive"
- name: Build FullScreen Version
- name: Build VoidLink
run: |
plutil -replace UIRequiresFullScreen -bool true Limelight/Limelight-Info.plist
xcodebuild -verbose -scheme Moonlight-ZWM -sdk iphoneos -configuration AppStoreDistribution archive -archivePath $PWD/build/MoonlightFullScreen.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
mkdir -p ./build/IPA_FullScreen && mkdir -p ./build/IPA_FullScreen/Payload
cp -r ./build/MoonlightFullScreen.xcarchive/Products/Applications/Moonlight-ZWM.app ./build/IPA_FullScreen/Payload/Moonlight-ZWM.app
cd ./build/IPA_FullScreen && zip -r ../../build/Moonlight-ZWM_FullScreen.ipa ./Payload/* && cd ../..
- name: Upload FullScreen IPA to Artifacts
uses: actions/upload-artifact@v4
with:
name: Moonlight-ZWM_FullScreen.ipa
path: build/Moonlight-ZWM_FullScreen.ipa
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: Build StageManager Version
run: |
plutil -replace UIRequiresFullScreen -bool false Limelight/Limelight-Info.plist
xcodebuild -verbose -scheme Moonlight-ZWM -sdk iphoneos -configuration AppStoreDistribution archive -archivePath $PWD/build/MoonlightStageManager.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
mkdir -p ./build/IPA_StageManager && mkdir -p ./build/IPA_StageManager/Payload
cp -r ./build/MoonlightStageManager.xcarchive/Products/Applications/Moonlight-ZWM.app ./build/IPA_StageManager/Payload/Moonlight-ZWM.app
cd ./build/IPA_StageManager && zip -r ../../build/Moonlight-ZWM_StageManager.ipa ./Payload/* && cd ../..
- name: Upload StageManager IPA to Artifacts
- name: Upload VoidLink IPA to Artifacts
uses: actions/upload-artifact@v4
with:
name: Moonlight-ZWM_StageManager.ipa
path: build/Moonlight-ZWM_StageManager.ipa
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
+31 -11
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
<device id="ipad11_0rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -12,7 +12,7 @@
<objects>
<viewController id="wb7-af-jn8" customClass="MainFrameViewController" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" indicatorStyle="black" dataMode="prototypes" id="TZj-Lc-M9d" customClass="AppCollectionView">
<rect key="frame" x="0.0" y="0.0" width="834" height="1120"/>
<rect key="frame" x="0.0" y="0.0" width="834" height="1136"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.3333333432674408" green="0.3333333432674408" blue="0.3333333432674408" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="f7l-kG-hJc">
@@ -601,6 +601,25 @@ ICAgICAgICAgICAgICA
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Player 1 Motion Mode" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0bl-Hh-8ot">
<rect key="frame" x="20" y="2154" width="164" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="WQX-ct-rnt" userLabel="Motion Mode Selector">
<rect key="frame" x="16" y="2183" width="459" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segments>
<segment title="Auto"/>
<segment title="Device Motion"/>
<segment title="Controller Motion"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl>
</subviews>
<color key="backgroundColor" systemColor="viewFlipsideBackgroundColor"/>
</view>
@@ -622,6 +641,7 @@ ICAgICAgICAgICAgICA
<outlet property="largerStickLR1Selector" destination="sIU-PH-H7V" id="YWg-40-eKd"/>
<outlet property="liftStreamViewForKeyboardSelector" destination="hm3-EV-h6d" id="fHG-J1-eQL"/>
<outlet property="localMousePointerModeSelector" destination="4FU-iP-c6T" id="4Gz-ao-xH2"/>
<outlet property="motionModeSelector" destination="WQX-ct-rnt" id="1N8-yA-54x"/>
<outlet property="mousePointerVelocityFactorSlider" destination="Ei2-FJ-9oC" id="1T3-QA-Jua"/>
<outlet property="mousePointerVelocityFactorUILabel" destination="oPM-G6-u5O" id="Eco-5u-PRR"/>
<outlet property="multiControllerSelector" destination="KlC-fG-wEi" id="giM-F7-So5"/>
@@ -664,7 +684,7 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="YRO-Lg-RCg"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="hGs-BR-t4b">
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
@@ -704,7 +724,7 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="vu8-3s-HlY"/>
</layoutGuides>
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="SMn-1j-R3I">
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.3333357871" green="0.33332890269999998" blue="0.33333355190000002" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
</view>
@@ -726,7 +746,7 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="YZj-An-sqS"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="le1-tU-NEp">
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="N0D-dj-EuF">
@@ -753,7 +773,7 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="eaS-aj-VtA"/>
</layoutGuides>
<view key="view" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="Acw-S4-f8a">
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view alpha="0.75" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fZU-G0-fgM" userLabel="Toolbar Root View" customClass="ToolBarContainerView">
@@ -971,11 +991,11 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="8ja-wQ-jbJ"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="PlJ-LJ-d1p">
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView opaque="NO" clearsContextBeforeDrawing="NO" alpha="0.5" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="BA3-hd-bDp">
<rect key="frame" x="0.0" y="44" width="834" height="1150"/>
<rect key="frame" x="0.0" y="44" width="834" height="1166"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView>
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="62V-DN-FUk">
@@ -1036,13 +1056,13 @@ ICAgICAgICAgICAgICA
<image name="SquaresArrowUp" width="40" height="40"/>
<image name="Trash" width="40" height="40"/>
<systemColor name="secondaryLabelColor">
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="tintColor">
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="viewFlipsideBackgroundColor">
<color red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+40 -3
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="DL0-L5-LOv">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="DL0-L5-LOv">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -570,7 +570,7 @@ ICAgICAgICAgICAgIA
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Local Mouse Pointer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VGn-Oc-mKc">
<rect key="frame" x="22" y="2007" width="165" height="21"/>
<rect key="frame" x="20" y="2008" width="165" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -604,6 +604,24 @@ ICAgICAgICAgICAgIA
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Player 1 Motion Mode" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c8T-vY-LOX">
<rect key="frame" x="22" y="2075" width="174" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.95132106540000005" green="0.97490358349999995" blue="0.99987185000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="6Lj-ss-Tj8" userLabel="Motion Mode Selector">
<rect key="frame" x="16" y="2104" width="450" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segments>
<segment title="Auto"/>
<segment title="Device Motion"/>
<segment title="Controller Motion"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FqF-bM-afQ">
<rect key="frame" x="17" y="14" width="219" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
@@ -617,6 +635,23 @@ ICAgICAgICAgICAgIA
<action selector="backToStreamingButtonTapped:" destination="rYd-e6-cQU" eventType="touchUpInside" id="9P0-Yw-T4W"/>
</connections>
</button>
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="dcP-yB-6Un" userLabel="PiP Selector">
<rect key="frame" x="18" y="2102" width="459" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segments>
<segment title="No"/>
<segment title="Yes"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Enable PiP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ELc-8r-KSB">
<rect key="frame" x="20" y="2075" width="86" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
@@ -646,6 +681,7 @@ ICAgICAgICAgICAgIA
<outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/>
<outlet property="oscTapExlusionAreaSizeLabel" destination="Dfl-gL-00M" id="Jbg-64-P3M"/>
<outlet property="oscTapExlusionAreaSizeSlider" destination="TSt-Hj-uTR" id="h9l-ap-AVd"/>
<outlet property="pipSelector" destination="dcP-yB-6Un" id="rrB-dR-2Bq"/>
<outlet property="pointerVelocityModeDividerSlider" destination="xet-C9-l5y" id="GnQ-xU-e0N"/>
<outlet property="pointerVelocityModeDividerUILabel" destination="VlN-ZW-1gm" id="vbe-QK-hZb"/>
<outlet property="resolutionDisplayView" destination="Dej-Bj-F6i" id="bi4-N6-snx"/>
@@ -665,6 +701,7 @@ ICAgICAgICAgICAgIA
<outlet property="touchPointerVelocityFactorUILabel" destination="c6f-bJ-Vji" id="uVM-o6-JPk"/>
<outlet property="unlockDisplayOrientationSelector" destination="R9O-Tw-UD4" id="0ak-pc-eva"/>
<outlet property="yuv444Selector" destination="vwT-tg-RfT" id="69G-bN-kJF"/>
<outlet property="motionModeSelector" destination="6Lj-ss-Tj8" id="RLW-3I-hvl"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="RWc-Km-KG5" userLabel="First Responder" sceneMemberID="firstResponder"/>
-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
-64
View File
@@ -1,64 +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
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
oscTapExlusionAreaSize:(CGFloat)oscTapExlusionAreaSize
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
largerStickLR1:(BOOL)largerStickLR1
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
useFramePacing:(BOOL)useFramePacing
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;
- (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
-65
View File
@@ -1,65 +0,0 @@
//
// TemporarySettings.h
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "Settings+CoreDataClass.h"
#define RELATIVE_TOUCH 0
#define REGULAR_NATIVE_TOUCH 1
#define PURE_NATIVE_TOUCH 2
#define ABSOLUTE_TOUCH 3
#define OSC_TOOL_FINGERS 4
@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 * oscLayoutToolFingers;
@property (nonatomic, retain) NSNumber * slideToSettingsScreenEdge;
@property (nonatomic, retain) NSNumber * slideToSettingsDistance;
@property (nonatomic, retain) NSNumber * oscTapExlusionAreaSize;
@property (nonatomic, retain) NSNumber * touchPointerVelocityFactor;
@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) enum {
CODEC_PREF_AUTO,
CODEC_PREF_H264,
CODEC_PREF_HEVC,
CODEC_PREF_AV1,
} preferredCodec;
@property (nonatomic) BOOL enableYUV444;
@property (nonatomic) BOOL reverseMouseWheelDirection;
@property (nonatomic) BOOL largerStickLR1;
@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, retain) NSNumber * touchMode;
@property (nonatomic, retain) NSNumber * statsOverlayLevel;
@property (nonatomic) BOOL statsOverlayEnabled;
@property (nonatomic) BOOL liftStreamViewForKeyboard;
@property (nonatomic) BOOL showKeyboardToolbar;
@property (nonatomic) BOOL unlockDisplayOrientation;
- (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

-149
View File
@@ -1,149 +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* streamView;
NSTimer* longPressTimer;
UITouch* lastTouchDown;
CGPoint lastTouchDownLocation;
UITouch* lastTouchUp;
CGPoint lastTouchUpLocation;
// upper screen edge check
bool touchPointSpawnedAtUpperScreenEdge;
CGFloat slideGestureVerticalThreshold;
CGFloat screenWidthWithThreshold;
CGFloat EDGE_TOLERANCE;
}
- (id)initWithView:(StreamView*)view {
self = [self init];
self->streamView = view;
// upper screen check
EDGE_TOLERANCE = 15.0;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
self->touchPointSpawnedAtUpperScreenEdge = false;
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 {
CGPoint initialPoint = [[touches anyObject] locationInView:streamView];
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
self->touchPointSpawnedAtUpperScreenEdge = true;
return; // we're done here. this touch event will not be sent to the remote PC.
}
touchPointSpawnedAtUpperScreenEdge = false; // reset this flag immediately if we get a touch event passing the check above, this fixes irresponsive touch after closing the command tool menu.
// Ignore touch down events with more than one finger
if ([[event allTouches] count] > 1) {
return;
}
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:streamView];
// 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 / streamView.bounds.size.width) - (lastTouchUpLocation.x / streamView.bounds.size.width), 2) +
pow((touchLocation.y / streamView.bounds.size.height) - (lastTouchUpLocation.y / streamView.bounds.size.height), 2)) > DOUBLE_TAP_DEAD_ZONE_DELTA) {
[streamView 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 {
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC.
// Ignore touch move events with more than one finger
if ([[event allTouches] count] > 1) {
return;
}
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:streamView];
if (sqrt(pow((touchLocation.x / streamView.bounds.size.width) - (lastTouchDownLocation.x / streamView.bounds.size.width), 2) +
pow((touchLocation.y / streamView.bounds.size.height) - (lastTouchDownLocation.y / streamView.bounds.size.height), 2)) > LONG_PRESS_ACTIVATION_DELTA) {
// Moved too far since touch down. Cancel the long press timer.
[longPressTimer invalidate];
longPressTimer = nil;
}
[streamView updateCursorLocation:[[touches anyObject] locationInView:streamView] isMouse:NO];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC.
// 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:streamView];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
// Treat this as a normal touchesEnded event
[self touchesEnded:touches withEvent:event];
}
@end
File diff suppressed because it is too large Load Diff
-32
View File
@@ -1,32 +0,0 @@
//
// OSCProfile.h
// Moonlight
//
// Created by Long Le on 12/22/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "OnScreenButtonState.h"
NS_ASSUME_NONNULL_BEGIN
/**
This object contains information pertaining to any of the user created, custom on screen controller layout configurations, or 'profiles.' The object contains a 'name' property for easy reference, as well as an 'isSelected' property which is used to determine whether this particular custom OSC layout should show on screen during game stream view. Only one 'OSCProfile' is set to 'isSelected' at any given time. The object also contains an array of 'OnScreenButtonStates' which provides information that allows us to move and hide/unhide each of the 19 on screen buttons. Note that the 'buttonStates' property should contain an NSMutableArray of ENCODED 'OnScreenButtonState' objects. This allows us to save the 'OSCProfile' object to NSUserDefaults.
Additionally the 'OSCProfile' object adopts encoding and decoding protocols so that we can encode the object before saving it to NSUserDefaults. By saving this object to NSUserDefaults we allow the user to save and load their custom on screen controller button layouts between app launches
*/
@interface OSCProfile : NSObject <NSCoding, NSSecureCoding>
@property NSString *name;
@property NSMutableArray <OnScreenButtonState *> *buttonStates;
@property BOOL isSelected;
- (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected;
+ (BOOL) supportsSecureCoding;
- (id) initWithCoder:(NSCoder*)decoder;
- (void) encodeWithCoder:(NSCoder*)encoder;
@end
NS_ASSUME_NONNULL_END
-43
View File
@@ -1,43 +0,0 @@
//
// OSCProfile.m
// Moonlight
//
// Created by Long Le on 12/22/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import "OSCProfile.h"
@implementation OSCProfile
- (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected {
if ((self = [self init])) {
self.name = name;
self.buttonStates = buttonStates;
self.isSelected = isSelected;
}
return self;
}
+ (BOOL) supportsSecureCoding {
return YES;
}
- (void) encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.buttonStates forKey:@"buttonStates"];
[encoder encodeBool:self.isSelected forKey:@"isSelected"];
}
- (id) initWithCoder:(NSCoder*)decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.buttonStates = [decoder decodeObjectForKey:@"buttonStates"];
self.isSelected = [decoder decodeBoolForKey:@"isSelected"];
}
return self;
}
@end
@@ -1,333 +0,0 @@
//
// OSCProfilesManager.m
// Moonlight
//
// Created by Long Le on 1/1/23.
// Copyright © 2023 Moonlight Game Streaming Project. All rights reserved.
//
#import "OSCProfilesManager.h"
#import "OnScreenButtonState.h"
#import "Moonlight-Swift.h"
#import "LayoutOnScreenControlsViewController.h"
#import "OnScreenControls.h"
@implementation OSCProfilesManager
static NSMutableSet *OnScreenWidgetViews;
#pragma mark - Initializer
+ (OSCProfilesManager *) sharedManager {
static OSCProfilesManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedManager = [[self alloc] init];
});
return _sharedManager;
}
#pragma mark - Class Helper Methods
+ (void) setOnScreenWidgetViewsSet:(NSMutableSet* )set{
OnScreenWidgetViews = set;
}
/**
* Returns the profile whose 'name' property has a value that is equal to the 'name' passed into the method
*/
- (OSCProfile *) OSCProfileWithName:(NSString*)name {
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) {
if ([profile.name isEqualToString:name]) {
return profile;
}
}
return nil;
}
/**
* Returns an array of encoded 'OSCProfile' objects from the array of decoded 'OSCProfile' objects passed into this method
*/
- (NSMutableArray *) encodedProfilesFromArray:(NSMutableArray *)profiles {
NSMutableArray *profilesEncoded = [[NSMutableArray alloc] init];
/* encode each profile and add them back into an array */
for (OSCProfile *profile in profiles) { //
NSData *profileEncoded = [NSKeyedArchiver archivedDataWithRootObject:profile requiringSecureCoding:YES error:nil];
[profilesEncoded addObject:profileEncoded];
}
return profilesEncoded;
}
/**
* Delete an 'OSCProfile' object of specific index for another in the 'OSCProfile' objects array stored in persistent storage
*/
-(void)deleteCurrentSelectedProfile{
NSMutableArray *profiles = [self getAllProfiles];
NSInteger selectedIndex = [self getIndexOfSelectedProfile];
/* Remove the selected profile */
if(selectedIndex != 0) [profiles removeObjectAtIndex:selectedIndex]; //deleting default profile (index 0) is not allowed
else {
NSLog(@"Default profile!"); // to be updated here.
}
selectedIndex--;
if(selectedIndex < 0) selectedIndex = 0;
OSCProfile *newSelectedProfile = [profiles objectAtIndex:selectedIndex];
newSelectedProfile.isSelected = YES;
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
/* Encode the array itself, NOT the objects in the array, which are already encoded. Save array to persistent storage */
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
/**
* Replaces one 'OSCProfile' object for another in the 'OSCProfile' objects array stored in persistent storage
*/
- (void) replaceProfile:(OSCProfile*)oldProfile withProfile:(OSCProfile*)newProfile {
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) { // set all profiles' 'isSelected' property to NO since the new profile we're saving over the old profile with will be the 'selected' profile
profile.isSelected = NO;
}
/* Set the new profile as the selected one. The reasoning behind this is that this method is currently being used when the user saves over an existing profile with another profile that has the same name. The expected behavior is that the newly saved profile becomes the selected profile which will show on screen when they launch the game stream view */
newProfile.isSelected = YES;
/* Remove the old profile from the array and insert the new profile into its place */
int index = 0;
for (int i = 0; i < profiles.count; i++) {
if ([[profiles[i] name] isEqualToString: oldProfile.name]) {
index = i;
}
}
[profiles removeObjectAtIndex:index];
[profiles insertObject:newProfile atIndex:index];
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
/* Encode the array itself, NOT the objects in the array, which are already encoded. Save array to persistent storage */
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
#pragma mark - Globally Accessible Methods
#pragma mark - Getters
- (NSMutableArray *) getAllProfiles {
NSData *profilesArrayEncoded = [[NSUserDefaults standardUserDefaults] objectForKey: @"OSCProfiles"]; // Get the encoded array of encoded OSC profiles from persistent storage
NSSet *classes = [NSSet setWithObjects:[NSString class], [NSMutableData class], [NSMutableArray class], [OSCProfile class], [OnScreenButtonState class], nil];
NSMutableArray *profilesEncoded = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:profilesArrayEncoded error:nil]; // Decode the encoded array itself, NOT the objects contained in the array
/* Decode each of the encoded profiles, place them into an array, then return the array */
NSMutableArray *profilesDecoded = [[NSMutableArray alloc] init];
OSCProfile *profileDecoded;
for (NSData *profileEncoded in profilesEncoded) {
profileDecoded = [NSKeyedUnarchiver unarchivedObjectOfClasses: classes fromData:profileEncoded error: nil];
[profilesDecoded addObject: profileDecoded];
}
return profilesDecoded;
}
- (OSCProfile *) getSelectedProfile {
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) {
if (profile.isSelected) {
return profile;
}
}
return nil;
}
- (NSInteger) getIndexOfSelectedProfile {
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) {
if (profile.isSelected == YES) {
return [profiles indexOfObject:profile];
}
}
return 0; // if none of the profiles in the array have their 'isSelected' property set to YES (which should not be possible) return the 'Default' profile as the 'selected' profile
}
#pragma mark - Setters
- (void) setProfileToSelected:(NSString *)name {
NSMutableArray *profiles = [self getAllProfiles];
/* Iterate through each profile. If its name equals the value of the 'name' parameter passed into this method then set the profile's 'isSelected' property to YES, otherwise set the value to NO */
for (OSCProfile *profile in profiles) {
if ([profile.name isEqualToString:name]) {
profile.isSelected = YES;
}
else {
profile.isSelected = NO;
}
}
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
/* Encode the array itself, NOT the objects inside the array, which have already been encoded by this point */
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (bool) updateSelectedProfile:(NSMutableArray *) oscButtonLayers {
NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndButtonsToButtonStates:oscButtonLayers];
if([self getIndexOfSelectedProfile]==0) return false;
OSCProfile *newProfile = [[OSCProfile alloc] initWithName:[self getSelectedProfile].name
buttonStates:buttonStatesEncoded isSelected:YES]; // create a new 'OSCProfile'. Set the array of encoded button states created above to the 'buttonStates' property of the new profile, along with a 'name'. Set 'isSelected' argument to YES which will set this saved profile as the one that will show up in the game stream view
/* set all saved OSCProfiles 'isSelected' property to NO since the new profile you're adding will be set as the selected profile */
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) {
profile.isSelected = NO;
}
[self replaceProfile:[self getSelectedProfile] withProfile:newProfile];
return true;
}
- (void) saveProfileWithName:(NSString*)name andButtonLayers:(NSMutableArray *)oscButtonLayers {
NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndButtonsToButtonStates:oscButtonLayers];
OSCProfile *newProfile = [[OSCProfile alloc] initWithName:name
buttonStates:buttonStatesEncoded isSelected:YES]; // create a new 'OSCProfile'. Set the array of encoded button states created above to the 'buttonStates' property of the new profile, along with a 'name'. Set 'isSelected' argument to YES which will set this saved profile as the one that will show up in the game stream view
/* set all saved OSCProfiles 'isSelected' property to NO since the new profile you're adding will be set as the selected profile */
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) {
profile.isSelected = NO;
}
if ([self profileNameAlreadyExist:name]) { // if a saved profile with the same 'name' already exists in persistent storage then overwrite it and save the change to persistent storage
[self replaceProfile:[self OSCProfileWithName:name] withProfile:newProfile];
}
else { // otherwise encode then add the new profile to the end of the OSCProfiles array
NSData *newProfileEncoded = [NSKeyedArchiver archivedDataWithRootObject:newProfile requiringSecureCoding:YES error:nil];
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles];
[profilesEncoded addObject:newProfileEncoded];
/* Encode the 'profilesEncoded' array itself, NOT the objects in the 'profilesEncoded' array, all of which are already encoded by this point */
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
[[NSUserDefaults standardUserDefaults] synchronize];
// saving test:
/*
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *path = [documentsPath stringByAppendingPathComponent:@"newDefault.bin"];
[newProfileEncoded writeToFile:path atomically:YES];
NSLog(@"默认 Profile 数据已保存到: %@", path);
*/
}
}
- (NSMutableArray* ) convertOnScreenControllerAndButtonsToButtonStates:(NSMutableArray *) oscButtonLayers {
/* iterate through each OSC button the user sees on screen, create an 'OnScreenButtonState' object from each button, encode each object, and then add each object to an array */
/*
NSSet *validPositionButtonNames = [NSSet setWithObjects:
@"l2Button",
@"l1Button",
@"dPad",
@"selectButton",
@"leftStickBackground",
@"rightStickBackground",
@"r2Button",
@"r1Button",
@"aButton",
@"bButton",
@"xButton",
@"yButton",
@"startButton",
nil]; */
NSMutableArray *buttonStatesEncoded = [[NSMutableArray alloc] init];
// save on-screen game controller buttons & sticks as buttonstate:
for (CALayer *oscButtonLayer in oscButtonLayers) {
OnScreenButtonState *buttonState = [[OnScreenButtonState alloc] initWithButtonName:oscButtonLayer.name buttonType:LegacyOscButton andPosition:oscButtonLayer.position];
// add hidden attr here
buttonState.isHidden = oscButtonLayer.isHidden;
buttonState.oscLayerSizeFactor = [OnScreenControls getControllerLayerSizeFactor:oscButtonLayer];
buttonState.backgroundAlpha = oscButtonLayer.opacity;
NSLog(@"oscLayerName: %@, opacity: %f, ", oscButtonLayer.name, buttonState.backgroundAlpha);
// buttonState.oscLayerSizeFactor = oscButtonLayer.bounds;
NSData *buttonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
[buttonStatesEncoded addObject: buttonStateEncoded];
}
// save on-screen widget views (keyboard & mouse command) as buttonstate:
for(OnScreenWidgetView* widgetView in OnScreenWidgetViews){
OnScreenButtonState *buttonState = [[OnScreenButtonState alloc] initWithButtonName:widgetView.keyString buttonType:CustomOnScreenWidget andPosition:widgetView.center];
buttonState.alias = widgetView.keyLabel;
buttonState.timestamp = widgetView.timestamp;
buttonState.widthFactor = widgetView.widthFactor;
buttonState.heightFactor = widgetView.heightFactor;
buttonState.backgroundAlpha = widgetView.backgroundAlpha;
buttonState.borderWidth = widgetView.borderWidth;
buttonState.sensitivityFactor = widgetView.sensitivityFactor;
buttonState.stickIndicatorOffset = widgetView.stickIndicatorOffset;
NSData *buttonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
[buttonStatesEncoded addObject: buttonStateEncoded];
}
return buttonStatesEncoded;
}
#pragma mark - Queries
- (BOOL) profileNameAlreadyExist:(NSString*)name {
NSMutableArray *profiles = [self getAllProfiles];
/* Iterate through the decoded profiles and return 'YES' if one of the profiles' 'name' properties equals the 'name' passed into this method */
for (OSCProfile *profile in profiles) {
if ([profile.name isEqualToString:name]) {
return YES;
}
}
return NO;
}
@end
@@ -1,64 +0,0 @@
//
// OnScreenButtonState.m
// Moonlight
//
// Created by Long Le on 10/20/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import "OnScreenButtonState.h"
@implementation OnScreenButtonState
- (id) initWithButtonName:(NSString*)name buttonType:(uint8_t)buttonType andPosition:(CGPoint)position {
if ((self = [self init])) {
self.name = name;
//self.isHidden = isHidden;
self.position = position;
self.buttonType = buttonType;
}
return self;
}
+ (BOOL) supportsSecureCoding {
return YES;
}
- (void) encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.alias forKey:@"alias"];
[encoder encodeDouble:self.timestamp forKey:@"timestamp"];
[encoder encodeInt:self.buttonType forKey:@"buttonType"];
[encoder encodeCGPoint:self.position forKey:@"position"];
[encoder encodeBool:self.isHidden forKey:@"isHidden"];
[encoder encodeFloat:self.widthFactor forKey:@"widthFactor"];
[encoder encodeFloat:self.heightFactor forKey:@"heightFactor"];
[encoder encodeFloat:self.sensitivityFactor forKey:@"velocityFactor"];
[encoder encodeFloat:self.stickIndicatorOffset forKey:@"stickIndicatorOffset"];
[encoder encodeFloat:self.oscLayerSizeFactor forKey:@"oscLayerSizeFactor"];
[encoder encodeFloat:self.backgroundAlpha forKey:@"backgroundAlpha"];
[encoder encodeFloat:self.borderWidth forKey:@"borderWidth"];
}
- (id) initWithCoder:(NSCoder*)decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.alias = [decoder decodeObjectForKey:@"alias"];
self.timestamp = [decoder decodeDoubleForKey:@"timestamp"];
self.buttonType = [decoder decodeIntForKey:@"buttonType"];
self.position = [decoder decodeCGPointForKey:@"position"];
self.isHidden = [decoder decodeBoolForKey:@"isHidden"];
self.widthFactor = [decoder decodeFloatForKey:@"widthFactor"];
self.heightFactor = [decoder decodeFloatForKey:@"heightFactor"];
self.sensitivityFactor = [decoder decodeFloatForKey:@"velocityFactor"];
self.stickIndicatorOffset = [decoder decodeFloatForKey:@"stickIndicatorOffset"];
self.oscLayerSizeFactor = [decoder decodeFloatForKey:@"oscLayerSizeFactor"];
self.backgroundAlpha = [decoder decodeFloatForKey:@"backgroundAlpha"];
self.borderWidth = [decoder decodeFloatForKey:@"borderWidth"];
}
return self;
}
@end
-164
View File
@@ -1,164 +0,0 @@
//
// NativeTouchHandler.m
// Moonlight
//
// Created by ZWM on 2024/6/16.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "NativeTouchHandler.h"
#import "NativeTouchPointer.h"
#import "OnScreenControls.h"
#import "StreamView.h"
#include <Limelight.h>
@implementation NativeTouchHandler {
StreamView* streamView;
TemporarySettings* currentSettings;
bool activateCoordSelector;
bool touchPointSpawnedAtUpperScreenEdge;
// Use a Dictionary to store UITouch object's memory address as key, and pointerId as value,字典存放UITouch对象地址和pointerId映射关系
// pointerId will be generated from a pre-defined pool
// Use a NSSet store active pointerId,
NSMutableDictionary *pointerIdDict; //pointerId Dict for active touches.
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
NSMutableSet<NSNumber *> *unassignedPointerIds;
CGFloat slideGestureVerticalThreshold;
CGFloat screenWidthWithThreshold;
CGFloat EDGE_TOLERANCE;
}
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings{
self = [super init];
self->streamView = view;
self->currentSettings = settings;
self->activateCoordSelector = currentSettings.pointerVelocityModeDivider.floatValue != 1.0;
// self->notPureNativeTouchMode = !(currentSettings.touchMode.intValue == PURE_NATIVE_TOUCH);
self->pointerIdDict = [NSMutableDictionary dictionary];
self->pointerIdPool = [NSMutableSet set];
for (uint8_t i = 0; i <= 10; i++) { //ipadOS supports upto 11 finger touches
[self->pointerIdPool addObject:@(i)];
}
self->activePointerIds = [NSMutableSet set];
// self->excludedPointerIds = [[NSMutableSet alloc] init];
self->touchPointSpawnedAtUpperScreenEdge = false;
EDGE_TOLERANCE = 15.0;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
[NativeTouchPointer setPointerVelocityDivider:settings.pointerVelocityModeDivider.floatValue];
[NativeTouchPointer setPointerVelocityFactor:settings.touchPointerVelocityFactor.floatValue];
[NativeTouchPointer initContextWithView:self->streamView];
//_touchesCapturedByOnScreenButtons = [[NSMutableSet alloc] init];
return self;
}
// generate & populate pointerId into NSDict & NSSet, called in touchesBegan
- (void)handleTouchDown:(UITouch*)touch{
//populate pointerId
uintptr_t memAddrValue = (uintptr_t)touch;
unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds
[unassignedPointerIds minusSet:activePointerIds];
uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue];
[pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)];
[activePointerIds addObject:@(pointerId)];
//check if touch point is spawned on the left or right upper half screen edges, event to remote PC. this is for better handling in-stream slide gesture
CGPoint initialPoint = [touch locationInView:self->streamView];
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
self->touchPointSpawnedAtUpperScreenEdge = true;
// [excludedPointerIds addObject:@(pointerId)];
}
}
// remove pointerId in touchesEnded or touchesCancelled
- (void)removePointerId:(UITouch*)touch{
uintptr_t memAddrValue = (uintptr_t)touch;
NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)];
if(pointerIdObj != nil){
[activePointerIds removeObject:pointerIdObj];
[pointerIdDict removeObjectForKey:@(memAddrValue)];
// if([excludedPointerIds containsObject:pointerIdObj]) [excludedPointerIds removeObject:pointerIdObj]; // remove pointer id from excludedPointerId NSSet
}
}
// 从字典中获取UITouch事件对应的pointerId
// called in method of sendTouchEvent
- (uint8_t) retrievePointerIdFromDict:(UITouch*)touch{
return [[pointerIdDict objectForKey:@((uintptr_t)touch)] unsignedIntValue];
}
- (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash!
CGPoint targetCoords;
// NSLog(@"excluded count: %d", (uint32_t)[excludedPointerIds count]);
if(activateCoordSelector && touch.phase == UITouchPhaseMoved) targetCoords = [NativeTouchPointer selectCoordsFor:touch]; // coordinates of touch pointer replaced to relative ones here.
else targetCoords = [touch locationInView:streamView];
CGPoint location = [streamView adjustCoordinatesForVideoArea:targetCoords];
CGSize videoSize = [streamView getVideoAreaSize];
CGFloat normalizedX = location.x / videoSize.width;
CGFloat normalizedY = location.y / videoSize.height;
uint8_t pointerId = [self retrievePointerIdFromDict:touch];
if([NativeTouchPointer getPointerObjFromDict:touch].needResetCoords){ // access whether the current pointer has reached the boundary, and need a coord reset.
LiSendTouchEvent(LI_TOUCH_EVENT_UP, pointerId, normalizedX, normalizedY, 0, 0, 0, 0); //event must sent from the lowest level directy by LiSendTouchEvent to simulate continous dragging to another point on screen
LiSendTouchEvent(LI_TOUCH_EVENT_DOWN, pointerId, 0.3, 0.4, 0, 0, 0, 0);
}else LiSendTouchEvent(touchType, pointerId, normalizedX, normalizedY,(touch.force / touch.maximumPossibleForce) / sin(touch.altitudeAngle),0.0f, 0.0f,[streamView getRotationFromAzimuthAngle:[touch azimuthAngleInView:streamView]]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self handleTouchDown:touch]; //generate & populate pointerId
if(activateCoordSelector) [NativeTouchPointer populatePointerObjIntoDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// NSLog(@"captured by OSB touches: %d", (uint32_t)[OnScreenControls.touchAddrsCapturedByOnScreenControls count]);
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
if(activateCoordSelector) [NativeTouchPointer updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[NativeTouchPointer getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
}
return;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(activateCoordSelector) [NativeTouchPointer removePointerObjFromDict:touch];
}
if(touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) touchPointSpawnedAtUpperScreenEdge = false;
return;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
@end
-37
View File
@@ -1,37 +0,0 @@
//
// NativeTouchPointer.h
// Moonlight
//
// Created by ZWM on 2024/5/14.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import "StreamView.h"
#import "NativeTouchHandler.h"
NS_ASSUME_NONNULL_BEGIN
@interface NativeTouchPointer : NSObject
@property (nonatomic, assign) bool needResetCoords;
+ (void)initContextWithView:(StreamView *)view;
+ (void)setPointerVelocityDivider:(CGFloat)dividerLocation;
+ (void)setPointerVelocityFactor:(CGFloat)velocityFactor;
+ (NativeTouchPointer* )getPointerObjFromDict:(UITouch*)touch;
- (bool)doesNeedResetCoords;
+ (void)populatePointerObjIntoDict:(UITouch*)touch;
+ (void)removePointerObjFromDict:(UITouch*)touch;
+ (void)updatePointerObjInDict:(UITouch *)touch;
+ (CGPoint)selectCoordsFor:(UITouch *)touch;
- (instancetype)initWithTouch:(UITouch *)touch;
@end
NS_ASSUME_NONNULL_END
-125
View File
@@ -1,125 +0,0 @@
//
// NativeTouchPointer.m
// Moonlight
//
// Created by ZWM on 2024/5/14.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "NativeTouchPointer.h"
#include <Limelight.h>
// native touch pointer ojbect that stores & manipulates touch coordinates
static NSMutableDictionary *pointerObjDict;
static CGFloat pointerVelocityFactor;
static CGFloat pointerVelocityDivider;
static CGFloat pointerVelocityDividerLocationByPoints;
static CGFloat streamViewHeight;
static CGFloat streamViewWidth;
static CGFloat fixedResetCoordX;
static CGFloat fixedResetCoordY;
static StreamView* _streamView;
@implementation NativeTouchPointer{
CGPoint initialPoint;
CGPoint latestPoint;
CGPoint previousPoint;
CGPoint latestRelativePoint;
CGPoint previousRelativePoint;
bool useRelativeCoords;
}
- (instancetype)initWithTouch:(UITouch *)touch{
self = [super init];
self->initialPoint = [touch locationInView:_streamView];
self->latestPoint = self->initialPoint;
self->latestRelativePoint = self->initialPoint;
return self;
}
- (bool)doesNeedResetCoords{
bool boundaryReached = (latestRelativePoint.x > streamViewWidth || latestRelativePoint.x < 0.0f || latestRelativePoint.y > streamViewHeight || latestRelativePoint.y < 0.0f);
bool withinExcludedArea = (initialPoint.x > streamViewWidth * (0.75) && initialPoint.x < streamViewWidth) && (initialPoint.y > streamViewHeight * (0.8) && initialPoint.y < streamViewHeight);
_needResetCoords = (pointerVelocityFactor > 1.0f) && self->useRelativeCoords && boundaryReached && !withinExcludedArea;
// boundary detection & coordinates reset to the specific point for HK:StarTrail(needs a very high pointer velocity)
// must exclude touch pointer that uses native coords instead of relative ones.
// also exclude touch pointer created within the bottom right corner
return _needResetCoords;
}
- (void)updatePointerCoords:(UITouch *)touch{
previousPoint = latestPoint;
latestPoint = [touch locationInView:_streamView];
previousRelativePoint = latestRelativePoint;
if(_needResetCoords){// boundary detection & coordinates reset to the central screen point for HK:StarTrail(needs a very high pointer velocity); // boundary detection & coordinates reset to specific point for HK:StarTrail(needs a very high pointer velocity)
previousRelativePoint.x = fixedResetCoordX;
previousRelativePoint.y = fixedResetCoordY;
}
latestRelativePoint.x = previousRelativePoint.x + pointerVelocityFactor * (latestPoint.x - previousPoint.x);
latestRelativePoint.y = previousRelativePoint.y + pointerVelocityFactor * (latestPoint.y - previousPoint.y);
}
+ (void)initContextWithView:(StreamView *)view {
_streamView = view;
streamViewWidth = _streamView.frame.size.width;
fixedResetCoordX = streamViewWidth * 0.3;
streamViewHeight = _streamView.frame.size.height;
fixedResetCoordY = streamViewHeight * 0.4;
NSLog(@"stream wdith %f, stream height %f", streamViewWidth, streamViewHeight);
pointerObjDict = [NSMutableDictionary dictionary];
pointerVelocityDividerLocationByPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]) * pointerVelocityDivider;
NSLog(@"pointerVelocityDivider: %.2f", pointerVelocityDivider);
NSLog(@"pointerVelocityFactor: %.2f", pointerVelocityFactor);
NSLog(@"pointerVelocityDividerLocationByPoints: %.2f", pointerVelocityDividerLocationByPoints);
}
+ (void)setPointerVelocityDivider:(CGFloat)dividerLocation{
pointerVelocityDivider = dividerLocation;
}
+ (void)setPointerVelocityFactor:(CGFloat)velocityFactor{
pointerVelocityFactor = velocityFactor;
}
+ (void)populatePointerObjIntoDict:(UITouch*)touch{
[pointerObjDict setObject:[[NativeTouchPointer alloc] initWithTouch:touch] forKey:@((uintptr_t)touch)];
}
+ (void)removePointerObjFromDict:(UITouch*)touch{
uintptr_t memAddrValue = (uintptr_t)touch;
NativeTouchPointer* pointer = [pointerObjDict objectForKey:@(memAddrValue)];
if(pointer != nil){
[pointerObjDict removeObjectForKey:@(memAddrValue)];
}
}
+ (NativeTouchPointer* )getPointerObjFromDict:(UITouch*)touch{
return [pointerObjDict objectForKey:@((uintptr_t)touch)];
}
+ (void)updatePointerObjInDict:(UITouch *)touch{
[[pointerObjDict objectForKey:@((uintptr_t)touch)] updatePointerCoords:touch];
}
+ (CGPoint)selectCoordsFor:(UITouch *)touch{
NativeTouchPointer *pointer = [pointerObjDict objectForKey:@((uintptr_t)touch)];
if(pointer == nil) return CGPointMake(0, 0);
if((pointer -> initialPoint).x > pointerVelocityDividerLocationByPoints){ //if first contact coords locates on the right side of divider.
pointer -> useRelativeCoords = true;
return pointer->latestRelativePoint;
}
else{
pointer -> useRelativeCoords = false;
return[touch locationInView:_streamView];
}
}
@end
File diff suppressed because it is too large Load Diff
-148
View File
@@ -1,148 +0,0 @@
//
// NativeTouchHandler.m
// Moonlight
//
// Created by ZWM on 2024/8/29.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "PureNativeTouchHandler.h"
#import "NativeTouchPointer.h"
#import "StreamView.h"
#include <Limelight.h>
@implementation PureNativeTouchHandler {
StreamView* streamView;
TemporarySettings* currentSettings;
bool activateCoordSelector;
bool touchPointSpawnedAtUpperScreenEdge;
// Use a Dictionary to store UITouch object's memory address as key, and pointerId as value,字典存放UITouch对象地址和pointerId映射关系
// pointerId will be generated from a pre-defined pool
// Use a NSSet store active pointerId,
NSMutableDictionary *pointerIdDict; //pointerId Dict for active touches.
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
NSMutableSet<NSNumber *> *unassignedPointerIds;
CGFloat slideGestureVerticalThreshold;
CGFloat screenWidthWithThreshold;
CGFloat EDGE_TOLERANCE;
}
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings{
self = [super init];
self->streamView = view;
self->currentSettings = settings;
self->activateCoordSelector = currentSettings.pointerVelocityModeDivider.floatValue != 1.0;
pointerIdDict = [NSMutableDictionary dictionary];
pointerIdPool = [NSMutableSet set];
for (uint8_t i = 0; i <= 10; i++) { //ipadOS supports upto 11 finger touches
[pointerIdPool addObject:@(i)];
}
activePointerIds = [NSMutableSet set];
self->touchPointSpawnedAtUpperScreenEdge = false;
EDGE_TOLERANCE = 15.0;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
[NativeTouchPointer setPointerVelocityDivider:settings.pointerVelocityModeDivider.floatValue];
[NativeTouchPointer setPointerVelocityFactor:settings.touchPointerVelocityFactor.floatValue];
[NativeTouchPointer initContextWithView:self->streamView];
return self;
}
// generate & populate pointerId into NSDict & NSSet, called in touchesBegan
- (void)handleTouchDown:(UITouch*)touch{
uintptr_t memAddrValue = (uintptr_t)touch;
unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds
[unassignedPointerIds minusSet:activePointerIds];
uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue];
[pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)];
[activePointerIds addObject:@(pointerId)];
//check if touch point is spawned on the left or right upper half screen edges,
CGPoint initialPoint = [touch locationInView:self->streamView];
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
self->touchPointSpawnedAtUpperScreenEdge = true;
}
}
// remove pointerId in touchesEnded or touchesCancelled
- (void)removePointerId:(UITouch*)touch{
uintptr_t memAddrValue = (uintptr_t)touch;
NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)];
if(pointerIdObj != nil){
[activePointerIds removeObject:pointerIdObj];
[pointerIdDict removeObjectForKey:@(memAddrValue)];
}
}
// 从字典中获取UITouch事件对应的pointerId
// called in method of sendTouchEvent
- (uint8_t) retrievePointerIdFromDict:(UITouch*)touch{
return [[pointerIdDict objectForKey:@((uintptr_t)touch)] unsignedIntValue];
}
- (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash!
CGPoint targetCoords;
if(activateCoordSelector && touch.phase == UITouchPhaseMoved) targetCoords = [NativeTouchPointer selectCoordsFor:touch]; // coordinates of touch pointer replaced to relative ones here.
else targetCoords = [touch locationInView:streamView];
CGPoint location = [streamView adjustCoordinatesForVideoArea:targetCoords];
CGSize videoSize = [streamView getVideoAreaSize];
CGFloat normalizedX = location.x / videoSize.width;
CGFloat normalizedY = location.y / videoSize.height;
uint8_t pointerId = [self retrievePointerIdFromDict:touch];
if([NativeTouchPointer getPointerObjFromDict:touch].needResetCoords){ // access whether the current pointer has reached the boundary, and need a coord reset.
LiSendTouchEvent(LI_TOUCH_EVENT_UP, pointerId, normalizedX, normalizedY, 0, 0, 0, 0); //event must sent from the lowest level directy by LiSendTouchEvent to simulate continous dragging to another point on screen
LiSendTouchEvent(LI_TOUCH_EVENT_DOWN, pointerId, 0.3, 0.4, 0, 0, 0, 0);
}else LiSendTouchEvent(touchType, pointerId, normalizedX, normalizedY,(touch.force / touch.maximumPossibleForce) / sin(touch.altitudeAngle),0.0f, 0.0f,[streamView getRotationFromAzimuthAngle:[touch azimuthAngleInView:streamView]]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch* touch in touches){
[self handleTouchDown:touch]; //generate & populate pointerId
if(activateCoordSelector) [NativeTouchPointer populatePointerObjIntoDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch* touch in touches){
if(activateCoordSelector) [NativeTouchPointer updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[NativeTouchPointer getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
}
return;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch* touch in touches){
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(activateCoordSelector) [NativeTouchPointer removePointerObjFromDict:touch];
}
if(touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) touchPointSpawnedAtUpperScreenEdge = false;
return;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
@end
@@ -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,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="23H420" 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="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="deviceGyroMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="enableYUV444" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="externalDisplayMode" optional="YES" attributeType="Integer 16" defaultValueString="0" 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="keyboardToggleFingers" optional="YES" attributeType="Integer 16" defaultValueString="3" usesScalarValueType="NO" syncable="YES"/>
<attribute name="largerStickLR1" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="liftStreamViewForKeyboard" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="localMousePointerMode" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="mousePointerVelocityFactor" optional="YES" attributeType="Float" defaultValueString="1" 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="oscLayoutToolFingers" optional="YES" attributeType="Integer 16" defaultValueString="4" usesScalarValueType="NO" syncable="YES"/>
<attribute name="oscTapExlusionAreaSize" optional="YES" attributeType="Float" defaultValueString="1.03" usesScalarValueType="NO" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="pointerVelocityModeDivider" optional="YES" attributeType="Float" defaultValueString="0.5" usesScalarValueType="NO" syncable="YES"/>
<attribute name="preferredCodec" attributeType="Integer 32" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="resolutionSelected" optional="YES" attributeType="Integer 16" defaultValueString="5" usesScalarValueType="NO" syncable="YES"/>
<attribute name="reverseMouseWheelDirection" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="showKeyboardToolbar" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="slideToSettingsDistance" optional="YES" attributeType="Float" defaultValueString="0.5" usesScalarValueType="NO" syncable="YES"/>
<attribute name="slideToSettingsScreenEdge" optional="YES" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
<attribute name="statsOverlayEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="statsOverlayLevel" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="touchMode" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="touchPointerVelocityFactor" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="unlockDisplayOrientation" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
</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,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" 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="statsOverlayEnabled" 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>
</model>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" 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="statsOverlayEnabled" 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>
</model>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" 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="statsOverlayEnabled" 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>
</model>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" 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="statsOverlayEnabled" 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>
</model>
File diff suppressed because it is too large Load Diff
-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
-689
View File
@@ -1,689 +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];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reinitializeDisplayLayer)
name:@"ScreenChanged"
object:nil];
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
-240
View File
@@ -1,240 +0,0 @@
//
// UIComputerView.m
// Moonlight
//
// Created by Diego Waxemberg on 10/22/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "UIComputerView.h"
#import "LocalizationHelper.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:[LocalizationHelper localizedStringForKey:@"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,51 +0,0 @@
//
// LayoutOnScreenControlsViewController.h
// Moonlight
//
// Created by Long Le on 9/27/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "LayoutOnScreenControls.h"
#import "ToolBarContainerView.h"
#import "OSCProfilesManager.h"
#import "OSCProfilesTableViewController.h"
#import "Moonlight-Swift.h"
NS_ASSUME_NONNULL_BEGIN
/**
This view controller provides the user interface which allows the user to position on screen controller buttons anywhere they'd like on the screen. It also provides the user with the abilities to undo a change, save the on screen controller layout for later retrieval, and load previously saved controller layouts
*/
@interface LayoutOnScreenControlsViewController : UIViewController
- (void)profileRefresh;
- (void)reloadOnScreenWidgetViews;
@property LayoutOnScreenControls *layoutOSC; // object that contains a view which contains the on screen controller buttons that allows the user to drag and positions each button on the screen using touch
@property (nonatomic) NSMutableSet* OnScreenWidgetViews;
@property int OSCSegmentSelected;
@property (weak, nonatomic) IBOutlet UIButton *trashCanButton;
@property (weak, nonatomic) IBOutlet UIButton *undoButton;
@property (weak, nonatomic) IBOutlet ToolBarContainerView *toolbarRootView;
@property (weak, nonatomic) IBOutlet UIView *chevronView;
@property (weak, nonatomic) IBOutlet UIImageView *chevronImageView;
@property (weak, nonatomic) IBOutlet UIStackView *toolbarStackView;
@property (strong, nonatomic) OSCProfilesTableViewController *oscProfilesTableViewController;
@property (nonatomic, assign) NSString *currentProfileName;
@property (strong, nonatomic) IBOutlet UILabel *currentProfileLabel;
@property (strong, nonatomic) IBOutlet UISlider *widgetSizeSlider;
@property (strong, nonatomic) IBOutlet UISlider *widgetHeightSlider;
@property (strong, nonatomic) IBOutlet UISlider *widgetAlphaSlider;
@property (strong, nonatomic) IBOutlet UISlider *sensitivityFactorSlider;
@property (strong, nonatomic) IBOutlet UISlider *stickIndicatorOffsetSlider;
@property (strong, nonatomic) IBOutlet UISlider *widgetBorderWidthSlider;
@end
NS_ASSUME_NONNULL_END
@@ -1,878 +0,0 @@
//
// LayoutOnScreenControlsViewController.m
// Moonlight
//
// Created by Long Le on 9/27/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import "LayoutOnScreenControlsViewController.h"
#import "OSCProfilesTableViewController.h"
#import "OnScreenButtonState.h"
//#import "OnScreenControls.h"
#import "OSCProfilesManager.h"
#import "LocalizationHelper.h"
#import "Moonlight-Swift.h"
@interface LayoutOnScreenControlsViewController ()
@end
@implementation LayoutOnScreenControlsViewController {
BOOL isToolbarHidden;
OSCProfilesManager* profilesManager;
OnScreenWidgetView* selectedWidgetView;
CALayer* selectedControllerLayer;
CGRect controllerLoadedBounds;
bool widgetViewSelected;
bool controllerLayerSelected;
__weak IBOutlet NSLayoutConstraint *toolbarTopConstraintiPhone;
__weak IBOutlet NSLayoutConstraint *toolbarTopConstraintiPad;
UILabel *widgetSizeSliderLabel;
UILabel *widgetHeightSliderLabel;
UILabel *widgetAlphaSliderLabel;
UILabel *widgetBorderWidthSliderLabel;
UILabel *sensitivitySliderLabel;
UILabel *stickIndicatorOffsetSliderLabel;
UILabel *stickIndicatorOffsetExplain;
}
@synthesize trashCanButton;
@synthesize undoButton;
@synthesize OSCSegmentSelected;
@synthesize toolbarRootView;
@synthesize chevronView;
@synthesize chevronImageView;
- (UIInterfaceOrientationMask)getCurrentOrientation{
CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]);
CGFloat screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]);
//lock the orientation accordingly after streaming is started
if(screenWidthInPoints > screenHeightInPoints) return UIInterfaceOrientationMaskLandscape;
else return UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
// Return the supported interface orientations acoordingly
return [self getCurrentOrientation]; // 90 Degree rotation not allowed in streaming or app view
}
- (void) viewWillDisappear:(BOOL)animated{
OnScreenWidgetView.editMode = false;
for (OnScreenWidgetView* widgetView in self.OnScreenWidgetViews){
[widgetView.stickBallLayer removeFromSuperlayer];
[widgetView.crossMarkLayer removeFromSuperlayer];
}
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:@"OscLayoutCloseNotification" object:self];
}
- (void) reloadOnScreenWidgetViews {
for (UIView *subview in self.view.subviews) {
// 检查子视图是否是特定类型的实例
if ([subview isKindOfClass:[OnScreenWidgetView class]]) {
// 如果是,就添加到将要被移除的数组中
[subview removeFromSuperview];
}
}
[self.OnScreenWidgetViews removeAllObjects];
NSLog(@"reload os Key here");
// _activeCustomOscButtonPositionDict will be updated every time when the osc profile is reloaded
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
OnScreenButtonState* buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
if(buttonState.buttonType == CustomOnScreenWidget){
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithKeyString:buttonState.name keyLabel:buttonState.alias]; //reconstruct widgetView
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.timestamp = buttonState.timestamp; // will be set as key in in the dict.
widgetView.widthFactor = buttonState.widthFactor;
widgetView.heightFactor = buttonState.heightFactor;
widgetView.borderWidth = buttonState.borderWidth;
widgetView.sensitivityFactor = buttonState.sensitivityFactor;
widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset;
// widgetView.backgroundAlpha = buttonState.backgroundAlpha;
// Add the widgetView to the view controller's view
[self.view addSubview:widgetView];
[widgetView setLocationWithXOffset:buttonState.position.x yOffset:buttonState.position.y];
[widgetView resizeWidgetView]; // resize must be called after relocation
[widgetView adjustTransparencyWithAlpha:buttonState.backgroundAlpha];
[widgetView adjustBorderWithWidth:buttonState.borderWidth];
[self.OnScreenWidgetViews addObject:widgetView];
}
}
}
- (void) viewDidLoad {
[super viewDidLoad];
profilesManager = [OSCProfilesManager sharedManager];
self.OnScreenWidgetViews = [[NSMutableSet alloc] init]; // will be revised to read persisted data , somewhere else
[OSCProfilesManager setOnScreenWidgetViewsSet:self.OnScreenWidgetViews]; // pass the keyboard button dict to profiles manager
isToolbarHidden = NO; // keeps track if the toolbar is hidden up above the screen so that we know whether to hide or show it when the user taps the toolbar's hide/show button
widgetSizeSliderLabel = [[UILabel alloc] init];
widgetHeightSliderLabel = [[UILabel alloc] init];
widgetAlphaSliderLabel = [[UILabel alloc] init];
widgetBorderWidthSliderLabel = [[UILabel alloc] init];
sensitivitySliderLabel = [[UILabel alloc] init];
stickIndicatorOffsetSliderLabel = [[UILabel alloc] init];
stickIndicatorOffsetExplain = [[UILabel alloc] init];
/* add curve to bottom of chevron tab view */
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.chevronView.bounds byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
self.chevronView.layer.mask = maskLayer;
/* Add swipe gesture to toolbar to allow user to swipe it up and off screen */
UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(moveToolbar:)];
swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
[self.toolbarRootView addGestureRecognizer:swipeUp];
/* Add tap gesture to toolbar's chevron to allow user to tap it in order to move the toolbar on and off screen */
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(moveToolbar:)];
[self.chevronView addGestureRecognizer:singleFingerTap];
self.layoutOSC = [[LayoutOnScreenControls alloc] initWithView:self.view controllerSup:nil streamConfig:nil oscLevel:OSCSegmentSelected];
self.layoutOSC._level = OnScreenControlsLevelCustom;
self.layoutOSC.layoutToolVC = self;
[self.layoutOSC show]; // draw on screen controls
[self addInnerAnalogSticksToOuterAnalogLayers]; // allows inner and analog sticks to be dragged together around the screen together as one unit which is the expected behavior
self.undoButton.alpha = 0.3; // no changes to undo yet, so fade out the undo button a bit
if ([[profilesManager getAllProfiles] count] == 0) { // if no saved OSC profiles exist yet then create one called 'Default' and associate it with Moonlight's legacy 'Full' OSC layout that's already been laid out on the screen at this point
[profilesManager saveProfileWithName:@"Default" andButtonLayers:self.layoutOSC.OSCButtonLayers];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearSelectedWidgetView)
name:@"LegacyOscCALayerSelectedNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(profileRefresh)
name:@"OscLayoutTableViewCloseNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reloadOnScreenWidgetViews)
name:@"OscLayoutProfileSelctedInTableView" // This is a special notification for reloading the on screen keyboard buttons. which can't be executed by _oscProfilesTableViewController.needToUpdateOscLayoutTVC code block, and has to be triggered by a notification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(widgetViewTapped:)
name:@"OnScreenWidgetViewSelected"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(setControllerCALayerSliderValues:)
name:@"ControllerCALayerSelected"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OSCLayoutChanged) name:@"OSCLayoutChanged" object:nil]; // used to notifiy this view controller that the user made a change to the OSC layout so that the VC can either fade in or out its 'Undo button' which will signify to the user whether there are any OSC layout changes to undo
/* This will animate the toolbar with a subtle up and down motion intended to telegraph to the user that they can hide the toolbar if they wish*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3
delay:0.25
usingSpringWithDamping:0.8
initialSpringVelocity:0.5
options:UIViewAnimationOptionCurveEaseInOut animations:^{ // Animate toolbar up a a very small distance. Note the 0.35 time delay is necessary to avoid a bug that keeps animations from playing if the animation is presented immediately on a modally presented VC
self.toolbarRootView.frame = CGRectMake(self.toolbarRootView.frame.origin.x, self.toolbarRootView.frame.origin.y - 25, self.toolbarRootView.frame.size.width, self.toolbarRootView.frame.size.height);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.3
delay:0
usingSpringWithDamping:0.7
initialSpringVelocity:1.0
options:UIViewAnimationOptionCurveEaseIn animations:^{ // Animate the toolbar back down that same distance
self.toolbarRootView.frame = CGRectMake(self.toolbarRootView.frame.origin.x, self.toolbarRootView.frame.origin.y + 25, self.toolbarRootView.frame.size.width, self.toolbarRootView.frame.size.height);
}
completion:^(BOOL finished) {
NSLog (@"done");
}];
}];
});
[self profileRefresh];
}
- (void) viewDidAppear:(BOOL)animated {
OnScreenWidgetView.editMode = true;
[super viewWillAppear:animated];
[self profileRefresh];
}
#pragma mark - Class Helper Functions
/* fades the 'Undo Button' in or out depending on whether the user has any OSC layout changes to undo */
- (void) OSCLayoutChanged {
if ([self.layoutOSC.layoutChanges count] > 0) {
self.undoButton.alpha = 1.0;
}
else {
self.undoButton.alpha = 0.3;
}
}
/* animates the toolbar up and off the screen or back down onto the screen */
- (void) moveToolbar:(UISwipeGestureRecognizer *)sender {
BOOL isPad = [[UIDevice currentDevice].model hasPrefix:@"iPad"];
NSLayoutConstraint *toolbarTopConstraint = isPad ? self->toolbarTopConstraintiPad : self->toolbarTopConstraintiPhone;
if (isToolbarHidden == NO) {
[UIView animateWithDuration:0.2 animations:^{ // animates toolbar up and off screen
toolbarTopConstraint.constant -= self.toolbarRootView.frame.size.height;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
if (finished) {
self->isToolbarHidden = YES;
self.chevronImageView.image = [UIImage imageNamed:@"ChevronCompactDown"];
}
}];
}
else {
[UIView animateWithDuration:0.2 animations:^{ // animates the toolbar back down into the screen
toolbarTopConstraint.constant += self.toolbarRootView.frame.size.height;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
if (finished) {
self->isToolbarHidden = NO;
self.chevronImageView.image = [UIImage imageNamed:@"ChevronCompactUp"];
}
}];
}
}
/**
* Makes the inner analog stick layers a child layer of its corresponding outer analog stick layers so that both the inner and its corresponding outer layers move together when the user drags them around the screen as is the expected behavior when laying out OSC. Note that this is NOT expected behavior on the game stream view where the inner analog sticks move to follow toward the user's touch and their corresponding outer analog stick layers do not move
*/
- (void)addInnerAnalogSticksToOuterAnalogLayers {
// right stick
[self.layoutOSC._rightStickBackground addSublayer: self.layoutOSC._rightStick];
self.layoutOSC._rightStick.position = CGPointMake(self.layoutOSC._rightStickBackground.frame.size.width / 2, self.layoutOSC._rightStickBackground.frame.size.height / 2);
// left stick
[self.layoutOSC._leftStickBackground addSublayer: self.layoutOSC._leftStick];
self.layoutOSC._leftStick.position = CGPointMake(self.layoutOSC._leftStickBackground.frame.size.width / 2, self.layoutOSC._leftStickBackground.frame.size.height / 2);
}
#pragma mark - UIButton Actions
- (IBAction) closeTapped:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction) trashCanTapped:(id)sender {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Delete Buttons Here"] message:[LocalizationHelper localizedStringForKey:@"Drag and drop buttons onto this trash can to remove them from the interface"] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
- (IBAction) undoTapped:(id)sender {
if ([self.layoutOSC.layoutChanges count] > 0) { // check if there are layout changes to roll back to
OnScreenButtonState *buttonState = [self.layoutOSC.layoutChanges lastObject]; // Get the 'OnScreenButtonState' object that contains the name, position, and visiblity state of the button the user last moved
CALayer *buttonLayer = [self.layoutOSC controllerLayerFromName:buttonState.name]; // get the on screen button layer that corresponds with the 'OnScreenButtonState' object that we retrieved above
/* Set the button's position and visiblity to what it was before the user last moved it */
buttonLayer.position = buttonState.position;
buttonLayer.hidden = buttonState.isHidden;
/* if user is showing or hiding dPad, then show or hide all four dPad button child layers as well since setting the 'hidden' property on the parent CALayer is not automatically setting the individual dPad child CALayers */
if ([buttonLayer.name isEqualToString:@"dPad"]) {
self.layoutOSC._upButton.hidden = buttonState.isHidden;
self.layoutOSC._rightButton.hidden = buttonState.isHidden;
self.layoutOSC._downButton.hidden = buttonState.isHidden;
self.layoutOSC._leftButton.hidden = buttonState.isHidden;
}
/* if user is showing or hiding the left or right analog sticks, then show or hide their corresponding inner analog stick child layers as well since setting the 'hidden' property on the parent analog stick doesn't automatically hide its child inner analog stick CALayer */
if ([buttonLayer.name isEqualToString:@"leftStickBackground"]) {
self.layoutOSC._leftStick.hidden = buttonState.isHidden;
}
if ([buttonLayer.name isEqualToString:@"rightStickBackground"]) {
self.layoutOSC._rightStick.hidden = buttonState.isHidden;
}
[self.layoutOSC.layoutChanges removeLastObject];
[self OSCLayoutChanged]; // will fade the undo button in or out depending on whether there are any further changes to undo
}
else { // there are no changes to undo. let user know there are no changes to undo
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Nothing to Undo"] message: [LocalizationHelper localizedStringForKey: @"There are no changes to undo"] preferredStyle:UIAlertControllerStyleAlert];
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[savedAlertController dismissViewControllerAnimated:NO completion:nil];
}]];
[self presentViewController:savedAlertController animated:YES completion:nil];
}
}
- (IBAction) addTapped:(id)sender{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"New On-Screen Widget"]
message:[LocalizationHelper localizedStringForKey:@"Enter the command & alias label"]
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = [LocalizationHelper localizedStringForKey:@"Command"];
textField.keyboardType = UIKeyboardTypeASCIICapable;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
textField.spellCheckingType = UITextSpellCheckingTypeNo;
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = [LocalizationHelper localizedStringForKey:@"Alias label (optional)"];
textField.keyboardType = UIKeyboardTypeASCIICapable;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
textField.spellCheckingType = UITextSpellCheckingTypeNo;
}];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"]
style:UIAlertActionStyleCancel
handler:nil];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
/*
alertController.textFields[0].keyboardType = UIKeyboardTypeASCIICapable;
alertController.textFields[0].autocorrectionType = UITextAutocorrectionTypeNo;
alertController.textFields[0].spellCheckingType = UITextSpellCheckingTypeNo;
alertController.textFields[1].keyboardType = UIKeyboardTypeASCIICapable;
alertController.textFields[1].autocorrectionType = UITextAutocorrectionTypeNo;
alertController.textFields[1].spellCheckingType = UITextSpellCheckingTypeNo;*/
NSString *cmdString = [alertController.textFields[0].text uppercaseString]; // convert to uppercase
NSString *keyLabel = alertController.textFields[1].text;
if([keyLabel isEqualToString:@""]) keyLabel = [[cmdString lowercaseString] capitalizedString];
bool noValidKeyboardString = [CommandManager.shared extractKeyStringsFromComboCommandFrom:cmdString] == nil; // this is a invalid string.
bool noValidSuperComboButtonString = [CommandManager.shared extractKeyStringsFromComboKeysFrom:cmdString] == nil; // this is a invalid string.
bool noValidMouseButtonString = ![CommandManager.mouseButtonMappings.allKeys containsObject:cmdString];
bool noValidTouchPadString = ![CommandManager.touchPadCmds containsObject:cmdString];
bool noValidOscButtonString = ![CommandManager.oscButtonMappings.allKeys containsObject:cmdString];
bool noValidSpecialButtonString = ![CommandManager.specialOverlayButtonCmds containsObject:cmdString];
if(noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidSpecialButtonString && noValidSuperComboButtonString) return;
//saving & present the keyboard button:
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithKeyString:cmdString keyLabel:keyLabel];
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.timestamp = CACurrentMediaTime(); // will be set as key in in the dict.
[self.OnScreenWidgetViews addObject:widgetView];
// Add the widgetView to the view controller's view
[self.view addSubview:widgetView];
[widgetView setLocationWithXOffset:50 yOffset:50];
[widgetView resizeWidgetView];
}];
[alertController addAction:cancelAction];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
/* show pop up notification that lets users choose to save the current OSC layout configuration as a profile they can load when they want. User can also choose to cancel out of this pop up */
- (IBAction) saveTapped:(id)sender {
if([self->profilesManager updateSelectedProfile:self.layoutOSC.OSCButtonLayers]){
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Current profile updated successfully"] preferredStyle:UIAlertControllerStyleAlert];
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
}]];
[self presentViewController:savedAlertController animated:YES completion:nil];
}
else{
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Profile Default can not be overwritten"] preferredStyle:UIAlertControllerStyleAlert];
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self.oscProfilesTableViewController profileViewRefresh]; // execute this will reset layout in OSC tool!
}]];
[self presentViewController:savedAlertController animated:YES completion:nil];
}
}
- (void)clearSelectedWidgetView{
selectedWidgetView = nil;
}
- (void)widgetViewTapped: (NSNotification *)notification{
// receive the selected widgetView obj passed from the notification
OnScreenWidgetView* widgetView = (OnScreenWidgetView* )notification.object;
self->widgetViewSelected = true;
self->controllerLayerSelected = false;
self->selectedWidgetView = widgetView;
// setup slider values
[self.widgetSizeSlider setValue: self->selectedWidgetView.widthFactor];
[self.widgetHeightSlider setValue: self->selectedWidgetView.heightFactor];
[self.widgetAlphaSlider setValue: self->selectedWidgetView.backgroundAlpha];
[self.widgetBorderWidthSlider setValue:self->selectedWidgetView.borderWidth];
NSSet *stickAndMouseTouchpads = [NSSet setWithObjects:@"LSPAD", @"RSPAD", @"LSVPAD", @"RSVPAD", @"MOUSEPAD", nil];
NSSet *nonVectorStickPads = [NSSet setWithObjects:@"LSPAD", @"RSPAD", nil];
bool showSensitivityFactorSlider = [stickAndMouseTouchpads containsObject:self->selectedWidgetView.keyString];
bool showStickIndicatorOffsetSlider = [nonVectorStickPads containsObject:self->selectedWidgetView.keyString];
self.sensitivityFactorSlider.hidden = self->sensitivitySliderLabel.hidden = !showSensitivityFactorSlider;
self.stickIndicatorOffsetSlider.hidden = self->stickIndicatorOffsetExplain.hidden = self->stickIndicatorOffsetSliderLabel.hidden = !showStickIndicatorOffsetSlider;
if(showSensitivityFactorSlider){
[self.sensitivityFactorSlider setValue:self->selectedWidgetView.sensitivityFactor];
[sensitivitySliderLabel setText:[LocalizationHelper localizedStringForKey:@"Sensitivity: %.2f", self->selectedWidgetView.sensitivityFactor]];
}
if(showStickIndicatorOffsetSlider){
// illustrating the indicator offset,
[selectedWidgetView.stickBallLayer removeFromSuperlayer];
[selectedWidgetView.crossMarkLayer removeFromSuperlayer];
selectedWidgetView.touchBeganLocation = CGPointMake(CGRectGetWidth(selectedWidgetView.frame)/2, CGRectGetHeight(selectedWidgetView.frame)/4);
[selectedWidgetView showStickIndicator];// this will create the indicator CAShapeLayers
[self.stickIndicatorOffsetSlider setValue:self->selectedWidgetView.stickIndicatorOffset];
[stickIndicatorOffsetSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Indicator Offset: %.2f", self->selectedWidgetView.stickIndicatorOffset]];
[self->selectedWidgetView updateStickIndicator];
}
[widgetSizeSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Size: %.2f", self->selectedWidgetView.widthFactor]];
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", self->selectedWidgetView.heightFactor]];
[widgetAlphaSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Alpha: %.2f", self->selectedWidgetView.backgroundAlpha]];
[widgetBorderWidthSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Border Width: %.2f", self->selectedWidgetView.borderWidth]];
}
- (void)setControllerCALayerSliderValues: (NSNotification *)notification{
// receive the selected widgetView obj passed from the notification
CALayer* controllerLayer = (CALayer* )notification.object;
self->widgetViewSelected = false;
self->stickIndicatorOffsetExplain.hidden = true;
self->stickIndicatorOffsetSliderLabel.hidden = true;
self.stickIndicatorOffsetSlider.hidden = true;
self->sensitivitySliderLabel.hidden = true;
self.sensitivityFactorSlider.hidden = true;
self->controllerLayerSelected = true;
self->selectedControllerLayer = controllerLayer;
self->controllerLoadedBounds = controllerLayer.bounds;
// setup slider values
CGFloat sizeFactor = [OnScreenControls getControllerLayerSizeFactor:controllerLayer]; // calculated sizeFactor from loaded layer bounds.
[self.widgetSizeSlider setValue:sizeFactor];
[self.widgetHeightSlider setValue:sizeFactor];
CGFloat alpha = [self.layoutOSC getControllerLayerOpacity:controllerLayer];
[self.widgetAlphaSlider setValue:alpha];
[widgetSizeSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Size: %.2f", sizeFactor]];
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", sizeFactor]];
[widgetAlphaSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Alpha: %.2f", alpha]];
}
- (void)widgetSizeSliderMoved{
[widgetSizeSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Size: %.2f", self.widgetSizeSlider.value]];
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", self.widgetSizeSlider.value]]; // resizing the whole button
[self.widgetHeightSlider setValue: self.widgetSizeSlider.value];
if(self->selectedWidgetView != nil && self->widgetViewSelected){
self->selectedWidgetView.translatesAutoresizingMaskIntoConstraints = true; // this is mandatory to prevent unexpected key view location change
// when adjusting width, the widgetView height will be syncronized
self->selectedWidgetView.widthFactor = self->selectedWidgetView.heightFactor = self.widgetSizeSlider.value;
[self->selectedWidgetView resizeWidgetView];
}
if(self->selectedControllerLayer != nil && self->controllerLayerSelected){
[self.layoutOSC resizeControllerLayerWith:self->selectedControllerLayer and:self.widgetSizeSlider.value];
}
}
- (void)widgetHeightSliderMoved{
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", self.widgetHeightSlider.value]];
if(self->selectedWidgetView != nil && self->widgetViewSelected){
self->selectedWidgetView.translatesAutoresizingMaskIntoConstraints = true; // this is mandatory to prevent unexpected key view location change
if([CommandManager.oscButtonMappings.allKeys containsObject:selectedWidgetView.keyString] && ![CommandManager.oscRectangleButtonCmds containsObject:selectedWidgetView.keyString]) return; // don't change custom osc button height, except for dPad buttons which are in rectangle shape
self->selectedWidgetView.heightFactor = self.widgetHeightSlider.value;
[self->selectedWidgetView resizeWidgetView];
}
}
- (void)widgetAlphaSliderMoved{
[widgetAlphaSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Alpha: %.2f", self.widgetAlphaSlider.value]];
if(self->selectedWidgetView != nil && self->widgetViewSelected){
[self->selectedWidgetView adjustTransparencyWithAlpha:self.widgetAlphaSlider.value];
}
if(self->selectedControllerLayer != nil && self->controllerLayerSelected){
[self.layoutOSC adjustControllerLayerOpacityWith:self->selectedControllerLayer and:self.widgetAlphaSlider.value];
}
return;
}
- (void)widgetBorderWidthSliderMoved{
[widgetBorderWidthSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Border Width: %.2f", self.widgetBorderWidthSlider.value]];
if(self->selectedWidgetView != nil && self->widgetViewSelected){
[self->selectedWidgetView adjustBorderWithWidth:self.widgetBorderWidthSlider.value];
}
return;
}
- (void)sensitivitySliderMoved{
[sensitivitySliderLabel setText:[LocalizationHelper localizedStringForKey:@"Sensitivity: %.2f", self.sensitivityFactorSlider.value]];
if(self->selectedWidgetView != nil && self->widgetViewSelected) self->selectedWidgetView.sensitivityFactor = self.sensitivityFactorSlider.value;
return;
}
- (void)stickIndicatorOffsetSliderMoved{
[stickIndicatorOffsetSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Indicator Offset: %.2f", self.stickIndicatorOffsetSlider.value]];
if(self->selectedWidgetView != nil && self->widgetViewSelected){
self->selectedWidgetView.stickIndicatorOffset = self.stickIndicatorOffsetSlider.value;
[self->selectedWidgetView updateStickIndicator];
}
return;
}
- (void)showIndicatorOffset{
selectedWidgetView.touchBeganLocation = CGPointMake(CGRectGetWidth(selectedWidgetView.frame)/2, CGRectGetHeight(selectedWidgetView.frame)/4);
[selectedWidgetView showStickIndicator];
}
- (void)setupProfileLableAndSliders{
// self.currentProfileLabel.frame = CGRectMake(0, 0, 180, 35);
// CGFloat profileLabelXPosition = self.view.bounds.size.width - self.currentProfileLabel.frame.size.width - 20;
CGFloat sliderXPosition = (self.view.bounds.size.width - self.widgetSizeSlider.frame.size.width)/2 ;
CGFloat profileLabelXPosition = self.view.bounds.size.width/2 + (self.widgetSizeSlider.frame.size.width)/2 + 8;
// Set the label's frame with the calculated x-position
self.currentProfileLabel.frame = CGRectMake(profileLabelXPosition, self.currentProfileLabel.frame.origin.y, self.currentProfileLabel.frame.size.width, self.currentProfileLabel.frame.size.height);
self.currentProfileLabel.hidden = NO; // Show Current Profile display
if (@available(iOS 13.0, *)) {
self.currentProfileLabel.textAlignment = NSTextAlignmentNatural;
self.currentProfileLabel.numberOfLines = 2;
} else {
// Fallback on earlier versions
}
[self.currentProfileLabel setText:[LocalizationHelper localizedStringForKey:@"Profile: %@",[profilesManager getSelectedProfile].name]]; // display current profile name when profile is being refreshed.
// button size sliders
self.widgetSizeSlider.hidden = NO;
self.widgetSizeSlider.frame = CGRectMake(sliderXPosition, self.widgetSizeSlider.frame.origin.y, self.widgetSizeSlider.frame.size.width, self.widgetSizeSlider.frame.size.height);
[self.widgetSizeSlider addTarget:self action:@selector(widgetSizeSliderMoved) forControlEvents:(UIControlEventValueChanged)];
widgetSizeSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Widget Size"];
widgetSizeSliderLabel.font = [UIFont systemFontOfSize:18];
widgetSizeSliderLabel.textColor = [UIColor whiteColor];
widgetSizeSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
widgetSizeSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
// Add slider and label to the view
[self.view addSubview:widgetSizeSliderLabel];
// Use Auto Layout to position the label relative to the slider
[NSLayoutConstraint activateConstraints:@[
// Position the label to the left of the slider
[widgetSizeSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetSizeSlider.leadingAnchor constant:-10],
// Align vertically with the slider
[widgetSizeSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetSizeSlider.centerYAnchor]
]];
widgetSizeSliderLabel.hidden = NO;
// button height sliders
self.widgetHeightSlider.hidden = NO;
self.widgetHeightSlider.frame = CGRectMake(sliderXPosition, self.widgetHeightSlider.frame.origin.y, self.widgetHeightSlider.frame.size.width, self.widgetHeightSlider.frame.size.height);
[self.widgetHeightSlider addTarget:self action:@selector(widgetHeightSliderMoved) forControlEvents:(UIControlEventValueChanged)];
// button height label
widgetHeightSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Widget Height"];
widgetHeightSliderLabel.font = [UIFont systemFontOfSize:18];
widgetHeightSliderLabel.textColor = [UIColor whiteColor];
widgetHeightSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
widgetHeightSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
// Add slider and label to the view
[self.view addSubview:widgetHeightSliderLabel];
// Use Auto Layout to position the label relative to the slider
[NSLayoutConstraint activateConstraints:@[
// Position the label to the left of the slider
[widgetHeightSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetHeightSlider.leadingAnchor constant:-10],
// Align vertically with the slider
[widgetHeightSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetHeightSlider.centerYAnchor]
]];
widgetHeightSliderLabel.hidden = NO;
// button alpha label
self.widgetAlphaSlider.hidden = NO;
self.widgetAlphaSlider.frame = CGRectMake(sliderXPosition, self.widgetAlphaSlider.frame.origin.y, self.widgetAlphaSlider.frame.size.width, self.widgetAlphaSlider.frame.size.height);
[self.widgetAlphaSlider addTarget:self action:@selector(widgetAlphaSliderMoved) forControlEvents:(UIControlEventValueChanged)];
widgetAlphaSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Widget Alpha"];
widgetAlphaSliderLabel.font = [UIFont systemFontOfSize:18];
widgetAlphaSliderLabel.textColor = [UIColor whiteColor];
widgetAlphaSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
widgetAlphaSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
// Add slider and label to the view
[self.view addSubview:widgetAlphaSliderLabel];
// Use Auto Layout to position the label relative to the slider
[NSLayoutConstraint activateConstraints:@[
// Position the label to the left of the slider
[widgetAlphaSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetAlphaSlider.leadingAnchor constant:-10],
// Align vertically with the slider
[widgetAlphaSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetAlphaSlider.centerYAnchor]
]];
widgetAlphaSliderLabel.hidden = NO;
// border Width slider
self.widgetBorderWidthSlider.hidden = NO;
self.widgetBorderWidthSlider.frame = CGRectMake(CGRectGetMaxX(self.currentProfileLabel.frame)-self.widgetBorderWidthSlider.frame.size.width, self.widgetBorderWidthSlider.frame.origin.y, self.widgetBorderWidthSlider.frame.size.width, self.widgetBorderWidthSlider.frame.size.height);
[self.widgetBorderWidthSlider addTarget:self action:@selector(widgetBorderWidthSliderMoved) forControlEvents:(UIControlEventValueChanged)];
widgetBorderWidthSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Border Width"];
widgetBorderWidthSliderLabel.font = [UIFont systemFontOfSize:18];
widgetBorderWidthSliderLabel.textColor = [UIColor whiteColor];
widgetBorderWidthSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
widgetBorderWidthSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
[self.view addSubview:widgetBorderWidthSliderLabel];
[NSLayoutConstraint activateConstraints:@[
// Position the label to the left of the slider
[widgetBorderWidthSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetBorderWidthSlider.leadingAnchor constant:-10],
// Align vertically with the slider
[widgetBorderWidthSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetBorderWidthSlider.centerYAnchor]
]];
widgetBorderWidthSliderLabel.hidden = NO;
// sensitivity slider
self.sensitivityFactorSlider.hidden = YES;
self.sensitivityFactorSlider.frame = CGRectMake(sliderXPosition, self.sensitivityFactorSlider.frame.origin.y, self.sensitivityFactorSlider.frame.size.width, self.sensitivityFactorSlider.frame.size.height);
[self.sensitivityFactorSlider addTarget:self action:@selector(sensitivitySliderMoved) forControlEvents:(UIControlEventValueChanged)];
sensitivitySliderLabel.text = [LocalizationHelper localizedStringForKey:@"Sensitivity"];
sensitivitySliderLabel.font = [UIFont systemFontOfSize:18];
sensitivitySliderLabel.textColor = [UIColor whiteColor];
sensitivitySliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
sensitivitySliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
// Add slider and label to the view
[self.view addSubview:sensitivitySliderLabel];
// Use Auto Layout to position the label relative to the slider
[NSLayoutConstraint activateConstraints:@[
// Position the label to the left of the slider
[sensitivitySliderLabel.trailingAnchor constraintEqualToAnchor:self.sensitivityFactorSlider.leadingAnchor constant:-10],
// Align vertically with the slider
[sensitivitySliderLabel.centerYAnchor constraintEqualToAnchor:self.sensitivityFactorSlider.centerYAnchor]
]];
sensitivitySliderLabel.hidden = self.sensitivityFactorSlider.hidden;
// stick indicator offset slider
self.stickIndicatorOffsetSlider.hidden = YES;
self.stickIndicatorOffsetSlider.frame = CGRectMake(sliderXPosition, self.stickIndicatorOffsetSlider.frame.origin.y, self.stickIndicatorOffsetSlider.frame.size.width, self.stickIndicatorOffsetSlider.frame.size.height); // make this label bigger to show some tips
[self.stickIndicatorOffsetSlider addTarget:self action:@selector(stickIndicatorOffsetSliderMoved) forControlEvents:(UIControlEventValueChanged)];
stickIndicatorOffsetSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Indicator Offset"];
stickIndicatorOffsetSliderLabel.font = [UIFont systemFontOfSize:18];
stickIndicatorOffsetSliderLabel.textColor = [UIColor whiteColor];
stickIndicatorOffsetSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
stickIndicatorOffsetSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
// tips for stick indicator
stickIndicatorOffsetExplain.text = [LocalizationHelper localizedStringForKey:@"Ball: assumed touch point. Cross: indicator location"];
//stickIndicatorOffsetExplain.frame = CGRectMake(sliderXPosition, self.stickIndicatorOffsetSlider.frame.origin.y + self.stickIndicatorOffsetSlider.frame.size.height, self.stickIndicatorOffsetSlider.frame.size.width, self.stickIndicatorOffsetSlider.frame.size.height); // label bigger show some tips
stickIndicatorOffsetExplain.font = [UIFont systemFontOfSize:18];
stickIndicatorOffsetExplain.textColor = [UIColor whiteColor];
stickIndicatorOffsetExplain.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
stickIndicatorOffsetExplain.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
// Add slider and label to the view
[self.view addSubview:stickIndicatorOffsetSliderLabel];
[self.view addSubview:stickIndicatorOffsetExplain];
// Use Auto Layout to position the label relative to the slider
[NSLayoutConstraint activateConstraints:@[
// Position the label to the left of the slider
[stickIndicatorOffsetSliderLabel.trailingAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.leadingAnchor constant:-10],
// Align vertically with the slider
[stickIndicatorOffsetSliderLabel.centerYAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.centerYAnchor],
// Position the explain label
[stickIndicatorOffsetExplain.centerXAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.leadingAnchor constant:-10],
[stickIndicatorOffsetExplain.topAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.bottomAnchor]
]];
stickIndicatorOffsetSliderLabel.hidden = stickIndicatorOffsetExplain.hidden = self.stickIndicatorOffsetSlider.hidden;
}
/* Basically the same method as loadTapped, without parameter*/
// Make sure whenever self view controller load the selected profile and layout its buttons.
- (void)profileRefresh{
UIStoryboard *storyboard;
BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
if (isIPhone) {
storyboard = [UIStoryboard storyboardWithName:@"iPhone" bundle:nil];
}
else {
storyboard = [UIStoryboard storyboardWithName:@"iPad" bundle:nil];
}
// setup: current profile lable, button width slider, button height slider & button alpha slider
[self setupProfileLableAndSliders];
//initialiaze _oscProfilesTableViewController
self->_oscProfilesTableViewController = [storyboard instantiateViewControllerWithIdentifier:@"OSCProfilesTableViewController"];
//this part is just for registration, will not be immediately executed.
self->_oscProfilesTableViewController.needToUpdateOscLayoutTVC = ^() { // a block that will be called when the modally presented 'OSCProfilesTableViewController' VC is dismissed. By the time the 'OSCProfilesTableViewController' VC is dismissed the user would have potentially selected a different OSC profile with a different layout and they want to see this layout on this 'LayoutOnScreenControlsViewController.' This block of code will load the profile and then hide/show and move each OSC button to their appropriate position
[self.layoutOSC updateControls]; // creates and saves a 'Default' OSC profile or loads the one the user selected on the previous screen
[self addInnerAnalogSticksToOuterAnalogLayers];
[self.layoutOSC.layoutChanges removeAllObjects]; // since a new OSC profile is being loaded, this will remove all previous layout changes made from the array
[self OSCLayoutChanged]; // fades the 'Undo Button' out
self->_oscProfilesTableViewController.currentOSCButtonLayers = self.layoutOSC.OSCButtonLayers; //pass updated OSCLayout to OSCProfileTableView again
//[self reloadOnScreenKeyboardButtons];
};
[self.oscProfilesTableViewController profileViewRefresh]; // execute this will make sure OSCLayout is updated from persisted profile, not any cache.
[self reloadOnScreenWidgetViews];
// [self presentViewController:vc animated:YES completion:nil];
}
/* Presents the view controller that lists all OSC profiles the user can choose from */
- (IBAction) loadTapped:(id)sender {
UIStoryboard *storyboard;
BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
if (isIPhone) {
storyboard = [UIStoryboard storyboardWithName:@"iPhone" bundle:nil];
}
else {
storyboard = [UIStoryboard storyboardWithName:@"iPad" bundle:nil];
}
_oscProfilesTableViewController = [storyboard instantiateViewControllerWithIdentifier:@"OSCProfilesTableViewController"] ;
_oscProfilesTableViewController.needToUpdateOscLayoutTVC = ^() { // a block that will be called when the modally presented 'OSCProfilesTableViewController' VC is dismissed. By the time the 'OSCProfilesTableViewController' VC is dismissed the user would have potentially selected a different OSC ofile with a different layout and they want to see this layout on this 'LayoutOnScreenControlsViewController.' This block of code will load the profile and then hide/show and move each OSC button to their appropriate position
[self.layoutOSC updateControls]; // creates and saves a 'Default' OSC profile or loads the one the user selected on the previous screen
[self addInnerAnalogSticksToOuterAnalogLayers];
[self.layoutOSC.layoutChanges removeAllObjects]; // since a new OSC profile is being loaded, this will remove all previous layout changes made from the array
[self OSCLayoutChanged]; // fades the 'Undo Button' out
};
self.currentProfileLabel.hidden = YES; // Hide Current Profile display before entering the profile table view
self.widgetSizeSlider.hidden = YES;
self.widgetHeightSlider.hidden = YES;
self.widgetAlphaSlider.hidden = YES;
widgetSizeSliderLabel.hidden = YES;
widgetHeightSliderLabel.hidden = YES;
widgetAlphaSliderLabel.hidden = YES;
_oscProfilesTableViewController.currentOSCButtonLayers = self.layoutOSC.OSCButtonLayers;
[self presentViewController:_oscProfilesTableViewController animated:YES completion:nil];
}
#pragma mark - Touch
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch* touch in touches) {
CGPoint touchLocation = [touch locationInView:self.view];
touchLocation = [[touch view] convertPoint:touchLocation toView:nil];
CALayer *layer = [self.view.layer hitTest:touchLocation];
if (layer == self.toolbarRootView.layer ||
layer == self.chevronView.layer ||
layer == self.chevronImageView.layer ||
layer == self.toolbarStackView.layer ||
layer == self.view.layer) { // don't let user move toolbar or toolbar UI buttons, toolbar's chevron 'pull tab', or the layer associated with this VC's view
return;
}
}
[self.layoutOSC touchesBegan:touches withEvent:event];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// for OnScreenWidgets:
// -------- for OSC buttons
[self.layoutOSC touchesMoved:touches withEvent:event];
if ([self.layoutOSC isLayer:self.layoutOSC.layerBeingDragged
hoveringOverButton:trashCanButton]) { // check if user is dragging around a button and hovering it over the trash can button
trashCanButton.tintColor = [UIColor redColor];
}
else {
trashCanButton.tintColor = [UIColor colorWithRed:171.0/255.0 green:157.0/255.0 blue:255.0/255.0 alpha:1];
}
// -------- for keyboard Buttons
UITouch *touch = [touches anyObject]; // Get the first touch in the set
if([self touchWithinTashcanButton:touch]){
trashCanButton.tintColor = [UIColor redColor];
}
else trashCanButton.tintColor = [UIColor colorWithRed:171.0/255.0 green:157.0/255.0 blue:255.0/255.0 alpha:1];
}
- (bool)touchWithinTashcanButton:(UITouch* )touch {
CGPoint locationInView = [touch locationInView:self.view];
// Convert the location to the button's coordinate system
CGPoint locationInButton = [self.view convertPoint:locationInView toView:trashCanButton];
bool ret = CGRectContainsPoint(trashCanButton.bounds, locationInButton);
// NSLog(@"within button: %d", ret);
// Check if the location is within the button's bounds
return ret;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// removing keyboard buttons objs
UITouch *touch = [touches anyObject]; // Get the first touch in the set
if([self touchWithinTashcanButton:touch]){
if(self->selectedWidgetView != nil){
[self->selectedWidgetView removeFromSuperview];
[self.OnScreenWidgetViews removeObject:self->selectedWidgetView];
[selectedWidgetView.stickBallLayer removeFromSuperlayer];
[selectedWidgetView.crossMarkLayer removeFromSuperlayer];
}
}
//removing OSC buttons
if (self.layoutOSC.layerBeingDragged != nil &&
[self.layoutOSC isLayer:self.layoutOSC.layerBeingDragged hoveringOverButton:trashCanButton]) { // check if user wants to throw OSC button into the trash can
// here we're going to delete something
self.layoutOSC.layerBeingDragged.hidden = YES;
if ([self.layoutOSC.layerBeingDragged.name isEqualToString:@"dPad"]) { // if user is hiding dPad, then hide all four dPad button child layers as well since setting the 'hidden' property on the parent dPad CALayer doesn't automatically hide the four child CALayer dPad buttons
self.layoutOSC._upButton.hidden = YES;
self.layoutOSC._rightButton.hidden = YES;
self.layoutOSC._downButton.hidden = YES;
self.layoutOSC._leftButton.hidden = YES;
}
/* if user is hiding left or right analog sticks, then hide their corresponding inner analog stick child layers as well since setting the 'hidden' property on the parent analog stick doesn't automatically hide its child inner analog stick CALayer */
if ([self.layoutOSC.layerBeingDragged.name isEqualToString:@"leftStickBackground"]) {
self.layoutOSC._leftStick.hidden = YES;
}
if ([self.layoutOSC.layerBeingDragged.name isEqualToString:@"rightStickBackground"]) {
self.layoutOSC._rightStick.hidden = YES;
}
}
[self.layoutOSC touchesEnded:touches withEvent:event];
// in case of default profile OSC change, popup msgbox & remind user it's not allowed.
if([profilesManager getIndexOfSelectedProfile] == 0 && [self.layoutOSC.layoutChanges count] > 0){
UIAlertController * movedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Layout of the Default profile can not be changed"] preferredStyle:UIAlertControllerStyleAlert];
[movedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self.oscProfilesTableViewController profileViewRefresh];
}]];
[self presentViewController:movedAlertController animated:YES completion:nil];
}
trashCanButton.tintColor = [UIColor colorWithRed:171.0/255.0 green:157.0/255.0 blue:255.0/255.0 alpha:1];
}
@end
@@ -1,30 +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;
@property (nonatomic, assign) bool settingsExpandedInStreamView;
-(void)simulateSettingsButtonPress;
-(void)reloadStreamConfig;
-(bool)isIPhonePortrait;
#endif
@end
@@ -22,6 +22,7 @@
@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;
@@ -29,6 +30,7 @@
@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;
@@ -228,7 +228,7 @@ BOOL isCustomResolution(CGSize res) {
}
- (void)updateResolutionTable{
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
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;
@@ -465,6 +465,7 @@ BOOL isCustomResolution(CGSize res) {
}
[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];
@@ -477,6 +478,8 @@ BOOL isCustomResolution(CGSize res) {
[self.resolutionSelector addTarget:self action:@selector(newResolutionChosen) forControlEvents:UIControlEventValueChanged];
[self.framerateSelector setSelectedSegmentIndex:framerate];
[self.framerateSelector addTarget:self action:@selector(updateBitrate) forControlEvents:UIControlEventValueChanged];
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];
@@ -1054,6 +1057,7 @@ 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);
@@ -1076,6 +1080,7 @@ BOOL isCustomResolution(CGSize res) {
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;
@@ -1087,12 +1092,14 @@ BOOL isCustomResolution(CGSize res) {
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
oscLayoutToolFingers:oscLayoutToolFingers
slideToSettingsScreenEdge:slideToSettingsScreenEdge
@@ -1111,6 +1118,7 @@ BOOL isCustomResolution(CGSize res) {
audioOnPC:audioOnPC
preferredCodec:preferredCodec
enableYUV444:enableYUV444
enablePIP:enablePIP
useFramePacing:useFramePacing
enableHdr:enableHdr
btMouseSupport:btMouseSupport
@@ -1,31 +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 "LayoutOnScreenControlsViewController.h"
#import "MainFrameViewController.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;
@property (nonatomic, assign) MainFrameViewController *mainFrameViewcontroller;
-(void)updatePreferredDisplayMode:(BOOL)streamActive;
@end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+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>
@@ -1,53 +0,0 @@
<?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>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/>
<key>IDESourceControlProjectIdentifier</key>
<string>054A62F3-1197-45FB-A607-EA7922396E24</string>
<key>IDESourceControlProjectName</key>
<string>Limelight</string>
<key>IDESourceControlProjectOriginsDictionary</key>
<dict>
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
<string>github.com:limelight-stream/limelight-ios.git</string>
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
<string>github.com:limelight-stream/limelight-common-c.git</string>
</dict>
<key>IDESourceControlProjectPath</key>
<string>Limelight.xcodeproj</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict>
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
<string>../..</string>
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
<string>../../limelight-common-c</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>github.com:limelight-stream/limelight-ios.git</string>
<key>IDESourceControlProjectVersion</key>
<integer>111</integer>
<key>IDESourceControlProjectWCCIdentifier</key>
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
<key>IDESourceControlProjectWCConfigurations</key>
<array>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>EB99D631D5149240E91F8D168F0F298D7A5BAA89</string>
<key>IDESourceControlWCCName</key>
<string>limelight-common-c</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
<key>IDESourceControlWCCName</key>
<string>limelight-ios</string>
</dict>
</array>
</dict>
</plist>
@@ -1,53 +0,0 @@
<?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>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/>
<key>IDESourceControlProjectIdentifier</key>
<string>AD7C6582-F39A-441A-A7FD-9EBF143920BA</string>
<key>IDESourceControlProjectName</key>
<string>Moonlight</string>
<key>IDESourceControlProjectOriginsDictionary</key>
<dict>
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
<string>github.com:limelight-stream/limelight-ios.git</string>
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
<string>github.com:limelight-stream/limelight-common-c.git</string>
</dict>
<key>IDESourceControlProjectPath</key>
<string>Moonlight.xcodeproj</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict>
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
<string>../..</string>
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
<string>../../limelight-common-c</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>github.com:limelight-stream/limelight-ios.git</string>
<key>IDESourceControlProjectVersion</key>
<integer>111</integer>
<key>IDESourceControlProjectWCCIdentifier</key>
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
<key>IDESourceControlProjectWCConfigurations</key>
<array>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>EB99D631D5149240E91F8D168F0F298D7A5BAA89</string>
<key>IDESourceControlWCCName</key>
<string>limelight-common-c</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
<key>IDESourceControlWCCName</key>
<string>moonlight-ios</string>
</dict>
</array>
</dict>
</plist>
@@ -1,53 +0,0 @@
<?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>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/>
<key>IDESourceControlProjectIdentifier</key>
<string>6524503E-ED62-4E8E-8A67-B6DFB5A532FD</string>
<key>IDESourceControlProjectName</key>
<string>project</string>
<key>IDESourceControlProjectOriginsDictionary</key>
<dict>
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
<string>github.com:limelight-stream/limelight-ios.git</string>
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
<string>github.com:limelight-stream/limelight-common-c.git</string>
</dict>
<key>IDESourceControlProjectPath</key>
<string>Moonlight.xcodeproj/project.xcworkspace</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict>
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
<string>../..</string>
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
<string>../..limelight-common-c/</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>github.com:limelight-stream/limelight-ios.git</string>
<key>IDESourceControlProjectVersion</key>
<integer>111</integer>
<key>IDESourceControlProjectWCCIdentifier</key>
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
<key>IDESourceControlProjectWCConfigurations</key>
<array>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>EB99D631D5149240E91F8D168F0F298D7A5BAA89</string>
<key>IDESourceControlWCCName</key>
<string>limelight-common-c</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
<key>IDESourceControlWCCName</key>
<string>limelight-ios</string>
</dict>
</array>
</dict>
</plist>
+36 -18
View File
@@ -1,24 +1,42 @@
# Moonlight iOS/tvOS
# VoidLink 已上架App Store.
# VoidLink is available on App Store
### [https://apps.apple.com/app/voidlink/id6747717070](https://apps.apple.com/cn/app/voidlink/id6747717070)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kwv8vpwr457lqn25/branch/master?svg=true)](https://ci.appveyor.com/project/cgutman/moonlight-ios/branch/master)
<br>
[Moonlight for iOS/tvOS](https://moonlight-stream.org) is an open source client for [Sunshine](https://github.com/LizardByte/Sunshine) and NVIDIA GameStream. Moonlight for iOS/tvOS allows you to stream your full collection of games and apps from your powerful desktop computer to your iOS device or Apple TV.
# 代码 Coding
- 代码提交在`Integration`分支。
- For latest coding commits, go to branch `Integration`.
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [Android client](https://github.com/moonlight-stream/moonlight-android).
<br>
Check out [the Moonlight wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed project information, setup guide, or troubleshooting steps.
# 关于VoidLink. About VoidLink
- 本项目最初基于开源项目 [moonlight-iOS] fork 而来。在此基础上,True砖家(True Zhuanjia)@ Bilibili 及其他社区开发者对项目进行了大量重构、重新设计与功能扩展,包括全新的用户界面和显著增强的功能特性。<br>我们对 moonlight-iOS 开发者的开创性工作表示衷心感谢。 <br><br>
- VoidLink was originally forked from the open-source project [moonlight-iOS], but has since been extensively reworked, redesigned, and expanded by True砖家 (True Zhuanjia) @ Bilibili and other community developers. These contributions include a completely new user interface and significant enhancements to the application's functionality.<br>We gratefully acknowledge the foundational work of the moonlight-iOS developers.
[![Moonlight for iOS and tvOS](https://moonlight-stream.org/images/App_Store_Badge_135x40.svg)](https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566)
<br>
## Building
* Install Xcode from the [App Store page](https://apps.apple.com/us/app/xcode/id497799835)
* Run `git clone --recursive https://github.com/moonlight-stream/moonlight-ios.git`
* If you've already clone the repo without `--recursive`, run `git submodule update --init --recursive`
* Open Moonlight.xcodeproj in Xcode
* To run on a real device, you will need to locally modify the signing options:
* Click on "Moonlight" at the top of the left sidebar
* Click on the "Signing & Capabilities" tab
* Under "Targets", select "Moonlight" (for iOS/iPadOS) or "Moonlight TV" (for tvOS)
* In the "Team" dropdown, select your name. If your name doesn't appear, you may need to sign into Xcode with your Apple account.
* Change the "Bundle Identifier" to something different. You can add your name or some random letters to make it unique.
* Now you can select your Apple device in the top bar as a target and click the Play button to run.
# App Store 分发一次性收费声明.
# Statement on One-Time App Store Distribution Fee.
- VoidLink 的 App Store 安装费用用于覆盖通过 Apple 框架进行安全可靠应用分发的成本。同时也支持项目维护者进行持续的开发、维护,以及公众用户访问并下载官方签名版本。感谢您对项目的支持,帮助我们持续改进和优化。<br><br>
- The App Store fee for VoidLink covers the cost of secure and trusted distribution using Apple's infrastructure. It supports ongoing development, maintenance, and access to the official signed build. Thank you for supporting the project and helping sustain ongoing improvements.
<br>
# 开发者B站号. Developer on Bilibili
如果你在用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>
# 贡献者 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>
[@seastwood](https://github.com/seastwood) <br>
[@Danos0100](https://github.com/Danos0100) <br>
[@xzzpig](https://github.com/xzzpig) <br>
[@King0fSpace](https://github.com/King0fSpace) <br>
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:/Users/admin/moonlight-ios/Moonlight-ZWM.xcodeproj">
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,51 @@
{
"originHash" : "d1fc50a28d75017e32553df2b233f8a7e2cf1542da32728ce6722b200f745608",
"pins" : [
{
"identity" : "cocoalumberjack",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git",
"state" : {
"revision" : "a9ed4b6f9bdedce7d77046f43adfb8ce1fd54114",
"version" : "3.9.0"
}
},
{
"identity" : "openssl-package",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/OpenSSL-Package.git",
"state" : {
"revision" : "b9eb055fdf73e595cb4b9f665d13dc85975bf80a",
"version" : "3.3.2000"
}
},
{
"identity" : "svgkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SVGKit/SVGKit.git",
"state" : {
"revision" : "58152b9f7c85eab239160b36ffdfd364aa43d666",
"version" : "3.0.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0",
"version" : "1.2.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log",
"state" : {
"revision" : "bc386b95f2a16ccd0150a8235e7c69eab2b866ca",
"version" : "1.8.0"
}
}
],
"version" : 3
}
BIN
View File
Binary file not shown.
+123
View File
@@ -0,0 +1,123 @@
//
// 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: 7) {
// 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: 90, height: 90)
.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)
}
if Locale.preferredLanguages.first!.hasPrefix("zh-Hans") {
if #available(iOS 14.0, *) {
Text("闽ICP备17012590号-3A 主办单位:福州创图信息技术有限公司").font(.caption2)
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
//.padding()
} else {
Text("闽ICP备17012590号-3A 主办单位:福州创图信息技术有限公司").font(.caption)
.multilineTextAlignment(.center)
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
//.padding()
}
}
// 说明文字
Text(SwiftLocalizationHelper.localizedString(forKey: "From the player community, to the player community."))
.multilineTextAlignment(.center)
.font(Font.caption.italic())
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
.padding()
Text(SwiftLocalizationHelper.localizedString(forKey: "VoidLink delivers better performance now!"))
.multilineTextAlignment(.center)
.font(Font.callout.bold())
.lineLimit(nil)
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
// 链接按钮
if #available(iOS 14.0, *) {
Link(SwiftLocalizationHelper.localizedString(forKey: "Learn more & 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: 430)
// ⚠️ 一定要设置以下样式才会生效 preferredContentSize
modalPresentationStyle = .formSheet
}
}
func isIPhone() -> Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
+88
View File
@@ -0,0 +1,88 @@
//
// AlertControllerUtil.swift
// VoidLink
//
// Created by Weimin on 2025/10/6.
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
//
import UIKit
@objc class AlertControllerUtil: NSObject {
/// 类方法,直接在任何 UIViewController 上显示倒计时弹窗
/// - Parameters:
/// - viewController: 要显示弹窗的控制器
/// - title: 弹窗标题
/// - message: 弹窗内容
/// - buttonTitle: 按钮初始文字(倒计时结束后显示)
/// - countdown: 倒计时秒数
/// - completion: 点击确认或倒计时结束的回调
@objc public static var actionCancelled:Bool = false
@objc public static var alertController:UIAlertController = UIAlertController()
@objc public static var cancelButtonString:String = "Cancel"
@objc public static var autoCompletion:Bool = false
@objc class func showAlert(
in viewController: UIViewController,
title: String?,
message: String?,
withCancel: Bool,
buttonTitle: String,
countdown: Int,
action: (() -> Void)? = nil,
completion: (() -> Void)? = nil
) {
var remainingSeconds = countdown
alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "\(remainingSeconds)", style: .default) { _ in
actionCancelled = false
completion?()
cancelButtonString = "Cancel"
}
let cancelAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: cancelButtonString), style: .cancel) { _ in
actionCancelled = true
completion?()
cancelButtonString = "Cancel"
}
confirmAction.isEnabled = false
if withCancel {alertController.addAction(cancelAction)}
if buttonTitle != "" && !autoCompletion {alertController.addAction(confirmAction)}
if(countdown == 0) {
confirmAction.setValue(buttonTitle, forKey: "title")
confirmAction.isEnabled = buttonTitle != ""
}
viewController.present(alertController, animated: true, completion: action)
if(countdown == 0) {return}
// 倒计时
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
timer.schedule(deadline: .now(), repeating: 1.0, leeway: .milliseconds(50))
timer.setEventHandler {
remainingSeconds -= 1
if remainingSeconds <= 0 {
timer.cancel()
confirmAction.isEnabled = true
confirmAction.setValue(buttonTitle, forKey: "title")
if autoCompletion {
autoCompletion = false
completion?()
alertController.dismiss(animated: false)
return
}
} else {
confirmAction.setValue("\(remainingSeconds)", forKey: "title")
}
}
timer.resume()
}
}
@@ -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,10 +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 "Moonlight-Swift.h"
#import "VoidLink-Swift.h"
@implementation AppDelegate
@@ -24,6 +27,15 @@ static NSString* DB_NAME = @"Moonlight_tvOS.bin";
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
#endif
#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
@@ -48,6 +60,27 @@ static NSString* DB_NAME = @"Limelight_iOS.sqlite";
- (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;
}
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};
}
BIN
View File
Binary file not shown.
+170
View File
@@ -0,0 +1,170 @@
//
// 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(NSInteger, ControllerGyroSwitchMode) {
ControllerGyroSwitchDisabled,
ControllerGyroSwitchPressToToggle,
ControllerGyroSwitchHoldDown
};
typedef NS_ENUM(NSInteger, TouchMode) {
RelativeTouch,
NativeTouch,
AbsoluteTouch,
TouchDisabled,
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, HapticEnginePreference) {
HapticEngineAuto,
RumbleDevice,
LeftRightSwapped,
RumbleOff
};
typedef NS_ENUM(NSInteger, ControllerMouseStick) {
LeftStickToMouse,
RightStickToMouse,
};
typedef NS_ENUM(NSInteger, FramePacingMode) {
FramePacingModeOff,
FramePacingModeLegacy,
FramePacingModeQueue
};
typedef NS_ENUM(NSInteger, SettingsMenuMode) {
AllSettings,
FavoriteSettings,
RemoveSettingItem,
};
typedef NS_ENUM(NSInteger, WidgetSizeTransition) {
keepWidgetSize,
transitionWithOrientation,
};
typedef NS_ENUM(NSInteger, PencilTickMode) {
PencilTickDisabled,
ManualTick
};
- (void) saveSettings:(Settings*)settings
withBitrate:(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
localVolume:(CGFloat)localVolume
micVolume:(CGFloat)micVolume
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
showKeyboardToolbar:(BOOL)showKeyboardToolbar
optimizeGames:(BOOL)optimizeGames
multiController:(BOOL)multiController
buttonVisualFeedback:(BOOL)buttonVisualFeedback
touchPointTracking:(BOOL)touchPointTracking
swapABXYButtons:(BOOL)swapABXYButtons
audioOnPC:(BOOL)audioOnPC
redirectMic:(BOOL)redirectMic
useBuiltinMic:(BOOL)useBuiltinMic
preferredCodec:(uint32_t)preferredCodec
enableYUV444:(BOOL)enableYUV444
enablePIP:(BOOL)enablePIP
fullColorRange:(BOOL)fullColorRange
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
sendDummyEvent:(BOOL)sendDummyEvent
rememberFoldState:(BOOL)rememberFoldState
singleTapSensitivy:(CGFloat)singleTapSensitivy
hapticEngine:(NSInteger)hapticEngine
edgeSlidingSensitivity:(CGFloat)edgeSlidingSensitivity
audioEngine:(NSInteger)audioEngine
delayLeftClick:(BOOL)delayLeftClick
duckOtherApps:(BOOL)duckOtherApps
muteInBackground:(BOOL)muteInBackground
relativeTouchSlideThreshold:(CGFloat)relativeTouchSlideThreshold
enablePinch:(BOOL)enablePinch
scrollSensitivity:(CGFloat)scrollSensitivity
pinchSensitivity:(CGFloat)pinchSensitivity
ctrlDownForPinch:(BOOL)ctrlDownForPinch
leftClickDelayMs:(CGFloat)leftClickDelayMs
settingsMenuOffset:(CGFloat)settingsMenuOffset
passthroughGestures:(BOOL)passthroughGestures
mapControllerToMouse:(BOOL)mapControllerToMouse
controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity
controllerMouseExpo:(CGFloat)controllerMouseExpo
controllerGyroSwitchMode:(NSInteger)controllerGyroSwitchMode
enableFrameTimebase:(BOOL)enableFrameTimebase
asyncFrameDequeue:(BOOL)asyncFrameDequeue
sdrPerformanceWorkaround:(BOOL)sdrPerformanceWorkaround
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"
@@ -52,50 +55,94 @@
return uid;
}
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
- (void) saveSettings:(Settings*)settings
withBitrate:(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
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
oscTapExlusionAreaSize:(CGFloat)oscTapExlusionAreaSize
gyroSensitivity:(CGFloat)gyroSensitivity
localVolume:(CGFloat)localVolume
micVolume:(CGFloat)micVolume
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
largerStickLR1:(BOOL)largerStickLR1
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
showKeyboardToolbar:(BOOL)showKeyboardToolbar
optimizeGames:(BOOL)optimizeGames
multiController:(BOOL)multiController
buttonVisualFeedback:(BOOL)buttonVisualFeedback
touchPointTracking:(BOOL)touchPointTracking
swapABXYButtons:(BOOL)swapABXYButtons
audioOnPC:(BOOL)audioOnPC
redirectMic:(BOOL)redirectMic
useBuiltinMic:(BOOL)useBuiltinMic
preferredCodec:(uint32_t)preferredCodec
enableYUV444:(BOOL)enableYUV444
useFramePacing:(BOOL)useFramePacing
enableYUV444:(BOOL)enableYUV444
enablePIP:(BOOL)enablePIP
fullColorRange:(BOOL)fullColorRange
enableHdr:(BOOL)enableHdr
btMouseSupport:(BOOL)btMouseSupport
// absoluteTouchMode:(BOOL)absoluteTouchMode
touchMode:(NSInteger)touchMode
statsOverlayLevel:(NSInteger)statsOverlayLevel
statsOverlayEnabled:(BOOL)statsOverlayEnabled
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
statsOverlayEnabled:(BOOL)statsOverlayEnabled
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
resolutionSelected:(NSInteger)resolutionSelected
externalDisplayMode:(NSInteger)externalDisplayMode
localMousePointerMode:(NSInteger)localMousePointerMode {
localMousePointerMode:(NSInteger)localMousePointerMode
frameQueueSize:(NSInteger)frameQueueSize
enableGraphs:(BOOL)enableGraphs
graphOpacity:(NSInteger)graphOpacity
renderingBackend:(NSInteger)renderingBackend
framePacingMode:(NSInteger)framePacingMode
sendDummyEvent:(BOOL)sendDummyEvent
rememberFoldState:(BOOL)rememberFoldState
singleTapSensitivy:(CGFloat)singleTapSensitivy
hapticEngine:(NSInteger)hapticEngine
edgeSlidingSensitivity:(CGFloat)edgeSlidingSensitivity
audioEngine:(NSInteger)audioEngine
delayLeftClick:(BOOL)delayLeftClick
duckOtherApps:(BOOL)duckOtherApps
muteInBackground:(BOOL)muteInBackground
relativeTouchSlideThreshold:(CGFloat)relativeTouchSlideThreshold
enablePinch:(BOOL)enablePinch
scrollSensitivity:(CGFloat)scrollSensitivity
pinchSensitivity:(CGFloat)pinchSensitivity
ctrlDownForPinch:(BOOL)ctrlDownForPinch
leftClickDelayMs:(CGFloat)leftClickDelayMs
settingsMenuOffset:(CGFloat)settingsMenuOffset
passthroughGestures:(BOOL)passthroughGestures
mapControllerToMouse:(BOOL)mapControllerToMouse
controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity
controllerMouseExpo:(CGFloat)controllerMouseExpo
controllerGyroSwitchMode:(NSInteger)controllerGyroSwitchMode
enableFrameTimebase:(BOOL)enableFrameTimebase
asyncFrameDequeue:(BOOL)asyncFrameDequeue
sdrPerformanceWorkaround:(BOOL)sdrPerformanceWorkaround
backgroundSessionTimer:(NSInteger)backgroundSessionTimer{
__block Settings* settingsToSave = settings;
[_managedObjectContext performBlockAndWait:^{
Settings* settingsToSave = [self retrieveSettings];
if(!settingsToSave) settingsToSave = [self retrieveSettings];
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
settingsToSave.height = [NSNumber numberWithInteger:height];
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.oscLayoutToolFingers = [NSNumber numberWithInteger:(uint16_t)oscLayoutToolFingers];
settingsToSave.slideToSettingsScreenEdge = [NSNumber numberWithInteger:(uint32_t)slideToSettingsScreenEdge];
@@ -103,18 +150,27 @@
settingsToSave.pointerVelocityModeDivider = [NSNumber numberWithFloat:pointerVelocityModeDivider];
settingsToSave.touchPointerVelocityFactor = [NSNumber numberWithFloat:touchPointerVelocityFactor];
settingsToSave.mousePointerVelocityFactor = [NSNumber numberWithFloat:mousePointerVelocityFactor];
settingsToSave.oscTapExlusionAreaSize = [NSNumber numberWithFloat:oscTapExlusionAreaSize];
settingsToSave.gyroSensitivity = [NSNumber numberWithFloat:gyroSensitivity];
settingsToSave.localVolume = [NSNumber numberWithFloat:localVolume];
settingsToSave.micVolume = [NSNumber numberWithFloat:micVolume];
settingsToSave.touchMoveEventInterval = [NSNumber numberWithInteger:touchMoveEventInterval];
settingsToSave.reverseMouseWheelDirection = reverseMouseWheelDirection;
settingsToSave.largerStickLR1 = largerStickLR1;
settingsToSave.asyncNativeTouchPriority = [NSNumber numberWithInteger:asyncNativeTouchPriority];
settingsToSave.liftStreamViewForKeyboard = liftStreamViewForKeyboard;
settingsToSave.showKeyboardToolbar = showKeyboardToolbar;
settingsToSave.optimizeGames = optimizeGames;
settingsToSave.multiController = multiController;
settingsToSave.buttonVisualFeedback = buttonVisualFeedback;
settingsToSave.touchPointTracking = touchPointTracking;
settingsToSave.swapABXYButtons = swapABXYButtons;
settingsToSave.playAudioOnPC = audioOnPC;
settingsToSave.redirectMic = redirectMic;
settingsToSave.useBuiltinMic = useBuiltinMic;
settingsToSave.preferredCodec = preferredCodec;
settingsToSave.enableYUV444 = enableYUV444;
settingsToSave.useFramePacing = useFramePacing;
settingsToSave.sdrPerformanceWorkaround = sdrPerformanceWorkaround;
settingsToSave.enablePIP = enablePIP;
settingsToSave.fullColorRange = fullColorRange;
settingsToSave.enableHdr = enableHdr;
settingsToSave.btMouseSupport = btMouseSupport;
// settingsToSave.absoluteTouchMode = absoluteTouchMode;
@@ -125,6 +181,35 @@
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.enableFrameTimebase = enableFrameTimebase;
settingsToSave.asyncFrameDequeue = asyncFrameDequeue;
settingsToSave.enableGraphs = enableGraphs;
settingsToSave.graphOpacity = [NSNumber numberWithInteger:graphOpacity];
settingsToSave.renderingBackend = [NSNumber numberWithInteger:renderingBackend];
settingsToSave.framePacingMode = [NSNumber numberWithInteger:framePacingMode];
settingsToSave.sendDummyEvent = sendDummyEvent;
settingsToSave.singleTapSensitivity = [NSNumber numberWithDouble:singleTapSensitivy];
settingsToSave.hapticEngine = [NSNumber numberWithInteger:hapticEngine];
settingsToSave.edgeSlidingSensitivity = [NSNumber numberWithFloat:edgeSlidingSensitivity];
settingsToSave.audioEngine = [NSNumber numberWithInteger:audioEngine];
settingsToSave.delayLeftClick = delayLeftClick;
settingsToSave.duckOtherApps = duckOtherApps;
settingsToSave.muteInBackground = muteInBackground;
settingsToSave.relativeTouchSlideThreshold = [NSNumber numberWithFloat:relativeTouchSlideThreshold];
settingsToSave.enablePinch = enablePinch;
settingsToSave.scrollSensitivity = [NSNumber numberWithFloat:scrollSensitivity];
settingsToSave.pinchSensitivity = [NSNumber numberWithFloat:pinchSensitivity];
settingsToSave.ctrlDownForPinch = ctrlDownForPinch;
settingsToSave.leftClickDelayMs = [NSNumber numberWithFloat:leftClickDelayMs];
settingsToSave.settingsMenuOffset = [NSNumber numberWithFloat:settingsMenuOffset];
settingsToSave.passthroughGestures = passthroughGestures;
settingsToSave.mapControllerToMouse = mapControllerToMouse;
settingsToSave.controllerMousePointerVelocity = [NSNumber numberWithFloat:controllerMousePointerVelocity];
settingsToSave.controllerMouseExpo = [NSNumber numberWithFloat:controllerMouseExpo];
settingsToSave.rememberFoldState = rememberFoldState;
[self saveData];
}];
}

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