Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cb38a7897 | ||
|
|
f285fd2d23 | ||
|
|
23103340f9 | ||
|
|
e5fd48cf3d | ||
|
|
6df81095d0 | ||
|
|
261b8294a0 | ||
|
|
8a66aadb40 | ||
|
|
ba9c2e5fab | ||
|
|
073f7f848c | ||
|
|
82f1c82ee4 | ||
|
|
6a3640b8be | ||
|
|
3e2e477fe8 | ||
|
|
b66c372192 | ||
|
|
12f0bad824 | ||
|
|
79c3c21ae0 | ||
|
|
39c5d58564 | ||
|
|
d8c58b45f0 | ||
|
|
4727139e0e | ||
|
|
b461145fe4 | ||
|
|
5a82b0f480 | ||
|
|
3d028d792f | ||
|
|
66c8e3a9d3 | ||
|
|
51e9269865 | ||
|
|
bc02f20762 | ||
|
|
e26d1b20b5 | ||
|
|
6ae8cea6f3 | ||
|
|
41e94455f4 | ||
|
|
d283ddf4ee | ||
|
|
6a4c709dec | ||
|
|
2c91ae2557 | ||
|
|
c629a1d5d5 | ||
|
|
1e54c2d16f | ||
|
|
42efa1f2c8 | ||
|
|
c9f4113534 | ||
|
|
e3bf553566 | ||
|
|
e2478660f3 | ||
|
|
9106cad14a | ||
|
|
8a361cf927 | ||
|
|
427873116b | ||
|
|
6c5cd094c3 | ||
|
|
9b270b2e90 | ||
|
|
b4bef088d4 | ||
|
|
e5fa7f9c72 | ||
|
|
746479d359 | ||
|
|
7f6c14f92d | ||
|
|
1e0a244b49 | ||
|
|
5b9f29296e | ||
|
|
2674267589 | ||
|
|
d64c746dab | ||
|
|
d54205dec8 | ||
|
|
6b48083ad9 | ||
|
|
7d9eb2aaa9 | ||
|
|
d7aa2d00ba | ||
|
|
743dad5f37 | ||
|
|
19dc6a68b3 | ||
|
|
8d9e822bbe | ||
|
|
5d7258a00a | ||
|
|
2b5601d154 | ||
|
|
a51d471f2d | ||
|
|
0f5ca5d8b0 | ||
|
|
f85b736c97 | ||
|
|
72547b1b59 | ||
|
|
4987298f79 | ||
|
|
b1b0be886e | ||
|
|
208688afe1 | ||
|
|
82de44df54 | ||
|
|
6207d77349 | ||
|
|
afc390bacf | ||
|
|
4b2ae15efc | ||
|
|
0fda662370 | ||
|
|
4e59c3375e | ||
|
|
2e7ffc2c52 | ||
|
|
b98ccb2a30 | ||
|
|
813a488844 | ||
|
|
4faa814ee1 | ||
|
|
f80e56fa0f | ||
|
|
a43905d7f4 | ||
|
|
616238e847 | ||
|
|
fd8cc416ab | ||
|
|
8dc445e744 | ||
|
|
218b203a0b | ||
|
|
65e71909ed | ||
|
|
f08d7c7ac9 | ||
|
|
b451b65251 | ||
|
|
9add83f2c8 | ||
|
|
023b013828 | ||
|
|
07af0e4457 | ||
|
|
164f5bff2e | ||
|
|
5e35318f07 | ||
|
|
fe73054771 | ||
|
|
a6adbfc8c6 | ||
|
|
f997b35b1a | ||
|
|
af37c253b1 | ||
|
|
600fc5885b | ||
|
|
8d751c0564 | ||
|
|
555726e417 | ||
|
|
1c90390262 | ||
|
|
98e6a28620 | ||
|
|
d7e03e6464 | ||
|
|
a435b5bb45 | ||
|
|
8e5c9b58e2 | ||
|
|
8d25d238b6 | ||
|
|
6a9150c447 | ||
|
|
2316a9eb63 | ||
|
|
2e403acf3e |
@@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
name: Build VoidLink IPA
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- Integration
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build .ipa and publish source
|
|
||||||
runs-on: macos
|
|
||||||
env:
|
|
||||||
# Per-app: change APP_NAME for each app repo. Used everywhere so the
|
|
||||||
# perlis dir name and file names always match (no hardcoded drift).
|
|
||||||
APP_NAME: VoidLink
|
|
||||||
# Point xcodebuild at full Xcode without sudo (sudo hangs in CI).
|
|
||||||
# DEVELOPER_DIR overrides the active developer directory for all steps.
|
|
||||||
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Checkout submodules
|
|
||||||
run: git submodule update --init --recursive
|
|
||||||
|
|
||||||
- name: Select Xcode
|
|
||||||
run: xcodebuild -version
|
|
||||||
|
|
||||||
- name: Build unsigned .ipa
|
|
||||||
run: |
|
|
||||||
# Build the app (no signing — SideStore signs on-device).
|
|
||||||
# VoidLink's scheme is auto-generated from the VoidLink target.
|
|
||||||
# Release config → bundle id com.voidlink.tf.iOS (Debug uses
|
|
||||||
# com.voidlink.tf.debug10.iOS), so the .ipa matches the metadata.
|
|
||||||
xcodebuild \
|
|
||||||
-scheme VoidLink \
|
|
||||||
-sdk iphoneos \
|
|
||||||
-configuration Release \
|
|
||||||
archive \
|
|
||||||
-archivePath $PWD/build/VoidLink.xcarchive \
|
|
||||||
CODE_SIGN_IDENTITY="" \
|
|
||||||
CODE_SIGNING_REQUIRED="NO" \
|
|
||||||
CODE_SIGN_ENTITLEMENTS="" \
|
|
||||||
CODE_SIGNING_ALLOWED="NO"
|
|
||||||
|
|
||||||
# Package the .app bundle into an .ipa (Payload/App.app structure)
|
|
||||||
mkdir -p build/IPA/Payload
|
|
||||||
cp -R build/VoidLink.xcarchive/Products/Applications/VoidLink.app build/IPA/Payload/VoidLink.app
|
|
||||||
cd build/IPA
|
|
||||||
zip -r ../../$APP_NAME.ipa Payload
|
|
||||||
cd ../..
|
|
||||||
ls -la $APP_NAME.ipa
|
|
||||||
|
|
||||||
- name: Generate app metadata
|
|
||||||
run: |
|
|
||||||
# Read the version from the built app so it always matches the .ipa
|
|
||||||
# (no hardcoded drift). CFBundleShortVersionString = MARKETING_VERSION.
|
|
||||||
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" \
|
|
||||||
build/VoidLink.xcarchive/Products/Applications/VoidLink.app/Info.plist)
|
|
||||||
SIZE=$(stat -f%z $APP_NAME.ipa)
|
|
||||||
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
# Per-app metadata file. The perlis aggregator folds this into the
|
|
||||||
# root source.json at https://apps.bueso.eu/source.json.
|
|
||||||
cat > $APP_NAME.json <<EOF
|
|
||||||
{
|
|
||||||
"name": "VoidLink",
|
|
||||||
"bundleIdentifier": "com.voidlink.tf.iOS",
|
|
||||||
"developerName": "Luis",
|
|
||||||
"localizedDescription": "VoidLink (Moonlight fork) - stream from your PC. Built via Gitea Actions.",
|
|
||||||
"iconURL": "https://apps.bueso.eu/$APP_NAME/icon.png",
|
|
||||||
"downloadURL": "https://apps.bueso.eu/$APP_NAME/$APP_NAME.ipa",
|
|
||||||
"version": "$VERSION",
|
|
||||||
"versionDate": "$DATE",
|
|
||||||
"size": $SIZE,
|
|
||||||
"permissions": []
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
cat $APP_NAME.json
|
|
||||||
|
|
||||||
- name: Upload to source host (perlis)
|
|
||||||
run: |
|
|
||||||
# Upload .ipa + per-app metadata + icon to perlis via SSH.
|
|
||||||
# PERLIS_SSH_HOST is the Tailscale MagicDNS name (perlis.bueso.loc),
|
|
||||||
# reachable from the iMac runner over the Tailscale mesh.
|
|
||||||
ssh -o StrictHostKeyChecking=no \
|
|
||||||
"${{ secrets.PERLIS_SSH_USER }}@${{ secrets.PERLIS_SSH_HOST }}" \
|
|
||||||
"mkdir -p /mnt/appdata/ios-apps/$APP_NAME"
|
|
||||||
scp -o StrictHostKeyChecking=no \
|
|
||||||
$APP_NAME.ipa $APP_NAME.json icon.png \
|
|
||||||
"${{ secrets.PERLIS_SSH_USER }}@${{ secrets.PERLIS_SSH_HOST }}:/mnt/appdata/ios-apps/$APP_NAME/"
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
name: Build VoidLink IPA
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "feat/frame_pacing_metal" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build VoidLink IPA
|
|
||||||
runs-on: macos-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Checkout submodules
|
|
||||||
run: "git submodule update --init --recursive"
|
|
||||||
|
|
||||||
- name: Build VoidLink
|
|
||||||
run: |
|
|
||||||
xcodebuild -verbose -scheme VoidLink -sdk iphoneos -configuration Debug archive -archivePath $PWD/build/VoidLink.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
|
|
||||||
mkdir -p ./build/IPA && mkdir -p ./build/IPA/Payload
|
|
||||||
cp -r ./build/VoidLink.xcarchive/Products/Applications/VoidLink.app ./build/IPA/Payload/VoidLink.app
|
|
||||||
cd ./build/IPA && zip -r ../../build/VoidLink.ipa ./Payload/* && cd ../..
|
|
||||||
|
|
||||||
- name: Upload VoidLink IPA to Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: VoidLink.ipa
|
|
||||||
path: build/VoidLink.ipa
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
**/xcuserdata/
|
**/xcuserdata/
|
||||||
**/Moonlight.xcscmblueprint
|
**/Moonlight.xcscmblueprint
|
||||||
**/xcschemes/*.xcscheme
|
**/xcschemes/*.xcscheme
|
||||||
.DS_Store
|
|
||||||
Build
|
Build
|
||||||
DerivedData
|
DerivedData
|
||||||
build-home/
|
|
||||||
*.xccheckout
|
*.xccheckout
|
||||||
.github
|
|
||||||
README.md
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
[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/TrueZhuangJia/voidlink-c.git
|
url = https://github.com/moonlight-stream/moonlight-common-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
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,657 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
<?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="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
<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">
|
||||||
<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="24504"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||||
<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>
|
||||||
@@ -13,13 +12,8 @@
|
|||||||
<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" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<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>
|
||||||
|
|||||||
@@ -21,6 +21,5 @@
|
|||||||
- (void)saveContext;
|
- (void)saveContext;
|
||||||
- (NSURL *)applicationDocumentsDirectory;
|
- (NSURL *)applicationDocumentsDirectory;
|
||||||
- (NSURL*) getStoreURL;
|
- (NSURL*) getStoreURL;
|
||||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0));
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -5,13 +5,8 @@
|
|||||||
// Created by Diego Waxemberg on 1/17/14.
|
// Created by Diego Waxemberg on 1/17/14.
|
||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
// Modified by True砖家 since 2024.7.12
|
|
||||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "MainFrameViewController.h"
|
|
||||||
#import "VoidLink-Swift.h"
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
|
|
||||||
@@ -27,66 +22,17 @@ static NSString* DB_NAME = @"Moonlight_tvOS.bin";
|
|||||||
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#pragma mark - UISceneSession lifecycle
|
|
||||||
|
|
||||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)){
|
|
||||||
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)){
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
|
||||||
|
|
||||||
/*
|
|
||||||
// orietation limitatioin test
|
|
||||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
|
|
||||||
UIViewController *topController = window.rootViewController;
|
|
||||||
while (topController.presentedViewController) {
|
|
||||||
topController = topController.presentedViewController;
|
|
||||||
}
|
|
||||||
return [topController supportedInterfaceOrientations];
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
|
|
||||||
NSLog(@"orientation limit");
|
|
||||||
return UIInterfaceOrientationMaskLandscape;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||||
// Override point for command tool customization after application launch (works only when user default is nil)
|
#if !TARGET_OS_TV
|
||||||
[CommandManager presetDefaultCommands];
|
UIApplicationShortcutItem* shortcut = [launchOptions valueForKey:UIApplicationLaunchOptionsShortcutItemKey];
|
||||||
[GenericUtils installSegmentedControlPreviousSelectionTracking];
|
if (shortcut != nil) {
|
||||||
[IAPManager shared];
|
_pcUuidToLoad = (NSString*)[shortcut.userInfo objectForKey:@"UUID"];
|
||||||
|
|
||||||
// For iOS 12 and below, we need to manually create the window
|
|
||||||
if (@available(iOS 13.0, *)) {
|
|
||||||
// Scene delegate will handle window creation
|
|
||||||
} else {
|
|
||||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
|
||||||
|
|
||||||
NSString *storyboardName;
|
|
||||||
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
|
||||||
storyboardName = @"iPad";
|
|
||||||
} else {
|
|
||||||
storyboardName = @"iPhone";
|
|
||||||
}
|
|
||||||
|
|
||||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
|
|
||||||
UIViewController *initialViewController = [storyboard instantiateInitialViewController];
|
|
||||||
|
|
||||||
self.window.rootViewController = initialViewController;
|
|
||||||
[self.window makeKeyAndVisible];
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !TARGET_OS_TV
|
||||||
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
|
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
|
||||||
_pcUuidToLoad = (NSString*)[shortcutItem.userInfo objectForKey:@"UUID"];
|
_pcUuidToLoad = (NSString*)[shortcutItem.userInfo objectForKey:@"UUID"];
|
||||||
_shortcutCompletionHandler = completionHandler;
|
_shortcutCompletionHandler = completionHandler;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
@@ -96,7 +96,6 @@ static NSData* p12 = nil;
|
|||||||
|
|
||||||
bio = BIO_new(BIO_s_mem());
|
bio = BIO_new(BIO_s_mem());
|
||||||
i2d_X509_bio(bio, x509);
|
i2d_X509_bio(bio, x509);
|
||||||
X509_free(x509);
|
|
||||||
|
|
||||||
BUF_MEM* mem;
|
BUF_MEM* mem;
|
||||||
BIO_get_mem_ptr(bio, &mem);
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
@@ -223,7 +222,6 @@ static NSData* p12 = nil;
|
|||||||
+ (NSData *)getSignatureFromCert:(NSData *)cert {
|
+ (NSData *)getSignatureFromCert:(NSData *)cert {
|
||||||
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
||||||
X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||||
BIO_free(bio);
|
|
||||||
|
|
||||||
if (!x509) {
|
if (!x509) {
|
||||||
Log(LOG_E, @"Unable to parse certificate in memory!");
|
Log(LOG_E, @"Unable to parse certificate in memory!");
|
||||||
@@ -4,12 +4,10 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
#include <OpenSSL/provider.h>
|
#include <openssl/rsa.h>
|
||||||
#include <OpenSSL/rsa.h>
|
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <OpenSSL/rand.h>
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
static const int NUM_BITS = 2048;
|
static const int NUM_BITS = 2048;
|
||||||
static const int SERIAL = 0;
|
static const int SERIAL = 0;
|
||||||
@@ -63,30 +61,22 @@ void mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CertKeyPair generateCertKeyPair(void) {
|
struct CertKeyPair generateCertKeyPair(void) {
|
||||||
|
BIO *bio_err;
|
||||||
X509 *x509 = NULL;
|
X509 *x509 = NULL;
|
||||||
EVP_PKEY *pkey = NULL;
|
EVP_PKEY *pkey = NULL;
|
||||||
PKCS12 *p12 = NULL;
|
PKCS12 *p12 = NULL;
|
||||||
|
|
||||||
|
bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
|
||||||
|
|
||||||
mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS);
|
mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS);
|
||||||
|
|
||||||
char* pass = "limelight";
|
p12 = PKCS12_create("limelight", "GameStream", pkey, x509, NULL, 0, 0, 0, 0, 0);
|
||||||
p12 = PKCS12_create(pass,
|
|
||||||
"GameStream",
|
|
||||||
pkey,
|
|
||||||
x509,
|
|
||||||
NULL,
|
|
||||||
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
|
|
||||||
-1, // disable certificate encryption
|
|
||||||
2048,
|
|
||||||
-1, // disable the automatic MAC
|
|
||||||
0);
|
|
||||||
// MAC it ourselves with SHA1 since iOS refuses to load anything else.
|
|
||||||
PKCS12_set_mac(p12, pass, -1, NULL, 0, 1, EVP_sha1());
|
|
||||||
|
|
||||||
if (p12 == NULL) {
|
if (p12 == NULL) {
|
||||||
printf("Error generating a valid PKCS12 certificate.\n");
|
printf("Error generating a valid PKCS12 certificate.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIO_free(bio_err);
|
||||||
|
|
||||||
return (CertKeyPair){x509, pkey, p12};
|
return (CertKeyPair){x509, pkey, p12};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
#ifndef Limelight_mkcert_h
|
#ifndef Limelight_mkcert_h
|
||||||
#define Limelight_mkcert_h
|
#define Limelight_mkcert_h
|
||||||
|
|
||||||
#include <sys/_types/_time_t.h>
|
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
#include <openssl/pkcs12.h>
|
#include <openssl/pkcs12.h>
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// DataManager.h
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Diego Waxemberg on 10/28/14.
|
||||||
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "TemporaryHost.h"
|
||||||
|
#import "TemporaryApp.h"
|
||||||
|
#import "TemporarySettings.h"
|
||||||
|
|
||||||
|
@interface DataManager : NSObject
|
||||||
|
|
||||||
|
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||||
|
framerate:(NSInteger)framerate
|
||||||
|
height:(NSInteger)height
|
||||||
|
width:(NSInteger)width
|
||||||
|
audioConfig:(NSInteger)audioConfig
|
||||||
|
onscreenControls:(NSInteger)onscreenControls
|
||||||
|
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
|
||||||
|
swipeExitScreenEdge:(NSInteger)swipeExitScreenEdge
|
||||||
|
swipeToExitDistance:(CGFloat)swipeToExitDistance
|
||||||
|
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
|
||||||
|
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
|
||||||
|
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
|
||||||
|
showKeyboardToolbar:(BOOL)showKeyboardToolbar
|
||||||
|
optimizeGames:(BOOL)optimizeGames
|
||||||
|
multiController:(BOOL)multiController
|
||||||
|
swapABXYButtons:(BOOL)swapABXYButtons
|
||||||
|
audioOnPC:(BOOL)audioOnPC
|
||||||
|
preferredCodec:(uint32_t)preferredCodec
|
||||||
|
useFramePacing:(BOOL)useFramePacing
|
||||||
|
enableHdr:(BOOL)enableHdr
|
||||||
|
btMouseSupport:(BOOL)btMouseSupport
|
||||||
|
absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||||
|
statsOverlay:(BOOL)statsOverlay;
|
||||||
|
|
||||||
|
- (NSArray*) getHosts;
|
||||||
|
- (void) updateHost:(TemporaryHost*)host;
|
||||||
|
- (void) updateAppsForExistingHost:(TemporaryHost *)host;
|
||||||
|
- (void) removeHost:(TemporaryHost*)host;
|
||||||
|
- (void) removeApp:(TemporaryApp*)app;
|
||||||
|
|
||||||
|
- (TemporarySettings*) getSettings;
|
||||||
|
|
||||||
|
- (void) updateUniqueId:(NSString*)uniqueId;
|
||||||
|
- (NSString*) getUniqueId;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
//
|
||||||
|
// DataManager.m
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Diego Waxemberg on 10/28/14.
|
||||||
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "DataManager.h"
|
||||||
|
#import "TemporaryApp.h"
|
||||||
|
#import "TemporarySettings.h"
|
||||||
|
|
||||||
|
@implementation DataManager {
|
||||||
|
NSManagedObjectContext *_managedObjectContext;
|
||||||
|
AppDelegate *_appDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) init {
|
||||||
|
self = [super init];
|
||||||
|
|
||||||
|
// HACK: Avoid calling [UIApplication delegate] off the UI thread to keep
|
||||||
|
// Main Thread Checker happy.
|
||||||
|
if ([NSThread isMainThread]) {
|
||||||
|
_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||||
|
self->_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||||
|
[_managedObjectContext setParentContext:[_appDelegate managedObjectContext]];
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) updateUniqueId:(NSString*)uniqueId {
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
[self retrieveSettings].uniqueId = uniqueId;
|
||||||
|
[self saveData];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString*) getUniqueId {
|
||||||
|
__block NSString *uid;
|
||||||
|
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
uid = [self retrieveSettings].uniqueId;
|
||||||
|
}];
|
||||||
|
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||||
|
framerate:(NSInteger)framerate
|
||||||
|
height:(NSInteger)height
|
||||||
|
width:(NSInteger)width
|
||||||
|
audioConfig:(NSInteger)audioConfig
|
||||||
|
onscreenControls:(NSInteger)onscreenControls
|
||||||
|
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
|
||||||
|
swipeExitScreenEdge:(NSInteger)swipeExitScreenEdge
|
||||||
|
swipeToExitDistance:(CGFloat)swipeToExitDistance
|
||||||
|
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
|
||||||
|
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
|
||||||
|
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
|
||||||
|
showKeyboardToolbar:(BOOL)showKeyboardToolbar
|
||||||
|
optimizeGames:(BOOL)optimizeGames
|
||||||
|
multiController:(BOOL)multiController
|
||||||
|
swapABXYButtons:(BOOL)swapABXYButtons
|
||||||
|
audioOnPC:(BOOL)audioOnPC
|
||||||
|
preferredCodec:(uint32_t)preferredCodec
|
||||||
|
useFramePacing:(BOOL)useFramePacing
|
||||||
|
enableHdr:(BOOL)enableHdr
|
||||||
|
btMouseSupport:(BOOL)btMouseSupport
|
||||||
|
absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||||
|
statsOverlay:(BOOL)statsOverlay {
|
||||||
|
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
Settings* settingsToSave = [self retrieveSettings];
|
||||||
|
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
|
||||||
|
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
|
||||||
|
settingsToSave.height = [NSNumber numberWithInteger:height];
|
||||||
|
settingsToSave.width = [NSNumber numberWithInteger:width];
|
||||||
|
settingsToSave.audioConfig = [NSNumber numberWithInteger:audioConfig];
|
||||||
|
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
|
||||||
|
settingsToSave.keyboardToggleFingers = [NSNumber numberWithInteger:(uint16_t)keyboardToggleFingers];
|
||||||
|
settingsToSave.swipeExitScreenEdge = [NSNumber numberWithInteger:(uint32_t)swipeExitScreenEdge];
|
||||||
|
settingsToSave.swipeToExitDistance = [NSNumber numberWithFloat:swipeToExitDistance];
|
||||||
|
settingsToSave.pointerVelocityModeDivider = [NSNumber numberWithFloat:pointerVelocityModeDivider];
|
||||||
|
settingsToSave.touchPointerVelocityFactor = [NSNumber numberWithFloat:touchPointerVelocityFactor];
|
||||||
|
settingsToSave.liftStreamViewForKeyboard = liftStreamViewForKeyboard;
|
||||||
|
settingsToSave.showKeyboardToolbar = showKeyboardToolbar;
|
||||||
|
settingsToSave.optimizeGames = optimizeGames;
|
||||||
|
settingsToSave.multiController = multiController;
|
||||||
|
settingsToSave.swapABXYButtons = swapABXYButtons;
|
||||||
|
settingsToSave.playAudioOnPC = audioOnPC;
|
||||||
|
settingsToSave.preferredCodec = preferredCodec;
|
||||||
|
settingsToSave.useFramePacing = useFramePacing;
|
||||||
|
settingsToSave.enableHdr = enableHdr;
|
||||||
|
settingsToSave.btMouseSupport = btMouseSupport;
|
||||||
|
settingsToSave.absoluteTouchMode = absoluteTouchMode;
|
||||||
|
settingsToSave.statsOverlay = statsOverlay;
|
||||||
|
|
||||||
|
[self saveData];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) updateHost:(TemporaryHost *)host {
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
// Add a new persistent managed object if one doesn't exist
|
||||||
|
Host* parent = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||||
|
if (parent == nil) {
|
||||||
|
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Host" inManagedObjectContext:self->_managedObjectContext];
|
||||||
|
parent = [[Host alloc] initWithEntity:entity insertIntoManagedObjectContext:self->_managedObjectContext];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push changes from the temp host to the persistent one
|
||||||
|
[host propagateChangesToParent:parent];
|
||||||
|
|
||||||
|
[self saveData];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) updateAppsForExistingHost:(TemporaryHost *)host {
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
Host* parent = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||||
|
if (parent == nil) {
|
||||||
|
// The host must exist to be updated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableSet *applist = [[NSMutableSet alloc] init];
|
||||||
|
NSArray *appRecords = [self fetchRecords:@"App"];
|
||||||
|
for (TemporaryApp* app in host.appList) {
|
||||||
|
// Add a new persistent managed object if one doesn't exist
|
||||||
|
App* parentApp = [self getAppForTemporaryApp:app withAppRecords:appRecords];
|
||||||
|
if (parentApp == nil) {
|
||||||
|
NSEntityDescription* entity = [NSEntityDescription entityForName:@"App" inManagedObjectContext:self->_managedObjectContext];
|
||||||
|
parentApp = [[App alloc] initWithEntity:entity insertIntoManagedObjectContext:self->_managedObjectContext];
|
||||||
|
}
|
||||||
|
|
||||||
|
[app propagateChangesToParent:parentApp withHost:parent];
|
||||||
|
|
||||||
|
[applist addObject:parentApp];
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.appList = applist;
|
||||||
|
|
||||||
|
[self saveData];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (TemporarySettings*) getSettings {
|
||||||
|
__block TemporarySettings *tempSettings;
|
||||||
|
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
tempSettings = [[TemporarySettings alloc] initFromSettings:[self retrieveSettings]];
|
||||||
|
}];
|
||||||
|
|
||||||
|
return tempSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (Settings*) retrieveSettings {
|
||||||
|
NSArray* fetchedRecords = [self fetchRecords:@"Settings"];
|
||||||
|
if (fetchedRecords.count == 0) {
|
||||||
|
// create a new settings object with the default values
|
||||||
|
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:_managedObjectContext];
|
||||||
|
Settings* settings = [[Settings alloc] initWithEntity:entity insertIntoManagedObjectContext:_managedObjectContext];
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
} else {
|
||||||
|
// we should only ever have 1 settings object stored
|
||||||
|
return [fetchedRecords objectAtIndex:0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) removeApp:(TemporaryApp*)app {
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
App* managedApp = [self getAppForTemporaryApp:app withAppRecords:[self fetchRecords:@"App"]];
|
||||||
|
if (managedApp != nil) {
|
||||||
|
[self->_managedObjectContext deleteObject:managedApp];
|
||||||
|
[self saveData];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) removeHost:(TemporaryHost*)host {
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
Host* managedHost = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||||
|
if (managedHost != nil) {
|
||||||
|
[self->_managedObjectContext deleteObject:managedHost];
|
||||||
|
[self saveData];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) saveData {
|
||||||
|
NSError* error;
|
||||||
|
if ([_managedObjectContext hasChanges] && ![_managedObjectContext save:&error]) {
|
||||||
|
Log(LOG_E, @"Unable to save hosts to database: %@", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[_appDelegate saveContext];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray*) getHosts {
|
||||||
|
__block NSMutableArray *tempHosts = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
[_managedObjectContext performBlockAndWait:^{
|
||||||
|
NSArray *hosts = [self fetchRecords:@"Host"];
|
||||||
|
|
||||||
|
for (Host* host in hosts) {
|
||||||
|
[tempHosts addObject:[[TemporaryHost alloc] initFromHost:host]];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
return tempHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only call from within performBlockAndWait!!!
|
||||||
|
- (Host*) getHostForTemporaryHost:(TemporaryHost*)tempHost withHostRecords:(NSArray*)hosts {
|
||||||
|
for (Host* host in hosts) {
|
||||||
|
if ([tempHost.uuid isEqualToString:host.uuid]) {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only call from within performBlockAndWait!!!
|
||||||
|
- (App*) getAppForTemporaryApp:(TemporaryApp*)tempApp withAppRecords:(NSArray*)apps {
|
||||||
|
for (App* app in apps) {
|
||||||
|
if ([app.id isEqualToString:tempApp.id] &&
|
||||||
|
[app.host.uuid isEqualToString:tempApp.host.uuid]) {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray*) fetchRecords:(NSString*)entityName {
|
||||||
|
NSArray* fetchedRecords;
|
||||||
|
|
||||||
|
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
|
||||||
|
NSEntityDescription* entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:_managedObjectContext];
|
||||||
|
[fetchRequest setEntity:entity];
|
||||||
|
|
||||||
|
NSError* error;
|
||||||
|
fetchedRecords = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||||
|
//TODO: handle errors
|
||||||
|
|
||||||
|
return fetchedRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
@property (atomic, nullable, retain) NSString *localAddress;
|
@property (atomic, nullable, retain) NSString *localAddress;
|
||||||
@property (atomic, nullable, retain) NSString *ipv6Address;
|
@property (atomic, nullable, retain) NSString *ipv6Address;
|
||||||
@property (atomic, nullable, retain) NSString *mac;
|
@property (atomic, nullable, retain) NSString *mac;
|
||||||
@property (atomic, nullable, retain) NSString *controllerNavigationHighlightedAppID;
|
|
||||||
@property (atomic) int serverCodecModeSupport;
|
@property (atomic) int serverCodecModeSupport;
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
@@ -32,7 +31,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (atomic, retain) NSString *name;
|
@property (atomic, retain) NSString *name;
|
||||||
@property (atomic, retain) NSString *uuid;
|
@property (atomic, retain) NSString *uuid;
|
||||||
@property (atomic, retain) NSSet *appList;
|
@property (atomic, retain) NSSet *appList;
|
||||||
@property (atomic, retain) NSMutableSet * activeAddressPool;
|
|
||||||
|
|
||||||
- (id) initFromHost:(Host*)host;
|
- (id) initFromHost:(Host*)host;
|
||||||
|
|
||||||
@@ -5,9 +5,6 @@
|
|||||||
// Created by Cameron Gutman on 12/1/15.
|
// Created by Cameron Gutman on 12/1/15.
|
||||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
// Modified by True砖家 since 2024.8.3
|
|
||||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "DataManager.h"
|
#import "DataManager.h"
|
||||||
#import "TemporaryHost.h"
|
#import "TemporaryHost.h"
|
||||||
@@ -28,24 +25,12 @@
|
|||||||
self = [self init];
|
self = [self init];
|
||||||
|
|
||||||
self.address = host.address;
|
self.address = host.address;
|
||||||
|
|
||||||
NSSet *classes = [NSSet setWithObjects: [NSMutableSet class], [NSString class], nil];
|
|
||||||
NSError *error = nil;
|
|
||||||
self.activeAddressPool = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:host.activeAddressPool error:&error];
|
|
||||||
if(self.activeAddressPool){
|
|
||||||
for(NSString* address in _activeAddressPool){
|
|
||||||
NSLog(@"%@, persisted addr: %@", host.name, address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else self.activeAddressPool = [[NSMutableSet alloc] init];
|
|
||||||
|
|
||||||
self.externalAddress = host.externalAddress;
|
self.externalAddress = host.externalAddress;
|
||||||
self.localAddress = host.localAddress;
|
self.localAddress = host.localAddress;
|
||||||
self.ipv6Address = host.ipv6Address;
|
self.ipv6Address = host.ipv6Address;
|
||||||
if(!(host.mac == nil || [host.mac isEqualToString:@"00:00:00:00:00:00"])) self.mac = host.mac; // try to fix invalid mac happens in some cases
|
self.mac = host.mac;
|
||||||
self.name = host.name;
|
self.name = host.name;
|
||||||
self.uuid = host.uuid;
|
self.uuid = host.uuid;
|
||||||
self.controllerNavigationHighlightedAppID = host.controllerNavigationHighlightedAppID;
|
|
||||||
self.serverCodecModeSupport = host.serverCodecModeSupport;
|
self.serverCodecModeSupport = host.serverCodecModeSupport;
|
||||||
self.serverCert = host.serverCert;
|
self.serverCert = host.serverCert;
|
||||||
|
|
||||||
@@ -69,7 +54,7 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) propagateChangesToParent:(Host*)parentHost { // update persisted host data
|
- (void) propagateChangesToParent:(Host*)parentHost {
|
||||||
// Avoid overwriting existing data with nil if
|
// Avoid overwriting existing data with nil if
|
||||||
// we don't have everything populated in the temporary
|
// we don't have everything populated in the temporary
|
||||||
// host.
|
// host.
|
||||||
@@ -85,24 +70,14 @@
|
|||||||
if (self.ipv6Address != nil) {
|
if (self.ipv6Address != nil) {
|
||||||
parentHost.ipv6Address = self.ipv6Address;
|
parentHost.ipv6Address = self.ipv6Address;
|
||||||
}
|
}
|
||||||
|
if (self.mac != nil) {
|
||||||
if(self.activeAddressPool.count>0){
|
parentHost.mac = self.mac;
|
||||||
NSError *error;
|
|
||||||
parentHost.activeAddressPool = [NSKeyedArchiver archivedDataWithRootObject:self.activeAddressPool requiringSecureCoding:YES error:&error];
|
|
||||||
}
|
}
|
||||||
else if(self.activeAddress) [self.activeAddressPool addObject:self.activeAddress];
|
|
||||||
|
|
||||||
// NSLog(@"Persisting activeAddressPool, pool count %lu ... %f, host: %@", (unsigned long)self.activeAddressPool.count, CACurrentMediaTime(), self.name);
|
|
||||||
|
|
||||||
// try to fix invalid mac happens in some cases
|
|
||||||
// 添加主机crash问题重点关注
|
|
||||||
if (!(self.mac == nil || [self.mac isEqualToString:@"00:00:00:00:00:00"])) parentHost.mac = self.mac;
|
|
||||||
if (self.serverCert != nil) {
|
if (self.serverCert != nil) {
|
||||||
parentHost.serverCert = self.serverCert;
|
parentHost.serverCert = self.serverCert;
|
||||||
}
|
}
|
||||||
parentHost.name = self.name;
|
parentHost.name = self.name;
|
||||||
parentHost.uuid = self.uuid;
|
parentHost.uuid = self.uuid;
|
||||||
parentHost.controllerNavigationHighlightedAppID = self.controllerNavigationHighlightedAppID;
|
|
||||||
parentHost.serverCodecModeSupport = self.serverCodecModeSupport;
|
parentHost.serverCodecModeSupport = self.serverCodecModeSupport;
|
||||||
parentHost.pairState = [NSNumber numberWithInt:self.pairState];
|
parentHost.pairState = [NSNumber numberWithInt:self.pairState];
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// TemporarySettings.h
|
||||||
|
// Moonlight
|
||||||
|
//
|
||||||
|
// Created by Cameron Gutman on 12/1/15.
|
||||||
|
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "Settings+CoreDataClass.h"
|
||||||
|
|
||||||
|
@interface TemporarySettings : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, retain) Settings * parent;
|
||||||
|
|
||||||
|
@property (nonatomic, retain) NSNumber * bitrate;
|
||||||
|
@property (nonatomic, retain) NSNumber * framerate;
|
||||||
|
@property (nonatomic, retain) NSNumber * height;
|
||||||
|
@property (nonatomic, retain) NSNumber * width;
|
||||||
|
@property (nonatomic, retain) NSNumber * audioConfig;
|
||||||
|
@property (nonatomic, retain) NSNumber * onscreenControls;
|
||||||
|
@property (nonatomic, retain) NSNumber * keyboardToggleFingers;
|
||||||
|
@property (nonatomic, retain) NSNumber * swipeExitScreenEdge;
|
||||||
|
@property (nonatomic, retain) NSNumber * swipeToExitDistance;
|
||||||
|
@property (nonatomic, retain) NSNumber * touchPointerVelocityFactor;
|
||||||
|
@property (nonatomic, retain) NSNumber * pointerVelocityModeDivider;
|
||||||
|
@property (nonatomic, retain) NSString * uniqueId;
|
||||||
|
@property (nonatomic) enum {
|
||||||
|
CODEC_PREF_AUTO,
|
||||||
|
CODEC_PREF_H264,
|
||||||
|
CODEC_PREF_HEVC,
|
||||||
|
CODEC_PREF_AV1,
|
||||||
|
} preferredCodec;
|
||||||
|
@property (nonatomic) BOOL useFramePacing;
|
||||||
|
@property (nonatomic) BOOL multiController;
|
||||||
|
@property (nonatomic) BOOL swapABXYButtons;
|
||||||
|
@property (nonatomic) BOOL playAudioOnPC;
|
||||||
|
@property (nonatomic) BOOL optimizeGames;
|
||||||
|
@property (nonatomic) BOOL enableHdr;
|
||||||
|
@property (nonatomic) BOOL btMouseSupport;
|
||||||
|
@property (nonatomic) BOOL absoluteTouchMode;
|
||||||
|
@property (nonatomic) BOOL statsOverlay;
|
||||||
|
@property (nonatomic) BOOL liftStreamViewForKeyboard;
|
||||||
|
@property (nonatomic) BOOL showKeyboardToolbar;
|
||||||
|
|
||||||
|
|
||||||
|
- (id) initFromSettings:(Settings*)settings;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// 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.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.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.btMouseSupport = settings.btMouseSupport;
|
||||||
|
self.absoluteTouchMode = settings.absoluteTouchMode;
|
||||||
|
self.statsOverlay = settings.statsOverlay;
|
||||||
|
self.keyboardToggleFingers = settings.keyboardToggleFingers;
|
||||||
|
self.swipeExitScreenEdge = settings.swipeExitScreenEdge;
|
||||||
|
self.swipeToExitDistance = settings.swipeToExitDistance;
|
||||||
|
self.liftStreamViewForKeyboard = settings.liftStreamViewForKeyboard;
|
||||||
|
self.showKeyboardToolbar = settings.showKeyboardToolbar;
|
||||||
|
self.touchPointerVelocityFactor = settings.touchPointerVelocityFactor;
|
||||||
|
self.pointerVelocityModeDivider = settings.pointerVelocityModeDivider;
|
||||||
|
#endif
|
||||||
|
self.uniqueId = settings.uniqueId;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -1,116 +1,112 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "Icon-App-40x40@1x 2.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"size" : "20x20",
|
||||||
"size" : "20x20"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-App-20x20@3x 1.png",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"size" : "20x20",
|
||||||
"size" : "20x20"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
"idiom" : "iphone",
|
"scale" : "2x"
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-29x29@3x.png",
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
"idiom" : "iphone",
|
"scale" : "3x"
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
"idiom" : "iphone",
|
"scale" : "2x"
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-40x40@3x.png",
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
"idiom" : "iphone",
|
"scale" : "3x"
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-60x60@2x.png",
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
"idiom" : "iphone",
|
"scale" : "2x"
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
"filename" : "Icon-App-60x60@3x.png",
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
"idiom" : "iphone",
|
"scale" : "3x"
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-App-20x20@1x 1.png",
|
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "1x",
|
"size" : "20x20",
|
||||||
"size" : "20x20"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon-App-40x40@1x 1.png",
|
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "2x",
|
"size" : "20x20",
|
||||||
"size" : "20x20"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
"idiom" : "ipad",
|
"scale" : "1x"
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
"filename" : "Icon-App-29x29@2x-1.png",
|
"filename" : "Icon-App-29x29@2x-1.png",
|
||||||
"idiom" : "ipad",
|
"scale" : "2x"
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
"filename" : "Icon-App-40x40@1x.png",
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
"idiom" : "ipad",
|
"scale" : "1x"
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
"filename" : "Icon-App-40x40@2x-1.png",
|
"filename" : "Icon-App-40x40@2x-1.png",
|
||||||
"idiom" : "ipad",
|
"scale" : "2x"
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
"filename" : "Icon-App-76x76@1x.png",
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
"idiom" : "ipad",
|
"scale" : "1x"
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "76x76"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
"filename" : "Icon-App-76x76@2x.png",
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
"idiom" : "ipad",
|
"scale" : "2x"
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "76x76"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
"idiom" : "ipad",
|
"scale" : "2x"
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "83.5x83.5"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "1024x icon.png",
|
"size" : "1024x1024",
|
||||||
"idiom" : "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"scale" : "1x",
|
"filename" : "1024x icon.png",
|
||||||
"size" : "1024x1024"
|
"scale" : "1x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"version" : 1,
|
||||||
"version" : 1
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 455 B After Width: | Height: | Size: 455 B |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 525 B |