1045 Commits
Author SHA1 Message Date
True Zhuanjia 089515443d dualSense emulation beta ready (works with Sunshine-Foundation) 2026-08-21 18:01:39 +08:00
True Zhuanjia e63e5f2120 nest qiin2333's haptic convertor 2026-08-18 15:52:31 +08:00
True Zhuanjia 5a5238afee dualSense haptic beta 2026-08-17 22:29:21 +08:00
True Zhuanjia e00e2f8000 Merge branch 'Integration' into ds5 2026-08-17 20:42:03 +08:00
True Zhuanjia 8795a9f173 onscreen widget fix
motionHandler fix
2026-08-17 20:41:02 +08:00
True Zhuanjia 5bd938abc5 Merge branch 'Integration' into ds5
emulated gyro fix
2026-08-17 17:43:23 +08:00
True Zhuanjia 514dbb98e7 fixed: emulated gyro data not enabled 2026-08-17 17:13:27 +08:00
True Zhuanjia 106b1548d4 update audio-haptic lib 2026-08-17 15:30:08 +08:00
True Zhuanjia cacf2b1951 EQ tmp fixes 2026-08-17 04:39:45 +08:00
True Zhuanjia f1a4a519c3 adaptive trigger fixes 2026-08-16 19:28:39 +08:00
True Zhuanjia 4f0be34947 dualSense haptic rumbling by qin2333's PCM convertor 2026-08-16 18:03:50 +08:00
True Zhuanjia 9da1121cdb dual sense adaptive trigger implementation working with Sunshine-Foundation 2026-08-16 15:29:49 +08:00
True Zhuanjia 0e92af13eb update c-module 2026-08-16 13:21:00 +08:00
True Zhuanjia cdd613cc63 fix build error for simulator 2026-08-13 08:33:55 +08:00
True Zhuanjia 58535c9997 minor ui fix 2026-08-12 19:41:07 +08:00
True Zhuanjia 48647d4d89 - update tip strings 2026-08-12 18:28:04 +08:00
True Zhuanjia 16c54ac966 ui & tip string fixes 2026-08-11 23:48:03 +08:00
True Zhuanjia 4214afd2b7 statsOverlay for frame interpolation 2026-08-11 13:32:24 +08:00
True Zhuanjia d28aa43319 resolution persistence fix 2026-08-10 15:08:40 +08:00
True Zhuanjia 4983aecb4c ui fixes 2026-08-10 02:59:11 +08:00
True Zhuanjia 5ba566ceee menu ui fixes 2026-08-09 21:42:19 +08:00
True Zhuanjia b0fd0222df Merge commit '6879bf63791c3422fd88095237bee508524b6959' into frameInterpolation
# Conflicts:
#	VoidLink/Localization/Localizable.xcstrings
#	VoidLink/Stream/VideoDecoderRenderer.m
2026-08-09 20:54:04 +08:00
True Zhuanjia 7b7bb3837c frameInterpolation beta ready 2026-08-09 20:34:39 +08:00
AcakiandClaude Fable 5 6879bf6379 Allow switching rendering backend without restarting the app
The renderer setting previously forced an app quit (exit(0)) because
switching backends between sessions crashed or leaked. Fix the
underlying lifecycle bugs and remove the restart requirement:

- FrameQueue: really release frames in clear(), clear on session
  start/stop, and guard start/stop with an owner so a stale session's
  async cleanup can't pause a new session's queue
- MetalView: weak delegate (breaks retain cycle leaking the whole Metal
  stack), never start two render threads, bound the shutdown wait to
  avoid deadlocking against the render thread's dispatch_sync to main
