2021-02-11 13:47:14 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-02-10 09:04:41 -04:00
# include "IOSDeviceHelper.h"
2021-02-10 07:28:32 -04:00
# include "IOSTargetPlatform.h"
# include "IOSTargetDeviceOutput.h"
# include "HAL/PlatformProcess.h"
# include "HAL/Runnable.h"
# include "HAL/RunnableThread.h"
# include "Interfaces/ITargetPlatformManagerModule.h"
# include "Interfaces/ITargetPlatform.h"
# include "Interfaces/IProjectManager.h"
2021-09-17 05:01:42 -04:00
# include "Misc/MessageDialog.h"
# define LOCTEXT_NAMESPACE "MiscIOSMessages"
2021-02-10 07:28:32 -04:00
DEFINE_LOG_CATEGORY_STATIC ( LogIOSDeviceHelper , Log , All ) ;
2021-02-17 09:08:31 -04:00
enum DeviceConnectionInterface
{
NoValue = 0 ,
USB = 1 ,
Network = 2 ,
Max = 4
} ;
struct FDeviceNotificationCallbackInformation
{
FString UDID ;
FString DeviceName ;
FString ProductType ;
DeviceConnectionInterface DeviceInterface ;
uint32 msgType ;
2021-09-17 05:01:42 -04:00
bool isAuthorized ;
2021-02-17 09:08:31 -04:00
} ;
2021-02-10 07:28:32 -04:00
struct LibIMobileDevice
{
FString DeviceName ;
FString DeviceID ;
FString DeviceType ;
2021-09-17 05:01:42 -04:00
bool isAuthorized ;
2021-02-17 09:08:31 -04:00
DeviceConnectionInterface DeviceInterface ;
2021-02-10 07:28:32 -04:00
} ;
static TArray < LibIMobileDevice > GetLibIMobileDevices ( )
{
FString OutStdOut ;
FString OutStdErr ;
FString LibimobileDeviceId = GetLibImobileDeviceExe ( " idevice_id " ) ;
int ReturnCode ;
// get the list of devices UDID
2021-03-05 05:26:15 -04:00
FPlatformProcess : : ExecProcess ( * LibimobileDeviceId , TEXT ( " " ) , & ReturnCode , & OutStdOut , & OutStdErr , NULL , true ) ;
2021-02-10 07:28:32 -04:00
TArray < LibIMobileDevice > ToReturn ;
// separate out each line
2021-02-17 09:08:31 -04:00
TArray < FString > OngoingDeviceIds ;
OutStdOut . ParseIntoArray ( OngoingDeviceIds , TEXT ( " \n " ) , true ) ;
2021-02-10 07:28:32 -04:00
TArray < FString > DeviceStrings ;
2021-02-17 09:08:31 -04:00
for ( int32 StringIndex = 0 ; StringIndex < OngoingDeviceIds . Num ( ) ; + + StringIndex )
2021-02-10 07:28:32 -04:00
{
2021-02-17 09:08:31 -04:00
const FString & DeviceID = OngoingDeviceIds [ StringIndex ] ;
DeviceConnectionInterface OngoingDeviceInterface = DeviceConnectionInterface : : NoValue ;
2021-02-10 07:28:32 -04:00
FString OutStdOutInfo ;
FString OutStdErrInfo ;
FString LibimobileDeviceInfo = GetLibImobileDeviceExe ( " ideviceinfo " ) ;
int ReturnCodeInfo ;
2021-02-17 09:08:31 -04:00
FString Arguments ;
2021-02-10 07:28:32 -04:00
2021-02-17 09:08:31 -04:00
if ( OngoingDeviceIds [ StringIndex ] . Contains ( " USB " ) )
{
OngoingDeviceInterface = DeviceConnectionInterface : : USB ;
}
else if ( OngoingDeviceIds [ StringIndex ] . Contains ( " Network " ) )
{
OngoingDeviceInterface = DeviceConnectionInterface : : Network ;
}
OngoingDeviceIds [ StringIndex ] . Split ( TEXT ( " " ) , & OngoingDeviceIds [ StringIndex ] , nullptr , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
if ( OngoingDeviceInterface = = DeviceConnectionInterface : : USB )
{
Arguments = " -u " + DeviceID ;
}
else if ( OngoingDeviceInterface = = DeviceConnectionInterface : : Network )
{
Arguments = " -n -u " + DeviceID ;
}
2021-03-05 05:26:15 -04:00
FPlatformProcess : : ExecProcess ( * LibimobileDeviceInfo , * Arguments , & ReturnCodeInfo , & OutStdOutInfo , & OutStdErrInfo , NULL , true ) ;
2021-09-17 05:01:42 -04:00
LibIMobileDevice ToAdd ;
// ideviceinfo can fail when the connected device is untrusted. It can be "Pairing dialog response pending (-19)", "Invalid HostID (-21)" or "User denied pairing (-18)"
// the only thing we can do is to make sure the Trust popup is correctly displayed.
if ( OutStdErrInfo . Contains ( " ERROR: " ) )
{
if ( OutStdErrInfo . Contains ( " Could not connect to lockdownd " ) )
{
UE_LOG ( LogIOSDeviceHelper , Warning , TEXT ( " Could not pair with connected iOS/tvOS device. Trust this computer by accepting the popup on device. " ) ) ;
FString LibimobileDevicePair = GetLibImobileDeviceExe ( " idevicepair " ) ;
FString PairArguments = " -u " + DeviceID + " pair " ;
FPlatformProcess : : ExecProcess ( * LibimobileDevicePair , * PairArguments , & ReturnCodeInfo , & OutStdOutInfo , & OutStdErrInfo , NULL , true ) ;
}
else
{
UE_LOG ( LogIOSDeviceHelper , Warning , TEXT ( " Libimobile call failed : %s " ) , * OutStdErrInfo ) ;
}
OutStdOutInfo . Empty ( ) ;
OutStdErrInfo . Empty ( ) ;
ToAdd . isAuthorized = false ;
}
else
{
ToAdd . isAuthorized = true ;
}
// parse product type and device name
FString DeviceName ;
2021-02-10 07:28:32 -04:00
OutStdOutInfo . Split ( TEXT ( " DeviceName: " ) , nullptr , & DeviceName , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
2021-09-17 05:01:42 -04:00
DeviceName . Split ( LINE_TERMINATOR , & DeviceName , nullptr , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
if ( ! ToAdd . isAuthorized )
{
DeviceName = LOCTEXT ( " IosTvosUnauthorizedDevice " , " iOS / tvOS (Unauthorized) " ) . ToString ( ) ;
}
2021-02-10 07:28:32 -04:00
FString ProductType ;
OutStdOutInfo . Split ( TEXT ( " ProductType: " ) , nullptr , & ProductType , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
2021-02-17 09:08:31 -04:00
ProductType . Split ( LINE_TERMINATOR , & ProductType , nullptr , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
2021-02-10 07:28:32 -04:00
ToAdd . DeviceID = DeviceID ;
ToAdd . DeviceName = DeviceName ;
ToAdd . DeviceType = ProductType ;
2021-02-17 09:08:31 -04:00
ToAdd . DeviceInterface = OngoingDeviceInterface ;
2021-02-10 07:28:32 -04:00
ToReturn . Add ( ToAdd ) ;
}
return ToReturn ;
}
class FIOSDevice
{
public :
FIOSDevice ( FString InID , FString InName )
: UDID ( InID )
, Name ( InName )
{
}
~ FIOSDevice ( )
{
}
FString SerialNumber ( ) const
{
return UDID ;
}
private :
FString UDID ;
FString Name ;
} ;
/**
* Delegate type for devices being connected or disconnected from the machine
*
* The first parameter is newly added or removed device
*/
DECLARE_MULTICAST_DELEGATE_OneParam ( FDeviceNotification , void * )
// recheck once per minute
# define RECHECK_COUNTER_RESET 12
class FDeviceQueryTask
: public FRunnable
{
public :
FDeviceQueryTask ( )
: Stopping ( false )
, bCheckDevices ( true )
, NeedSDKCheck ( true )
, RetryQuery ( 5 )
{ }
virtual bool Init ( ) override
{
return true ;
}
virtual uint32 Run ( ) override
{
while ( ! Stopping )
{
if ( IsEngineExitRequested ( ) )
{
break ;
}
if ( GetTargetPlatformManager ( ) )
{
FString OutTutorialPath ;
const ITargetPlatform * Platform = GetTargetPlatformManager ( ) - > FindTargetPlatform ( TEXT ( " IOS " ) ) ;
if ( Platform )
{
if ( Platform - > IsSdkInstalled ( false , OutTutorialPath ) )
{
break ;
}
}
Enable ( false ) ;
return 0 ;
}
else
{
FPlatformProcess : : Sleep ( 1.0f ) ;
}
}
int RecheckCounter = RECHECK_COUNTER_RESET ;
while ( ! Stopping )
{
if ( IsEngineExitRequested ( ) )
{
break ;
}
if ( bCheckDevices )
{
# if WITH_EDITOR
if ( ! IsRunningCommandlet ( ) )
{
//if (NeedSDKCheck)
//{
// NeedSDKCheck = false;
// FProjectStatus ProjectStatus;
// if (!IProjectManager::Get().QueryStatusForCurrentProject(ProjectStatus) || (!ProjectStatus.IsTargetPlatformSupported(TEXT("IOS")) && !ProjectStatus.IsTargetPlatformSupported(TEXT("TVOS"))))
// {
// Enable(false);
// }
//}
//else
{
// BHP - Turning off device check to prevent it from interfering with packaging
QueryDevices ( ) ;
}
}
2021-04-13 10:11:56 -04:00
# else
QueryDevices ( ) ;
2021-02-10 07:28:32 -04:00
# endif
}
RecheckCounter - - ;
if ( RecheckCounter < 0 )
{
RecheckCounter = RECHECK_COUNTER_RESET ;
bCheckDevices = true ;
NeedSDKCheck = true ;
}
FPlatformProcess : : Sleep ( 5.0f ) ;
}
return 0 ;
}
virtual void Stop ( ) override
{
Stopping = true ;
}
virtual void Exit ( ) override
{ }
FDeviceNotification & OnDeviceNotification ( )
{
return DeviceNotification ;
}
void Enable ( bool bInCheckDevices )
{
bCheckDevices = bInCheckDevices ;
}
private :
2021-09-17 05:01:42 -04:00
void NotifyDeviceChange ( LibIMobileDevice & Device , bool bAdd )
{
FDeviceNotificationCallbackInformation CallbackInfo ;
2021-02-10 07:28:32 -04:00
2021-09-17 05:01:42 -04:00
if ( bAdd )
{
CallbackInfo . DeviceName = Device . DeviceName ;
CallbackInfo . UDID = Device . DeviceID ;
CallbackInfo . DeviceInterface = Device . DeviceInterface ;
CallbackInfo . ProductType = Device . DeviceType ;
CallbackInfo . msgType = 1 ;
CallbackInfo . isAuthorized = Device . isAuthorized ;
}
else
{
CallbackInfo . UDID = Device . DeviceID ;
CallbackInfo . msgType = 2 ;
DeviceNotification . Broadcast ( & CallbackInfo ) ;
}
DeviceNotification . Broadcast ( & CallbackInfo ) ;
}
2021-02-10 07:28:32 -04:00
void QueryDevices ( )
{
FString OutStdOut ;
FString OutStdErr ;
FString LibimobileDeviceId = GetLibImobileDeviceExe ( " idevice_id " ) ;
int ReturnCode ;
// get the list of devices UDID
2021-03-05 05:26:15 -04:00
FPlatformProcess : : ExecProcess ( * LibimobileDeviceId , TEXT ( " " ) , & ReturnCode , & OutStdOut , & OutStdErr , NULL , true ) ;
2021-02-10 07:28:32 -04:00
if ( OutStdOut . Len ( ) = = 0 )
{
RetryQuery - - ;
if ( RetryQuery < 0 )
{
UE_LOG ( LogIOSDeviceHelper , Verbose , TEXT ( " IOS device listing is disabled for 1 minute (too many failed attempts)! " ) ) ;
Enable ( false ) ;
}
2021-09-17 05:01:42 -04:00
for ( LibIMobileDevice device : CachedDevices )
{
NotifyDeviceChange ( device , false ) ;
}
CachedDevices . Empty ( ) ;
2021-02-10 07:28:32 -04:00
return ;
}
RetryQuery = 5 ;
TArray < LibIMobileDevice > ParsedDevices = GetLibIMobileDevices ( ) ;
for ( int32 Index = 0 ; Index < ParsedDevices . Num ( ) ; + + Index )
{
2021-09-17 05:01:42 -04:00
TObjectPtr < LibIMobileDevice > Found = CachedDevices . FindByPredicate (
[ & ] ( LibIMobileDevice Element ) {
return Element . DeviceID = = ParsedDevices [ Index ] . DeviceID ;
} ) ;
if ( Found ! = nullptr )
{
if ( Found - > isAuthorized ! = ParsedDevices [ Index ] . isAuthorized )
{
NotifyDeviceChange ( ParsedDevices [ Index ] , false ) ;
NotifyDeviceChange ( ParsedDevices [ Index ] , true ) ;
}
Found - > DeviceID = " DealtWith " ;
}
else
{
NotifyDeviceChange ( ParsedDevices [ Index ] , true ) ;
}
}
2021-02-10 07:28:32 -04:00
2021-09-17 05:01:42 -04:00
for ( int32 Index = 0 ; Index < CachedDevices . Num ( ) ; + + Index )
2021-02-10 07:28:32 -04:00
{
2021-09-17 05:01:42 -04:00
if ( CachedDevices [ Index ] . DeviceID ! = " DealtWith " )
{
NotifyDeviceChange ( CachedDevices [ Index ] , false ) ;
}
2021-02-10 07:28:32 -04:00
}
2021-09-17 05:01:42 -04:00
CachedDevices . Empty ( ) ;
CachedDevices = ParsedDevices ;
2021-02-10 07:28:32 -04:00
}
bool Stopping ;
bool bCheckDevices ;
bool NeedSDKCheck ;
int RetryQuery ;
2021-09-17 05:01:42 -04:00
TArray < LibIMobileDevice > CachedDevices ;
2021-02-10 07:28:32 -04:00
FDeviceNotification DeviceNotification ;
} ;
/* FIOSDeviceHelper structors
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static TMap < FIOSDevice * , FIOSLaunchDaemonPong > ConnectedDevices ;
static FDeviceQueryTask * QueryTask = NULL ;
static FRunnableThread * QueryThread = NULL ;
static TArray < FDeviceNotificationCallbackInformation > NotificationMessages ;
static FTickerDelegate TickDelegate ;
bool FIOSDeviceHelper : : MessageTickDelegate ( float DeltaTime )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FIOSDeviceHelper_MessageTickDelegate ) ;
for ( int Index = 0 ; Index < NotificationMessages . Num ( ) ; + + Index )
{
FDeviceNotificationCallbackInformation cbi = NotificationMessages [ Index ] ;
FIOSDeviceHelper : : DeviceCallback ( & cbi ) ;
}
NotificationMessages . Empty ( ) ;
return true ;
}
void FIOSDeviceHelper : : Initialize ( bool bIsTVOS )
{
if ( ! bIsTVOS )
{
// add the message pump
TickDelegate = FTickerDelegate : : CreateStatic ( MessageTickDelegate ) ;
2021-08-16 11:09:22 -04:00
FTSTicker : : GetCoreTicker ( ) . AddTicker ( TickDelegate , 5.0f ) ;
2021-02-10 07:28:32 -04:00
// kick off a thread to query for connected devices
QueryTask = new FDeviceQueryTask ( ) ;
QueryTask - > OnDeviceNotification ( ) . AddStatic ( FIOSDeviceHelper : : DeviceCallback ) ;
static int32 QueryTaskCount = 1 ;
if ( QueryTaskCount = = 1 )
{
// create the socket subsystem (loadmodule in game thread)
ISocketSubsystem * SSS = ISocketSubsystem : : Get ( ) ;
QueryThread = FRunnableThread : : Create ( QueryTask , * FString : : Printf ( TEXT ( " FIOSDeviceHelper.QueryTask_%d " ) , QueryTaskCount + + ) , 128 * 1024 , TPri_Normal ) ;
}
}
}
void FIOSDeviceHelper : : DeviceCallback ( void * CallbackInfo )
{
struct FDeviceNotificationCallbackInformation * cbi = ( FDeviceNotificationCallbackInformation * ) CallbackInfo ;
if ( ! IsInGameThread ( ) )
{
NotificationMessages . Add ( * cbi ) ;
}
else
{
switch ( cbi - > msgType )
{
case 1 :
FIOSDeviceHelper : : DoDeviceConnect ( CallbackInfo ) ;
break ;
case 2 :
FIOSDeviceHelper : : DoDeviceDisconnect ( CallbackInfo ) ;
break ;
}
}
}
void FIOSDeviceHelper : : DoDeviceConnect ( void * CallbackInfo )
{
// connect to the device
struct FDeviceNotificationCallbackInformation * cbi = ( FDeviceNotificationCallbackInformation * ) CallbackInfo ;
FIOSDevice * Device = new FIOSDevice ( cbi - > UDID , cbi - > DeviceName ) ;
// fire the event
FIOSLaunchDaemonPong Event ;
Event . DeviceID = FString : : Printf ( TEXT ( " %s@%s " ) , cbi - > ProductType . Contains ( TEXT ( " AppleTV " ) ) ? TEXT ( " TVOS " ) : TEXT ( " IOS " ) , * ( cbi - > UDID ) ) ;
Event . DeviceName = cbi - > DeviceName ;
Event . DeviceType = cbi - > ProductType ;
2021-09-17 05:01:42 -04:00
Event . bIsAuthorized = cbi - > isAuthorized ;
2021-02-10 07:28:32 -04:00
Event . bCanReboot = false ;
Event . bCanPowerOn = false ;
Event . bCanPowerOff = false ;
FIOSDeviceHelper : : OnDeviceConnected ( ) . Broadcast ( Event ) ;
// add to the device list
ConnectedDevices . Add ( Device , Event ) ;
}
void FIOSDeviceHelper : : DoDeviceDisconnect ( void * CallbackInfo )
{
struct FDeviceNotificationCallbackInformation * cbi = ( FDeviceNotificationCallbackInformation * ) CallbackInfo ;
FIOSDevice * device = NULL ;
for ( auto DeviceIterator = ConnectedDevices . CreateIterator ( ) ; DeviceIterator ; + + DeviceIterator )
{
if ( DeviceIterator . Key ( ) - > SerialNumber ( ) = = cbi - > UDID )
{
device = DeviceIterator . Key ( ) ;
break ;
}
}
if ( device ! = NULL )
{
// extract the device id from the connected list
FIOSLaunchDaemonPong Event = ConnectedDevices . FindAndRemoveChecked ( device ) ;
// fire the event
FIOSDeviceHelper : : OnDeviceDisconnected ( ) . Broadcast ( Event ) ;
// delete the device
delete device ;
}
}
bool FIOSDeviceHelper : : InstallIPAOnDevice ( const FTargetDeviceId & DeviceId , const FString & IPAPath )
{
return false ;
}
void FIOSDeviceHelper : : EnableDeviceCheck ( bool OnOff )
{
QueryTask - > Enable ( OnOff ) ;
}
2021-09-22 06:23:52 -04:00
# undef LOCTEXT_NAMESPACE