- MetalViewController: explicit idempotent shutdown called from stream
  teardown (viewDidDisappear isn't guaranteed); snapshot the renderer
  in render-thread callbacks to avoid use-after-free during shutdown
- MetalVideoRenderer: make the shared colorspace name thread-safe and
  ARC-managed instead of a CFStringRef over-released in dealloc; guard
  NULL format description extensions
- Connection: drop the static renderer reference in DrCleanup
- VideoDecoderRenderer: cache the application background state via
  notifications instead of calling UIKit from the decode queue
  (Main Thread Checker violation)
- Frame: don't CFRetain(NULL) when a format desc has no extensions
- StreamFrameViewController: fix _streamView cleanup ordering
- Settings: apply the backend change in place (with the Metal caveats
  alert) and update localized tips; takes effect on next stream

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 17:14:01 +08:00
True Zhuanjia fc2b1689d0 frame interpolation Demo1 2026-08-08 03:45:50 +08:00
True Zhuanjia 9c9ef4a13f make pixelBuffer available to metalRenderer 2026-08-07 16:39:46 +08:00
True Zhuanjia a92010f5b1 in-flight bitrate request fix 2026-08-07 00:26:56 +08:00
True Zhuanjia 813005694a frame interpolation preparations 2026-08-06 19:57:24 +08:00
True Zhuanjia 11dd542cb3 controller navigation fixes 2026-08-04 15:14:21 +08:00
True Zhuanjia 949a0e0bfb revert hud visual feedback demo 2026-08-04 15:14:08 +08:00
True Zhuanjia ed8a246bad hud visual feedback demo 2026-08-04 09:30:39 +08:00
True Zhuanjia cfb8946ac3 widget label fix 2026-08-03 14:20:12 +08:00
True Zhuanjia 660a015f8b minor tweak and fixes 2026-08-03 11:10:30 +08:00
True Zhuanjia 54ac7f6522 minor fixes for controller navigation 2026-08-02 17:54:28 +08:00
True Zhuanjia a01af9ee21 refresh partial settings update flag 2026-08-02 00:23:51 +08:00
True Zhuanjia 71b7634069 refresh profileSelector tip update flag 2026-08-02 00:00:58 +08:00
True Zhuanjia 32718071e0 set widget profile update flag 2026-08-01 22:51:46 +08:00
True Zhuanjia 30d602af29 update ui string 2026-08-01 22:36:17 +08:00
True Zhuanjia f0043a763b new Tips for users 2026-08-01 21:09:02 +08:00
True Zhuanjia 516648cf34 on screen widget sublabel 2026-08-01 16:04:30 +08:00
True Zhuanjia 5bd9c6bd98 generic improvements & fixes 2026-07-31 22:13:13 +08:00
True Zhuanjia 3d82009d4a minor change 2026-07-31 00:05:40 +08:00
True Zhuanjia 51c13faa0c pencil tip offset 2026-07-30 23:22:50 +08:00
True Zhuanjia e0aff4b532 - genshin touch control enhancement preview for settings menu
- controller navigation & first connection tips
- enhancedTouchPreview bug fix
2026-07-30 14:14:33 +08:00
True Zhuanjia b0c16922a2 controller navigation improvements and fixes 2026-07-30 01:05:51 +08:00
True Zhuanjia ef5d839f5e refactor profileSelector view 2026-07-29 18:01:33 +08:00
True Zhuanjia 4a3ca7774e refactor toolboxView 2026-07-28 17:56:12 +08:00
True Zhuanjia 8f93cce257 change default settings for controller mouse mode 2026-07-27 21:42:29 +08:00
True Zhuanjia 9a634da3ed - feat: global controller navigation
- shorten initial host detection
2026-07-26 16:07:03 +08:00
True Zhuanjia 6460036661 update aboutView 2026-07-07 16:35:58 +08:00
True Zhuanjia c968a1df2a - controller navigation preparations
- disable tilt button for pencil
- widget picker UI improvement & fixes
- allow triggers to be controller mouse buttons
2026-07-07 16:13:46 +08:00
True Zhuanjia 54c106e01e radial menu demo 2026-07-01 15:20:57 +08:00
True Zhuanjia 41a5855211 minor fixes 2026-06-23 12:19:14 +08:00
True Zhuanjia 2652c2a99b set default dark theme for iOS 13 2026-06-23 11:00:09 +08:00
True Zhuanjia e987965765 tweak widget templates 2026-06-23 00:48:36 +08:00
True Zhuanjia a7d7a7975f fixed pencil touch for absoluteTouch 2026-06-22 22:12:15 +08:00
True Zhuanjia c79929f37e iap minor fix 2026-06-22 17:34:56 +08:00
True Zhuanjia d286c8b073 update widget templates 2026-06-22 14:32:37 +08:00
True Zhuanjia 2cef75fb32 localization fixes 2026-06-22 12:35:07 +08:00
True Zhuanjia ca17c1cc66 - handle iap purchase on app launch
- localization fix
- iap workflow fixes
2026-06-22 12:30:17 +08:00
True Zhuanjia 98e8305c94 update ui string 2026-06-22 01:04:05 +08:00
True Zhuanjia 45ccfa0c7f Merge branch 'IAPImrovements' into Integration
# Conflicts:
#	VoidLink/Localization/Localizable.xcstrings
xiap button for aboutView
2026-06-22 00:57:49 +08:00
True Zhuanjia 7d7b909c66 iap button for aboutView 2026-06-22 00:56:28 +08:00
True Zhuanjia d6c1059e99 improved iap workflow 2026-06-22 00:53:01 +08:00
True Zhuanjia 9c43e39ffb UI bug fix for game profile selector 2026-06-21 15:49:51 +08:00
True Zhuanjia adb5091dc2 improved motion control 2026-06-21 15:29:40 +08:00
True Zhuanjia 7d3c2d93ac add support for purchase intents 2026-06-20 22:56:04 +08:00
True Zhuanjia f4b161ed06 Set correct default mouse cursor mode 2026-06-20 16:03:14 +08:00
True Zhuanjia d57334265b improved motion control 2026-06-19 16:32:34 +08:00
True Zhuanjia f7efa48ae8 fixed: folder expansion compatibility issue on low versions of iOS 2026-06-17 00:58:21 +08:00
True Zhuanjia deb335efc3 auto restore for docked folder when app becomes active 2026-06-14 18:33:17 +08:00
True Zhuanjia f039f5af29 update aboutView 2026-06-14 14:17:07 +08:00
True Zhuanjia 286b5e94e6 fixed: undoRelocation not working for widget just collected by folder button 2026-06-14 13:00:29 +08:00
True Zhuanjia 1a719e11e6 - tweak widget templates
- minor improvements & fixes
2026-06-08 01:16:00 +08:00
True Zhuanjia becfc596c1 minor improvement & fixes 2026-06-07 13:24:09 +08:00
True Zhuanjia 1b76d0e0a6 - update tip message strings
- folder widget collection fixes
2026-06-06 22:09:48 +08:00
True Zhuanjia 53209ce89e - update default widget template with profile name localized
- improve widget import
2026-06-06 20:07:01 +08:00
True Zhuanjia 384724ff27 refactor oscprofilemanager to swift 2026-06-05 21:48:57 +08:00
True Zhuanjia e2eccd7968 - tweak widget templates
- multiple on-screen widget tweaks and improvements
2026-06-05 16:58:51 +08:00
True Zhuanjia f762ecc3b2 detailed definitions for connection termination errors 2026-06-05 16:53:40 +08:00
True Zhuanjia fa0b864933 - layout tool improvement: handles subfolder visibility in edit tool
- tweak iPad widget template
2026-05-31 14:00:44 +08:00
True Zhuanjia e0f3921a75 - template file update
- minor fixes
2026-05-31 02:11:30 +08:00
True Zhuanjia f698ad2fb6 default template update flag 2026-05-30 21:36:14 +08:00
True Zhuanjia a0179cc6cc new widget template demo 2026-05-30 19:28:37 +08:00
True Zhuanjia 511e7c0e4b folder widget & autodock improvement and fixes 2026-05-30 17:42:15 +08:00
True Zhuanjia e734973be1 widget layout tool fixes 2026-05-30 11:05:30 +08:00
True Zhuanjia 9979408b1a folder behavior minor fixes 2026-05-28 21:04:43 +08:00
True Zhuanjia a5388bb60d revert default mouse cursor mode to "captured" 2026-05-28 16:44:51 +08:00
True Zhuanjia 4c32ec59d6 tweak button sliding 2026-05-28 16:33:50 +08:00
True Zhuanjia af1f0e75c7 more preparations for new widget template 2026-05-28 14:21:02 +08:00
True Zhuanjia 3aa7a90a2f string localization for new default widget template 2026-05-28 11:59:40 +08:00
True Zhuanjia ec956218fd add entries to shortcut lib 2026-05-27 17:46:12 +08:00
True Zhuanjia 02fcdcecea shortcut library for widget picker 2026-05-27 13:01:12 +08:00
True Zhuanjia 3575e84165 tweak functional tab of widget picker view 2026-05-27 10:59:11 +08:00
True Zhuanjia 90116e7ff6 folder behavior improvement 2026-05-27 10:17:39 +08:00
True Zhuanjia ffe48a5382 deprecate NativeTouchOnly mode 2026-05-26 16:17:52 +08:00
True Zhuanjia c4ddc0c4fc minor fixes for absolute touch mode 2026-05-26 15:05:10 +08:00
True Zhuanjia 23299ad73d folder widget fixes 2026-05-26 13:20:30 +08:00
True Zhuanjia accd07f808 minor fix for pencil handler 2026-05-25 12:13:22 +08:00
True Zhuanjia ed39f85508 - c-module update
- minor fixes
2026-05-22 02:35:34 +08:00
True Zhuanjia e8349b2a6f update ui string 2026-05-21 20:37:17 +08:00
True Zhuanjia c36051fffc improve widget alignment 2026-05-21 20:31:54 +08:00
True Zhuanjia 5f4fe6a040 widget layering fixes 2026-05-21 19:44:28 +08:00
True Zhuanjia 843570dbca feat: on-screen button auto tap repeats 2026-05-21 17:54:29 +08:00
True Zhuanjia bb44b2067e 4*16 widget pool grid for button macro 2026-05-21 16:05:57 +08:00
True Zhuanjia 2c2088e4b1 - button widget skill combo enhanced to macro
- widget picker view UI fixes
2026-05-21 14:18:50 +08:00
True Zhuanjia a7088c70e5 app launch background color 2026-05-20 10:13:36 +08:00
True Zhuanjia 091fd828ab widget layout tool layering fix 2026-05-20 02:14:43 +08:00
True Zhuanjia d168ffbd5a minor fix 2026-05-20 01:15:05 +08:00
True Zhuanjia 0af4c70e16 - walk-sprint sign for direction pad
- session launch UI & animation fixes
2026-05-20 00:59:34 +08:00
True Zhuanjia e402c17fa6 adopted qiin2333's network related common-c commits 2026-05-19 15:39:23 +08:00
True Zhuanjia ab9b2509ff allow reversed input for motion control channels 2026-05-19 13:35:12 +08:00
True Zhuanjia 535d6009ad controller report issue fixes 2026-05-19 11:23:30 +08:00
True Zhuanjia 09981df37e - mic audio encryption for sunshine-foundation
- merge c-module updates from upstream
2026-05-18 22:12:26 +08:00
True Zhuanjia f7eae4cee9 controller report issue 2026-05-18 11:19:10 +08:00
True Zhuanjia ab14e18b4a folder expansion fix 2026-05-17 21:40:24 +08:00
True Zhuanjia 1630f7987a home indicator interaction fix 2026-05-16 22:45:45 +08:00
True Zhuanjia 5a9bcab368 Merge branch 'fix/resolution_change_stretch' into Integration 2026-05-14 14:58:08 +08:00
True Zhuanjia f7b118b671 tip string fixes 2026-05-14 00:24:48 +08:00
True Zhuanjia b49f4a6891 update default widget profiles 2026-05-13 22:52:11 +08:00
True Zhuanjia 3fd0c1b397 update ui string 2026-05-13 20:41:43 +08:00
True Zhuanjia 9282c3f404 touch mode change tip 2026-05-13 20:16:00 +08:00
True Zhuanjia ef3fca754f minor fixes 2026-05-13 17:25:35 +08:00
True Zhuanjia c08bb232cd layout tool batch edit improvement and tips 2026-05-13 17:04:19 +08:00
True Zhuanjia 2d1a1573e1 folder alpha tweak bug fixes 2026-05-13 16:23:43 +08:00
True Zhuanjia 92b65b4a17 update ui tip string 2026-05-13 15:56:35 +08:00
True Zhuanjia f72e0f6292 - valid & invalid button mode tips
- widget panel tips
2026-05-13 15:45:25 +08:00
True Zhuanjia 57a8c22a3f minor improvements 2026-05-12 22:09:03 +08:00
True Zhuanjia 4195f97d56 widget layout tool minor improvements 2026-05-12 20:46:28 +08:00
True Zhuanjia c734030e87 widget resize improvement 2026-05-12 19:09:44 +08:00
True Zhuanjia 96d997d41e button widget batch resize 2026-05-12 17:09:14 +08:00
True Zhuanjia 023cc0b583 highlight touchpad widget border during resizing 2026-05-12 16:20:53 +08:00
True Zhuanjia 57dac128e2 globe as escape switch 2026-05-12 11:56:15 +08:00
True Zhuanjia 77e0048c57 more tips for on screen widgets 2026-05-11 22:57:47 +08:00
True Zhuanjia fe6954cb9c tips for first keyboard or mouse connection on iOS 2026-05-11 16:32:17 +08:00
True Zhuanjia 237f03a501 streamview bug fixes 2026-05-11 12:43:45 +08:00
True Zhuanjia 94f61ebf0c shortcut button triggering fix 2026-05-09 19:42:25 +08:00
True Zhuanjia c3a0004c68 add lsvpad 2026-05-08 21:49:09 +08:00
True Zhuanjia d7aa5cc9fc improved displacement based stick pads 2026-05-08 21:05:03 +08:00
True Zhuanjia 26c1d068a8 inertial scrolling for displacement based stickpads 2026-05-05 23:54:54 +08:00
True Zhuanjia 35ceebfe0d - update default widget template
- change default button action for mousepad
- minor fixes
2026-05-01 00:21:55 +08:00
True Zhuanjia 5daf158908 update widget localization strings 2026-04-30 01:24:15 +08:00
True Zhuanjia 8f12a4f598 minor tweak 2026-04-29 22:17:44 +08:00
True Zhuanjia 233a23c6c8 refactor touchMode to game profile 2026-04-29 22:01:18 +08:00
True Zhuanjia cf48d4d7c3 new widget, magnifier 2026-04-29 18:43:06 +08:00
True Zhuanjia 5bf8eeb873 widget picker view ui string update 2026-04-29 10:48:11 +08:00
True Zhuanjia 8f5d58a25c minor fix 2026-04-29 00:07:44 +08:00
True Zhuanjia 363b91b244 folder import fixes 2026-04-28 23:52:14 +08:00
True Zhuanjia efc34ae1fc mac desktop experience fixes & tips 2026-04-28 17:09:33 +08:00
True Zhuanjia 9e858520c8 tweak touchHandler 2026-04-28 15:04:54 +08:00
True Zhuanjia 20aba200b3 new touchpad widget: absolute mousepad 2026-04-28 14:25:25 +08:00
True Zhuanjia cb235d9255 tweak inputAccessoryBar 2026-04-28 10:21:06 +08:00
True Zhuanjia b0224d502a button-combo available for folder 2026-04-27 23:15:08 +08:00
True Zhuanjia ce67aefc3b motion control for external gamepad 2026-04-27 16:23:45 +08:00
True Zhuanjia e8d864ff66 soft keyboard fixes 2026-04-26 21:40:24 +08:00
True Zhuanjia 9e2ef6cc0d audio resume fix 2026-04-26 19:38:34 +08:00
True Zhuanjia 9d2df484d1 make inputAccessoryBar delete button easier to tap 2026-04-26 17:36:35 +08:00
True Zhuanjia d28573dcd4 widgetPickerView logic fix 2026-04-26 17:20:22 +08:00
True Zhuanjia c3cb31d443 widgetPicker view compatibility fixes for low iOS versions 2026-04-26 16:42:15 +08:00
True Zhuanjia b258c58d59 soft keyboard activation fix 2026-04-24 23:37:34 +08:00
True Zhuanjia d8e83d1704 Merge branch 'Integration' into inputAccessoryBar
# Conflicts:
#	VoidLink/Utility/GenericUtils.swift
2026-04-24 23:27:21 +08:00
True Zhuanjia e78a351c28 inputAccessoryBar for soft keyboard 2026-04-24 22:59:37 +08:00
True Zhuanjia 5ee443337c autoDock and gesture interaction fixes 2026-04-23 17:09:53 +08:00
True Zhuanjia 4dc96ba189 widget alpha tweak fix 2026-04-23 15:14:33 +08:00
True Zhuanjia ecacb1afa7 lower iOS compatibility fixes 2026-04-22 23:53:19 +08:00
True Zhuanjia 7e21292a62 game profile system & selector 2026-04-22 12:46:51 +08:00
True Zhuanjia 9104a7b7b9 GUI command picker for toolbox menu 2026-04-16 17:31:49 +08:00
True Zhuanjia ecc7a79a8c layout profile table improvements 2026-04-16 16:03:52 +08:00
True Zhuanjia a1a70fcca2 - tool box menu improvements
- autoDock improvements
- live gamepad overlay close button
2026-04-16 14:31:21 +08:00
True Zhuanjia d769c98e22 layout tool ui fixes 2026-04-15 14:15:39 +08:00
True Zhuanjia ce59a9eb05 auto dock fixes 2026-04-14 17:16:11 +08:00
True Zhuanjia b2984e0af5 Stop tracking .DS_Store files 2026-04-14 15:56:52 +08:00
True Zhuanjia bed2232aff widget template update 2026-04-14 15:46:31 +08:00
True Zhuanjia e87f0f1aa0 system audio playback fix 2026-04-14 14:46:18 +08:00
True Zhuanjia 9807a77d9b gamepad overlay crash fix 2026-04-14 11:08:59 +08:00
True Zhuanjia 3e1423ff46 multi gamepad input fixes 2026-04-14 10:45:50 +08:00
True Zhuanjia a4f805db26 audio playback fixes 2026-04-14 09:48:11 +08:00
True Zhuanjia 0195aeb765 Revert "audio playback improvement"
This reverts commit 46834a8a8b.
2026-04-14 08:43:13 +08:00
True Zhuanjia fcfe83572f autoDock fixes 2026-04-13 18:16:37 +08:00
True Zhuanjia d61942caec TF widget template fix 2026-04-13 12:51:46 +08:00
True Zhuanjia 515360166c ui string fixes 2026-04-13 12:44:22 +08:00
True Zhuanjia 5c5507b02f update ui string 2026-04-13 11:05:35 +08:00
True Zhuanjia 7e0d2af5b7 - update widget templates
- minor fixes
2026-04-13 09:44:55 +08:00
True Zhuanjia 780a0e7ab8 - update widget tempates
- minor fixes
2026-04-13 01:58:57 +08:00
True Zhuanjia 9359333b15 widget batch edit fix 2026-04-12 22:40:21 +08:00
True Zhuanjia 819aa436e8 iOS 12 app crash fixes 2026-04-12 22:03:47 +08:00
True Zhuanjia 5beee0ffe4 update UI string 2026-04-12 20:48:03 +08:00
True Zhuanjia 30aa9a6b8d - update UI string
- minor improvements
2026-04-12 20:03:06 +08:00
True Zhuanjia 7e088acd49 dpad improvements 2026-04-12 19:23:47 +08:00
True Zhuanjia f702f726b7 - functional button fixes
- tweak auto dock animation
- input fixes
2026-04-11 14:34:54 +08:00
True Zhuanjia 46834a8a8b audio playback improvement 2026-04-10 23:35:21 +08:00
True Zhuanjia ca69fd62d9 persist pickerView tab selection 2026-04-10 23:25:46 +08:00
True Zhuanjia 8fd43d47dd more functional buttons 2026-04-10 21:34:14 +08:00
True Zhuanjia 732ec9a9cd widget picker fix 2026-04-10 19:19:22 +08:00
True Zhuanjia a0d1982a14 autoDock & toolbox gesture fixes 2026-04-10 17:05:04 +08:00
True Zhuanjia bb63183a51 update copyrights 2026-04-10 10:13:19 +08:00
True Zhuanjia 707beddcae refactor target and scheme names 2026-04-09 16:06:15 +08:00
True Zhuanjia c4c896e43e live gamepad overlay tip fix 2026-04-09 11:48:35 +08:00
True Zhuanjia e2acf5ac4b disable toolbox menu gesture 2026-04-08 20:03:27 +08:00
True Zhuanjia fcecae3a87 - update default Templates
- make RsWheel available
2026-04-08 11:56:05 +08:00
True Zhuanjia 55a2bb7576 disable soft keyboard tap gesture by default 2026-04-08 02:38:10 +08:00
True Zhuanjia 6b085982f1 cancel tap gesture for widget layout tool 2026-04-08 02:25:57 +08:00
True Zhuanjia 94f1c625be update default widget templates 2026-04-08 02:15:07 +08:00
True Zhuanjia b0842ea4dc autoDock fixes 2026-04-08 00:21:55 +08:00
True Zhuanjia 49431b5eba - widget template editing code
- widgetPickerView fixes
2026-04-08 00:07:13 +08:00
True Zhuanjia 68ee5d879b minor changes 2026-04-07 21:43:14 +08:00
True Zhuanjia 984a1acd48 turn on live feedback overlay on controller arrival 2026-04-07 19:49:07 +08:00
True Zhuanjia 00b06b7b70 - update ui string
- improve autodock
2026-04-07 15:09:30 +08:00
True Zhuanjia 84adf3ac95 command picker for drawing toolkit 2026-04-07 14:35:06 +08:00
True Zhuanjia 9cd72271ca minor fixes 2026-04-06 23:58:49 +08:00
True Zhuanjia e71ce58d9b enable on-screen widget by default 2026-04-06 23:22:07 +08:00
True Zhuanjia eb03b055ea folder button auto dock 2026-04-06 23:07:43 +08:00
True Zhuanjia 5af39e8fb6 gamepad live feedback overlay 2026-04-06 01:19:44 +08:00
Acaki b12cb96416 fix: video streatched when changing resolution in stream 2026-04-04 17:02:52 +08:00
True Zhuanjia 7f4108c4e3 minor fix 2026-04-04 03:25:35 +08:00
True Zhuanjia be48a48704 bulk edit stacks fix 2026-04-04 02:53:06 +08:00
True Zhuanjia 88bc5c7551 minor fixes 2026-04-04 01:19:18 +08:00
True Zhuanjia 7cd857a38b highlight color fixes 2026-04-04 00:43:38 +08:00
True Zhuanjia 60e92c458e - widget bulk edit, clear, import
- refactor coding
2026-04-03 22:14:05 +08:00
True Zhuanjia 605f276a71 widget picker bug fixes 2026-04-02 10:33:28 +08:00
True Zhuanjia 210ca4190a refactor coding 2026-04-02 10:23:16 +08:00
True Zhuanjia bd2d9c05bc gui widgetPicker 2026-04-02 06:51:33 +08:00
True Zhuanjia ae526ecddf fixed: soft keyboard brought up by iOS26 system-level action 2026-03-23 12:22:56 +08:00
True Zhuanjia 37d7949304 widget profile template update 2026-03-22 15:29:22 +08:00
True Zhuanjia 6092839004 folder button bulk move fix 2026-03-22 15:02:53 +08:00
True Zhuanjia 31dc13b87d minor change 2026-03-22 14:10:42 +08:00
True Zhuanjia dbe91f5b07 widget layout tool improvements 2026-03-22 14:01:09 +08:00
True Zhuanjia d092b93a29 - allow wider size range for on-screen widgets
- minor improvements
2026-03-22 11:21:51 +08:00
True Zhuanjia eff35450f6 feat: folder button bulk move 2026-03-22 00:32:48 +08:00
True Zhuanjia 6204feefc0 feat: on-screen button "DisableTouch" 2026-03-21 22:06:40 +08:00
True Zhuanjia 924374bccd Merge branch 'keyboardHeight' into Integration 2026-03-21 19:45:35 +08:00
True Zhuanjia eac8d15240 feat: designate soft keyboard height manually 2026-03-21 19:45:21 +08:00
True Zhuanjia a55d840bce minor tweak for pencilHandler 2026-03-21 17:06:45 +08:00
True Zhuanjia 22aa0a5249 tweak curve view UI 2026-03-21 13:27:45 +08:00
True Zhuanjia c3358efa9d undo last move button for pressure curve tool 2026-03-21 13:13:39 +08:00
True Zhuanjia e59f3ba1ab stroke start equalization and filtering 2026-03-21 12:43:16 +08:00
True Zhuanjia 42f29074fa block keyboard action initiated by physical gamepad for iOS 26 2026-03-16 16:25:46 +08:00
True Zhuanjia 6299401286 enable liquid glass 2026-03-15 11:44:16 +08:00
True Zhuanjia deba65857d Merge branch 'iOS26UI' into Integration 2026-03-15 11:43:36 +08:00
True Zhuanjia 691aa9c899 liquid glass for soft keyboard toolbar 2026-03-15 11:43:20 +08:00
True Zhuanjia bd9dccd834 Merge branch 'iOS26UI' into Integration 2026-03-14 14:15:28 +08:00
True Zhuanjia 0dd419a5a3 tweak toolbox view 2026-03-14 14:15:16 +08:00
True Zhuanjia 2410222c19 Merge branch 'iOS26UI' into Integration 2026-03-14 11:50:48 +08:00
True Zhuanjia 1269c46f56 UI improvements: widget profile selector table 2026-03-14 11:50:07 +08:00
True Zhuanjia 2c787a8d77 Merge branch 'iOS26UI' into Integration 2026-03-12 19:46:26 +08:00
True Zhuanjia 92fca9bca1 try to fix app crash caused by host discovery 2026-03-12 19:44:13 +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 4dd1aad123 Merge branch 'iOS26UI' into Integration 2026-03-12 10:19:47 +08:00
True Zhuanjia 165b5e6638 tweak settings menu navigation bar height 2026-03-12 10:19:08 +08:00
True Zhuanjia d8ac06ddca dummy commit 2026-03-12 09:56:39 +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
1078 changed files with 115374 additions and 156262 deletions

No files matched your search

+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: on:
push: push:
branches: [ "noWaterMark" ] branches: [ "feat/frame_pacing_metal" ]
jobs: jobs:
build: build:
name: Build IPAs name: Build VoidLink IPA
runs-on: macos-latest runs-on: macos-latest
steps: steps:
@@ -16,30 +16,15 @@ jobs:
- name: Checkout submodules - name: Checkout submodules
run: "git submodule update --init --recursive" run: "git submodule update --init --recursive"
- name: Build FullScreen Version - name: Build VoidLink
run: | run: |
plutil -replace UIRequiresFullScreen -bool true Limelight/Limelight-Info.plist 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"
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 && mkdir -p ./build/IPA/Payload
mkdir -p ./build/IPA_FullScreen && mkdir -p ./build/IPA_FullScreen/Payload cp -r ./build/VoidLink.xcarchive/Products/Applications/VoidLink.app ./build/IPA/Payload/VoidLink.app
cp -r ./build/MoonlightFullScreen.xcarchive/Products/Applications/Moonlight-ZWM.app ./build/IPA_FullScreen/Payload/Moonlight-ZWM.app cd ./build/IPA && zip -r ../../build/VoidLink.ipa ./Payload/* && cd ../..
cd ./build/IPA_FullScreen && zip -r ../../build/Moonlight-ZWM_FullScreen.ipa ./Payload/* && cd ../..
- name: Upload FullScreen IPA to Artifacts - name: Upload VoidLink IPA to Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Moonlight-ZWM_FullScreen.ipa name: VoidLink.ipa
path: build/Moonlight-ZWM_FullScreen.ipa path: build/VoidLink.ipa
- 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
uses: actions/upload-artifact@v4
with:
name: Moonlight-ZWM_StageManager.ipa
path: build/Moonlight-ZWM_StageManager.ipa
+4
View File
@@ -1,6 +1,10 @@
**/xcuserdata/ **/xcuserdata/
**/Moonlight.xcscmblueprint **/Moonlight.xcscmblueprint
**/xcschemes/*.xcscheme **/xcschemes/*.xcscheme
.DS_Store
Build Build
DerivedData DerivedData
build-home/
*.xccheckout *.xccheckout
.github
README.md
+7 -1
View File
@@ -1,6 +1,12 @@
[submodule "moonlight-common/moonlight-common-c"] [submodule "moonlight-common/moonlight-common-c"]
path = 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"] [submodule "X1Kit"]
path = X1Kit path = X1Kit
url = https://github.com/cgutman/X1Kit.git url = https://github.com/cgutman/X1Kit.git
[submodule "third_party/imgui"]
path = third_party/imgui
url = https://github.com/ocornut/imgui.git
[submodule "third_party/moonlight-audio-haptics"]
path = third_party/moonlight-audio-haptics
url = https://github.com/TrueZhuangJia/moonlight-audio-haptics.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
+9 -3
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="landscape" appearance="light"/> <device id="retina6_1" orientation="landscape" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@@ -12,8 +13,13 @@
<view contentMode="scaleToFill" id="VWy-YH-qYj"> <view contentMode="scaleToFill" id="VWy-YH-qYj">
<rect key="frame" x="0.0" y="0.0" width="896" height="414"/> <rect key="frame" x="0.0" y="0.0" width="896" height="414"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<point key="canvasLocation" x="33" y="187"/> <point key="canvasLocation" x="33" y="187"/>
</view> </view>
</objects> </objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document> </document>
+32 -12
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"/> <device id="ipad11_0rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <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"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@@ -12,7 +12,7 @@
<objects> <objects>
<viewController id="wb7-af-jn8" customClass="MainFrameViewController" sceneMemberID="viewController"> <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"> <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"/> <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"/> <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"> <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="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"/> <color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl> </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> </subviews>
<color key="backgroundColor" systemColor="viewFlipsideBackgroundColor"/> <color key="backgroundColor" systemColor="viewFlipsideBackgroundColor"/>
</view> </view>
@@ -622,6 +641,7 @@ ICAgICAgICAgICAgICA
<outlet property="largerStickLR1Selector" destination="sIU-PH-H7V" id="YWg-40-eKd"/> <outlet property="largerStickLR1Selector" destination="sIU-PH-H7V" id="YWg-40-eKd"/>
<outlet property="liftStreamViewForKeyboardSelector" destination="hm3-EV-h6d" id="fHG-J1-eQL"/> <outlet property="liftStreamViewForKeyboardSelector" destination="hm3-EV-h6d" id="fHG-J1-eQL"/>
<outlet property="localMousePointerModeSelector" destination="4FU-iP-c6T" id="4Gz-ao-xH2"/> <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="mousePointerVelocityFactorSlider" destination="Ei2-FJ-9oC" id="1T3-QA-Jua"/>
<outlet property="mousePointerVelocityFactorUILabel" destination="oPM-G6-u5O" id="Eco-5u-PRR"/> <outlet property="mousePointerVelocityFactorUILabel" destination="oPM-G6-u5O" id="Eco-5u-PRR"/>
<outlet property="multiControllerSelector" destination="KlC-fG-wEi" id="giM-F7-So5"/> <outlet property="multiControllerSelector" destination="KlC-fG-wEi" id="giM-F7-So5"/>
@@ -664,7 +684,7 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="YRO-Lg-RCg"/> <viewControllerLayoutGuide type="bottom" id="YRO-Lg-RCg"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="hGs-BR-t4b"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
@@ -704,7 +724,7 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="vu8-3s-HlY"/> <viewControllerLayoutGuide type="bottom" id="vu8-3s-HlY"/>
</layoutGuides> </layoutGuides>
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="SMn-1j-R3I"> <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"/> <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"/> <color key="backgroundColor" red="0.3333357871" green="0.33332890269999998" blue="0.33333355190000002" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
</view> </view>
@@ -726,7 +746,7 @@ ICAgICAgICAgICAgICA
<viewControllerLayoutGuide type="bottom" id="YZj-An-sqS"/> <viewControllerLayoutGuide type="bottom" id="YZj-An-sqS"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="le1-tU-NEp"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="N0D-dj-EuF"> <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"/> <viewControllerLayoutGuide type="bottom" id="eaS-aj-VtA"/>
</layoutGuides> </layoutGuides>
<view key="view" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="Acw-S4-f8a"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view alpha="0.75" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fZU-G0-fgM" userLabel="Toolbar Root View" customClass="ToolBarContainerView"> <view alpha="0.75" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fZU-G0-fgM" userLabel="Toolbar Root View" customClass="ToolBarContainerView">
@@ -965,17 +985,17 @@ ICAgICAgICAgICAgICA
<!--Profiles Table View Controller--> <!--Profiles Table View Controller-->
<scene sceneID="W0E-wz-n2b"> <scene sceneID="W0E-wz-n2b">
<objects> <objects>
<viewController storyboardIdentifier="OSCProfilesTableViewController" id="Atv-Nj-0qv" customClass="OSCProfilesTableViewController" sceneMemberID="viewController"> <viewController storyboardIdentifier="OSCProfilesTableViewController" id="Atv-Nj-0qv" customClass="OSCProfilesTableViewController" customModule="VoidLink" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides> <layoutGuides>
<viewControllerLayoutGuide type="top" id="osA-Uy-SE1"/> <viewControllerLayoutGuide type="top" id="osA-Uy-SE1"/>
<viewControllerLayoutGuide type="bottom" id="8ja-wQ-jbJ"/> <viewControllerLayoutGuide type="bottom" id="8ja-wQ-jbJ"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="PlJ-LJ-d1p"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <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"> <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"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView> </tableView>
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="62V-DN-FUk"> <navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="62V-DN-FUk">
@@ -1036,13 +1056,13 @@ ICAgICAgICAgICAgICA
<image name="SquaresArrowUp" width="40" height="40"/> <image name="SquaresArrowUp" width="40" height="40"/>
<image name="Trash" width="40" height="40"/> <image name="Trash" width="40" height="40"/>
<systemColor name="secondaryLabelColor"> <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>
<systemColor name="systemBackgroundColor"> <systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor> </systemColor>
<systemColor name="tintColor"> <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>
<systemColor name="viewFlipsideBackgroundColor"> <systemColor name="viewFlipsideBackgroundColor">
<color red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+41 -4
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <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="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@@ -570,7 +570,7 @@ ICAgICAgICAgICAgIA
<color key="selectedSegmentTintColor" 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> </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"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <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"/> <color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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"> <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"/> <rect key="frame" x="17" y="14" width="219" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <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"/> <action selector="backToStreamingButtonTapped:" destination="rYd-e6-cQU" eventType="touchUpInside" id="9P0-Yw-T4W"/>
</connections> </connections>
</button> </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> </subviews>
<color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
@@ -646,6 +681,7 @@ ICAgICAgICAgICAgIA
<outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/> <outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/>
<outlet property="oscTapExlusionAreaSizeLabel" destination="Dfl-gL-00M" id="Jbg-64-P3M"/> <outlet property="oscTapExlusionAreaSizeLabel" destination="Dfl-gL-00M" id="Jbg-64-P3M"/>
<outlet property="oscTapExlusionAreaSizeSlider" destination="TSt-Hj-uTR" id="h9l-ap-AVd"/> <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="pointerVelocityModeDividerSlider" destination="xet-C9-l5y" id="GnQ-xU-e0N"/>
<outlet property="pointerVelocityModeDividerUILabel" destination="VlN-ZW-1gm" id="vbe-QK-hZb"/> <outlet property="pointerVelocityModeDividerUILabel" destination="VlN-ZW-1gm" id="vbe-QK-hZb"/>
<outlet property="resolutionDisplayView" destination="Dej-Bj-F6i" id="bi4-N6-snx"/> <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="touchPointerVelocityFactorUILabel" destination="c6f-bJ-Vji" id="uVM-o6-JPk"/>
<outlet property="unlockDisplayOrientationSelector" destination="R9O-Tw-UD4" id="0ak-pc-eva"/> <outlet property="unlockDisplayOrientationSelector" destination="R9O-Tw-UD4" id="0ak-pc-eva"/>
<outlet property="yuv444Selector" destination="vwT-tg-RfT" id="69G-bN-kJF"/> <outlet property="yuv444Selector" destination="vwT-tg-RfT" id="69G-bN-kJF"/>
<outlet property="motionModeSelector" destination="6Lj-ss-Tj8" id="RLW-3I-hvl"/>
</connections> </connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="RWc-Km-KG5" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="RWc-Km-KG5" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -968,7 +1005,7 @@ ICAgICAgICAgICAgIA
<!--Profiles Table View Controller--> <!--Profiles Table View Controller-->
<scene sceneID="C7c-yg-gQK"> <scene sceneID="C7c-yg-gQK">
<objects> <objects>
<viewController storyboardIdentifier="OSCProfilesTableViewController" id="0K8-kQ-xlN" customClass="OSCProfilesTableViewController" sceneMemberID="viewController"> <viewController storyboardIdentifier="OSCProfilesTableViewController" id="0K8-kQ-xlN" customClass="OSCProfilesTableViewController" customModule="VoidLink" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides> <layoutGuides>
<viewControllerLayoutGuide type="top" id="Nat-X1-41w"/> <viewControllerLayoutGuide type="top" id="Nat-X1-41w"/>
<viewControllerLayoutGuide type="bottom" id="9SV-fx-82v"/> <viewControllerLayoutGuide type="bottom" id="9SV-fx-82v"/>
-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
-114
View File
@@ -1,114 +0,0 @@
//
// TemporarySettings.m
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "TemporarySettings.h"
#import "OnScreenControls.h"
@implementation TemporarySettings
- (id) initFromSettings:(Settings*)settings {
self = [self init];
self.parent = settings;
#if TARGET_OS_TV
// Apply default values from our Root.plist
NSString* settingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
NSDictionary* settingsData = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:@"Root.plist"]];
NSArray* preferences = [settingsData objectForKey:@"PreferenceSpecifiers"];
NSMutableDictionary* defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for (NSDictionary* prefSpecification in preferences) {
NSString* key = [prefSpecification objectForKey:@"Key"];
if (key != nil) {
[defaultsToRegister setObject:[prefSpecification objectForKey:@"DefaultValue"] forKey:key];
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
self.bitrate = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"bitrate"]];
assert([self.bitrate intValue] != 0);
self.framerate = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"framerate"]];
assert([self.framerate intValue] != 0);
self.audioConfig = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"audioConfig"]];
assert([self.audioConfig intValue] != 0);
self.preferredCodec = (typeof(self.preferredCodec))[[NSUserDefaults standardUserDefaults] integerForKey:@"preferredCodec"];
self.enableYUV444 = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableYUV444"];
self.useFramePacing = [[NSUserDefaults standardUserDefaults] integerForKey:@"useFramePacing"] != 0;
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"];
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"];
self.optimizeGames = [[NSUserDefaults standardUserDefaults] boolForKey:@"optimizeGames"];
self.multiController = [[NSUserDefaults standardUserDefaults] boolForKey:@"multipleControllers"];
self.swapABXYButtons = [[NSUserDefaults standardUserDefaults] boolForKey:@"swapABXYButtons"];
self.btMouseSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"btMouseSupport"];
self.statsOverlay = [[NSUserDefaults standardUserDefaults] boolForKey:@"statsOverlay"];
NSInteger _screenSize = [[NSUserDefaults standardUserDefaults] integerForKey:@"streamResolution"];
switch (_screenSize) {
case 0:
self.height = [NSNumber numberWithInteger:720];
self.width = [NSNumber numberWithInteger:1280];
break;
case 1:
self.height = [NSNumber numberWithInteger:1080];
self.width = [NSNumber numberWithInteger:1920];
break;
case 2:
self.height = [NSNumber numberWithInteger:2160];
self.width = [NSNumber numberWithInteger:3840];
break;
case 3:
self.height = [NSNumber numberWithInteger:1440];
self.width = [NSNumber numberWithInteger:2560];
break;
default:
abort();
}
self.onscreenControls = [NSNumber numberWithInteger:OnScreenControlsLevelOff];
#else
self.bitrate = settings.bitrate;
self.framerate = settings.framerate;
self.height = settings.height;
self.width = settings.width;
self.audioConfig = settings.audioConfig;
self.preferredCodec = settings.preferredCodec;
self.enableYUV444 = settings.enableYUV444;
self.useFramePacing = settings.useFramePacing;
self.playAudioOnPC = settings.playAudioOnPC;
self.enableHdr = settings.enableHdr;
self.optimizeGames = settings.optimizeGames;
self.multiController = settings.multiController;
self.swapABXYButtons = settings.swapABXYButtons;
self.onscreenControls = settings.onscreenControls;
self.reverseMouseWheelDirection = settings.reverseMouseWheelDirection;
self.largerStickLR1 = settings.largerStickLR1;
self.btMouseSupport = settings.btMouseSupport;
// self.absoluteTouchMode = settings.absoluteTouchMode;
self.touchMode = settings.touchMode;
self.statsOverlayLevel = settings.statsOverlayLevel;
self.statsOverlayEnabled = settings.statsOverlayEnabled;
self.keyboardToggleFingers = settings.keyboardToggleFingers;
self.oscLayoutToolFingers = settings.oscLayoutToolFingers;
self.slideToSettingsScreenEdge = settings.slideToSettingsScreenEdge;
self.slideToSettingsDistance = settings.slideToSettingsDistance;
self.liftStreamViewForKeyboard = settings.liftStreamViewForKeyboard;
self.showKeyboardToolbar = settings.showKeyboardToolbar;
self.oscTapExlusionAreaSize = settings.oscTapExlusionAreaSize;
self.touchPointerVelocityFactor = settings.touchPointerVelocityFactor;
self.mousePointerVelocityFactor = settings.mousePointerVelocityFactor;
self.pointerVelocityModeDivider = settings.pointerVelocityModeDivider;
self.unlockDisplayOrientation = settings.unlockDisplayOrientation;
self.resolutionSelected = settings.resolutionSelected;
self.externalDisplayMode = settings.externalDisplayMode;
self.localMousePointerMode = settings.localMousePointerMode;
#endif
self.uniqueId = settings.uniqueId;
return self;
}
@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
-492
View File
@@ -1,492 +0,0 @@
//
// KeyManager.swift
// Moonlight-ZWM
//
// Created by ZWM on 2024/7/23.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
import Foundation
import UIKit
// Define the RemoteCommand class
@objc public class RemoteCommand: NSObject, NSSecureCoding {
// MARK: - NSSecureCoding
public static var supportsSecureCoding: Bool {
return true
}
// MARK: - Properties
@objc var keyboardCmdString: String
@objc var alias: String
// MARK: - Initialization
init(keyboardCmdString: String, alias: String) {
self.keyboardCmdString = keyboardCmdString
self.alias = alias
}
// MARK: - NSSecureCoding
required public init?(coder: NSCoder) {
guard let keyboardCmdString = coder.decodeObject(of: NSString.self, forKey: "keyboardCmdString") as String?,
let alias = coder.decodeObject(of: NSString.self, forKey: "alias") as String? else {
return nil
}
self.keyboardCmdString = keyboardCmdString
self.alias = alias
}
public func encode(with coder: NSCoder) {
coder.encode(keyboardCmdString, forKey: "keyboardCmdString")
coder.encode(alias, forKey: "alias")
}
}
// Define the CommandManager class
@objc public class CommandManager: NSObject {
@objc public static let shared = CommandManager()
@objc public static let mouseButtonMappings: [String: Int32] = ["M_LEFT" : BUTTON_LEFT,
"M_MIDDLE" : BUTTON_MIDDLE,
"M_RIGHT" : BUTTON_RIGHT,
"M_X1" : BUTTON_X1,
"M_X2" : BUTTON_X2]
@objc public static let oscButtonMappings: [String: Int32] = ["OSCA" : A_FLAG,
"OSCB" : B_FLAG,
"OSCX" : X_FLAG,
"OSCY" : Y_FLAG,
"OSCL1" : LB_FLAG,
"OSCR1" : RB_FLAG,
"OSCL3" : LS_CLK_FLAG,
"OSCR3" : RS_CLK_FLAG,
"OSCSTART" : PLAY_FLAG,
"OSCSELECT" : BACK_FLAG,
"OSCUP" : UP_FLAG,
"OSCDOWN" : DOWN_FLAG,
"OSCLEFT" : LEFT_FLAG,
"OSCRIGHT" : RIGHT_FLAG,
"OSCL2" : 0,
"OSCR2" : 0]
@objc public static let oscRectangleButtonCmds: [String] = [
"OSCUP",
"OSCDOWN",
"OSCLEFT",
"OSCRIGHT",
"OSCSTART",
"OSCSELECT"]
@objc public static let touchPadCmds: [String] = ["LSVPAD", "RSVPAD", "LSPAD", "RSPAD", "MOUSEPAD", "DPAD", "WASDPAD", "ARROWPAD"]
@objc public static let stickTouchPads: [String] = ["LSVPAD", "RSVPAD", "LSPAD", "RSPAD"]
@objc public static let nonVectorStickPads: [String] = ["LSPAD", "RSPAD"]
@objc public static let specialOverlayButtonCmds: [String] = ["SETTINGS"]
static let keyboardButtonMappings: [String: Int16] = [
// Windows Key Codes
"CTRL": 0x11, // VK_CONTROL
"SHIFT": 0x10, // VK_SHIFT
"ALT": 0x12, // VK_MENU
"F1": 0x70, // VK_F1
"F2": 0x71, // VK_F2
"F3": 0x72, // VK_F3
"F4": 0x73, // VK_F4
"F5": 0x74, // VK_F5
"F6": 0x75, // VK_F6
"F7": 0x76, // VK_F7
"F8": 0x77, // VK_F8
"F9": 0x78, // VK_F9
"F10": 0x79, // VK_F10
"F11": 0x7A, // VK_F11
"F12": 0x7B, // VK_F12
"A": 0x41, // 'A' key
"B": 0x42, // 'B' key
"C": 0x43, // 'C' key
"D": 0x44, // 'D' key
"E": 0x45, // 'E' key
"F": 0x46, // 'F' key
"G": 0x47, // 'G' key
"H": 0x48, // 'H' key
"I": 0x49, // 'I' key
"J": 0x4A, // 'J' key
"K": 0x4B, // 'K' key
"L": 0x4C, // 'L' key
"M": 0x4D, // 'M' key
"N": 0x4E, // 'N' key
"O": 0x4F, // 'O' key
"P": 0x50, // 'P' key
"Q": 0x51, // 'Q' key
"R": 0x52, // 'R' key
"S": 0x53, // 'S' key
"T": 0x54, // 'T' key
"U": 0x55, // 'U' key
"V": 0x56, // 'V' key
"W": 0x57, // 'W' key
"X": 0x58, // 'X' key
"Y": 0x59, // 'Y' key
"Z": 0x5A, // 'Z' key
"0": 0x30, // '0' key
"1": 0x31, // '1' key
"2": 0x32, // '2' key
"3": 0x33, // '3' key
"4": 0x34, // '4' key
"5": 0x35, // '5' key
"6": 0x36, // '6' key
"7": 0x37, // '7' key
"8": 0x38, // '8' key
"9": 0x39, // '9' key
"ESC": 0x1B, // VK_ESCAPE
"SPACE": 0x20, // VK_SPACE
"ENTER": 0x0D, // VK_RETURN
"TAB": 0x09, // VK_TAB
"BACKSPACE": 0x08, // VK_BACK
"INSERT": 0x2D, // VK_INSERT
"DEL": 0x2E, // VK_DELETE
"HOME": 0x24, // VK_HOME
"END": 0x23, // VK_END
"PG_UP": 0x21, // VK_PRIOR
"PG_DOWN": 0x22, // VK_NEXT
"UP_ARROW": 0x26, // VK_UP
"DOWN_ARROW": 0x28, // VK_DOWN
"LEFT_ARROW": 0x25, // VK_LEFT
"RIGHT_ARROW": 0x27, // VK_RIGHT
"NUM_LCK": 0x90, // VK_NUMLOCK
"SCR_LCK": 0x91, // VK_SCROLL
"CAPS_LOCK": 0x14, // VK_CAPITAL
"PAUSE": 0x13, // VK_PAUSE
"PR_SCR": 0x2C, // VK_SNAPSHOT
"NUMPAD0": 0x60, // VK_NUMPAD0
"NUMPAD1": 0x61, // VK_NUMPAD1
"NUMPAD2": 0x62, // VK_NUMPAD2
"NUMPAD3": 0x63, // VK_NUMPAD3
"NUMPAD4": 0x64, // VK_NUMPAD4
"NUMPAD5": 0x65, // VK_NUMPAD5
"NUMPAD6": 0x66, // VK_NUMPAD6
"NUMPAD7": 0x67, // VK_NUMPAD7
"NUMPAD8": 0x68, // VK_NUMPAD8
"NUMPAD9": 0x69, // VK_NUMPAD9
"MULTIPLY": 0x6A, // VK_MULTIPLY
"ADD": 0x6B, // VK_ADD
"SUBTRACT": 0x6D, // VK_SUBTRACT
"DECIMAL": 0x6E, // VK_DECIMAL
"DIVIDE": 0x6F, // VK_DIVIDE
"SEMI_COLON": 0xBA, // VK_OEM_1
"EQUALS": 0xBB, // VK_OEM_PLUS
"COMMA": 0xBC, // VK_OEM_COMMA
"MINUS": 0xBD, // VK_OEM_MINUS
"PERIOD": 0xBE, // VK_OEM_PERIOD
"FORWARD_SLASH": 0xBF, // VK_OEM_2
"GRAVE_ACCENT": 0xC0, // VK_OEM_3
"OPEN_BRACKET": 0xDB, // VK_OEM_4
"BACKSLASH": 0xDC, // VK_OEM_5
"CLOSE_BRACKET": 0xDD, // VK_OEM_6
"SINGLE_QUOTE": 0xDE, // VK_OEM_7
"VOLUME_MUTE": 0xAD, // VK_VOLUME_MUTE
"VOLUME_DOWN": 0xAE, // VK_VOLUME_DOWN
"VOLUME_UP": 0xAF, // VK_VOLUME_UP
"MEDIA_NEXT": 0xB0, // VK_MEDIA_NEXT_TRACK
"MEDIA_PREV": 0xB1, // VK_MEDIA_PREV_TRACK
"MEDIA_STOP": 0xB2, // VK_MEDIA_STOP
"MEDIA_PLAY_PAUSE": 0xB3, // VK_MEDIA_PLAY_PAUSE
"LAUNCH_MAIL": 0xB4, // VK_LAUNCH_MAIL
"LAUNCH_MEDIA_SELECT": 0xB5, // VK_LAUNCH_MEDIA_SELECT
"LAUNCH_APP1": 0xB6, // VK_LAUNCH_APP1
"LAUNCH_APP2": 0xB7, // VK_LAUNCH_APP2
"WIN": 0x5B,
"LEFT_WIN": 0x5B, // VK_LWIN
"RIGHT_WIN": 0x5C, // VK_RWIN
"APPS": 0x5D, // VK_APPS
// macOS Key Codes
"CMD": 0x37, // ⌘ Command
"OPT": 0x3A, // ⌥ Option
"CONTROL": 0x3B, // ⌃ Control
"SHIFT_MAC": 0x38, // ⇧ Shift
"FUNCTION": 0x3F, // fn
"DELETE_MAC": 0x75, // Forward Delete
"RETURN_MAC": 0x24, // Return
"ENTER_MAC": 0x4C, // Enter
"ESCAPE_MAC": 0x35, // Escape
"TAB_MAC": 0x30, // Tab
"SPACE_MAC": 0x31, // Space
"UP_ARROW_MAC": 0x7E, // Up Arrow
"DOWN_ARROW_MAC": 0x7D, // Down Arrow
"LEFT_ARROW_MAC": 0x7B, // Left Arrow
"RIGHT_ARROW_MAC": 0x7C, // Right Arrow
"F1_MAC": 0x7A, // F1
"F2_MAC": 0x78, // F2
"F3_MAC": 0x63, // F3
"F4_MAC": 0x76, // F4
"F5_MAC": 0x60, // F5
"F6_MAC": 0x61, // F6
"F7_MAC": 0x62, // F7
"F8_MAC": 0x64, // F8
"F9_MAC": 0x65, // F9
"F10_MAC": 0x6D, // F10
"F11_MAC": 0x67, // F11
"F12_MAC": 0x6F, // F12
"0_MAC": 0x52, // 0
"1_MAC": 0x53, // 1
"2_MAC": 0x54, // 2
"3_MAC": 0x55, // 3
"4_MAC": 0x56, // 4
"5_MAC": 0x57, // 5
"6_MAC": 0x58, // 6
"7_MAC": 0x59, // 7
"8_MAC": 0x5A, // 8
"9_MAC": 0x5B, // 9
"NUMPAD0_MAC": 0x4F, // Numpad 0
"NUMPAD1_MAC": 0x50, // Numpad 1
"NUMPAD2_MAC": 0x51, // Numpad 2
"NUMPAD3_MAC": 0x52, // Numpad 3
"NUMPAD4_MAC": 0x53, // Numpad 4
"NUMPAD5_MAC": 0x54, // Numpad 5
"NUMPAD6_MAC": 0x55, // Numpad 6
"NUMPAD7_MAC": 0x56, // Numpad 7
"NUMPAD8_MAC": 0x57, // Numpad 8
"NUMPAD9_MAC": 0x58, // Numpad 9
"NUMPAD_ADD_MAC": 0x45, // Numpad Add
"NUMPAD_SUBTRACT_MAC": 0x4A, // Numpad Subtract
"NUMPAD_MULTIPLY_MAC": 0x43, // Numpad Multiply
"NUMPAD_DIVIDE_MAC": 0x4B, // Numpad Divide
"NUMPAD_DECIMAL_MAC": 0x41, // Numpad Decimal
]
private var commands: [RemoteCommand] = []
public weak var viewController: CommandManagerViewController?
private override init() {
super.init()
loadCommands()
}
@objc static func presetDefaultCommands() {
let defaults = UserDefaults.standard
//if true { // save default entries if the data is empty.
if defaults.data(forKey: "savedCommands") == nil { // save default entries if the data is empty.
let defaultCommands: [RemoteCommand] = [
RemoteCommand(keyboardCmdString: "WIN", alias: "WIN"),
RemoteCommand(keyboardCmdString: "F11", alias: "F11"),
RemoteCommand(keyboardCmdString: "ESC", alias: "ESC"),
RemoteCommand(keyboardCmdString: "CTRL+SHIFT+ESC", alias: "任务管理器(Task Manager)"),
RemoteCommand(keyboardCmdString: "ALT+F1", alias: "N卡截图(Nvidia Screenshot)"),
RemoteCommand(keyboardCmdString: "ALT+F9", alias: "N卡录屏(Nvidia Screen Recording)"),
RemoteCommand(keyboardCmdString: "ALT+F4", alias: "关闭窗口(ALT+F4)"),
RemoteCommand(keyboardCmdString: "CTRL+A", alias: "全选(Select All)"),
RemoteCommand(keyboardCmdString: "CTRL+C", alias: "复制(Copy)"),
RemoteCommand(keyboardCmdString: "CTRL+V", alias: "粘贴(Paste)"),
RemoteCommand(keyboardCmdString: "WIN+D", alias: "切换桌面(Switch to Desktop)"),
RemoteCommand(keyboardCmdString: "WIN+P", alias: "多显模式(Project)"),
RemoteCommand(keyboardCmdString: "WIN+G", alias: "Xbox Game Bar"),
RemoteCommand(keyboardCmdString: "SHIFT+TAB", alias: "Steam Overlay"),
]
let data = try? NSKeyedArchiver.archivedData(withRootObject: defaultCommands, requiringSecureCoding: false)
defaults.set(data, forKey: "savedCommands")
}
}
@objc public func createTestKeyMappings() -> [String: Int16] {
return CommandManager.keyboardButtonMappings
}
// extractKeyStrings from keyboardCMDString
@objc public func extractKeyStringsFromComboCommand(from input: String) -> [String]? {
let keys = CommandManager.keyboardButtonMappings.keys.joined(separator: "|")
let pattern = "^(?:(\(keys))(?:\\+(\(keys))*)*)$"
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
print("Failed to create regex")
return nil
}
let range = NSRange(location: 0, length: input.utf16.count)
guard let match = regex.firstMatch(in: input, options: [], range: range) else {
print("No match found for input: \(input)")
return nil
}
print("Regex matched for input: \(input)")
let matchedString = (input as NSString).substring(with: match.range(at: 0))
let keyStrings = matchedString.split(separator: "+").map { String($0) }
guard !keyStrings.isEmpty else {
print("No key strings found in the matched string")
return nil
}
var validKeyStrings: [String] = []
for key in keyStrings {
if CommandManager.keyboardButtonMappings.keys.contains(key) {
validKeyStrings.append(key)
} else {
print(" '\(key)' is not defined in key mappings")
return nil //treat any illegal string as a whole
}
}
if validKeyStrings.isEmpty {
print("No valid key strings found in the matched string")
return nil
}
for (index, key) in validKeyStrings.enumerated() {
print("Valid Key \(index): \(key)")
}
return validKeyStrings
}
//super combo key button strings
@objc public func extractKeyStringsFromComboKeys(from input: String) -> [String]? {
let combinedStrings = [CommandManager.keyboardButtonMappings.keys.map { $0 as String },
CommandManager.oscButtonMappings.keys.map { $0 as String },
CommandManager.mouseButtonMappings.keys.map { $0 as String }]
.lazy // 零内存开销
.flatMap { $0 } // 三维展开
.map(String.init(describing:)) // 安全类型转换
let keys = combinedStrings.joined(separator: "|")
// print(keys)
let pattern = "^(?:(\(keys))(?:\\-(\(keys))*)*)$"
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
print("Failed to create regex")
return nil
}
let range = NSRange(location: 0, length: input.utf16.count)
guard let match = regex.firstMatch(in: input, options: [], range: range) else {
print("No match found for input: \(input)")
return nil
}
print("Regex matched for input: \(input)")
let matchedString = (input as NSString).substring(with: match.range(at: 0))
let keyStrings = matchedString.split(separator: "-").map { String($0) }
guard !keyStrings.isEmpty else {
print("No key strings found in the matched string")
return nil
}
var validKeyStrings: [String] = []
for key in keyStrings {
validKeyStrings.append(key)
}
if validKeyStrings.isEmpty {
print("No valid key strings found in the matched string")
return nil
}
for (index, key) in validKeyStrings.enumerated() {
print("Valid Key \(index): \(key)")
}
return validKeyStrings
}
@objc public func addCommand(_ command: RemoteCommand) -> Bool {
command.keyboardCmdString = command.keyboardCmdString.uppercased() // convert all letters to upper case
if(command.alias.trimmingCharacters(in: .whitespacesAndNewlines).count == 0) {command.alias = command.keyboardCmdString} // copy cmd string as alias when alias is empty
let keyStrings = extractKeyStringsFromComboCommand(from: command.keyboardCmdString)
if (keyStrings == nil) {return false} // in case of non-keyboard command strings, return false
commands.append(command)
saveCommands()
viewController?.reloadTableView() // don't know why but this reload has to be called from the CommandManager, doesn't work by calling it in the viewcontroller, probably related with the dialog box.
return true
}
@objc public func deleteCommand(at index: Int) {
guard index >= 0 && index < commands.count else {
return
}
commands.remove(at: index)
saveCommands()
}
@objc public func getAllCommands() -> [RemoteCommand] {
return commands
}
private func loadCommands() {
if let savedCommandsData = UserDefaults.standard.data(forKey: "savedCommands") {
do {
// Attempt to unarchive the data into an array of RemoteCommand
if let savedCommands = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, RemoteCommand.self], from: savedCommandsData) as? [RemoteCommand] {
// Assign the unarchived commands to your property
print(" Assign the unarchived commands to your property ")
commands = savedCommands
} else {
// Handle the case where the data could not be unarchived into the expected type
print("Data could not be unarchived into [RemoteCommand]")
}
} catch {
// Handle any errors that occur during unarchiving
print("Failed to unarchive savedCommands with error: \(error)")
}
}
}
private func saveCommands() {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: commands, requiringSecureCoding: false) {
UserDefaults.standard.set(data, forKey: "savedCommands")
}
}
@objc public func sendKeyComboCommand(keyboardCmdStrings: [String], delay: TimeInterval = 0.2, index: Int = 0) { // we need a large delay for WAN streaming
// 如果已处理完所有按键,则开始释放按键
guard index < keyboardCmdStrings.count else {
// 释放按键
for keyStr in keyboardCmdStrings.reversed() { // 从后往前释放按键
if let keyCode = CommandManager.keyboardButtonMappings[keyStr] {
LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_UP), 0) // 释放按键
}
}
return
}
// 获取当前按键的映射值
if let keyCode = CommandManager.keyboardButtonMappings[keyboardCmdStrings[index]] {
// 发送当前按键的按下事件
LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_DOWN), 0)
// 延迟后递归处理下一个按键
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings, delay: delay, index: index + 1)
}
} else {
print("No mapping found for \(keyboardCmdStrings[index])")
// 如果当前按键没有映射,跳过当前按键并继续下一个
self.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings, delay: delay, index: index + 1)
}
}
@objc public func sendKeyComboDown(keyboardCmdStrings: [String]) { // we need a large delay for WAN streaming
for keyStr in keyboardCmdStrings {
if let keyCode = CommandManager.keyboardButtonMappings[keyStr] {
LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_DOWN), 0) // 释放按键
}
}
}
@objc public func sendKeyComboUp(keyboardCmdStrings: [String]) { // we need a large delay for WAN streaming
for keyStr in keyboardCmdStrings {
if let keyCode = CommandManager.keyboardButtonMappings[keyStr] {
LiSendKeyboardEvent(keyCode, Int8(KEY_ACTION_UP), 0) // 释放按键
}
}
return
}
}
-55
View File
@@ -1,55 +0,0 @@
//
// ControllerSupport.h
// Moonlight
//
// Created by Cameron Gutman on 10/20/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "StreamConfiguration.h"
#import "Controller.h"
@class OnScreenControls;
@protocol ControllerSupportDelegate <NSObject>
- (void) gamepadPresenceChanged;
- (void) mousePresenceChanged;
- (void) streamExitRequested;
@end
@interface ControllerSupport : NSObject
-(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate;
-(void) connectionEstablished;
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc;
-(void) cleanup;
-(Controller*) getOscController;
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y;
-(void) updateRightStick:(Controller*)controller x:(short)x y:(short)y;
-(void) updateLeftTrigger:(Controller*)controller left:(unsigned char)left;
-(void) updateRightTrigger:(Controller*)controller right:(unsigned char)right;
-(void) updateTriggers:(Controller*)controller left:(unsigned char)left right:(unsigned char)right;
-(void) updateButtonFlags:(Controller*)controller flags:(int)flags;
-(void) setButtonFlag:(Controller*)controller flags:(int)flags;
-(void) clearButtonFlag:(Controller*)controller flags:(int)flags;
-(void) updateFinished:(Controller*)controller;
-(void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor;
-(void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger;
-(void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz;
-(void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b;
-(uint16_t) getActiveGamepadMask;
+(int) getConnectedGamepadMask:(StreamConfiguration*)streamConfig;
-(NSUInteger) getConnectedGamepadCount;
@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,69 +0,0 @@
//
// OSCProfilesManager.h
// Moonlight
//
// Created by Long Le on 1/1/23.
// Copyright © 2023 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "OSCProfile.h"
NS_ASSUME_NONNULL_BEGIN
/**
This singleton object can be accessed from any class and provides methods to get and set on screen controller profile related data.
Note that the implementation file contains a number of 'Helper' methods. These helper methods are only used in this class's implementation file and help to reduce re-writing large blocks of code that are called multiple times throughout the file
*/
@interface OSCProfilesManager : NSObject
+ (OSCProfilesManager *) sharedManager;
+ (void) setOnScreenWidgetViewsSet:(NSMutableSet* )set;
#pragma mark - Getters
/**
* Returns an array of decoded profile objects
*/
- (NSMutableArray *) getAllProfiles;
/**
* Returns the OSC Profile that is currently selected to be displayed on screen during game streaming
*/
- (OSCProfile *) getSelectedProfile;
/**
* Returns the index of the 'selected' profile within the array it's in
*/
- (NSInteger) getIndexOfSelectedProfile;
#pragma mark - Setters
/**
* Sets the profile object with the particular 'name' as the selected profile to be displayed on screen during game streaming
*/
- (void) setProfileToSelected:(NSString *)name;
/**
* Saves a profile object with a particular 'name' and an array of button layers (the CALayer button layers are the objects currently visible on screen) to persistent storage
*/
- (void) saveProfileWithName:(NSString*)name andButtonLayers:(NSMutableArray *)buttonLayers;
- (bool) updateSelectedProfile:(NSMutableArray *) oscButtonLayers;
/**
* Delete current selected profile.
*/
- (void) deleteCurrentSelectedProfile;
#pragma mark - Queries
/**
* Lets the caller of this method know whether a profile with a given name already exists in persistent storage
*/
- (BOOL) profileNameAlreadyExist:(NSString*)name;
@end
NS_ASSUME_NONNULL_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,52 +0,0 @@
//
// OnScreenButtonState.h
// Moonlight
//
// Created by Long Le on 10/20/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
This object is used to save positional and visibility information for any particular on screen virtual controller button.
We are able to associate this 'OnScreenButtonState' object and its corresponding CALayer onscreen controller button by setting their 'name' properties equal; in our particular case we give them descriptive names such as 'aButton', 'upButton', 'leftStick', etc. By keeping references to the 19 on screen controller buttons (CALayers) in an array, and creating 19 'OnScreenButtonState' objects with names corresponding to these 19 CALayers and keeping them in an array, we can iterate through both arrays to find OSC buttons (CALayers) with the same name as one of the 'OnScreenButtonStateObjects' and then set the CALayer on screen control button's 'position' and 'hidden' property according to the value of the 'OnScreenButtonState' objects 'position' and 'isHidden' properties.
Naturally we would like the user to be able to save their controller layout configurations so that they can load them between app launches, so we adopt encoding/decoding related protocols so we encode these 'OnScreenButtonState' objects and save them to NSUserDefaults
*/
// OS Button state info obj to be modified
@interface OnScreenButtonState : NSObject <NSCoding, NSSecureCoding>
@property NSString *name;
@property NSString *alias;
@property CGPoint position;
@property (nonatomic, assign) BOOL isHidden;
@property (nonatomic, assign) uint8_t buttonType;
@property (nonatomic, assign) NSTimeInterval timestamp;
@property (nonatomic, assign) CGFloat widthFactor; // for OnScreenWidgetView
@property (nonatomic, assign) CGFloat heightFactor; // for OnScreenWidgetView
@property (nonatomic, assign) CGFloat borderWidth; // for OnScreenWidgetView
@property (nonatomic, assign) CGFloat sensitivityFactor; // for OnScreenWidgetView
@property (nonatomic, assign) CGFloat stickIndicatorOffset; // for OnScreenWidgetView
@property (nonatomic, assign) CGFloat oscLayerSizeFactor; // for OnScreenController CALayer
@property (nonatomic, assign) CGFloat backgroundAlpha; // for OnScreenController CALayer
// @property (nonatomic, assign) BOOL hasValidPosition;
typedef NS_ENUM(NSInteger, OnScreenButtonType) {
LegacyOscButton,
CustomOnScreenWidget
};
- (id) initWithButtonName:(NSString*)name buttonType:(uint8_t)buttonType andPosition:(CGPoint)position;
+ (BOOL) supportsSecureCoding;
- (void) encodeWithCoder:(NSCoder*)encoder;
- (id) initWithCoder:(NSCoder*)decoder;
@end
NS_ASSUME_NONNULL_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
-170
View File
@@ -1,170 +0,0 @@
//
// HapticContext.m
// Moonlight
//
// Created by Cameron Gutman on 9/17/20.
// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved.
//
#import "HapticContext.h"
@import CoreHaptics;
@import GameController;
@implementation HapticContext {
GCControllerPlayerIndex _playerIndex;
CHHapticEngine* _hapticEngine API_AVAILABLE(ios(13.0), tvos(14.0));
id<CHHapticPatternPlayer> _hapticPlayer API_AVAILABLE(ios(13.0), tvos(14.0));
BOOL _playing;
}
-(void)cleanup API_AVAILABLE(ios(14.0), tvos(14.0)) {
if (_hapticPlayer != nil) {
[_hapticPlayer cancelAndReturnError:nil];
_hapticPlayer = nil;
}
if (_hapticEngine != nil) {
[_hapticEngine stopWithCompletionHandler:nil];
_hapticEngine = nil;
}
}
-(void)setMotorAmplitude:(unsigned short)amplitude API_AVAILABLE(ios(14.0), tvos(14.0)) {
NSError* error;
// Check if the haptic engine died
if (_hapticEngine == nil) {
return;
}
// Stop the effect entirely if the amplitude is 0
if (amplitude == 0) {
if (_playing) {
[_hapticPlayer stopAtTime:0 error:&error];
_playing = NO;
}
return;
}
if (_hapticPlayer == nil) {
// We must initialize the intensity to 1.0f because the dynamic parameters are multiplied by this value before being applied
CHHapticEventParameter* intensityParameter = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
CHHapticEvent* hapticEvent = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObject:intensityParameter] relativeTime:0 duration:GCHapticDurationInfinite];
CHHapticPattern* hapticPattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:hapticEvent] parameters:[[NSArray alloc] init] error:&error];
if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic pattern creation failed: %@", _playerIndex, error);
return;
}
_hapticPlayer = [_hapticEngine createPlayerWithPattern:hapticPattern error:&error];
if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic player creation failed: %@", _playerIndex, error);
return;
}
}
CHHapticDynamicParameter* intensityParameter = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:amplitude / 65535.0f relativeTime:0];
[_hapticPlayer sendParameters:[NSArray arrayWithObject:intensityParameter] atTime:CHHapticTimeImmediate error:&error];
if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic player parameter update failed: %@", _playerIndex, error);
return;
}
if (!_playing) {
[_hapticPlayer startAtTime:0 error:&error];
if (error != nil) {
_hapticPlayer = nil;
Log(LOG_W, @"Controller %d: Haptic playback start failed: %@", _playerIndex, error);
return;
}
_playing = YES;
}
}
-(id) initWithGamepad:(GCController*)gamepad locality:(GCHapticsLocality)locality API_AVAILABLE(ios(14.0), tvos(14.0)) {
if (gamepad.haptics == nil) {
Log(LOG_W, @"Controller %d does not support haptics", gamepad.playerIndex);
return nil;
}
if (![[gamepad.haptics supportedLocalities] containsObject:locality]) {
Log(LOG_W, @"Controller %d does not support haptic locality: %@", gamepad.playerIndex, locality);
return nil;
}
_playerIndex = gamepad.playerIndex;
_hapticEngine = [gamepad.haptics createEngineWithLocality:locality];
NSError* error;
[_hapticEngine startAndReturnError:&error];
if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic engine failed to start: %@", gamepad.playerIndex, error);
return nil;
}
__weak typeof(self) weakSelf = self;
_hapticEngine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
HapticContext* me = weakSelf;
if (me == nil) {
return;
}
Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason);
me->_hapticPlayer = nil;
me->_hapticEngine = nil;
me->_playing = NO;
};
_hapticEngine.resetHandler = ^{
HapticContext* me = weakSelf;
if (me == nil) {
return;
}
Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex);
me->_hapticPlayer = nil;
me->_playing = NO;
[me->_hapticEngine startAndReturnError:nil];
};
return self;
}
+(HapticContext*) createContextForHighFreqMotor:(GCController*)gamepad {
if (@available(iOS 14.0, tvOS 14.0, *)) {
return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityRightHandle];
}
else {
return nil;
}
}
+(HapticContext*) createContextForLowFreqMotor:(GCController*)gamepad {
if (@available(iOS 14.0, tvOS 14.0, *)) {
return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityLeftHandle];
}
else {
return nil;
}
}
+(HapticContext*) createContextForLeftTrigger:(GCController*)gamepad {
if (@available(iOS 14.0, tvOS 14.0, *)) {
return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityLeftTrigger];
}
else {
return nil;
}
}
+(HapticContext*) createContextForRightTrigger:(GCController*)gamepad {
if (@available(iOS 14.0, tvOS 14.0, *)) {
return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityRightTrigger];
}
else {
return nil;
}
}
@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
-22
View File
@@ -1,22 +0,0 @@
//
// NativeTouchHandler.h
// Moonlight
//
// Created by ZWM on 2024/6/16.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
#import "StreamView.h"
#import "DataManager.h"
#include <Limelight.h>
NS_ASSUME_NONNULL_BEGIN
@interface PureNativeTouchHandler : UIResponder
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings;
@end
NS_ASSUME_NONNULL_END
-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
-58
View File
@@ -1,58 +0,0 @@
//
// StreamView.h
// Moonlight
//
// Created by Cameron Gutman on 10/19/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "ControllerSupport.h"
#import "OnScreenControls.h"
#import "Moonlight-Swift.h"
#import "StreamConfiguration.h"
@protocol UserInteractionDelegate <NSObject>
- (void) userInteractionBegan;
- (void) userInteractionEnded;
- (void) streamExitRequested;
- (void) toggleStatsOverlay;
- (void) localMousePointerMode;
- (void) toggleMouseVisible;
@end
#if TARGET_OS_TV
@interface StreamView : UIView <X1KitMouseDelegate, UITextFieldDelegate>
#else
@interface StreamView : UIView <X1KitMouseDelegate, UITextFieldDelegate, UIPointerInteractionDelegate>
#endif
@property (assign, nonatomic) UIView* streamFrameTopLayerView;
- (void) setupStreamView:(ControllerSupport*)controllerSupport
interactionDelegate:(id<UserInteractionDelegate>)interactionDelegate
config:(StreamConfiguration*)streamConfig
streamFrameTopLayerView:(UIView* )topLayerView
;
- (void) showOnScreenControls;
- (void) setOnScreenControls;
- (void) disableOnScreenControls;
- (void) reloadOnScreenControlsRealtimeWith:(ControllerSupport*)controllerSupport
andConfig:(StreamConfiguration*)streamConfig;
- (void) reloadOnScreenControlsWith:(ControllerSupport*)controllerSupport
andConfig:(StreamConfiguration*)streamConfig;
- (void) clearOnScreenKeyboardButtons;
- (void) reloadOnScreenWidgetViews;
- (CGSize) getVideoAreaSize;
- (CGPoint) adjustCoordinatesForVideoArea:(CGPoint)point;
- (uint16_t)getRotationFromAzimuthAngle:(float)azimuthAngle;
- (OnScreenControlsLevel) getCurrentOscState;
#if !TARGET_OS_TV
- (void) updateCursorLocation:(CGPoint)location isMouse:(BOOL)isMouse;
#endif
@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
-7
View File
@@ -1,7 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#include <Limelight.h>
#import "OnScreenControls.h"
// #import "RelativeTouchHandler.h"
-34
View File
@@ -1,34 +0,0 @@
//
// Connection.h
// Moonlight
//
// Created by Diego Waxemberg on 1/19/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "VideoDecoderRenderer.h"
#import "StreamConfiguration.h"
#define CONN_TEST_SERVER "ios.conntest.moonlight-stream.org"
typedef struct {
CFTimeInterval startTime;
CFTimeInterval endTime;
int totalFrames;
int receivedFrames;
int networkDroppedFrames;
int totalHostProcessingLatency;
int framesWithHostProcessingLatency;
int maxHostProcessingLatency;
int minHostProcessingLatency;
} video_stats_t;
@interface Connection : NSOperation <NSStreamDelegate>
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks;
-(void) terminate;
-(void) main;
-(BOOL) getVideoStats:(video_stats_t*)stats;
-(NSString*) getActiveCodecName;
@end
-508
View File
@@ -1,508 +0,0 @@
//
// Connection.m
// Moonlight
//
// Created by Diego Waxemberg on 1/19/14.
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
//
#import "Connection.h"
#import "Utils.h"
#import <VideoToolbox/VideoToolbox.h>
#define SDL_MAIN_HANDLED
#import <SDL.h>
#include "Limelight.h"
#include "opus_multistream.h"
@implementation Connection {
SERVER_INFORMATION _serverInfo;
STREAM_CONFIGURATION _streamConfig;
CONNECTION_LISTENER_CALLBACKS _clCallbacks;
DECODER_RENDERER_CALLBACKS _drCallbacks;
AUDIO_RENDERER_CALLBACKS _arCallbacks;
char _hostString[256];
char _appVersionString[32];
char _gfeVersionString[32];
char _rtspSessionUrl[128];
}
static NSLock* initLock;
static OpusMSDecoder* opusDecoder;
static id<ConnectionCallbacks> _callbacks;
static int lastFrameNumber;
static int activeVideoFormat;
static video_stats_t currentVideoStats;
static video_stats_t lastVideoStats;
static NSLock* videoStatsLock;
static SDL_AudioDeviceID audioDevice;
static OPUS_MULTISTREAM_CONFIGURATION audioConfig;
static void* audioBuffer;
static int audioFrameSize;
static VideoDecoderRenderer* renderer;
int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags)
{
[renderer setupWithVideoFormat:videoFormat width:width height:height frameRate:redrawRate];
lastFrameNumber = 0;
activeVideoFormat = videoFormat;
memset(&currentVideoStats, 0, sizeof(currentVideoStats));
memset(&lastVideoStats, 0, sizeof(lastVideoStats));
return 0;
}
void DrStart(void)
{
[renderer start];
}
void DrStop(void)
{
[renderer stop];
}
-(BOOL) getVideoStats:(video_stats_t*)stats
{
// We return lastVideoStats because it is a complete 1 second window
[videoStatsLock lock];
if (lastVideoStats.endTime != 0) {
memcpy(stats, &lastVideoStats, sizeof(*stats));
[videoStatsLock unlock];
return YES;
}
// No stats yet
[videoStatsLock unlock];
return NO;
}
-(NSString*) getActiveCodecName
{
switch (activeVideoFormat)
{
case VIDEO_FORMAT_H264:
return @"H.264";
case VIDEO_FORMAT_H264_HIGH8_444:
return @"H.264 YUV444";
case VIDEO_FORMAT_H265:
return @"HEVC";
case VIDEO_FORMAT_H265_REXT8_444:
return @"HEVC YUV444";
case VIDEO_FORMAT_H265_MAIN10:
if (LiGetCurrentHostDisplayHdrMode()) {
return @"HEVC Main 10 HDR";
}
else {
return @"HEVC Main 10 SDR";
}
case VIDEO_FORMAT_H265_REXT10_444:
if (LiGetCurrentHostDisplayHdrMode()) {
return @"HEVC Main 10 YUV444 HDR";
}
else {
return @"HEVC Main 10 YUV444 SDR";
}
case VIDEO_FORMAT_AV1_MAIN8:
return @"AV1";
case VIDEO_FORMAT_AV1_HIGH8_444:
return @"AV1 YUV444";
case VIDEO_FORMAT_AV1_MAIN10:
if (LiGetCurrentHostDisplayHdrMode()) {
return @"AV1 10-bit HDR";
}
else {
return @"AV1 10-bit SDR";
}
case VIDEO_FORMAT_AV1_HIGH10_444:
if (LiGetCurrentHostDisplayHdrMode()) {
return @"AV1 10-bit YUV444 HDR";
}
else {
return @"AV1 10-bit YUV444 SDR";
}
default:
return @"UNKNOWN";
}
}
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
{
int offset = 0;
int ret;
unsigned char* data = (unsigned char*) malloc(decodeUnit->fullLength);
if (data == NULL) {
// A frame was lost due to OOM condition
return DR_NEED_IDR;
}
CFTimeInterval now = CACurrentMediaTime();
if (!lastFrameNumber) {
currentVideoStats.startTime = now;
lastFrameNumber = decodeUnit->frameNumber;
}
else {
// Flip stats roughly every second
if (now - currentVideoStats.startTime >= 1.0f) {
currentVideoStats.endTime = now;
[videoStatsLock lock];
lastVideoStats = currentVideoStats;
[videoStatsLock unlock];
memset(&currentVideoStats, 0, sizeof(currentVideoStats));
currentVideoStats.startTime = now;
}
// Any frame number greater than m_LastFrameNumber + 1 represents a dropped frame
currentVideoStats.networkDroppedFrames += decodeUnit->frameNumber - (lastFrameNumber + 1);
currentVideoStats.totalFrames += decodeUnit->frameNumber - (lastFrameNumber + 1);
lastFrameNumber = decodeUnit->frameNumber;
}
if (decodeUnit->frameHostProcessingLatency != 0) {
if (currentVideoStats.minHostProcessingLatency == 0 || decodeUnit->frameHostProcessingLatency < currentVideoStats.minHostProcessingLatency) {
currentVideoStats.minHostProcessingLatency = decodeUnit->frameHostProcessingLatency;
}
if (decodeUnit->frameHostProcessingLatency > currentVideoStats.maxHostProcessingLatency) {
currentVideoStats.maxHostProcessingLatency = decodeUnit->frameHostProcessingLatency;
}
currentVideoStats.framesWithHostProcessingLatency++;
currentVideoStats.totalHostProcessingLatency += decodeUnit->frameHostProcessingLatency;
}
currentVideoStats.receivedFrames++;
currentVideoStats.totalFrames++;
PLENTRY entry = decodeUnit->bufferList;
while (entry != NULL) {
// Submit parameter set NALUs directly since no copy is required by the decoder
if (entry->bufferType != BUFFER_TYPE_PICDATA) {
ret = [renderer submitDecodeBuffer:(unsigned char*)entry->data
length:entry->length
bufferType:entry->bufferType
decodeUnit:decodeUnit];
if (ret != DR_OK) {
free(data);
return ret;
}
}
else {
memcpy(&data[offset], entry->data, entry->length);
offset += entry->length;
}
entry = entry->next;
}
// This function will take our picture data buffer
return [renderer submitDecodeBuffer:data
length:offset
bufferType:BUFFER_TYPE_PICDATA
decodeUnit:decodeUnit];
}
int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int flags)
{
int err;
SDL_AudioSpec want, have;
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
Log(LOG_E, @"Failed to initialize audio subsystem: %s\n", SDL_GetError());
return -1;
}
SDL_zero(want);
want.freq = opusConfig->sampleRate;
want.format = AUDIO_S16;
want.channels = opusConfig->channelCount;
want.samples = opusConfig->samplesPerFrame;
audioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (audioDevice == 0) {
Log(LOG_E, @"Failed to open audio device: %s\n", SDL_GetError());
ArCleanup();
return -1;
}
audioConfig = *opusConfig;
audioFrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
audioBuffer = SDL_malloc(audioFrameSize);
if (audioBuffer == NULL) {
Log(LOG_E, @"Failed to allocate audio frame buffer");
ArCleanup();
return -1;
}
opusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
opusConfig->channelCount,
opusConfig->streams,
opusConfig->coupledStreams,
opusConfig->mapping,
&err);
if (opusDecoder == NULL) {
Log(LOG_E, @"Failed to create Opus decoder");
ArCleanup();
return -1;
}
// Start playback
SDL_PauseAudioDevice(audioDevice, 0);
return 0;
}
void ArCleanup(void)
{
if (opusDecoder != NULL) {
opus_multistream_decoder_destroy(opusDecoder);
opusDecoder = NULL;
}
if (audioDevice != 0) {
SDL_CloseAudioDevice(audioDevice);
audioDevice = 0;
}
if (audioBuffer != NULL) {
SDL_free(audioBuffer);
audioBuffer = NULL;
}
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
void ArDecodeAndPlaySample(char* sampleData, int sampleLength)
{
int decodeLen;
// Don't queue if there's already more than 30 ms of audio data waiting
// in Moonlight's audio queue.
if (LiGetPendingAudioDuration() > 30) {
return;
}
decodeLen = opus_multistream_decode(opusDecoder, (unsigned char *)sampleData, sampleLength,
(short*)audioBuffer, audioConfig.samplesPerFrame, 0);
if (decodeLen > 0) {
// Provide backpressure on the queue to ensure too many frames don't build up
// in SDL's audio queue.
while (SDL_GetQueuedAudioSize(audioDevice) / audioFrameSize > 10) {
SDL_Delay(1);
}
if (SDL_QueueAudio(audioDevice,
audioBuffer,
sizeof(short) * decodeLen * audioConfig.channelCount) < 0) {
Log(LOG_E, @"Failed to queue audio sample: %s\n", SDL_GetError());
}
}
}
void ClStageStarting(int stage)
{
[_callbacks stageStarting:LiGetStageName(stage)];
}
void ClStageComplete(int stage)
{
[_callbacks stageComplete:LiGetStageName(stage)];
}
void ClStageFailed(int stage, int errorCode)
{
[_callbacks stageFailed:LiGetStageName(stage) withError:errorCode portTestFlags:LiGetPortFlagsFromStage(stage)];
}
void ClConnectionStarted(void)
{
[_callbacks connectionStarted];
}
void ClConnectionTerminated(int errorCode)
{
[_callbacks connectionTerminated: errorCode];
}
void ClLogMessage(const char* format, ...)
{
va_list va;
va_start(va, format);
vfprintf(stderr, format, va);
va_end(va);
}
void ClRumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor)
{
[_callbacks rumble:controllerNumber lowFreqMotor:lowFreqMotor highFreqMotor:highFreqMotor];
}
void ClConnectionStatusUpdate(int status)
{
[_callbacks connectionStatusUpdate:status];
}
void ClSetHdrMode(bool enabled)
{
[renderer setHdrMode:enabled];
[_callbacks setHdrMode:enabled];
}
void ClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTriggerMotor, uint16_t rightTriggerMotor)
{
[_callbacks rumbleTriggers:controllerNumber leftTrigger:leftTriggerMotor rightTrigger:rightTriggerMotor];
}
void ClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz)
{
[_callbacks setMotionEventState:controllerNumber motionType:motionType reportRateHz:reportRateHz];
}
void ClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b)
{
[_callbacks setControllerLed:controllerNumber r:r g:g b:b];
}
-(void) terminate
{
// Interrupt any action blocking LiStartConnection(). This is
// thread-safe and done outside initLock on purpose, since we
// won't be able to acquire it if LiStartConnection is in
// progress.
LiInterruptConnection();
// We dispatch this async to get out because this can be invoked
// on a thread inside common and we don't want to deadlock. It also avoids
// blocking on the caller's thread waiting to acquire initLock.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[initLock lock];
LiStopConnection();
[initLock unlock];
});
}
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks
{
self = [super init];
// Use a lock to ensure that only one thread is initializing
// or deinitializing a connection at a time.
if (initLock == nil) {
initLock = [[NSLock alloc] init];
}
if (videoStatsLock == nil) {
videoStatsLock = [[NSLock alloc] init];
}
NSString *rawAddress = [Utils addressPortStringToAddress:config.host];
strncpy(_hostString,
[rawAddress cStringUsingEncoding:NSUTF8StringEncoding],
sizeof(_hostString) - 1);
strncpy(_appVersionString,
[config.appVersion cStringUsingEncoding:NSUTF8StringEncoding],
sizeof(_appVersionString) - 1);
if (config.gfeVersion != nil) {
strncpy(_gfeVersionString,
[config.gfeVersion cStringUsingEncoding:NSUTF8StringEncoding],
sizeof(_gfeVersionString) - 1);
}
if (config.rtspSessionUrl != nil) {
strncpy(_rtspSessionUrl,
[config.rtspSessionUrl cStringUsingEncoding:NSUTF8StringEncoding],
sizeof(_rtspSessionUrl) - 1);
}
LiInitializeServerInformation(&_serverInfo);
_serverInfo.address = _hostString;
_serverInfo.serverInfoAppVersion = _appVersionString;
if (config.gfeVersion != nil) {
_serverInfo.serverInfoGfeVersion = _gfeVersionString;
}
if (config.rtspSessionUrl != nil) {
_serverInfo.rtspSessionUrl = _rtspSessionUrl;
}
_serverInfo.serverCodecModeSupport = config.serverCodecModeSupport;
renderer = myRenderer;
_callbacks = callbacks;
LiInitializeStreamConfiguration(&_streamConfig);
_streamConfig.width = config.width;
_streamConfig.height = config.height;
_streamConfig.fps = config.frameRate;
_streamConfig.bitrate = config.bitRate;
_streamConfig.supportedVideoFormats = config.supportedVideoFormats;
_streamConfig.audioConfiguration = config.audioConfiguration;
// Since we require iOS 12 or above, we're guaranteed to be running
// on a 64-bit device with ARMv8 crypto instructions, so we don't
// need to check for that here.
_streamConfig.encryptionFlags = ENCFLG_ALL;
if ([Utils isActiveNetworkVPN]) {
// Force remote streaming mode when a VPN is connected
_streamConfig.streamingRemotely = STREAM_CFG_REMOTE;
_streamConfig.packetSize = 1024;
}
else {
// Detect remote streaming automatically based on the IP address of the target
_streamConfig.streamingRemotely = STREAM_CFG_AUTO;
_streamConfig.packetSize = 1392;
}
memcpy(_streamConfig.remoteInputAesKey, [config.riKey bytes], [config.riKey length]);
memset(_streamConfig.remoteInputAesIv, 0, 16);
int riKeyId = htonl(config.riKeyId);
memcpy(_streamConfig.remoteInputAesIv, &riKeyId, sizeof(riKeyId));
LiInitializeVideoCallbacks(&_drCallbacks);
_drCallbacks.setup = DrDecoderSetup;
_drCallbacks.start = DrStart;
_drCallbacks.stop = DrStop;
_drCallbacks.capabilities = CAPABILITY_PULL_RENDERER |
CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC |
CAPABILITY_REFERENCE_FRAME_INVALIDATION_AV1;
LiInitializeAudioCallbacks(&_arCallbacks);
_arCallbacks.init = ArInit;
_arCallbacks.cleanup = ArCleanup;
_arCallbacks.decodeAndPlaySample = ArDecodeAndPlaySample;
_arCallbacks.capabilities = CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
LiInitializeConnectionCallbacks(&_clCallbacks);
_clCallbacks.stageStarting = ClStageStarting;
_clCallbacks.stageComplete = ClStageComplete;
_clCallbacks.stageFailed = ClStageFailed;
_clCallbacks.connectionStarted = ClConnectionStarted;
_clCallbacks.connectionTerminated = ClConnectionTerminated;
_clCallbacks.logMessage = ClLogMessage;
_clCallbacks.rumble = ClRumble;
_clCallbacks.connectionStatusUpdate = ClConnectionStatusUpdate;
_clCallbacks.setHdrMode = ClSetHdrMode;
_clCallbacks.rumbleTriggers = ClRumbleTriggers;
_clCallbacks.setMotionEventState = ClSetMotionEventState;
_clCallbacks.setControllerLED = ClSetControllerLED;
return self;
}
-(void) main
{
[initLock lock];
LiStartConnection(&_serverInfo,
&_streamConfig,
&_clCallbacks,
&_drCallbacks,
&_arCallbacks,
NULL, 0,
NULL, 0);
[initLock unlock];
}
@end
-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
-28
View File
@@ -1,28 +0,0 @@
//
// UIAppView.h
// Moonlight
//
// Created by Diego Waxemberg on 10/22/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TemporaryApp.h"
@protocol AppCallback <NSObject>
- (void) appClicked:(TemporaryApp*)app view:(UIView*)view;
- (void) appLongClicked:(TemporaryApp*)app view:(UIView*)view;
@end
#if !TARGET_OS_TV
@interface UIAppView : UIButton <UIContextMenuInteractionDelegate>
#else
@interface UIAppView : UIButton
#endif
- (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback;
- (void) updateAppImage;
@end
-237
View File
@@ -1,237 +0,0 @@
//
// UIAppView.m
// Moonlight
//
// Created by Diego Waxemberg on 10/22/14.
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
//
#import "UIAppView.h"
#import "AppAssetManager.h"
static const float REFRESH_CYCLE = 1.0f;
@implementation UIAppView {
TemporaryApp* _app;
UILabel* _appLabel;
UIImageView* _appOverlay;
UIImageView* _appImage;
NSCache* _artCache;
id<AppCallback> _callback;
}
static UIImage* noImage;
- (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback {
self = [super init];
_app = app;
_callback = callback;
_artCache = cache;
// Cache the NoAppImage ourselves to avoid
// having to load it each time
if (noImage == nil) {
noImage = [UIImage imageNamed:@"NoAppImage"];
}
#if TARGET_OS_TV
self.frame = CGRectMake(0, 0, 200, 265);
#else
self.frame = CGRectMake(0, 0, 150, 200);
#endif
[self setAlpha:app.hidden ? 0.4 : 1.0];
_appImage = [[UIImageView alloc] initWithFrame:self.frame];
[_appImage setImage:noImage];
[self addSubview:_appImage];
// 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(appLongClicked:)];
[self addGestureRecognizer:longPressRecognizer];
}
[self addTarget:self action:@selector(appClicked:) forControlEvents:UIControlEventPrimaryActionTriggered];
[self addTarget:self action:@selector(buttonSelected:) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:@selector(buttonDeselected:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit];
#if TARGET_OS_TV
_appImage.adjustsImageWhenAncestorFocused = YES;
#else
// Rasterizing the cell layer increases rendering performance by quite a bit
// but we want it unrasterized for tvOS where it must be scaled.
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
if (@available(iOS 13.4.1, *)) {
// Allow the button style to change when moused over
self.pointerInteractionEnabled = YES;
}
#endif
[self updateAppImage];
return self;
}
- (void)didMoveToSuperview {
// Start our update loop when we are added to our cell
if (self.superview != nil) {
[self updateLoop];
}
}
- (void) appClicked:(UIView *)view {
[_callback appClicked:_app view:view];
}
- (void) appLongClicked:(UILongPressGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
[_callback appLongClicked:_app 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 appLongClicked:_app view:self];
return nil;
}
#endif
- (void) updateAppImage {
if (_appOverlay != nil) {
[_appOverlay removeFromSuperview];
_appOverlay = nil;
}
if (_appLabel != nil) {
[_appLabel removeFromSuperview];
_appLabel = nil;
}
BOOL noAppImage = false;
// First check the memory cache
UIImage* appImage = [_artCache objectForKey:_app];
if (appImage == nil) {
// Next try to load from the on disk cache
appImage = [UIImage imageWithContentsOfFile:[AppAssetManager boxArtPathForApp:_app]];
if (appImage != nil) {
[_artCache setObject:appImage forKey:_app];
}
}
if (appImage != nil) {
// This size of image might be blank image received from GameStream.
// TODO: Improve no-app image detection
if (!(appImage.size.width == 130.f && appImage.size.height == 180.f) && // GFE 2.0
!(appImage.size.width == 628.f && appImage.size.height == 888.f)) { // GFE 3.0
[_appImage setImage:appImage];
} else {
noAppImage = true;
}
} else {
noAppImage = true;
}
if ([_app.id isEqualToString:_app.host.currentGame]) {
// Only create the app overlay if needed
_appOverlay = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Play"]];
_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
_appOverlay.layer.shadowOffset = CGSizeMake(0, 0);
_appOverlay.layer.shadowOpacity = 1;
_appOverlay.layer.shadowRadius = 4.0;
_appOverlay.contentMode = UIViewContentModeScaleAspectFit;
}
if (noAppImage) {
_appLabel = [[UILabel alloc] init];
[_appLabel setTextColor:[UIColor whiteColor]];
[_appLabel setText:_app.name];
[_appLabel setFont:[UIFont systemFontOfSize:24]];
[_appLabel setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
[_appLabel setTextAlignment:NSTextAlignmentCenter];
[_appLabel setLineBreakMode:NSLineBreakByWordWrapping];
[_appLabel setNumberOfLines:0];
}
[self positionSubviews];
#if TARGET_OS_TV
[_appImage.overlayContentView addSubview:_appLabel];
[_appImage.overlayContentView addSubview:_appOverlay];
#else
[self addSubview:_appLabel];
[self addSubview:_appOverlay];
#endif
}
- (void) buttonSelected:(id)sender {
_appImage.layer.opacity = 0.5f;
}
- (void) buttonDeselected:(id)sender {
_appImage.layer.opacity = 1.0f;
}
- (void) positionSubviews {
CGFloat padding = 5.f;
CGSize frameSize = _appImage.frame.size;
CGPoint center = _appImage.center;
if (_appLabel != nil) {
if (_appOverlay != nil) {
_appOverlay.frame = CGRectMake(0, 0, frameSize.width / 3, frameSize.width / 3);
_appOverlay.center = CGPointMake(frameSize.width / 2, padding + _appOverlay.frame.size.height / 2);
[_appLabel setFrame:CGRectMake(padding, _appOverlay.frame.size.height + padding, frameSize.width - 2 * padding, frameSize.height - _appOverlay.frame.size.height - 2 * padding)];
}
else {
[_appLabel setFrame:CGRectMake(padding, padding, frameSize.width - 2 * padding, frameSize.height - 2 * padding)];
}
}
else if (_appOverlay != nil) {
_appOverlay.frame = CGRectMake(0, 0, frameSize.width / 2, frameSize.width / 2);
_appOverlay.center = center;
}
}
- (void) updateLoop {
// Stop immediately if the view has been detached
if (self.superview == nil) {
return;
}
// Update the app image if neccessary
if ((_appOverlay != nil && ![_app.id isEqualToString:_app.host.currentGame]) ||
(_appOverlay == nil && [_app.id isEqualToString:_app.host.currentGame])) {
[self updateAppImage];
}
// Show no shadow for hidden apps. Because we adjust the opacity of the
// cells for hidden apps, it makes them look bad when the shadow draws
// through the app tile.
self.superview.layer.shadowOpacity = _app.hidden ? 0.0f : 0.5f;
// Update opacity if neccessary
[self setAlpha:_app.hidden ? 0.4 : 1.0];
// Queue the next refresh cycle
[self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE];
}
@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,310 +0,0 @@
//
// KeyManagerViewController.swift
// Moonlight-ZWM
//
// Created by ZWM on 2024/7/23.
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
//
import UIKit
@objc public class CommandManagerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
public let tableView = UITableView()
private let addButton = UIButton(type: .system)
private let deleteButton = UIButton(type: .system)
private let editButton = UIButton(type: .system)
private let exitButton = UIButton(type: .system)
private let pinButton = UIButton(type: .system)
private let viewBackgroundColor = UIColor(white: 0.2, alpha: 0.8);
private let highlightColor = UIColor(white: 0.39, alpha: 0.8);
private let titleLabel = UILabel()
private var viewPinned: Bool = false
private var isEditingMode: Bool = false {
didSet {
updateEditingMode()
}
}
override public func viewDidLoad() {
super.viewDidLoad()
setupViews()
updateEditingMode()
tableView.dataSource = self
tableView.delegate = self
// Register the cell class
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
//pass self to the CommandManager
CommandManager.shared.viewController = self
}
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupConstraints()
}
private func setupViews() {
// Set corner radius
view.layer.cornerRadius = 20 // Adjust the corner radius
view.layer.masksToBounds = true
// Set up the title label
titleLabel.text = SwiftLocalizationHelper.localizedString(forKey: "Send Commands")
titleLabel.font = UIFont.boldSystemFont(ofSize: 20) // Adjust font size as needed
titleLabel.textColor = UIColor.white // Adjust color as needed
titleLabel.textAlignment = .center
view.addSubview(titleLabel)
view.backgroundColor = viewBackgroundColor
tableView.backgroundColor = .clear
tableView.rowHeight = 60
tableView.separatorColor = highlightColor
// Configure buttons
addButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Add / Duplicate"), for: .normal)
deleteButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Delete"), for: .normal)
editButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Edit"), for: .normal)
exitButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Exit"), for: .normal)
pinButton.setTitle("📌", for: .normal)
addButton.titleLabel?.font = UIFont.systemFont(ofSize: 20) // Adjust the size as needed
deleteButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
editButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
exitButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
pinButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
// Add subviews
view.addSubview(tableView)
view.addSubview(addButton)
view.addSubview(deleteButton)
view.addSubview(editButton)
view.addSubview(exitButton)
view.addSubview(pinButton)
// Setup button targets
addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)
exitButton.addTarget(self, action: #selector(exitButtonTapped), for: .touchUpInside)
pinButton.addTarget(self, action: #selector(pinButtonTapped), for: .touchUpInside)
pinButton.contentEdgeInsets = UIEdgeInsets(top: 7, left: 20, bottom: 7, right: 20)
}
private func setupConstraints() {
guard let superview = view.superview else {
return
}
view.translatesAutoresizingMaskIntoConstraints = false
tableView.translatesAutoresizingMaskIntoConstraints = false
addButton.translatesAutoresizingMaskIntoConstraints = false
deleteButton.translatesAutoresizingMaskIntoConstraints = false
editButton.translatesAutoresizingMaskIntoConstraints = false
exitButton.translatesAutoresizingMaskIntoConstraints = false
pinButton.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// Set the width and height of the view
view.widthAnchor.constraint(equalTo: view.superview!.widthAnchor, multiplier: 1),
view.heightAnchor.constraint(equalTo: view.superview!.heightAnchor, multiplier: 1),
// Center the view horizontally and vertically
view.centerXAnchor.constraint(equalTo: view.superview!.centerXAnchor),
view.centerYAnchor.constraint(equalTo: view.superview!.centerYAnchor),
// ViewTitle constrains
titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 13.5),
titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
// TableView constraints
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60),
// ExitButton constraints
exitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25),
exitButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// EditButton constraints
editButton.leadingAnchor.constraint(equalTo: exitButton.trailingAnchor, constant: 50),
editButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// AddButton constraints
addButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -25),
addButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// DeleteButton constraints
deleteButton.trailingAnchor.constraint(equalTo: addButton.leadingAnchor, constant: -50),
deleteButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
// DeleteButton constraints
pinButton.trailingAnchor.constraint(equalTo: deleteButton.leadingAnchor, constant: -50),
pinButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
])
}
private func updateEditingMode() {
addButton.isEnabled = isEditingMode
deleteButton.isEnabled = isEditingMode
if(isEditingMode){ editButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Done"), for: .normal) }
else{ editButton.setTitle(SwiftLocalizationHelper.localizedString(forKey: "Edit"), for: .normal) }
}
@objc private func pinButtonTapped() {
viewPinned = !viewPinned
if viewPinned {
pinButton.backgroundColor = highlightColor
}
else{
pinButton.backgroundColor = viewBackgroundColor
}
}
@objc private func addButtonTapped() {
let previouslySelectedIndexPath = tableView.indexPathForSelectedRow //memorize selected indexpath
let alert = UIAlertController(title: SwiftLocalizationHelper.localizedString(forKey: "New Command"), message: SwiftLocalizationHelper.localizedString(forKey: "Enter a new command and alias"), preferredStyle: .alert)
alert.addTextField { $0.placeholder = SwiftLocalizationHelper.localizedString(forKey:"Command") }
alert.addTextField { $0.placeholder = SwiftLocalizationHelper.localizedString(forKey: "Alias (optional)") }
alert.textFields?[0].keyboardType = .asciiCapable
alert.textFields?[0].autocorrectionType = .no
alert.textFields?[0].spellCheckingType = .no
alert.textFields?[1].keyboardType = .asciiCapable
alert.textFields?[1].autocorrectionType = .no
alert.textFields?[1].spellCheckingType = .no
let submitAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: "Add"), style: .default) { [unowned alert] _ in
let keyboardCmdString = alert.textFields?[0].text ?? ""
let alias = alert.textFields?[1].text ?? keyboardCmdString
let newCommand = RemoteCommand(keyboardCmdString: keyboardCmdString, alias: alias)
let addCommandSuceeded = CommandManager.shared.addCommand(newCommand)
//self.reloadTableView() // don't know why but this reload has to be called from the CommandManager, it doesn't work here.
//if previouslySelectedIndexPath == nil { return }
if addCommandSuceeded {
let lastRow = self.tableView.numberOfRows(inSection: 0) - 1 //for now there's only 1 section for the tableview, just use setion 0
let newEntryIndexPath = IndexPath(row: lastRow, section: 0)
self.tableView.selectRow(at: newEntryIndexPath, animated: true, scrollPosition: .middle) // shift the highlight to the newly added entry
}
else {
self.tableView.selectRow(at: previouslySelectedIndexPath, animated: true, scrollPosition: .middle) // keep the highlight on the previous entry if failed to add command
}
}
let cancelAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey:"Cancel"), style: .cancel)
alert.addAction(submitAction)
alert.addAction(cancelAction)
if let selectedIndexPath = self.tableView.indexPathForSelectedRow {
let selectedCommand = CommandManager.shared.getAllCommands()[selectedIndexPath.row]
alert.textFields?[0].text = selectedCommand.keyboardCmdString // load selected keyboard cmd string
//alert.textFields?[1].text = selectedCommand.alias // leave the alias input field empty
}
self.present(alert, animated: true)
}
@objc private func deleteButtonTapped() {
guard let selectedIndexPath = tableView.indexPathForSelectedRow else {
return
}
let previouslySelectedIndexPath = selectedIndexPath // memorize selected row before reloading tableview
CommandManager.shared.deleteCommand(at: selectedIndexPath.row)
reloadTableView()
// target new entry after deletion
var targetRowAfterDel = previouslySelectedIndexPath.row - 1
if targetRowAfterDel < 0 { targetRowAfterDel = 0 }
let targetIndexPathAfterDel = IndexPath(row: targetRowAfterDel, section: previouslySelectedIndexPath.section)
if targetRowAfterDel < tableView.numberOfRows(inSection: previouslySelectedIndexPath.section) {
tableView.selectRow(at: targetIndexPathAfterDel, animated: true, scrollPosition: .middle) // shift highlight to the target entry after deletion
}
}
@objc private func editButtonTapped() {
isEditingMode.toggle()
}
@objc private func exitButtonTapped() {
dismiss(animated: true, completion: nil)
}
@objc public func reloadTableView() {
tableView.layoutIfNeeded()
tableView.reloadData()
/*
let previouslySelectedIndexPath = tableView.indexPathForSelectedRow
if let indexPath = previouslySelectedIndexPath {
// Make sure the indexPath is still valid and scroll to the selected indexPath
if indexPath.row < tableView.numberOfRows(inSection: indexPath.section) {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) // keep the entry of previous index selected.
}
} */
}
// UITableViewDataSource
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return CommandManager.shared.getAllCommands().count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Configure cell appearance
cell.backgroundColor = .clear // Set background color of the cell
cell.textLabel?.textColor = UIColor.white // Set font color of the text
// Set selected background view
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = highlightColor // Color for the selected state
cell.selectedBackgroundView = selectedBackgroundView
let command = CommandManager.shared.getAllCommands()[indexPath.row]
cell.textLabel?.text = command.alias
return cell
}
// UITableViewDelegate
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
// Set the cell background color to the flashing color
// Animate the flash effect
UIView.animate(withDuration: 0.1, animations: {
cell.selectedBackgroundView?.backgroundColor = .clear
}) { _ in
// Reset the cell background color after the animation
UIView.animate(withDuration: 0.1) {
cell.selectedBackgroundView?.backgroundColor = self.highlightColor
}
}
}
if !isEditingMode {
// Sending keyboard command
let command = CommandManager.shared.getAllCommands()[indexPath.row]
sendKeyboardCommand(command)
if !viewPinned { dismiss(animated: false, completion: nil) } // dimiss the view in sending mode & the view is not pinned
}
}
private func sendKeyboardCommand(_ cmd: RemoteCommand) {
print("Sending key-value")
let keyboardCmdStrings = CommandManager.shared.extractKeyStringsFromComboCommand(from: cmd.keyboardCmdString)
CommandManager.shared.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings!)
}
}
@@ -1,19 +0,0 @@
//
// ProfileTableViewCell.h
// Moonlight
//
// Created by Long Le on 12/11/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ProfileTableViewCell : UITableViewCell
@property (weak, nonatomic) IBOutlet UILabel *name;
@end
NS_ASSUME_NONNULL_END
@@ -1,21 +0,0 @@
//
// ProfileTableViewCell.m
// Moonlight
//
// Created by Long Le on 12/11/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import "ProfileTableViewCell.h"
@implementation ProfileTableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
}
@end
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" accessoryType="checkmark" indentationWidth="10" reuseIdentifier="Cell" id="KGk-i7-Jjw" customClass="ProfileTableViewCell">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="280" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Profile Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="b1d-b4-TCK">
<rect key="frame" x="16.000000000000007" y="11.666666666666664" width="97.666666666666686" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.95294117647058818" green="0.97647058823529409" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.1215686275" green="0.12941176469999999" blue="0.14117647059999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="b1d-b4-TCK" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="pxI-vz-oR6"/>
<constraint firstItem="b1d-b4-TCK" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="xCR-Og-ne7"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="aW0-zy-SZf"/>
<color key="backgroundColor" red="0.1215686275" green="0.12941176469999999" blue="0.14117647059999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="0.6705882353" green="0.61568627450000002" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="name" destination="b1d-b4-TCK" id="CX8-IC-jlM"/>
</connections>
<point key="canvasLocation" x="108" y="39"/>
</tableViewCell>
</objects>
</document>
@@ -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,27 +0,0 @@
//
// OSCProfilesTableViewController.h
// Moonlight
//
// Created by Long Le on 11/28/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
This view displays a list of on screen controller profiles and gives the user the ability to select any of the profiles to be the 'Selected' profile whose on screen controller layout configuration will be shown on the game stream view, or in the on screen controller layout view. This view also allows the user to swipe and delete any of the listed profiles.
*/
@interface OSCProfilesTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (void)profileViewRefresh;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, copy) void (^needToUpdateOscLayoutTVC)(void);
@property (nonatomic, assign) NSMutableArray *currentOSCButtonLayers;
@end
NS_ASSUME_NONNULL_END
@@ -1,317 +0,0 @@
//
// OSCProfilesTableViewController.m
// Moonlight
//
// Created by Long Le on 11/28/22.
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
//
#import "OSCProfilesTableViewController.h"
#import "LayoutOnScreenControlsViewController.h"
#import "ProfileTableViewCell.h"
#import "OSCProfile.h"
#import "OnScreenButtonState.h"
#import "OSCProfilesManager.h"
#import "LocalizationHelper.h"
const double NAV_BAR_HEIGHT = 50;
@interface OSCProfilesTableViewController ()
@end
@implementation OSCProfilesTableViewController {
OSCProfilesManager *profilesManager;
}
@synthesize tableView;
- (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{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:@"OscLayoutTableViewCloseNotification" object:self]; // notify other view that oscLayoutManager is closing
}
- (void) viewDidLoad {
[super viewDidLoad];
profilesManager = [OSCProfilesManager sharedManager];
self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, NAV_BAR_HEIGHT)];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerNib:[UINib nibWithNibName:@"ProfileTableViewCell" bundle:nil]
forCellReuseIdentifier:@"Cell"]; // Register the custom cell nib file with the table view
self.tableView.alpha = 0.5;
self.tableView.backgroundColor = [[UIColor colorWithRed:0.5 green:0.7 blue:1.0 alpha:1.0] colorWithAlphaComponent:0.2]; // set background color & transparency
// self.tableView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2]; // set background color & transparency
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear: animated];
self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, NAV_BAR_HEIGHT)];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerNib:[UINib nibWithNibName:@"ProfileTableViewCell" bundle:nil]
forCellReuseIdentifier:@"Cell"]; // Register the custom cell nib file with the table view
if ([[profilesManager getAllProfiles] count] > 0) { // scroll to selected profile if user has any saved profiles
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[profilesManager getIndexOfSelectedProfile] inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
}
#pragma mark - UIButton Actions
/* Loads the OSC profile that user selected, dismisses this VC, then tells the presenting view controller to lay out the on screen buttons according to the selected profile's instructions */
- (IBAction) duplicateTapped:(id)sender {
UIAlertController * inputNameAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Enter the name you want to save this profile as"] message: @"" preferredStyle:UIAlertControllerStyleAlert];
[inputNameAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { // pop up notification with text field where user can enter the text they wish to name their OSC layout profile
textField.placeholder = [LocalizationHelper localizedStringForKey:@"name"];
textField.textColor = [UIColor lightGrayColor];
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.borderStyle = UITextBorderStyleNone;
}];
[inputNameAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { // adds a button that allows user to decline the option to save the controller layout they currently see on screen
[inputNameAlertController dismissViewControllerAnimated:NO completion:nil];
}]];
[inputNameAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Save"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { // add save button to allow user to save the on screen controller configuration
NSArray *textFields = inputNameAlertController.textFields;
UITextField *nameField = textFields[0];
NSString *enteredProfileName = nameField.text;
if ([enteredProfileName isEqualToString:@"Default"]) { // don't let user user overwrite the 'Default' profile
UIAlertController *alertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Saving over the 'Default' profile is not allowed"] preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[alertController dismissViewControllerAnimated:NO completion:^{
[self presentViewController:inputNameAlertController animated:YES completion:nil];
}];
}]];
[self presentViewController:alertController animated:YES completion:nil];
}
else if ([enteredProfileName length] == 0) { // if user entered no text and taps the 'Save' button let them know they can't do that
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Profile name cannot be blank!"] preferredStyle:UIAlertControllerStyleAlert];
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { // show pop up notification letting user know they must enter a name in the text field if they wish to save the controller profile
[savedAlertController dismissViewControllerAnimated:NO completion:^{
[self presentViewController:inputNameAlertController animated:YES completion:nil];
}];
}]];
[self presentViewController:savedAlertController animated:YES completion:nil];
}
else if ([self->profilesManager profileNameAlreadyExist:enteredProfileName] == YES) { // if the entered profile name already
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Profile name already exists"] preferredStyle:UIAlertControllerStyleAlert];
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
//[savedAlertController dismissViewControllerAnimated:NO completion:nil];
//[self profileViewRefresh]; //refresh profile view after saving new profile;
}]];
[self presentViewController:savedAlertController animated:YES completion:nil];
}
else { // if user entered a valid name that doesn't already exist then save the profile to persistent storage
[self->profilesManager saveProfileWithName: enteredProfileName andButtonLayers:self.currentOSCButtonLayers]; // the OSC layout here is passed from parent LayoutOSCViewController;
[self->profilesManager setProfileToSelected: enteredProfileName];
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Profile %@ duplicated from current layout", enteredProfileName] preferredStyle:UIAlertControllerStyleAlert]; // Let user know this profile has been duplicated & saved
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[savedAlertController dismissViewControllerAnimated:NO completion:nil];
[self profileViewRefresh]; //refresh profile view after saving new profile;
}]];
[self presentViewController:savedAlertController animated:YES completion:nil];
}
}]];
[self presentViewController:inputNameAlertController animated:YES completion:nil];
}
/* basically the same with loadTapped */
- (void) profileViewRefresh{
//[self dismissViewControllerAnimated:YES completion:nil];
//[selfparentLayoutOSCViewController]
[self.tableView reloadData]; // table view will be refreshed by calling reloadData
if (self.needToUpdateOscLayoutTVC) { // tells the presenting view controller to lay out the on screen buttons according to the selected profile's instructions
self.needToUpdateOscLayoutTVC();
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"OscLayoutProfileSelctedInTableView" object:self]; // notify other view that oscLayoutManager is closing
}
- (IBAction) deleteTapped:(id)sender{// delete can be executed simply by calling this 2 methods.
[profilesManager deleteCurrentSelectedProfile];
[self profileViewRefresh];
}
- (IBAction) exitTapped:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - TableView DataSource
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[profilesManager getAllProfiles] count];
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ProfileTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
OSCProfile *profile = [[profilesManager getAllProfiles] objectAtIndex:indexPath.row];
cell.name.text = profile.name;
cell.name.backgroundColor = [UIColor clearColor];
cell.name.alpha = 1.0;
cell.name.textColor = [UIColor whiteColor]; // Opaque white text
cell.name.shadowColor = [UIColor blackColor]; // Black shadow
// Set the shadow offset
cell.name.shadowOffset = CGSizeMake(1.0, 1.5);
// Set cell and contentView background colors
cell.backgroundColor = [UIColor clearColor];
cell.contentView.backgroundColor = [UIColor clearColor];
// Configure the checkmark accessory
if ([profile.name isEqualToString:[profilesManager getSelectedProfile].name]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
// Remove existing custom separators to avoid duplicates
UIView *existingSeparator = [cell.contentView viewWithTag:100];
if (existingSeparator) {
[existingSeparator removeFromSuperview];
}
// Add custom separator with increased height (thickness)
CGFloat separatorHeight = 1.0; // Adjust this value for thicker line
UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(0, cell.contentView.frame.size.height - separatorHeight, cell.contentView.frame.size.width, separatorHeight)];
separatorView.backgroundColor = [UIColor whiteColor]; // Set your desired color
separatorView.tag = 100; // Assign a tag to identify the separator view
separatorView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
[cell.contentView addSubview:separatorView];
[cell.contentView bringSubviewToFront:separatorView];
// Replace the default checkmark with a UILabel displaying a checkmark character
if ([profile.name isEqualToString:[profilesManager getSelectedProfile].name]) {
UILabel *checkmarkLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; // Adjust size as needed
checkmarkLabel.text = @"✓"; // The checkmark character
checkmarkLabel.font = [UIFont systemFontOfSize:25]; // Adjust font size as needed
checkmarkLabel.textAlignment = NSTextAlignmentCenter;
checkmarkLabel.textColor = [UIColor greenColor]; // Set the color of the checkmark
cell.accessoryView = checkmarkLabel;
checkmarkLabel.layer.zPosition = 0; // Push checkmark to the back
} else {
cell.accessoryView = nil; // Remove checkmark if not selected
}
[cell.contentView bringSubviewToFront:separatorView];
separatorView.layer.zPosition = 1; // Bring separator to the top layer
return cell;
}
- (BOOL) tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 60.0; // Set your desired cell height here
}
- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
NSMutableArray *profiles = [profilesManager getAllProfiles];
if ([[[profiles objectAtIndex:indexPath.row] name] isEqualToString:@"Default"]) { // if user is attempting to delete the 'Default' profile then show a pop up telling user they can't do that and return out of this method
UIAlertController *alertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: @"Deleting the 'Default' profile is not allowed" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[alertController dismissViewControllerAnimated:NO completion:nil];
}]];
[self presentViewController:alertController animated:YES completion:nil];
return;
}
if (editingStyle == UITableViewCellEditingStyleDelete) {
OSCProfile *profile = [profiles objectAtIndex:indexPath.row];
if (profile.isSelected) { // if user is deleting the currently selected OSC profile then make the profile at its previous index the currently selected profile
if (indexPath.row > 0) { // check that row is greater than zero to avoid an out of bounds crash, although that should not be possible right now since the 'Default' profile is always at row 0 and they're not allowed to delete it
OSCProfile *profile = [profiles objectAtIndex:indexPath.row - 1];
profile.isSelected = YES;
}
}
[profiles removeObjectAtIndex:indexPath.row];
/* save OSC profiles array to persistent storage */
NSMutableArray *profilesEncoded = [[NSMutableArray alloc] init];
for (OSCProfile *profileDecoded in profiles) { // encode each OSC profile object and add them to an array
NSData *profileEncoded = [NSKeyedArchiver archivedDataWithRootObject:profileDecoded requiringSecureCoding:YES error:nil];
[profilesEncoded addObject:profileEncoded];
}
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded
requiringSecureCoding:YES error:nil]; // encode the array itself, NOT the objects in the array
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
[[NSUserDefaults standardUserDefaults] synchronize];
[tableView reloadData];
}
}
#pragma mark - TableView Delegate
/* When user taps a cell it moves the checkmark to that cell indicating to the user the profile associated with that cell is now the selected profile. It also sets that cell's associated OSCProfile object's 'isSelected' property to YES */
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:0];
NSIndexPath *lastSelectedIndexPath = [NSIndexPath indexPathForRow:[profilesManager getIndexOfSelectedProfile] inSection:0];
if (selectedIndexPath != lastSelectedIndexPath) {
/* Place checkmark on selected cell and set profile associated with cell as selected profile */
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath: selectedIndexPath];
selectedCell.accessoryType = UITableViewCellAccessoryCheckmark; // add checkmark to the cell the user tapped
selectedCell.accessoryView.tintColor = [[UIColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:0.85] colorWithAlphaComponent:1.0];
OSCProfile *profile = [[profilesManager getAllProfiles] objectAtIndex:indexPath.row];
[profilesManager setProfileToSelected: profile.name]; // set the profile associated with this cell's 'isSelected' property to YES
/* Remove checkmark on the previously selected cell */
UITableViewCell *lastSelectedCell = [tableView cellForRowAtIndexPath: lastSelectedIndexPath];
lastSelectedCell.accessoryType = UITableViewCellAccessoryNone;
[tableView deselectRowAtIndexPath:lastSelectedIndexPath animated:YES];
}
[self profileViewRefresh]; // update OSC layout when table view option is changed
}
@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
File diff suppressed because it is too large. Load diff
@@ -22,6 +22,7 @@
@property (strong, nonatomic) IBOutlet UISegmentedControl *touchModeSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *touchModeSelector;
@property (strong, nonatomic) IBOutlet UILabel *onscreenControllerLabel; @property (strong, nonatomic) IBOutlet UILabel *onscreenControllerLabel;
@property (strong, nonatomic) IBOutlet UISegmentedControl *onscreenControlSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *onscreenControlSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *motionModeSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *largerStickLR1Selector; @property (strong, nonatomic) IBOutlet UISegmentedControl *largerStickLR1Selector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *optimizeSettingsSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *optimizeSettingsSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *multiControllerSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *multiControllerSelector;
@@ -29,6 +30,7 @@
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector; @property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *pipSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
@@ -228,7 +228,7 @@ BOOL isCustomResolution(CGSize res) {
} }
- (void)updateResolutionTable{ - (void)updateResolutionTable{
UIWindow *window = UIApplication.sharedApplication.windows.firstObject; UIWindow *window = self.view.window;
CGFloat screenScale = window.screen.scale; CGFloat screenScale = window.screen.scale;
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale; CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
CGFloat appWindowWidth = window.frame.size.width * screenScale; CGFloat appWindowWidth = window.frame.size.width * screenScale;
@@ -465,6 +465,7 @@ BOOL isCustomResolution(CGSize res) {
} }
[self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0]; [self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0];
[self.pipSelector setSelectedSegmentIndex:currentSettings.enablePIP ? 1 : 0];
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue]; [self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0]; [self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 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.resolutionSelector addTarget:self action:@selector(newResolutionChosen) forControlEvents:UIControlEventValueChanged];
[self.framerateSelector setSelectedSegmentIndex:framerate]; [self.framerateSelector setSelectedSegmentIndex:framerate];
[self.framerateSelector addTarget:self action:@selector(updateBitrate) forControlEvents:UIControlEventValueChanged]; [self.framerateSelector addTarget:self action:@selector(updateBitrate) forControlEvents:UIControlEventValueChanged];
NSInteger motionMode = [currentSettings.motionMode integerValue];
[self.motionModeSelector setSelectedSegmentIndex:motionMode];
[self.bitrateSlider setMinimumValue:0]; [self.bitrateSlider setMinimumValue:0];
[self.bitrateSlider setMaximumValue:(sizeof(bitrateTable) / sizeof(*bitrateTable)) - 1]; [self.bitrateSlider setMaximumValue:(sizeof(bitrateTable) / sizeof(*bitrateTable)) - 1];
[self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES]; [self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES];
@@ -1054,6 +1057,7 @@ BOOL isCustomResolution(CGSize res) {
NSInteger height = [self getChosenStreamHeight]; NSInteger height = [self getChosenStreamHeight];
NSInteger width = [self getChosenStreamWidth]; NSInteger width = [self getChosenStreamWidth];
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex]; NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
NSInteger motionMode = [self.motionModeSelector selectedSegmentIndex];
NSInteger keyboardToggleFingers = (uint16_t)self.keyboardToggleFingerNumSlider.value; NSInteger keyboardToggleFingers = (uint16_t)self.keyboardToggleFingerNumSlider.value;
NSInteger oscLayoutToolFingers = (uint16_t)self->oscLayoutFingers; NSInteger oscLayoutToolFingers = (uint16_t)self->oscLayoutFingers;
// NSLog(@"saveSettings keyboardToggleFingers %d", (uint16_t)keyboardToggleFingers); // NSLog(@"saveSettings keyboardToggleFingers %d", (uint16_t)keyboardToggleFingers);
@@ -1076,6 +1080,7 @@ BOOL isCustomResolution(CGSize res) {
BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1; BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1;
uint32_t preferredCodec = [self getChosenCodecPreference]; uint32_t preferredCodec = [self getChosenCodecPreference];
BOOL enableYUV444 = [self.yuv444Selector selectedSegmentIndex] == 1; BOOL enableYUV444 = [self.yuv444Selector selectedSegmentIndex] == 1;
BOOL enablePIP = [self.pipSelector selectedSegmentIndex] == 1;
BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1; BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1;
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1; BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
// BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1; // BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
@@ -1087,12 +1092,14 @@ BOOL isCustomResolution(CGSize res) {
NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex]; NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex];
NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex]; NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex];
NSInteger localMousePointerMode = [self.localMousePointerModeSelector selectedSegmentIndex]; NSInteger localMousePointerMode = [self.localMousePointerModeSelector selectedSegmentIndex];
NSLog(@"saving motionMode: %ld", motionMode);
[dataMan saveSettingsWithBitrate:_bitrate [dataMan saveSettingsWithBitrate:_bitrate
framerate:framerate framerate:framerate
height:height height:height
width:width width:width
audioConfig:2 // Stereo audioConfig:2 // Stereo
onscreenControls:onscreenControls onscreenControls:onscreenControls
motionMode:motionMode
keyboardToggleFingers:keyboardToggleFingers keyboardToggleFingers:keyboardToggleFingers
oscLayoutToolFingers:oscLayoutToolFingers oscLayoutToolFingers:oscLayoutToolFingers
slideToSettingsScreenEdge:slideToSettingsScreenEdge slideToSettingsScreenEdge:slideToSettingsScreenEdge
@@ -1111,6 +1118,7 @@ BOOL isCustomResolution(CGSize res) {
audioOnPC:audioOnPC audioOnPC:audioOnPC
preferredCodec:preferredCodec preferredCodec:preferredCodec
enableYUV444:enableYUV444 enableYUV444:enableYUV444
enablePIP:enablePIP
useFramePacing:useFramePacing useFramePacing:useFramePacing
enableHdr:enableHdr enableHdr:enableHdr
btMouseSupport:btMouseSupport 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> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Moonlight-ZWM</string> <string>VoidLink</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <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 # App Store 分发一次性收费声明.
* Install Xcode from the [App Store page](https://apps.apple.com/us/app/xcode/id497799835) # Statement on One-Time App Store Distribution Fee.
* Run `git clone --recursive https://github.com/moonlight-stream/moonlight-ios.git` - VoidLink 的 App Store 安装费用用于覆盖通过 Apple 框架进行安全可靠应用分发的成本。同时也支持项目维护者进行持续的开发、维护,以及公众用户访问并下载官方签名版本。感谢您对项目的支持,帮助我们持续改进和优化。<br><br>
* If you've already clone the repo without `--recursive`, run `git submodule update --init --recursive` - 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.
* Open Moonlight.xcodeproj in Xcode
* To run on a real device, you will need to locally modify the signing options: <br>
* Click on "Moonlight" at the top of the left sidebar
* Click on the "Signing & Capabilities" tab # 开发者B站号. Developer on Bilibili
* 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. 如果你在用Bilibili, 请关注`True砖家`,了解该fork的最新消息。 <br>
* Change the "Bundle Identifier" to something different. You can add your name or some random letters to make it unique. If you are on Bilibili, subscribe `True砖家` to get the latest news of this fork: <br>
* Now you can select your Apple device in the top bar as a target and click the Play button to run. 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 <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "self:/Users/admin/moonlight-ios/Moonlight-ZWM.xcodeproj"> location = "self:">
</FileRef> </FileRef>
</Workspace> </Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<true/>
</dict>
</plist>
@@ -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
}
Loaded 100 of 1078 files, more files were not shown because too many files have changed in this diff. Show more