Merge pull request #100 from libretro/master

Update
This commit is contained in:
alphanu1 2019-02-01 20:27:40 +00:00 committed by GitHub
commit b1a20b6ea7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
185 changed files with 20574 additions and 1255 deletions

2
.gitignore vendored
View File

@ -76,6 +76,7 @@ libretro-super
run.sh
convert_rumble.awk
*~
assets
# Wii U
*.depend
@ -121,7 +122,6 @@ wiiu/wut/elf2rpl/elf2rpl
/media/libretrodb/
pkg/apple/iOS/build/
pkg/apple/iOS/modules/
pkg/apple/build/
ui/drivers/qt/moc_*
ui/drivers/moc_*

View File

@ -1,15 +1,21 @@
# 1.7.6 (future)
- ANDROID: Fix Xperia Play input binding
- CHEEVOS: Reset when hardcore mode is toggled
# 1.7.7 (future)
# 1.7.6
- ANDROID: Fix Xperia Play input binding.
- CHEEVOS: Reset when hardcore mode is toggled.
- CHEEVOS: Update the hashing methods to identify NES, SNES and Lynx games (more accurate and accepting headerless ROMs).
- COMMON: Add new JSON playlist format
- COMMON: Fix playlist corruption when deleting items
- COMMON: Fix archive progress display calculation
- COMMON: Fix playlist entries appearing with previously used names
- COMMON: Fix screenshot filename with no core or content
- COMMON: Allow compiling without menu support
- CORE UPDATER: Allow sideloading cores from the menu
- COMMON: Add new JSON playlist format.
- COMMON: Fix playlist corruption when deleting items.
- COMMON: Fix archive progress display calculation.
- COMMON: Fix playlist entries appearing with previously used names.
- COMMON: Fix screenshot filename with no core or content.
- COMMON: Allow compiling without menu support.
- CORE UPDATER: Allow sideloading cores from the menu.
- CPU FILTERS: Add Normal2x filter.
- CRT/LINUX: New Linux switching method partially implemented.
- CRT/LINUX: Linux restore desktop resolution fixed.
- CRT/LINUX: Monitor index switching and auto enumerate for output detection in Linux (still working on the windows method).
- CRT/RASPBERRY PI: Initial support.
- DATE: Add Date / Time style options.
- DEBUGGING: Add an integrated crash handler for debug builds (see https://docs.libretro.com/tech/debugging)
- DISCORD: Register the application name properly.
@ -26,13 +32,21 @@
- MENU: New "ozone" menu driver.
- MENU: Only show CRT SwitchRes if video display server is implemented (Windows/Linux for now)
- MENU: User Interface -> Appearance -> 'Menu Font Green/Blue Color' settings now work properly.
- MENU: Add option to enable in-menu sound effects
- MENU/QT/WIMP: Allow building with MSVC2017
- MENU/QT/WIMP: Add detailed file browser table
- MENU/QT/WIMP: New grid view implementation that is faster and loads thumbnails on-demand
- MENU: Add option to enable in-menu sound effects.
- MENU/D3D: Scissoring support (will be used for Ozone and menu widgets).
- MENU/QT/WIMP: Allow building with MSVC2017.
- MENU/QT/WIMP: Add detailed file browser table.
- MENU/QT/WIMP: New grid view implementation that is faster and loads thumbnails on-demand.
- MENU/QT/WIMP: Thumbnail drag and drop support.
- MENU/RGUI: Overhaul custom theme interface + add wallpaper support.
- MENU/RGUI: Thumbnail support and thumbnail downscaling.
- MENU: Hide password values.
- MENU/SOUNDS: Implement in-menu sound effects (not enabled by default for now, still experimental).
- MIDI: Add a Linux ALSA driver for MIDI.
- NETPLAY: Force fast-save-states when netlay is enabled
- NETPLAY: Allow quick joining subsystem lobbies
- NETPLAY: Force fast-save-states when netplay is enabled.
- NETPLAY: Allow quick joining subsystem lobbies.
- OSX: Initial CoreAudio V3 audio driver (not yet used in release builds).
- OSX: OpenGL 3.2 Core support for cores.
- PS2: Initial PlayStation2 port.
- PS4: Initial PlayStation4 port.
- RECORDING: Implement recording options in the menu complete with quality profiles, streaming, and proper file naming
@ -43,20 +57,23 @@
- SUBSYSTEM: Remember the last used folder to make loading subsystem type content faster
- SWITCH/LIBNX: Improve touch scaling calculation.
- SWITCH: Proper button labels.
- TVOS: Initial tvOS port.
- VULKAN: Fix RGUI crashing at startup.
- VULKAN/RGUI: Enable 'Menu Linear Filter' option.
- VULKAN: Fix secondary screens in overlays not working.
- WAYLAND: Implement idle-inhibit support (needed for screensaver suspend).
- WAYLAND: Fix fullscreen toggle
- WAYLAND: Fix fullscreen toggle.
- WIIU: Initial netplay peer-to-peer support. Network information working.
- WINDOWS/WSA: Network Information info is blank until first network operation.
- WINDOWS: Fix an ancient bug that caused wrong mappings for keyboard arrows
- WINDOWS: Remember window size and position if so desired
- WINDOWS: Fix an ancient bug that caused wrong mappings for keyboard arrows.
- WINDOWS: Remember window size and position if so desired.
- WINDOWS: SSL/TLS connections now work properly.
- WINDOWS: Fall back to GDI driver if no accelerated graphics driver is found.
- UWP: Initial UWP port.
- VFS: Update to version 3
- VFS: Update to version 3.
- XBONE: Initial Xbox One port.
- XMB/OZONE: Add more icons
- ???: Easter Egg
- XMB/OZONE: Add more icons.
- ???: Easter Egg.
# 1.7.5
- CAMERA: Fix Video4Linux2 driver that broke years ago.

View File

@ -19,6 +19,7 @@ TARGET = retroarch
OBJ :=
LIBS :=
DEF_FLAGS :=
ASFLAGS :=
DEFINES := -DHAVE_CONFIG_H -DRARCH_INTERNAL -D_FILE_OFFSET_BITS=64
DEFINES += -DGLOBAL_CONFIG_DIR='"$(GLOBAL_CONFIG_DIR)"'

View File

@ -27,10 +27,6 @@ ifeq ($(HAVE_LIBRETRODB),)
HAVE_LIBRETRODB = 1
endif
ifeq ($(HAVE_VIDEO_PROCESSOR), 1)
DEFINES += -DHAVE_VIDEO_PROCESSOR
endif
ifeq ($(HAVE_SOCKET_LEGACY), 1)
DEFINES += -DHAVE_SOCKET_LEGACY
endif
@ -67,8 +63,14 @@ ifeq ($(VULKAN_DEBUG), 1)
DEF_FLAGS += -DVULKAN_DEBUG
endif
ifeq ($(HAVE_HARD_FLOAT), 1)
DEFINES += -mfloat-abi=hard
ifeq ($(HAVE_FLOATHARD), 1)
DEF_FLAGS += $(FLOATHARD_CFLAGS)
ASFLAGS += $(FLOATHARD_CFLAGS)
endif
ifeq ($(HAVE_FLOATSOFTFP), 1)
DEF_FLAGS += $(FLOATSOFTFP_CFLAGS)
ASFLAGS += $(FLOATSOFTFP_CFLAGS)
endif
ifeq ($(TDM_GCC),)
@ -374,6 +376,10 @@ ifeq ($(HAVE_SSA),1)
LIBS += $(SSA_LIBS)
endif
ifeq ($(HAVE_SSE),1)
DEF_FLAGS += $(SSE_LIBS)
endif
# LibretroDB
ifeq ($(HAVE_LIBRETRODB), 1)
@ -504,9 +510,19 @@ endif
# Audio
ifeq ($(HAVE_COREAUDIO), 1)
OBJ += audio/drivers/coreaudio.o
LIBS += -framework CoreServices -framework CoreAudio -framework AudioUnit
HAVE_COREAUDIO_LIBS = 1
endif
ifeq ($(HAVE_COREAUDIO3), 1)
OBJ += audio/drivers/coreaudio3.o
HAVE_COREAUDIO_LIBS = 1
endif
ifeq ($(HAVE_COREAUDIO_LIBS), 1)
LIBS += -framework CoreServices -framework CoreAudio -framework AudioUnit
endif
ifeq ($(HAVE_CORETEXT), 1)
@ -625,7 +641,10 @@ ifeq ($(HAVE_NEON),1)
OBJ += $(LIBRETRO_COMM_DIR)/audio/resampler/drivers/sinc_resampler_neon.o \
audio/drivers_resampler/cc_resampler_neon.o \
memory/neon/memcpy-neon.o
DEFINES += -DHAVE_NEON
DEFINES += -DHAVE_NEON
ASFLAGS += $(NEON_ASFLAGS)
DEF_FLAGS += $(NEON_CFLAGS)
endif
OBJ += $(LIBRETRO_COMM_DIR)/audio/conversion/s16_to_float.o \
@ -1244,8 +1263,6 @@ endif
ifeq ($(HAVE_DISPMANX), 1)
OBJ += gfx/drivers/dispmanx_gfx.o
HAVE_VIDEOCORE = 1
LIBS += $(DISPMANX_LIBS)
DEFINES += $(DISPMANX_CFLAGS)
endif
ifeq ($(HAVE_SUNXI), 1)
@ -1602,7 +1619,8 @@ endif
ifeq ($(HAVE_V4L2),1)
OBJ += camera/drivers/video4linux2.o
ifeq ($(HAVE_VIDEO_PROCESSOR),1)
ifeq ($(HAVE_VIDEOPROCESSOR),1)
DEFINES += -DHAVE_VIDEOPROCESSOR
OBJ += cores/libretro-video-processor/video_processor_v4l2.o
endif
DEFINES += -DHAVE_V4L2

View File

@ -41,7 +41,7 @@ HAVE_NETWORKING = 1
HAVE_NETPLAYDISCOVERY = 1
HAVE_STB_FONT = 1
HAVE_CHEEVOS = 1
HAVE_CHD = 1
HAVE_CHD = 0 # disabled due to static libretro-common and libchdr conflicts between different cores
HAVE_STB_VORBIS = 1
# RetroArch libnx useful flags

88
audio/audio_defines.h Normal file
View File

@ -0,0 +1,88 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __AUDIO_DEFINES__H
#define __AUDIO_DEFINES__H
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
#define AUDIO_CHUNK_SIZE_BLOCKING 512
/* So we don't get complete line-noise when fast-forwarding audio. */
#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048
#define AUDIO_MAX_RATIO 16
#define AUDIO_MIXER_MAX_STREAMS 16
#define AUDIO_MIXER_MAX_SYSTEM_STREAMS (AUDIO_MIXER_MAX_STREAMS + 4)
/* do not define more than (MAX_SYSTEM_STREAMS - MAX_STREAMS) */
enum audio_mixer_system_slot
{
AUDIO_MIXER_SYSTEM_SLOT_OK = AUDIO_MIXER_MAX_STREAMS,
AUDIO_MIXER_SYSTEM_SLOT_CANCEL,
AUDIO_MIXER_SYSTEM_SLOT_NOTICE,
AUDIO_MIXER_SYSTEM_SLOT_BGM
};
enum audio_action
{
AUDIO_ACTION_NONE = 0,
AUDIO_ACTION_RATE_CONTROL_DELTA,
AUDIO_ACTION_MIXER_MUTE_ENABLE,
AUDIO_ACTION_MUTE_ENABLE,
AUDIO_ACTION_VOLUME_GAIN,
AUDIO_ACTION_MIXER_VOLUME_GAIN,
AUDIO_ACTION_MIXER
};
enum audio_mixer_slot_selection_type
{
AUDIO_MIXER_SLOT_SELECTION_AUTOMATIC = 0,
AUDIO_MIXER_SLOT_SELECTION_MANUAL
};
enum audio_mixer_stream_type
{
AUDIO_STREAM_TYPE_NONE = 0,
AUDIO_STREAM_TYPE_USER,
AUDIO_STREAM_TYPE_SYSTEM
};
enum audio_mixer_state
{
AUDIO_STREAM_STATE_NONE = 0,
AUDIO_STREAM_STATE_STOPPED,
AUDIO_STREAM_STATE_PLAYING,
AUDIO_STREAM_STATE_PLAYING_LOOPED,
AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL
};
typedef struct audio_statistics
{
float average_buffer_saturation;
float std_deviation_percentage;
float close_to_underrun;
float close_to_blocking;
unsigned samples;
} audio_statistics_t;
RETRO_END_DECLS
#endif

View File

@ -21,6 +21,7 @@
#include <lists/string_list.h>
#include <audio/conversion/float_to_s16.h>
#include <audio/conversion/s16_to_float.h>
#include <audio/audio_resampler.h>
#include <audio/dsp_filter.h>
#include <file/file_path.h>
#include <lists/dir_list.h>
@ -36,6 +37,7 @@
#include "../gfx/video_driver.h"
#include "../record/record_driver.h"
#include "../frontend/frontend_driver.h"
#include "../tasks/task_audio_mixer.h"
#include "../tasks/tasks_internal.h"
#include "../command.h"
@ -87,6 +89,9 @@ static const audio_driver_t *audio_drivers[] = {
#ifdef HAVE_COREAUDIO
&audio_coreaudio,
#endif
#ifdef HAVE_COREAUDIO3
&audio_coreaudio3,
#endif
#ifdef HAVE_AL
&audio_openal,
#endif
@ -224,7 +229,7 @@ static void audio_mixer_menu_stop_cb(
#define audio_driver_unlock()
#endif
enum resampler_quality audio_driver_get_resampler_quality(void)
static enum resampler_quality audio_driver_get_resampler_quality(void)
{
settings_t *settings = config_get_ptr();

View File

@ -22,65 +22,14 @@
#include <sys/types.h>
#include <boolean.h>
#include <audio/audio_mixer.h>
#include <audio/audio_resampler.h>
#include <retro_common_api.h>
#include <audio/audio_mixer.h>
#include "audio_defines.h"
RETRO_BEGIN_DECLS
#define AUDIO_CHUNK_SIZE_BLOCKING 512
/* So we don't get complete line-noise when fast-forwarding audio. */
#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048
#define AUDIO_MAX_RATIO 16
#define AUDIO_MIXER_MAX_STREAMS 16
#define AUDIO_MIXER_MAX_SYSTEM_STREAMS (AUDIO_MIXER_MAX_STREAMS + 4)
/* do not define more than (MAX_SYSTEM_STREAMS - MAX_STREAMS) */
enum audio_mixer_system_slot
{
AUDIO_MIXER_SYSTEM_SLOT_OK = AUDIO_MIXER_MAX_STREAMS,
AUDIO_MIXER_SYSTEM_SLOT_CANCEL,
AUDIO_MIXER_SYSTEM_SLOT_NOTICE,
AUDIO_MIXER_SYSTEM_SLOT_BGM
};
enum audio_action
{
AUDIO_ACTION_NONE = 0,
AUDIO_ACTION_RATE_CONTROL_DELTA,
AUDIO_ACTION_MIXER_MUTE_ENABLE,
AUDIO_ACTION_MUTE_ENABLE,
AUDIO_ACTION_VOLUME_GAIN,
AUDIO_ACTION_MIXER_VOLUME_GAIN,
AUDIO_ACTION_MIXER
};
enum audio_mixer_slot_selection_type
{
AUDIO_MIXER_SLOT_SELECTION_AUTOMATIC = 0,
AUDIO_MIXER_SLOT_SELECTION_MANUAL
};
enum audio_mixer_stream_type
{
AUDIO_STREAM_TYPE_NONE = 0,
AUDIO_STREAM_TYPE_USER,
AUDIO_STREAM_TYPE_SYSTEM
};
enum audio_mixer_state
{
AUDIO_STREAM_STATE_NONE = 0,
AUDIO_STREAM_STATE_STOPPED,
AUDIO_STREAM_STATE_PLAYING,
AUDIO_STREAM_STATE_PLAYING_LOOPED,
AUDIO_STREAM_STATE_PLAYING_SEQUENTIAL
};
typedef struct audio_mixer_stream
{
audio_mixer_sound_t *handle;
@ -95,14 +44,19 @@ typedef struct audio_mixer_stream
size_t bufsize;
} audio_mixer_stream_t;
typedef struct audio_statistics
typedef struct audio_mixer_stream_params
{
float average_buffer_saturation;
float std_deviation_percentage;
float close_to_underrun;
float close_to_blocking;
unsigned samples;
} audio_statistics_t;
float volume;
enum audio_mixer_slot_selection_type slot_selection_type;
unsigned slot_selection_idx;
enum audio_mixer_stream_type stream_type;
enum audio_mixer_type type;
enum audio_mixer_state state;
void *buf;
char *basename;
size_t bufsize;
audio_mixer_stop_cb_t cb;
} audio_mixer_stream_params_t;
typedef struct audio_driver
{
@ -186,20 +140,6 @@ typedef struct audio_driver
size_t (*buffer_size)(void *data);
} audio_driver_t;
typedef struct audio_mixer_stream_params
{
float volume;
enum audio_mixer_slot_selection_type slot_selection_type;
unsigned slot_selection_idx;
enum audio_mixer_stream_type stream_type;
enum audio_mixer_type type;
enum audio_mixer_state state;
void *buf;
char *basename;
size_t bufsize;
audio_mixer_stop_cb_t cb;
} audio_mixer_stream_params_t;
void audio_driver_destroy_data(void);
void audio_driver_set_own_driver(void);
@ -340,8 +280,6 @@ void audio_driver_mixer_set_stream_volume(unsigned i, float vol);
void audio_driver_mixer_remove_stream(unsigned i);
enum resampler_quality audio_driver_get_resampler_quality(void);
enum audio_mixer_state audio_driver_mixer_get_stream_state(unsigned i);
const char *audio_driver_mixer_get_stream_name(unsigned i);
@ -366,6 +304,7 @@ extern audio_driver_t audio_pulse;
extern audio_driver_t audio_dsound;
extern audio_driver_t audio_wasapi;
extern audio_driver_t audio_coreaudio;
extern audio_driver_t audio_coreaudio3;
extern audio_driver_t audio_xenon360;
extern audio_driver_t audio_ps3;
extern audio_driver_t audio_gx;

View File

@ -125,7 +125,9 @@ static OSStatus audio_write_cb(void *userdata,
static void coreaudio_interrupt_listener(void *data, UInt32 interrupt_state)
{
(void)data;
#if TARGET_OS_IOS
g_interrupted = (interrupt_state == kAudioSessionBeginInterruption);
#endif
}
#else
static void choose_output_device(coreaudio_t *dev, const char* device)
@ -218,7 +220,7 @@ static void *coreaudio_init(const char *device,
dev->lock = slock_new();
dev->cond = scond_new();
#if TARGET_OS_IPHONE
#if TARGET_OS_IOS
if (!session_initialized)
{
session_initialized = true;

382
audio/drivers/coreaudio3.m Normal file
View File

@ -0,0 +1,382 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2019 - Stuart Carnie
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#include <stdio.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <memory.h>
#include "../audio_driver.h"
#pragma mark - ringbuffer
typedef struct ringbuffer
{
float *buffer;
size_t cap;
atomic_int len;
size_t writePtr;
size_t readPtr;
} ringbuffer_t;
typedef ringbuffer_t * ringbuffer_h;
static inline size_t rb_len(ringbuffer_h r)
{
return atomic_load_explicit(&r->len, memory_order_relaxed);
}
static inline size_t rb_cap(ringbuffer_h r)
{
return (r->readPtr + r->cap - r->writePtr) % r->cap;
}
static inline size_t rb_avail(ringbuffer_h r)
{
return r->cap - rb_len(r);
}
static inline void rb_advance_write(ringbuffer_h r)
{
r->writePtr = (r->writePtr + 1) % r->cap;
}
static inline void rb_advance_write_n(ringbuffer_h r, size_t n)
{
r->writePtr = (r->writePtr + n) % r->cap;
}
static inline void rb_advance_read(ringbuffer_h r)
{
r->readPtr = (r->readPtr + 1) % r->cap;
}
static inline void rb_len_add(ringbuffer_h r, int n)
{
atomic_fetch_add(&r->len, n);
}
static inline void rb_len_sub(ringbuffer_h r, int n)
{
atomic_fetch_sub(&r->len, n);
}
static void rb_init(ringbuffer_h r, size_t cap)
{
r->buffer = malloc(cap * sizeof(float));
r->cap = cap;
atomic_init(&r->len, 0);
r->writePtr = 0;
r->readPtr = 0;
}
static void rb_free(ringbuffer_h r)
{
free(r->buffer);
bzero(r, sizeof(*r));
}
#define UNLIKELY(x) __builtin_expect((x), 0)
#define LIKELY(x) __builtin_expect((x), 1)
static void rb_write_data(ringbuffer_h r, const float *data, size_t len)
{
size_t avail = rb_avail(r);
size_t n = MIN(len, avail);
size_t first_write = n;
size_t rest_write = 0;
if (r->writePtr + n > r->cap)
{
first_write = r->cap - r->writePtr;
rest_write = n - first_write;
}
memcpy(r->buffer + r->writePtr, data, first_write*sizeof(float));
memcpy(r->buffer, data + first_write, rest_write*sizeof(float));
rb_advance_write_n(r, n);
rb_len_add(r, (int)n);
}
static void rb_read_data(ringbuffer_h r, float *d0, float *d1, size_t len)
{
size_t need = len*2;
do {
size_t have = rb_len(r);
size_t n = MIN(have, need);
int i = 0;
for (; i < n/2; i++)
{
d0[i] = r->buffer[r->readPtr];
rb_advance_read(r);
d1[i] = r->buffer[r->readPtr];
rb_advance_read(r);
}
need -= n;
rb_len_sub(r, (int)n);
if (UNLIKELY(need > 0))
{
/* we got more data */
if (rb_len(r) > 0)
continue;
// underflow
const float quiet = 0.0f;
size_t fill = (need/2)*sizeof(float);
memset_pattern4(&d0[i], &quiet, fill);
memset_pattern4(&d1[i], &quiet, fill);
}
} while (0);
}
#pragma mark - CoreAudio3
static bool g_interrupted;
@interface CoreAudio3 : NSObject {
ringbuffer_t _rb;
dispatch_semaphore_t _sema;
AUAudioUnit *_au;
size_t _bufferSize;
BOOL _nonBlock;
}
@property (nonatomic, readwrite) BOOL nonBlock;
@property (nonatomic, readonly) BOOL paused;
@property (nonatomic, readonly) size_t writeAvailableInBytes;
@property (nonatomic, readonly) size_t bufferSizeInBytes;
- (instancetype)initWithRate:(NSUInteger)rate
latency:(NSUInteger)latency;
- (ssize_t)writeFloat:(const float *)data samples:(size_t)samples;
- (void)start;
- (void)stop;
@end
@implementation CoreAudio3
- (instancetype)initWithRate:(NSUInteger)rate
latency:(NSUInteger)latency {
if (self = [super init])
{
_sema = dispatch_semaphore_create(0);
_bufferSize = (latency * rate) / 1000;
_bufferSize *= 2; // stereo
rb_init(&_rb, _bufferSize);
AudioComponentDescription desc = {
.componentType = kAudioUnitType_Output,
.componentSubType = kAudioUnitSubType_DefaultOutput,
.componentManufacturer = kAudioUnitManufacturer_Apple,
};
NSError *err;
AUAudioUnit *au = [[AUAudioUnit alloc] initWithComponentDescription:desc error:&err];
if (err != nil)
return nil;
AVAudioFormat *format = au.outputBusses[0].format;
if (format.channelCount != 2)
return nil;
AVAudioFormat *renderFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:rate channels:2];
[au.inputBusses[0] setFormat:renderFormat error:&err];
if (err != nil)
return nil;
ringbuffer_h rb = &_rb;
__block dispatch_semaphore_t sema = _sema;
au.outputProvider = ^AUAudioUnitStatus(AudioUnitRenderActionFlags * actionFlags, const AudioTimeStamp * timestamp, AUAudioFrameCount frameCount, NSInteger inputBusNumber, AudioBufferList * inputData) {
rb_read_data(rb, inputData->mBuffers[0].mData, inputData->mBuffers[1].mData, frameCount);
dispatch_semaphore_signal(sema);
return 0;
};
[au allocateRenderResourcesAndReturnError:&err];
if (err != nil)
return nil;
_au = au;
RARCH_LOG("[CoreAudio3]: Using buffer size of %u bytes: (latency = %u ms)\n", (unsigned)self.bufferSizeInBytes, latency);
[self start];
}
return self;
}
- (void)dealloc {
rb_free(&_rb);
}
- (BOOL)paused {
return !_au.running;
}
- (size_t)bufferSizeInBytes {
return _bufferSize * sizeof(float);
}
- (size_t)writeAvailableInBytes {
return rb_avail(&_rb) * sizeof(float);
}
- (void)start {
NSError *err;
[_au startHardwareAndReturnError:&err];
}
- (void)stop {
[_au stopHardware];
}
- (ssize_t)writeFloat:(const float *)data samples:(size_t)samples {
size_t written = 0;
while (!g_interrupted && samples > 0)
{
size_t write_avail = rb_avail(&_rb);
if (write_avail > samples)
write_avail = samples;
rb_write_data(&_rb, data, write_avail);
data += write_avail;
written += write_avail;
samples -= write_avail;
if (_nonBlock)
break;
if (write_avail == 0)
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
}
return written;
}
@end
static void coreaudio3_free(void *data)
{
CoreAudio3 *dev = (__bridge_transfer CoreAudio3 *)data;
if (dev == nil)
return;
[dev stop];
dev = nil;
}
static void *coreaudio3_init(const char *device,
unsigned rate, unsigned latency,
unsigned block_frames,
unsigned *new_rate)
{
CoreAudio3 *dev = [[CoreAudio3 alloc] initWithRate:rate
latency:latency];
*new_rate = rate;
return (__bridge_retained void *)dev;
}
static ssize_t coreaudio3_write(void *data, const void *buf_, size_t size)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
return [dev writeFloat:(const float *)buf_ samples:size/sizeof(float)] * sizeof(float);
}
static void coreaudio3_set_nonblock_state(void *data, bool state)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
if (dev == nil)
return;
dev.nonBlock = state;
}
static bool coreaudio3_alive(void *data)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
if (dev == nil)
return NO;
return !dev.paused;
}
static bool coreaudio3_stop(void *data)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
if (dev == nil)
return NO;
[dev stop];
return dev.paused;
}
static bool coreaudio3_start(void *data, bool is_shutdown)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
if (dev == nil)
return NO;
[dev start];
return !dev.paused;
}
static bool coreaudio3_use_float(void *data)
{
return YES;
}
static size_t coreaudio3_write_avail(void *data)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
if (dev == nil)
return 0;
return dev.writeAvailableInBytes;
}
static size_t coreaudio3_buffer_size(void *data)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
if (dev == nil)
return 0;
return dev.bufferSizeInBytes;
}
audio_driver_t audio_coreaudio3 = {
coreaudio3_init,
coreaudio3_write,
coreaudio3_stop,
coreaudio3_start,
coreaudio3_alive,
coreaudio3_set_nonblock_state,
coreaudio3_free,
coreaudio3_use_float,
"coreaudio3",
coreaudio3_write_avail,
coreaudio3_buffer_size,
};

View File

@ -84,22 +84,21 @@ static void audioCreateThread(ps2_audio_t *ps2)
ps2->running = true;
ps2->worker_thread = CreateThread(&thread);
if (ps2->worker_thread >= 0) {
if (ps2->worker_thread >= 0)
{
ret = StartThread(ps2->worker_thread, NULL);
if (ret < 0) {
if (ret < 0)
printf("sound_init: StartThread returned %d\n", ret);
}
} else {
printf("CreateThread failed: %d\n", ps2->worker_thread);
}
else
printf("CreateThread failed: %d\n", ps2->worker_thread);
}
static void audioStopNDeleteThread(ps2_audio_t *ps2)
{
ps2->running = false;
if (ps2->worker_thread) {
if (ps2->worker_thread)
ps2->worker_thread = 0;
}
}
static void audioConfigure(ps2_audio_t *ps2, unsigned rate)
@ -187,14 +186,14 @@ static ssize_t ps2_audio_write(void *data, const void *buf, size_t size)
if (!ps2->running)
return -1;
if (ps2->nonblocking){
if (ps2->nonblocking)
{
if (fifo_write_avail(ps2->buffer) < size)
return 0;
}
while (fifo_write_avail(ps2->buffer) < size) {
while (fifo_write_avail(ps2->buffer) < size)
WaitSema(ps2->cond_lock);
}
WaitSema(ps2->lock);
fifo_write(ps2->buffer, buf, size);
@ -205,22 +204,22 @@ static ssize_t ps2_audio_write(void *data, const void *buf, size_t size)
static bool ps2_audio_alive(void *data)
{
bool alive = false;
bool alive = false;
ps2_audio_t* ps2 = (ps2_audio_t*)data;
if (ps2) {
if (ps2)
alive = ps2->running;
}
return alive;
}
static bool ps2_audio_stop(void *data)
{
bool stop = true;
bool stop = true;
ps2_audio_t* ps2 = (ps2_audio_t*)data;
if (ps2) {
if (ps2)
{
audioStopNDeleteThread(ps2);
audsrv_stop_audio();
}
@ -231,12 +230,12 @@ static bool ps2_audio_stop(void *data)
static bool ps2_audio_start(void *data, bool is_shutdown)
{
ps2_audio_t* ps2 = (ps2_audio_t*)data;
bool start = true;
bool start = true;
if (ps2) {
if (!ps2->running && !ps2->worker_thread) {
if (ps2)
{
if (!ps2->running && !ps2->worker_thread)
audioCreateThread(ps2);
}
}
return start;
@ -246,9 +245,8 @@ static void ps2_audio_set_nonblock_state(void *data, bool toggle)
{
ps2_audio_t* ps2 = (ps2_audio_t*)data;
if (ps2) {
if (ps2)
ps2->nonblocking = toggle;
}
}
static bool ps2_audio_use_float(void *data)
@ -260,7 +258,8 @@ static size_t ps2_audio_write_avail(void *data)
{
ps2_audio_t* ps2 = (ps2_audio_t*)data;
if (ps2 && ps2->running) {
if (ps2 && ps2->running)
{
size_t size;
WaitSema(ps2->lock);
size = AUDIO_BUFFER - fifo_read_avail(ps2->buffer);

View File

@ -52,7 +52,7 @@
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <poll.h>
#endif
#include <fcntl.h>
#ifdef _WIN32

View File

@ -175,7 +175,7 @@ static bool command_version(const char* arg)
{
char reply[256] = {0};
sprintf(reply, "%s\n", PACKAGE_VERSION);
snprintf(reply, sizeof(reply), "%s\n", PACKAGE_VERSION);
#if defined(HAVE_CHEEVOS) && (defined(HAVE_STDIN_CMD) || defined(HAVE_NETWORK_CMD) && defined(HAVE_NETWORKING))
command_reply(reply, strlen(reply));
#endif
@ -292,7 +292,7 @@ static bool command_read_ram(const char *arg)
if (data)
{
for (i=0;i<nbytes;i++)
for (i = 0; i < nbytes; i++)
sprintf(reply_at+3*i, " %.2X", data[i]);
reply_at[3*nbytes] = '\n';
command_reply(reply, reply_at+3*nbytes+1 - reply);
@ -304,7 +304,7 @@ static bool command_read_ram(const char *arg)
}
free(reply);
#else
cheevos_var_t var;
cheevos_var_t var;
unsigned i;
char reply[256] = {0};
const uint8_t * data = NULL;
@ -324,7 +324,7 @@ static bool command_read_ram(const char *arg)
{
unsigned nbytes = strtol(reply_at, NULL, 10);
for (i=0;i<nbytes;i++)
for (i = 0; i < nbytes; i++)
sprintf(reply_at+3*i, " %.2X", data[i]);
reply_at[3*nbytes] = '\n';
command_reply(reply, reply_at+3*nbytes+1 - reply);

View File

@ -401,6 +401,8 @@ static unsigned menu_toggle_gamepad_combo = INPUT_TOGGLE_HOLD_START;
static unsigned menu_toggle_gamepad_combo = INPUT_TOGGLE_L1_R1_START_SELECT;
#elif defined(SWITCH) || defined(ORBIS)
static unsigned menu_toggle_gamepad_combo = INPUT_TOGGLE_START_SELECT;
#elif TARGET_OS_TV
static unsigned menu_toggle_gamepad_combo = INPUT_TOGGLE_DOWN_Y_L_R;
#else
static unsigned menu_toggle_gamepad_combo = INPUT_TOGGLE_NONE;
#endif

View File

@ -152,6 +152,12 @@ static const bool _coreaudio_supp = true;
static const bool _coreaudio_supp = false;
#endif
#ifdef HAVE_COREAUDIO3
static const bool _coreaudio3_supp = true;
#else
static const bool _coreaudio3_supp = false;
#endif
#if defined(HAVE_OSS) || defined(HAVE_OSS_BSD)
static const bool _oss_supp = true;
#else

View File

@ -191,6 +191,7 @@ enum audio_driver_enum
AUDIO_DSOUND,
AUDIO_WASAPI,
AUDIO_COREAUDIO,
AUDIO_COREAUDIO3,
AUDIO_PS3,
AUDIO_XENON360,
AUDIO_WII,
@ -394,6 +395,8 @@ static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_TINYALSA;
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_OSS;
#elif defined(HAVE_JACK)
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_JACK;
#elif defined(HAVE_COREAUDIO3)
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_COREAUDIO3;
#elif defined(HAVE_COREAUDIO)
static enum audio_driver_enum AUDIO_DEFAULT_DRIVER = AUDIO_COREAUDIO;
#elif defined(HAVE_XAUDIO)
@ -526,14 +529,14 @@ static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_ANDROID;
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_SDL;
#elif defined(DJGPP)
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_DOS;
#elif defined(IOS)
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_MFI;
#elif defined(HAVE_HID)
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_HID;
#elif defined(__QNX__)
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_QNX;
#elif defined(EMSCRIPTEN)
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_RWEBPAD;
#elif defined(IOS)
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_MFI;
#else
static enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_NULL;
#endif
@ -667,6 +670,8 @@ const char *config_get_default_audio(void)
return "roar";
case AUDIO_COREAUDIO:
return "coreaudio";
case AUDIO_COREAUDIO3:
return "coreaudio3";
case AUDIO_AL:
return "openal";
case AUDIO_SL:
@ -1685,7 +1690,7 @@ static struct config_uint_setting *populate_settings_uint(settings_t *settings,
SETTING_UINT("materialui_menu_color_theme", &settings->uints.menu_materialui_color_theme, true, MATERIALUI_THEME_BLUE, false);
SETTING_UINT("menu_shader_pipeline", &settings->uints.menu_xmb_shader_pipeline, true, menu_shader_pipeline, false);
#ifdef HAVE_OZONE
SETTING_UINT("ozone_menu_color_theme", &settings->uints.menu_ozone_color_theme, true, 0, false);
SETTING_UINT("ozone_menu_color_theme", &settings->uints.menu_ozone_color_theme, true, 1, false);
#endif
#endif
SETTING_UINT("audio_out_rate", &settings->uints.audio_out_rate, true, out_rate, false);

View File

@ -25,7 +25,7 @@
#include <retro_common_api.h>
#include <retro_miscellaneous.h>
#include "gfx/video_driver.h"
#include "gfx/video_defines.h"
#include "input/input_defines.h"
#include "led/led_defines.h"

View File

@ -22,6 +22,8 @@
#include "config.h"
#endif
#include <audio/audio_resampler.h>
#ifdef HAVE_MENU
#include "menu/menu_driver.h"
#endif

View File

@ -104,7 +104,7 @@ static dylib_t lib_handle;
#define SYMBOL_NETRETROPAD(x) current_core->x = libretro_netretropad_##x
#endif
#if defined(HAVE_VIDEO_PROCESSOR)
#if defined(HAVE_VIDEOPROCESSOR)
#define SYMBOL_VIDEOPROCESSOR(x) current_core->x = libretro_videoprocessor_##x
#endif
@ -748,7 +748,7 @@ bool init_libretro_sym_custom(enum rarch_core_type type, struct retro_core_t *cu
#endif
break;
case CORE_TYPE_VIDEO_PROCESSOR:
#if defined(HAVE_VIDEO_PROCESSOR)
#if defined(HAVE_VIDEOPROCESSOR)
SYMBOL_VIDEOPROCESSOR(retro_init);
SYMBOL_VIDEOPROCESSOR(retro_deinit);

View File

@ -115,7 +115,11 @@ static NSSearchPathDirectory NSConvertFlagsCF(unsigned flags)
switch (flags)
{
case CFDocumentDirectory:
return NSDocumentDirectory;
#if TARGET_OS_TV
return NSCachesDirectory;
#else
return NSDocumentDirectory;
#endif
}
return 0;
@ -363,7 +367,7 @@ static void frontend_darwin_get_environment_settings(int *argc, char *argv[],
home_dir_buf, "shaders_glsl",
sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
#endif
#if TARGET_OS_IPHONE
#if TARGET_OS_IOS
int major, minor;
get_ios_version(&major, &minor);
if (major >= 10 )
@ -372,6 +376,16 @@ static void frontend_darwin_get_environment_settings(int *argc, char *argv[],
else
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE],
home_dir_buf, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
#elif TARGET_OS_TV
printf("tvOS bundle path = %s",bundle_path_buf);
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE],
bundle_path_buf, "modules", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
NSError *fileError;
NSArray *bundleFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSString stringWithUTF8String:g_defaults.dirs[DEFAULT_DIR_CORE]] error:&fileError];
NSLog(@"tvOS Files in modules:");
for (NSString *f in bundleFiles) {
NSLog(@"%@",f);
}
#else
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], home_dir_buf, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
#endif
@ -423,7 +437,7 @@ static void frontend_darwin_get_environment_settings(int *argc, char *argv[],
#endif
#endif
#if TARGET_OS_IPHONE
#if TARGET_OS_IOS
char assets_zip_path[PATH_MAX_LENGTH];
if (major > 8)
strlcpy(g_defaults.path.buildbot_server_url, "http://buildbot.libretro.com/nightly/apple/ios9/latest/", sizeof(g_defaults.path.buildbot_server_url));
@ -582,7 +596,7 @@ static enum frontend_powerstate frontend_darwin_get_powerstate(int *seconds, int
end:
if (blob)
CFRelease(blob);
#elif defined(IOS)
#elif TARGET_OS_IOS
float level;
UIDevice *uidev = [UIDevice currentDevice];

View File

@ -92,8 +92,10 @@ RETRO_BEGIN_DECLS
#endif
#if defined(__APPLE__) || defined(HAVE_PSGL)
#ifndef GL_RGBA32F
#define GL_RGBA32F GL_RGBA32F_ARB
#endif
#endif
#if defined(HAVE_PSGL)
#define RARCH_GL_INTERNAL_FORMAT32 GL_ARGB_SCE

View File

@ -405,7 +405,7 @@ bool x11_get_metrics(void *data,
return true;
}
static void x11_handle_key_event(XEvent *event, XIC ic, bool filter)
static void x11_handle_key_event(unsigned keycode, XEvent *event, XIC ic, bool filter)
{
int i;
Status status;
@ -419,6 +419,7 @@ static void x11_handle_key_event(XEvent *event, XIC ic, bool filter)
chars[0] = '\0';
/* this code generates the localized chars using keysyms */
if (!filter)
{
if (down)
@ -454,8 +455,10 @@ static void x11_handle_key_event(XEvent *event, XIC ic, bool filter)
* to feed it keysyms anyway, so here is a little hack... */
if (keysym >= XK_A && keysym <= XK_Z)
keysym += XK_z - XK_Z;
key = input_keymaps_translate_keysym_to_rk(keysym);
/* Get the real keycode,
that correctly ignores international layouts as windows code does. */
key = input_keymaps_translate_keysym_to_rk(keycode);
if (state & ShiftMask)
mod |= RETROKMOD_SHIFT;
@ -465,10 +468,10 @@ static void x11_handle_key_event(XEvent *event, XIC ic, bool filter)
mod |= RETROKMOD_CTRL;
if (state & Mod1Mask)
mod |= RETROKMOD_ALT;
if (state & Mod2Mask)
mod |= RETROKMOD_NUMLOCK;
if (state & Mod4Mask)
mod |= RETROKMOD_META;
if (IsKeypadKey(keysym))
mod |= RETROKMOD_NUMLOCK;
input_keyboard_event(down, key, chars[0], mod, RETRO_DEVICE_KEYBOARD);
@ -483,9 +486,14 @@ bool x11_alive(void *data)
{
XEvent event;
bool filter = false;
unsigned keycode = 0;
/* Can get events from older windows. Check this. */
XNextEvent(g_x11_dpy, &event);
/* IMPORTANT - Get keycode before XFilterEvent
because the event is localizated after the call */
keycode = event.xkey.keycode;
filter = XFilterEvent(&event, g_x11_win);
switch (event.type)
@ -548,7 +556,7 @@ bool x11_alive(void *data)
case KeyPress:
case KeyRelease:
if (event.xkey.window == g_x11_win)
x11_handle_key_event(&event, g_x11_xic, filter);
x11_handle_key_event(keycode, &event, g_x11_xic, filter);
break;
}
}

View File

@ -72,23 +72,21 @@ static void x11_display_server_destroy(void *data)
if (crt_en)
{
snprintf(output, sizeof(output),
"xrandr --newmode \"700x480_59.94\" 13.849698 700 742 801 867 480 490 496 533 interlace -hsync -vsync");
"xrandr --newmode \"700x480_59.94\" 13.849698 700 742 801 867 480 490 496 533 interlace -hsync -vsync");
system(output);
snprintf(output, sizeof(output),
"xrandr --addmode %s 700x480_59.94", orig_output);
"xrandr --addmode %s 700x480_59.94", orig_output);
system(output);
snprintf(output, sizeof(output),
"xrandr --output %s --mode 700x480_59.94", orig_output);
"xrandr --output %s --mode 700x480_59.94", orig_output);
system(output);
snprintf(output, sizeof(output),
"xrandr --delmode %s %s",orig_output, old_mode);
"xrandr --delmode %s %s",orig_output, old_mode);
system(output);
snprintf(output, sizeof(output), "xrandr --rmmode %s", old_mode);
system(output);
@ -133,38 +131,41 @@ static bool x11_display_server_set_window_decorations(void *data, bool on)
static bool x11_display_server_set_resolution(void *data,
unsigned width, unsigned height, int int_hz, float hz, int center, int monitor_index)
{
int i = 0;
int hfp = 0;
int hsp = 0;
int hbp = 0;
int vfp = 0;
int vsp = 0;
int vbp = 0;
int hmax = 0;
int vmax = 0;
int pdefault = 8;
int pwidth = 0;
float roundw = 0.0f;
float roundh = 0.0f;
float pixel_clock = 0;
int screen;
Window window;
XRRScreenResources *res = NULL;
Display *dsp = NULL;
Screen *scrn = NULL;
int i = 0;
int hfp = 0;
int hsp = 0;
int hbp = 0;
int vfp = 0;
int vsp = 0;
int vbp = 0;
int hmax = 0;
int vmax = 0;
int pdefault = 8;
int pwidth = 0;
float roundw = 0.0f;
float roundh = 0.0f;
float pixel_clock = 0;
crt_en = true;
sprintf(old_mode,"%s", new_mode);
Display* dsp = XOpenDisplay(NULL);
Screen* scrn = DefaultScreenOfDisplay(dsp);
XRRScreenResources *res;
int screen = DefaultScreen ( dsp );
Window window = RootWindow ( dsp, screen );
crt_en = true;
snprintf(old_mode, sizeof(old_mode), "%s", new_mode);
dsp = XOpenDisplay(NULL);
scrn = DefaultScreenOfDisplay(dsp);
screen = DefaultScreen ( dsp );
window = RootWindow ( dsp, screen );
/* set core refresh from hz */
video_monitor_set_refresh_rate(hz);
/* following code is the mode line generator */
hsp = width * 1.140;
hfp = width * 1.055;
hsp = width * 1.140;
hfp = width * 1.055;
pwidth = width;
if (height < 400 && width > 400)
@ -181,7 +182,7 @@ static bool x11_display_server_set_resolution(void *data,
if (roundw < 1.20)
roundw = 1.34;
hbp = width * roundw - 8;
hbp = width * roundw - 8;
hmax = hbp;
if (height < 241)

View File

@ -1789,6 +1789,12 @@ static void *gl_init(const video_info_t *video,
if (!string_is_empty(version))
sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);
#ifdef _WIN32
if (string_is_equal(vendor, "Microsoft Corporation"))
if (string_is_equal(renderer, "GDI Generic"))
rarch_force_video_driver_fallback("gdi");
#endif
hwr = video_driver_get_hw_context();
if (hwr->context_type == RETRO_HW_CONTEXT_OPENGL_CORE)

View File

@ -194,7 +194,7 @@ static uintptr_t metal_load_texture(void *video_data, void *data,
return (uintptr_t)(__bridge_retained void *)(t);
}
static void metal_unload_texture(void *data, uintptr_t handle, bool threaded)
static void metal_unload_texture(void *data, uintptr_t handle)
{
if (!handle)
return;

View File

@ -133,12 +133,16 @@ static void glkitview_init_xibs(void)
void *glkitview_init(void)
{
#if defined(HAVE_COCOATOUCH)
#if TARGET_OS_IOS
glkitview_init_xibs();
#endif
g_view = [GLKView new];
#if TARGET_OS_IOS
g_view.multipleTouchEnabled = YES;
[g_view addSubview:g_pause_indicator_view];
#endif
g_view.enableSetNeedsDisplay = NO;
[g_view addSubview:g_pause_indicator_view];
return (BRIDGE void *)((GLKView*)g_view);
#else

View File

@ -29,7 +29,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/poll.h>
#include <poll.h>
#include <libdrm/drm.h>
#include <gbm.h>

View File

@ -14,7 +14,7 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/poll.h>
#include <poll.h>
#include <unistd.h>
#include <wayland-client.h>

View File

@ -24,8 +24,12 @@
#include "video_crt_switch.h"
#include "video_display_server.h"
#if defined(__arm__)
#include "include/userland/interface/vmcs_host/vc_vchi_gencmd.h"
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#if defined(HAVE_VIDEOCORE)
#include "../include/userland/interface/vmcs_host/vc_vchi_gencmd.h"
#endif
static unsigned ra_core_width = 0;
@ -35,7 +39,7 @@ static unsigned ra_tmp_height = 0;
static unsigned ra_set_core_hz = 0;
static unsigned orig_width = 0;
static unsigned orig_height = 0;
static int crt_center_adjust = 0;
static int crt_center_adjust = 0;
static bool first_run = true;
@ -91,23 +95,23 @@ void crt_aspect_ratio_switch(unsigned width, unsigned height)
static void switch_res_crt(unsigned width, unsigned height)
{
video_display_server_set_resolution(width, height,
ra_set_core_hz, ra_core_hz, crt_center_adjust, crt_index);
#if defined(__arm__)
crt_rpi_switch(width, height, ra_core_hz);
video_monitor_set_refresh_rate(ra_core_hz);
crt_switch_driver_reinit();
#endif
video_driver_apply_state_changes();
video_display_server_set_resolution(width, height,
ra_set_core_hz, ra_core_hz, crt_center_adjust, crt_index);
#if defined(HAVE_VIDEOCORE)
crt_rpi_switch(width, height, ra_core_hz);
video_monitor_set_refresh_rate(ra_core_hz);
crt_switch_driver_reinit();
#endif
video_driver_apply_state_changes();
}
/* Create correct aspect to fit video if resolution does not exist */
static void crt_screen_setup_aspect(unsigned width, unsigned height)
{
#if defined(__arm__)
if (height > 300)
height = height/2;
#endif
#if defined(HAVE_VIDEOCORE)
if (height > 300)
height = height/2;
#endif
switch_crt_hz();
/* get original resolution of core */
@ -170,7 +174,9 @@ static void crt_screen_setup_aspect(unsigned width, unsigned height)
switch_res_crt(width, height);
}
void crt_switch_res_core(unsigned width, unsigned height, float hz, unsigned crt_mode, int crt_switch_center_adjust, int monitor_index)
void crt_switch_res_core(unsigned width, unsigned height,
float hz, unsigned crt_mode,
int crt_switch_center_adjust, int monitor_index)
{
/* ra_core_hz float passed from within
* void video_driver_monitor_adjust_system_rates(void) */
@ -218,9 +224,12 @@ void crt_video_restore(void)
first_run = true;
}
#if defined(__arm__)
#if defined(HAVE_VIDEOCORE)
static void crt_rpi_switch(int width, int height, float hz)
{
VCHI_INSTANCE_T vchi_instance;
VCHI_CONNECTION_T *vchi_connection = NULL;
char buffer[1024];
static char output[250] = {0};
static char output1[250] = {0};
static char output2[250] = {0};
@ -301,51 +310,53 @@ static void crt_rpi_switch(int width, int height, float hz)
if (height < 300)
vsp = vfp + 3; /* needs to be 3 for progressive */
if (height > 300)
vsp = vfp + 6; /* needs to be 6 for interlaced */
vsp = vfp + 6; /* needs to be 6 for interlaced */
vsp = 3;
vsp = 3;
vbp = (vmax-height)-vsp-vfp;
vbp = (vmax-height)-vsp-vfp;
hmax = width+hfp+hsp+hbp;
if (height < 300)
{
hmax = width+hfp+hsp+hbp;
if (height < 300)
{
pixel_clock = (hmax * vmax * hz) ;
ip_flag = 0;
}
}
if (height > 300)
{
pixel_clock = (hmax * vmax * (hz/2)) /2 ;
ip_flag = 1;
}
/* above code is the modeline generator */
snprintf(set_hdmi_timing, sizeof(set_hdmi_timings), "hdmi_timings %d 1 %d %d %d %d 1 %d %d %d 0 0 0 %f %d %f 1 ", width, hfp, hsp, hbp, height, vfp,vsp, vbp, hz, ip_flag, pixel_clock);
VCHI_INSTANCE_T vchi_instance;
VCHI_CONNECTION_T *vchi_connection = NULL;
char buffer[1024];
snprintf(set_hdmi_timing, sizeof(set_hdmi_timings),
"hdmi_timings %d 1 %d %d %d %d 1 %d %d %d 0 0 0 %f %d %f 1 ",
width, hfp, hsp, hbp, height, vfp,vsp, vbp,
hz, ip_flag, pixel_clock);
vcos_init ();
vcos_init ();
vchi_initialise (&vchi_instance);
vchi_initialise (&vchi_instance);
vchi_connect (NULL, 0, vchi_instance);
vchi_connect (NULL, 0, vchi_instance);
vc_vchi_gencmd_init (vchi_instance, &vchi_connection, 1);
vc_vchi_gencmd_init (vchi_instance, &vchi_connection, 1);
vc_gencmd (buffer, sizeof (buffer), set_hdmi_timing);
vc_gencmd (buffer, sizeof (buffer), set_hdmi_timing);
vc_gencmd_stop ();
vc_gencmd_stop ();
vchi_disconnect (vchi_instance);
vchi_disconnect (vchi_instance);
snprintf(output1, sizeof(output1),"tvservice -e \"DMT 87\" > /dev/null");
snprintf(output1, sizeof(output1),
"tvservice -e \"DMT 87\" > /dev/null");
system(output1);
snprintf(output2, sizeof(output1),"fbset -g %d %d %d %d 24 > /dev/null",width, height, width, height);
snprintf(output2, sizeof(output1),
"fbset -g %d %d %d %d 24 > /dev/null",
width, height, width, height);
system(output2);
}
#endif

View File

@ -135,6 +135,16 @@ enum text_alignment
#define FONT_COLOR_GET_ALPHA(col) (((col) >> 0) & 0xff)
#define FONT_COLOR_ARGB_TO_RGBA(col) ( (((col) >> 24) & 0xff) | (((unsigned)(col) << 8) & 0xffffff00) )
typedef struct video_viewport
{
int x;
int y;
unsigned width;
unsigned height;
unsigned full_width;
unsigned full_height;
} video_viewport_t;
RETRO_END_DECLS
#endif

View File

@ -37,9 +37,8 @@
#include "video_coord_array.h"
#include "video_filter.h"
#include "video_shader_parse.h"
#include "video_state_tracker.h"
#include "../input/input_driver.h"
#include "../input/input_types.h"
#define RARCH_SCALE_BASE 256
@ -682,16 +681,6 @@ typedef struct gfx_ctx_ident
const char *ident;
} gfx_ctx_ident_t;
typedef struct video_viewport
{
int x;
int y;
unsigned width;
unsigned height;
unsigned full_width;
unsigned full_height;
} video_viewport_t;
struct aspect_ratio_elem
{
char name[64];

View File

@ -728,7 +728,7 @@ CAMERA
#include "../camera/drivers/video4linux2.c"
#endif
#ifdef HAVE_VIDEO_PROCESSOR
#ifdef HAVE_VIDEOPROCESSOR
#include "../cores/libretro-video-processor/video_processor_v4l2.c"
#endif

View File

@ -38,11 +38,11 @@
#if defined(HAVE_COCOATOUCH)
#if TARGET_OS_IPHONE
#if TARGET_OS_IOS
#include "../ui/drivers/cocoa/cocoatouch_menu.m"
#endif
#include "../ui/drivers/ui_cocoatouch.m"
#endif
#elif defined(HAVE_COCOA)
#include "../ui/drivers/ui_cocoa.m"
@ -64,6 +64,10 @@
#include "../input/drivers_joypad/mfi_joypad.m"
#endif
#ifdef HAVE_COREAUDIO3
#include "../audio/drivers/coreaudio3.m"
#endif
#if defined(HAVE_DISCORD)
#include "../deps/discord-rpc/src/discord_register_osx.m"
#endif

View File

@ -44,7 +44,7 @@
#elif defined(HAVE_KQUEUE)
#include <sys/event.h>
#endif
#include <sys/poll.h>
#include <poll.h>
#include <libudev.h>
#ifdef __linux__

View File

@ -75,7 +75,7 @@ static void *x_input_init(const char *joypad_driver)
static bool x_keyboard_pressed(x11_input_t *x11, unsigned key)
{
int keycode = XKeysymToKeycode(x11->display, rarch_keysym_lut[(enum retro_key)key]);
int keycode = rarch_keysym_lut[(enum retro_key)key];
return x11->state[keycode >> 3] & (1 << (keycode & 7));
}
@ -155,23 +155,20 @@ static int16_t x_pressed_analog(x11_input_t *x11,
unsigned id_plus = 0;
int id_minus_key = 0;
int id_plus_key = 0;
unsigned sym = 0;
int keycode = 0;
unsigned keycode = 0;
input_conv_analog_id_to_bind_id(idx, id, &id_minus, &id_plus);
id_minus_key = binds[id_minus].key;
id_plus_key = binds[id_plus].key;
sym = rarch_keysym_lut[(enum retro_key)id_minus_key];
keycode = XKeysymToKeycode(x11->display, sym);
keycode = rarch_keysym_lut[(enum retro_key)id_minus_key];
if ( binds[id_minus].valid
&& (id_minus_key < RETROK_LAST)
&& (x11->state[keycode >> 3] & (1 << (keycode & 7))))
pressed_minus = -0x7fff;
sym = rarch_keysym_lut[(enum retro_key)id_plus_key];
keycode = XKeysymToKeycode(x11->display, sym);
keycode = rarch_keysym_lut[(enum retro_key)id_plus_key];
if ( binds[id_plus].valid
&& (id_plus_key < RETROK_LAST)
&& (x11->state[keycode >> 3] & (1 << (keycode & 7))))

View File

@ -1,7 +1,7 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
@ -22,7 +22,10 @@
#include <boolean.h>
#include <AvailabilityMacros.h>
#import "../include/GameController/GameController.h"
#include "../../tasks/tasks_internal.h"
#import <GameController/GameController.h>
#ifndef MAX_MFI_CONTROLLERS
#define MAX_MFI_CONTROLLERS 4
@ -33,110 +36,151 @@ static int16_t mfi_axes[MAX_USERS][4];
static uint32_t mfi_controllers[MAX_MFI_CONTROLLERS];
static NSMutableArray *mfiControllers;
enum
{
GCCONTROLLER_PLAYER_INDEX_UNSET = -1,
GCCONTROLLER_PLAYER_INDEX_UNSET = -1,
};
static bool apple_gamecontroller_available(void)
{
int major, minor;
get_ios_version(&major, &minor);
int major, minor;
get_ios_version(&major, &minor);
if (major <= 6)
return false;
if (major <= 6)
return false;
return true;
return true;
}
static void apple_gamecontroller_joypad_poll_internal(GCController *controller)
{
uint32_t slot, pause;
uint32_t *buttons;
if (!controller)
return;
uint32_t slot, pause, select, l3, r3;
uint32_t *buttons;
if (!controller)
return;
slot = (uint32_t)controller.playerIndex;
buttons = &mfi_buttons[slot];
/* retain the start (pause) value */
pause = *buttons & (1 << RETRO_DEVICE_ID_JOYPAD_START);
*buttons = 0 | pause;
memset(mfi_axes[slot], 0, sizeof(mfi_axes[0]));
slot = (uint32_t)controller.playerIndex;
buttons = &mfi_buttons[slot];
if (controller.extendedGamepad)
{
GCExtendedGamepad *gp = (GCExtendedGamepad *)controller.extendedGamepad;
/* retain the values from the paused controller handler and pass them through */
pause = *buttons & (1 << RETRO_DEVICE_ID_JOYPAD_START);
select = *buttons & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT);
l3 = *buttons & ( 1 << RETRO_DEVICE_ID_JOYPAD_L3 );
r3 = *buttons & ( 1 << RETRO_DEVICE_ID_JOYPAD_R3 );
*buttons = 0 | pause | select | l3 | r3;
*buttons |= gp.dpad.up.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_UP) : 0;
*buttons |= gp.dpad.down.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_DOWN) : 0;
*buttons |= gp.dpad.left.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_LEFT) : 0;
*buttons |= gp.dpad.right.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT) : 0;
*buttons |= gp.buttonA.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_B) : 0;
*buttons |= gp.buttonB.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_A) : 0;
*buttons |= gp.buttonX.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_Y) : 0;
*buttons |= gp.buttonY.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_X) : 0;
*buttons |= gp.leftShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L) : 0;
*buttons |= gp.rightShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R) : 0;
*buttons |= gp.leftTrigger.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L2) : 0;
*buttons |= gp.rightTrigger.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R2) : 0;
mfi_axes[slot][0] = gp.leftThumbstick.xAxis.value * 32767.0f;
mfi_axes[slot][1] = gp.leftThumbstick.yAxis.value * 32767.0f;
mfi_axes[slot][2] = gp.rightThumbstick.xAxis.value * 32767.0f;
mfi_axes[slot][3] = gp.rightThumbstick.yAxis.value * 32767.0f;
memset(mfi_axes[slot], 0, sizeof(mfi_axes[0]));
}
else if (controller.gamepad)
{
GCGamepad *gp = (GCGamepad *)controller.gamepad;
if (controller.extendedGamepad)
{
GCExtendedGamepad *gp = (GCExtendedGamepad *)controller.extendedGamepad;
*buttons |= gp.dpad.up.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_UP) : 0;
*buttons |= gp.dpad.down.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_DOWN) : 0;
*buttons |= gp.dpad.left.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_LEFT) : 0;
*buttons |= gp.dpad.right.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT) : 0;
*buttons |= gp.buttonA.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_B) : 0;
*buttons |= gp.buttonB.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_A) : 0;
*buttons |= gp.buttonX.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_Y) : 0;
*buttons |= gp.buttonY.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_X) : 0;
*buttons |= gp.leftShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L) : 0;
*buttons |= gp.rightShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R) : 0;
}
*buttons |= gp.dpad.up.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_UP) : 0;
*buttons |= gp.dpad.down.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_DOWN) : 0;
*buttons |= gp.dpad.left.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_LEFT) : 0;
*buttons |= gp.dpad.right.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT) : 0;
*buttons |= gp.buttonA.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_B) : 0;
*buttons |= gp.buttonB.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_A) : 0;
*buttons |= gp.buttonX.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_Y) : 0;
*buttons |= gp.buttonY.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_X) : 0;
*buttons |= gp.leftShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L) : 0;
*buttons |= gp.rightShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R) : 0;
*buttons |= gp.leftTrigger.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L2) : 0;
*buttons |= gp.rightTrigger.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R2) : 0;
mfi_axes[slot][0] = gp.leftThumbstick.xAxis.value * 32767.0f;
mfi_axes[slot][1] = gp.leftThumbstick.yAxis.value * 32767.0f;
mfi_axes[slot][2] = gp.rightThumbstick.xAxis.value * 32767.0f;
mfi_axes[slot][3] = gp.rightThumbstick.yAxis.value * 32767.0f;
}
else if (controller.gamepad)
{
GCGamepad *gp = (GCGamepad *)controller.gamepad;
*buttons |= gp.dpad.up.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_UP) : 0;
*buttons |= gp.dpad.down.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_DOWN) : 0;
*buttons |= gp.dpad.left.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_LEFT) : 0;
*buttons |= gp.dpad.right.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT) : 0;
*buttons |= gp.buttonA.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_B) : 0;
*buttons |= gp.buttonB.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_A) : 0;
*buttons |= gp.buttonX.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_Y) : 0;
*buttons |= gp.buttonY.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_X) : 0;
*buttons |= gp.leftShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L) : 0;
*buttons |= gp.rightShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R) : 0;
}
}
static void apple_gamecontroller_joypad_poll(void)
{
if (!apple_gamecontroller_available())
return;
if (!apple_gamecontroller_available())
return;
for (GCController *controller in [GCController controllers])
apple_gamecontroller_joypad_poll_internal(controller);
for (GCController *controller in [GCController controllers])
apple_gamecontroller_joypad_poll_internal(controller);
}
static void apple_gamecontroller_joypad_register(GCGamepad *gamepad)
{
gamepad.valueChangedHandler = ^(GCGamepad *updateGamepad, GCControllerElement *element)
{
apple_gamecontroller_joypad_poll_internal(updateGamepad.controller);
};
gamepad.valueChangedHandler = ^(GCGamepad *updateGamepad, GCControllerElement *element)
{
apple_gamecontroller_joypad_poll_internal(updateGamepad.controller);
};
gamepad.controller.controllerPausedHandler = ^(GCController *controller)
{
uint32_t slot = (uint32_t)controller.playerIndex;
mfi_buttons[slot] |= (1 << RETRO_DEVICE_ID_JOYPAD_START);
gamepad.controller.controllerPausedHandler = ^(GCController *controller)
{
uint32_t slot = (uint32_t)controller.playerIndex;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Support buttons that aren't supported by the mFi controller via "hotkey" combinations:
//
// LS + Menu => Select
// LT + Menu => L3
// RT + Menu => R3
// Note that these are just button presses, and it does not simulate holding down the button
if ( controller.gamepad.leftShoulder.pressed || controller.extendedGamepad.leftShoulder.pressed ) {
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_START);
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_L);
mfi_buttons[slot] |= (1 << RETRO_DEVICE_ID_JOYPAD_SELECT);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_SELECT);
});
return;
}
if ( controller.extendedGamepad.leftTrigger.pressed ) {
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_L2);
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_START);
mfi_buttons[slot] |= (1 << RETRO_DEVICE_ID_JOYPAD_L3);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_L3);
});
return;
}
if ( controller.extendedGamepad.rightTrigger.pressed ) {
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_R2);
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_START);
mfi_buttons[slot] |= (1 << RETRO_DEVICE_ID_JOYPAD_R3);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_R3);
});
return;
}
};
mfi_buttons[slot] |= (1 << RETRO_DEVICE_ID_JOYPAD_START);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
mfi_buttons[slot] &= ~(1 << RETRO_DEVICE_ID_JOYPAD_START);
});
};
}
static void apple_gamecontroller_joypad_connect(GCController *controller)
{
signed desired_index = (int32_t)controller.playerIndex;
desired_index = (desired_index >= 0 && desired_index < MAX_MFI_CONTROLLERS)
? desired_index : 0;
? desired_index : 0;
/* prevent same controller getting set twice */
if (mfi_controllers[desired_index] != (uint32_t)controller.hash)
{
@ -148,60 +192,91 @@ static void apple_gamecontroller_joypad_connect(GCController *controller)
}
else
{
/* find a new slot for this controller that's unused */
unsigned i;
/* find a new slot for this controller that's unused */
unsigned i;
for (i = 0; i < MAX_MFI_CONTROLLERS; ++i)
{
if (mfi_controllers[i])
continue;
for (i = 0; i < MAX_MFI_CONTROLLERS; ++i)
{
if (mfi_controllers[i])
continue;
mfi_controllers[i] = (uint32_t)controller.hash;
controller.playerIndex = i;
break;
}
mfi_controllers[i] = (uint32_t)controller.hash;
controller.playerIndex = i;
break;
}
}
apple_gamecontroller_joypad_register(controller.gamepad);
[mfiControllers addObject:controller];
// move any non-game controllers (like the siri remote) to the end
if ( mfiControllers.count > 1 ) {
NSInteger connectedNonGameControllerIndex = NSNotFound;
NSUInteger index = 0;
for (GCController *connectedController in mfiControllers) {
if ( connectedController.gamepad == nil && connectedController.extendedGamepad == nil ) {
connectedNonGameControllerIndex = index;
}
index++;
}
if ( connectedNonGameControllerIndex != NSNotFound ) {
GCController *nonGameController = [mfiControllers objectAtIndex:connectedNonGameControllerIndex];
[mfiControllers removeObjectAtIndex:connectedNonGameControllerIndex];
[mfiControllers addObject:nonGameController];
}
int newPlayerIndex = 0;
for (GCController *gc in mfiControllers) {
gc.playerIndex = newPlayerIndex++;
}
}
apple_gamecontroller_joypad_register(controller.gamepad);
}
}
static void apple_gamecontroller_joypad_disconnect(GCController* controller)
{
signed pad = (int32_t)controller.playerIndex;
if (pad == GCCONTROLLER_PLAYER_INDEX_UNSET)
return;
signed pad = (int32_t)controller.playerIndex;
mfi_controllers[pad] = 0;
if (pad == GCCONTROLLER_PLAYER_INDEX_UNSET)
return;
mfi_controllers[pad] = 0;
[mfiControllers removeObject:controller];
}
static void mfi_joypad_autodetect_add(unsigned autoconf_pad)
{
if ( !input_autoconfigure_connect("mFi Controller", NULL, mfi_joypad.ident, autoconf_pad, 0, 0) ) {
input_config_set_device(autoconf_pad, "mFi Controller");
}
}
bool apple_gamecontroller_joypad_init(void *data)
{
static bool inited = false;
if (inited)
return true;
if (!apple_gamecontroller_available())
return false;
mfi_joypad_autodetect_add(0);
static bool inited = false;
if (inited)
return true;
if (!apple_gamecontroller_available())
return false;
mfiControllers = [[NSMutableArray alloc] initWithCapacity:MAX_MFI_CONTROLLERS];
#ifdef __IPHONE_7_0
[[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
apple_gamecontroller_joypad_connect([note object]);
}];
[[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
apple_gamecontroller_joypad_disconnect([note object]);
} ];
[[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
apple_gamecontroller_joypad_connect([note object]);
}];
[[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note)
{
apple_gamecontroller_joypad_disconnect([note object]);
} ];
#endif
return true;
return true;
}
static void apple_gamecontroller_joypad_destroy(void)
@ -210,90 +285,89 @@ static void apple_gamecontroller_joypad_destroy(void)
static bool apple_gamecontroller_joypad_button(unsigned port, uint16_t joykey)
{
/* Check hat. */
if (GET_HAT_DIR(joykey))
return false;
/* Check hat. */
if (GET_HAT_DIR(joykey))
return false;
/* Check the button. */
if ((port < MAX_USERS) && (joykey < 32))
return ((mfi_buttons[port] & (1 << joykey)) != 0);
/* Check the button. */
if ((port < MAX_USERS) && (joykey < 32))
return ((mfi_buttons[port] & (1 << joykey)) != 0);
return false;
return false;
}
static void apple_gamecontroller_joypad_get_buttons(unsigned port,
input_bits_t *state)
input_bits_t *state)
{
BITS_COPY16_PTR(state, mfi_buttons[port]);
BITS_COPY16_PTR(state, mfi_buttons[port]);
}
static int16_t apple_gamecontroller_joypad_axis(unsigned port, uint32_t joyaxis)
{
int16_t val = 0;
int16_t axis = -1;
bool is_neg = false;
bool is_pos = false;
int16_t val = 0;
int16_t axis = -1;
bool is_neg = false;
bool is_pos = false;
if (joyaxis == AXIS_NONE)
return 0;
if (joyaxis == AXIS_NONE)
return 0;
if (AXIS_NEG_GET(joyaxis) < 4)
{
axis = AXIS_NEG_GET(joyaxis);
is_neg = true;
}
else if(AXIS_POS_GET(joyaxis) < 4)
{
axis = AXIS_POS_GET(joyaxis);
is_pos = true;
}
switch (axis)
{
case 0:
val = mfi_axes[port][0];
break;
case 1:
val = mfi_axes[port][1];
break;
case 2:
val = mfi_axes[port][2];
break;
case 3:
val = mfi_axes[port][3];
break;
}
if (is_neg && val > 0)
val = 0;
else if (is_pos && val < 0)
val = 0;
if (AXIS_NEG_GET(joyaxis) < 4)
{
axis = AXIS_NEG_GET(joyaxis);
is_neg = true;
}
else if(AXIS_POS_GET(joyaxis) < 4)
{
axis = AXIS_POS_GET(joyaxis);
is_pos = true;
}
return val;
switch (axis)
{
case 0:
val = mfi_axes[port][0];
break;
case 1:
val = mfi_axes[port][1];
break;
case 2:
val = mfi_axes[port][2];
break;
case 3:
val = mfi_axes[port][3];
break;
}
if (is_neg && val > 0)
val = 0;
else if (is_pos && val < 0)
val = 0;
return val;
}
static bool apple_gamecontroller_joypad_query_pad(unsigned pad)
{
return pad < MAX_USERS;
return pad < MAX_USERS;
}
static const char *apple_gamecontroller_joypad_name(unsigned pad)
{
if (pad >= MAX_USERS)
return NULL;
return "MFi pad";
if (pad >= MAX_USERS)
return NULL;
return "mFi Controller";
}
input_device_driver_t mfi_joypad = {
apple_gamecontroller_joypad_init,
apple_gamecontroller_joypad_query_pad,
apple_gamecontroller_joypad_destroy,
apple_gamecontroller_joypad_button,
apple_gamecontroller_joypad_get_buttons,
apple_gamecontroller_joypad_axis,
apple_gamecontroller_joypad_poll,
NULL,
apple_gamecontroller_joypad_name,
"mfi",
apple_gamecontroller_joypad_init,
apple_gamecontroller_joypad_query_pad,
apple_gamecontroller_joypad_destroy,
apple_gamecontroller_joypad_button,
apple_gamecontroller_joypad_get_buttons,
apple_gamecontroller_joypad_axis,
apple_gamecontroller_joypad_poll,
NULL,
apple_gamecontroller_joypad_name,
"mfi",
};

View File

@ -17,6 +17,7 @@
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
#include <linux/parport.h>

View File

@ -23,7 +23,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <poll.h>
#include <libudev.h>
#ifdef __linux__
#include <linux/types.h>

View File

@ -0,0 +1,176 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
/* generated from /usr/share/X11/xkb/keycodes/evdev */
#ifndef __XFREE86_KEYCODES__H
#define __XFREE86_KEYCODES__H
enum xfvk_key
{
XFVK_UNKNOWN = 0,
XFVK_FIRST = 0,
XFVK_ESC = 9,
XFVK_FK01 = 67,
XFVK_FK02 = 68,
XFVK_FK03 = 69,
XFVK_FK04 = 70,
XFVK_FK05 = 71,
XFVK_FK06 = 72,
XFVK_FK07 = 73,
XFVK_FK08 = 74,
XFVK_FK09 = 75,
XFVK_FK10 = 76,
XFVK_FK11 = 95,
XFVK_FK12 = 96,
// Added for pc105 compatibility
XFVK_LSGT = 94,
XFVK_TLDE = 49,
XFVK_AE01 = 10,
XFVK_AE02 = 11,
XFVK_AE03 = 12,
XFVK_AE04 = 13,
XFVK_AE05 = 14,
XFVK_AE06 = 15,
XFVK_AE07 = 16,
XFVK_AE08 = 17,
XFVK_AE09 = 18,
XFVK_AE10 = 19,
XFVK_AE11 = 20,
XFVK_AE12 = 21,
XFVK_BKSP = 22,
XFVK_TAB = 23,
XFVK_AD01 = 24,
XFVK_AD02 = 25,
XFVK_AD03 = 26,
XFVK_AD04 = 27,
XFVK_AD05 = 28,
XFVK_AD06 = 29,
XFVK_AD07 = 30,
XFVK_AD08 = 31,
XFVK_AD09 = 32,
XFVK_AD10 = 33,
XFVK_AD11 = 34,
XFVK_AD12 = 35,
XFVK_BKSL = 51,
XFVK_AC12 = XFVK_BKSL,
XFVK_RTRN = 36,
XFVK_CAPS = 66,
XFVK_AC01 = 38,
XFVK_AC02 = 39,
XFVK_AC03 = 40,
XFVK_AC04 = 41,
XFVK_AC05 = 42,
XFVK_AC06 = 43,
XFVK_AC07 = 44,
XFVK_AC08 = 45,
XFVK_AC09 = 46,
XFVK_AC10 = 47,
XFVK_AC11 = 48,
XFVK_LFSH = 50,
XFVK_AB01 = 52,
XFVK_AB02 = 53,
XFVK_AB03 = 54,
XFVK_AB04 = 55,
XFVK_AB05 = 56,
XFVK_AB06 = 57,
XFVK_AB07 = 58,
XFVK_AB08 = 59,
XFVK_AB09 = 60,
XFVK_AB10 = 61,
XFVK_RTSH = 62,
XFVK_LALT = 64,
XFVK_LCTL = 37,
XFVK_SPCE = 65,
XFVK_RCTL = 105,
XFVK_RALT = 108,
XFVK_PRSC = 107,
// SYRQ = 107,
XFVK_SCLK = 78,
XFVK_PAUS = 127,
// BRK = 419,
XFVK_INS = 118,
XFVK_HOME = 110,
XFVK_PGUP = 112,
XFVK_DELE = 119,
XFVK_END = 115,
XFVK_PGDN = 117,
XFVK_UP = 111,
XFVK_LEFT = 113,
XFVK_DOWN = 116,
XFVK_RGHT = 114,
XFVK_NMLK = 77,
XFVK_KPDV = 106,
XFVK_KPMU = 63,
XFVK_KPSU = 82,
XFVK_KP7 = 79,
XFVK_KP8 = 80,
XFVK_KP9 = 81,
XFVK_KPAD = 86,
XFVK_KP4 = 83,
XFVK_KP5 = 84,
XFVK_KP6 = 85,
XFVK_KP1 = 87,
XFVK_KP2 = 88,
XFVK_KP3 = 89,
XFVK_KPEN = 104,
XFVK_KP0 = 90,
XFVK_KPDL = 91,
XFVK_KPEQ = 125,
// Microsoft keyboard extra keys
XFVK_LWIN = 133,
XFVK_RWIN = 134,
XFVK_COMP = 135,
XFVK_MENU = XFVK_COMP,
// Extended keys
XFVK_CALC = 148,
XFVK_FK13 = 191,
XFVK_FK14 = 192,
XFVK_FK15 = 193,
XFVK_FK16 = 194,
XFVK_FK17 = 195,
XFVK_FK18 = 196,
XFVK_FK19 = 197,
XFVK_FK20 = 198,
XFVK_FK21 = 199,
XFVK_FK22 = 200,
XFVK_FK23 = 201,
XFVK_FK24 = 202,
XFVK_LAST,
XFVK_DUMMY = 255
};
#endif /* __XFREE86_KEYCODES__H */

View File

@ -630,6 +630,32 @@ DECL_AXIS(r_x_minus, -2) \
DECL_AXIS(r_y_plus, +3) \
DECL_AXIS(r_y_minus, -3)
#define IOS_MFI_DEFAULT_BINDS \
DECL_BTN(a, 8) \
DECL_BTN(b, 0) \
DECL_BTN(x, 9) \
DECL_BTN(y, 1) \
DECL_BTN(up, 4) \
DECL_BTN(down, 5) \
DECL_BTN(left, 6) \
DECL_BTN(right, 7) \
DECL_BTN(l, 10) \
DECL_BTN(r, 11) \
DECL_BTN(start, 3) \
DECL_BTN(select, 2) \
DECL_BTN(l2, 12) \
DECL_BTN(r2, 13) \
DECL_BTN(l3, 14) \
DECL_BTN(r3, 15) \
DECL_AXIS(l_x_plus, +0) \
DECL_AXIS(l_x_minus, -0) \
DECL_AXIS(l_y_plus, -1) \
DECL_AXIS(l_y_minus, +1) \
DECL_AXIS(r_x_plus, +2) \
DECL_AXIS(r_x_minus, -2) \
DECL_AXIS(r_y_plus, -3) \
DECL_AXIS(r_y_minus, +3)
const char* const input_builtin_autoconfs[] =
{
#if defined(_WIN32) && defined(_XBOX)
@ -704,6 +730,9 @@ const char* const input_builtin_autoconfs[] =
#endif
#ifdef EMSCRIPTEN
DECL_AUTOCONF_PID(1, 1, "rwebpad", EMSCRIPTEN_DEFAULT_BINDS),
#endif
#if TARGET_OS_IPHONE
DECL_AUTOCONF_DEVICE("mFi Controller", "mfi", IOS_MFI_DEFAULT_BINDS),
#endif
NULL
};

View File

@ -50,9 +50,7 @@
#endif
#ifdef HAVE_X11
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include "input/include/xfree86_keycodes.h"
#endif
#ifdef HAVE_DINPUT
@ -693,156 +691,123 @@ const struct rarch_key_map rarch_key_map_wiiu[] = {
#ifdef HAVE_X11
#ifndef XF68XK_Calculator
#define XF86XK_Calculator 0x1008FF1D
#endif
const struct rarch_key_map rarch_key_map_x11[] = {
{ XK_BackSpace, RETROK_BACKSPACE },
{ XK_Tab, RETROK_TAB },
{ XK_Clear, RETROK_CLEAR },
{ XK_Return, RETROK_RETURN },
{ XK_Pause, RETROK_PAUSE },
{ XK_Escape, RETROK_ESCAPE },
{ XK_space, RETROK_SPACE },
{ XK_exclam, RETROK_EXCLAIM },
{ XK_quotedbl, RETROK_QUOTEDBL },
{ XK_numbersign, RETROK_HASH },
{ XK_dollar, RETROK_DOLLAR },
{ XK_ampersand, RETROK_AMPERSAND },
{ XK_apostrophe, RETROK_QUOTE },
{ XK_parenleft, RETROK_LEFTPAREN },
{ XK_parenright, RETROK_RIGHTPAREN },
{ XK_asterisk, RETROK_ASTERISK },
{ XK_plus, RETROK_PLUS },
{ XK_comma, RETROK_COMMA },
{ XK_minus, RETROK_MINUS },
{ XK_period, RETROK_PERIOD },
{ XK_slash, RETROK_SLASH },
{ XK_0, RETROK_0 },
{ XK_1, RETROK_1 },
{ XK_2, RETROK_2 },
{ XK_3, RETROK_3 },
{ XK_4, RETROK_4 },
{ XK_5, RETROK_5 },
{ XK_6, RETROK_6 },
{ XK_7, RETROK_7 },
{ XK_8, RETROK_8 },
{ XK_9, RETROK_9 },
{ XK_colon, RETROK_COLON },
{ XK_semicolon, RETROK_SEMICOLON },
{ XK_less, RETROK_LESS },
{ XK_equal, RETROK_EQUALS },
{ XK_greater, RETROK_GREATER },
{ XK_question, RETROK_QUESTION },
{ XK_at, RETROK_AT },
{ XK_bracketleft, RETROK_LEFTBRACKET },
{ XK_backslash, RETROK_BACKSLASH },
{ XK_bracketright, RETROK_RIGHTBRACKET },
{ XK_dead_circumflex, RETROK_CARET },
{ XK_underscore, RETROK_UNDERSCORE },
{ XK_grave, RETROK_BACKQUOTE },
{ XK_a, RETROK_a },
{ XK_b, RETROK_b },
{ XK_c, RETROK_c },
{ XK_d, RETROK_d },
{ XK_e, RETROK_e },
{ XK_f, RETROK_f },
{ XK_g, RETROK_g },
{ XK_h, RETROK_h },
{ XK_i, RETROK_i },
{ XK_j, RETROK_j },
{ XK_k, RETROK_k },
{ XK_l, RETROK_l },
{ XK_m, RETROK_m },
{ XK_n, RETROK_n },
{ XK_o, RETROK_o },
{ XK_p, RETROK_p },
{ XK_q, RETROK_q },
{ XK_r, RETROK_r },
{ XK_s, RETROK_s },
{ XK_t, RETROK_t },
{ XK_u, RETROK_u },
{ XK_v, RETROK_v },
{ XK_w, RETROK_w },
{ XK_x, RETROK_x },
{ XK_y, RETROK_y },
{ XK_z, RETROK_z },
{ XK_Delete, RETROK_DELETE },
{ XK_KP_0, RETROK_KP0 },
{ XK_KP_1, RETROK_KP1 },
{ XK_KP_2, RETROK_KP2 },
{ XK_KP_3, RETROK_KP3 },
{ XK_KP_4, RETROK_KP4 },
{ XK_KP_5, RETROK_KP5 },
{ XK_KP_6, RETROK_KP6 },
{ XK_KP_7, RETROK_KP7 },
{ XK_KP_8, RETROK_KP8 },
{ XK_KP_9, RETROK_KP9 },
{ XK_KP_Decimal, RETROK_KP_PERIOD },
{ XK_KP_Divide, RETROK_KP_DIVIDE },
{ XK_KP_Multiply, RETROK_KP_MULTIPLY },
{ XK_KP_Subtract, RETROK_KP_MINUS },
{ XK_KP_Add, RETROK_KP_PLUS },
{ XK_KP_Enter, RETROK_KP_ENTER },
{ XK_KP_Equal, RETROK_KP_EQUALS },
{ XK_Up, RETROK_UP },
{ XK_Down, RETROK_DOWN },
{ XK_Right, RETROK_RIGHT },
{ XK_Left, RETROK_LEFT },
{ XK_Insert, RETROK_INSERT },
{ XK_Home, RETROK_HOME },
{ XK_End, RETROK_END },
{ XK_Page_Up, RETROK_PAGEUP },
{ XK_Page_Down, RETROK_PAGEDOWN },
{ XK_F1, RETROK_F1 },
{ XK_F2, RETROK_F2 },
{ XK_F3, RETROK_F3 },
{ XK_F4, RETROK_F4 },
{ XK_F5, RETROK_F5 },
{ XK_F6, RETROK_F6 },
{ XK_F7, RETROK_F7 },
{ XK_F8, RETROK_F8 },
{ XK_F9, RETROK_F9 },
{ XK_F10, RETROK_F10 },
{ XK_F11, RETROK_F11 },
{ XK_F12, RETROK_F12 },
{ XK_F13, RETROK_F13 },
{ XK_F14, RETROK_F14 },
{ XK_F15, RETROK_F15 },
{ XK_Num_Lock, RETROK_NUMLOCK },
{ XK_Caps_Lock, RETROK_CAPSLOCK },
{ XK_Scroll_Lock, RETROK_SCROLLOCK },
{ XK_Shift_R, RETROK_RSHIFT },
{ XK_Shift_L, RETROK_LSHIFT },
{ XK_Control_R, RETROK_RCTRL },
{ XK_Control_L, RETROK_LCTRL },
{ XK_Alt_R, RETROK_RALT },
{ XK_Alt_L, RETROK_LALT },
{ XK_Meta_R, RETROK_RMETA },
{ XK_Meta_L, RETROK_LMETA },
{ XK_Super_L, RETROK_LSUPER },
{ XK_Super_R, RETROK_RSUPER },
{ XK_Mode_switch, RETROK_MODE },
{ XK_Multi_key, RETROK_COMPOSE },
{ XK_Help, RETROK_HELP },
{ XK_Print, RETROK_PRINT },
{ XK_Sys_Req, RETROK_SYSREQ },
{ XK_Break, RETROK_BREAK },
{ XK_Menu, RETROK_MENU },
/*{ ?, RETROK_POWER },*/
{ XK_EuroSign, RETROK_EURO },
{ XK_Undo, RETROK_UNDO },
/*{ ?, RETROK_OEM_102 },*/
/* FIXME(shizeeg): RetroArch can't handle these buttons atm.
* Do we really need RETROK_KP_INSERT, RETROK_KP_END,
* RETROK_KP_DOWN, RETROK_KP_PAGEDOWN ???
*
{ XK_KP_Insert, RETROK_KP0 },
{ XK_KP_End, RETROK_KP1 },
{ XK_KP_Down, RETROK_KP2 },
{ XK_KP_Page_Down, RETROK_KP3 },*/
{ XF86XK_Calculator, RETROK_HELP },
{ XFVK_ESC, RETROK_ESCAPE },
{ XFVK_FK01, RETROK_F1 },
{ XFVK_FK02, RETROK_F2 },
{ XFVK_FK03, RETROK_F3 },
{ XFVK_FK04, RETROK_F4 },
{ XFVK_FK05, RETROK_F5 },
{ XFVK_FK06, RETROK_F6 },
{ XFVK_FK07, RETROK_F7 },
{ XFVK_FK08, RETROK_F8 },
{ XFVK_FK09, RETROK_F9 },
{ XFVK_FK10, RETROK_F10 },
{ XFVK_FK11, RETROK_F11 },
{ XFVK_FK12, RETROK_F12 },
{ XFVK_TLDE, RETROK_BACKQUOTE },
{ XFVK_AE01, RETROK_1 },
{ XFVK_AE02, RETROK_2 },
{ XFVK_AE03, RETROK_3 },
{ XFVK_AE04, RETROK_4 },
{ XFVK_AE05, RETROK_5 },
{ XFVK_AE06, RETROK_6 },
{ XFVK_AE07, RETROK_7 },
{ XFVK_AE08, RETROK_8 },
{ XFVK_AE09, RETROK_9 },
{ XFVK_AE10, RETROK_0 },
{ XFVK_AE11, RETROK_MINUS },
{ XFVK_AE12, RETROK_EQUALS },
{ XFVK_BKSP, RETROK_BACKSPACE },
{ XFVK_TAB, RETROK_TAB },
{ XFVK_AD01, RETROK_q },
{ XFVK_AD02, RETROK_w },
{ XFVK_AD03, RETROK_e },
{ XFVK_AD04, RETROK_r },
{ XFVK_AD05, RETROK_t },
{ XFVK_AD06, RETROK_y },
{ XFVK_AD07, RETROK_u },
{ XFVK_AD08, RETROK_i },
{ XFVK_AD09, RETROK_o },
{ XFVK_AD10, RETROK_p },
{ XFVK_AD11, RETROK_LEFTBRACKET },
{ XFVK_AD12, RETROK_RIGHTBRACKET },
{ XFVK_RTRN, RETROK_RETURN },
{ XFVK_CAPS, RETROK_CAPSLOCK },
{ XFVK_AC01, RETROK_a },
{ XFVK_AC02, RETROK_s },
{ XFVK_AC03, RETROK_d },
{ XFVK_AC04, RETROK_f },
{ XFVK_AC05, RETROK_g },
{ XFVK_AC06, RETROK_h },
{ XFVK_AC07, RETROK_j },
{ XFVK_AC08, RETROK_k },
{ XFVK_AC09, RETROK_l },
{ XFVK_AC10, RETROK_SEMICOLON },
{ XFVK_AC11, RETROK_QUOTE },
{ XFVK_AC12, RETROK_BACKSLASH },
{ XFVK_LFSH, RETROK_LSHIFT },
{ XFVK_AB01, RETROK_z },
{ XFVK_AB02, RETROK_x },
{ XFVK_AB03, RETROK_c },
{ XFVK_AB04, RETROK_v },
{ XFVK_AB05, RETROK_b },
{ XFVK_AB06, RETROK_n },
{ XFVK_AB07, RETROK_m },
{ XFVK_AB08, RETROK_COMMA },
{ XFVK_AB09, RETROK_PERIOD },
{ XFVK_AB10, RETROK_SLASH },
{ XFVK_RTSH, RETROK_RSHIFT },
{ XFVK_LALT, RETROK_LALT },
{ XFVK_LCTL, RETROK_LCTRL },
{ XFVK_SPCE, RETROK_SPACE },
{ XFVK_RCTL, RETROK_RCTRL },
{ XFVK_RALT, RETROK_RALT },
{ XFVK_LSGT, RETROK_OEM_102 },
{ XFVK_MENU, RETROK_MENU },
{ XFVK_LWIN, RETROK_LSUPER },
{ XFVK_RWIN, RETROK_RSUPER },
{ XFVK_CALC, RETROK_HELP },
{ XFVK_PRSC, RETROK_PRINT },
{ XFVK_SCLK, RETROK_SCROLLOCK },
{ XFVK_PAUS, RETROK_PAUSE },
{ XFVK_INS, RETROK_INSERT },
{ XFVK_HOME, RETROK_HOME },
{ XFVK_PGUP, RETROK_PAGEUP },
{ XFVK_DELE, RETROK_DELETE },
{ XFVK_END, RETROK_END },
{ XFVK_PGDN, RETROK_PAGEDOWN },
{ XFVK_UP, RETROK_UP },
{ XFVK_LEFT, RETROK_LEFT },
{ XFVK_DOWN, RETROK_DOWN },
{ XFVK_RGHT, RETROK_RIGHT },
{ XFVK_NMLK, RETROK_NUMLOCK },
{ XFVK_KPDV, RETROK_KP_DIVIDE },
{ XFVK_KPMU, RETROK_KP_MULTIPLY },
{ XFVK_KPSU, RETROK_KP_MINUS },
{ XFVK_KP7, RETROK_KP7 },
{ XFVK_KP8, RETROK_KP8 },
{ XFVK_KP9, RETROK_KP9 },
{ XFVK_KPAD, RETROK_KP_PLUS },
{ XFVK_KP4, RETROK_KP4 },
{ XFVK_KP5, RETROK_KP5 },
{ XFVK_KP6, RETROK_KP6 },
{ XFVK_KP1, RETROK_KP1 },
{ XFVK_KP2, RETROK_KP2 },
{ XFVK_KP3, RETROK_KP3 },
{ XFVK_KPEN, RETROK_KP_ENTER },
{ XFVK_KP0, RETROK_KP0 },
{ XFVK_KPDL, RETROK_KP_PERIOD },
{ XFVK_KPEQ, RETROK_KP_EQUALS },
{ 0, RETROK_UNKNOWN },
};

View File

@ -3752,3 +3752,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -4764,3 +4764,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3522,3 +3522,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3651,3 +3651,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -7748,3 +7748,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3410,3 +3410,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -7884,3 +7884,11 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_HOLD_START,
"Mantener Start (2 segundos)"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3566,3 +3566,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3607,3 +3607,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Ricorda la dimensione e posizione della finestra")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -4056,3 +4056,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"ウィンドウの位置とサイズを記憶")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3517,3 +3517,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"창 위치와 사이즈를 기억합니다.")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3396,3 +3396,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio ondersteuning"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3810,3 +3810,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Zapamiętaj położenie i rozmiar okna")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -8036,3 +8036,11 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_HOLD_START,
"Segurar Start (2 segundos)"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3476,3 +3476,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3679,3 +3679,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -6870,6 +6870,10 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_QT_ITEMS_COUNT,
"%1 items"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_QT_DROP_IMAGE_HERE,
"Drop image here"
)
#ifdef HAVE_QT
MSG_HASH(
MENU_ENUM_LABEL_VALUE_QT_SCAN_FINISHED,
@ -8112,3 +8116,15 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_DOWN_SELECT,
"Down + Select"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_DRIVER_FALLBACK,
"Your graphics driver is not compatible with the current video driver in RetroArch, falling back to the %s driver. Please restart RetroArch for the changes to take effect."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -3566,3 +3566,11 @@ MSG_HASH(
)
MSG_HASH(MENU_ENUM_LABEL_VALUE_VIDEO_WINDOW_SAVE_POSITION,
"Remember Window Position and Size")
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
"CoreAudio support"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
"CoreAudio V3 support"
)

View File

@ -240,12 +240,20 @@ static int zip_file_read(
const char *needle, void **buf,
const char *optional_outfile)
{
file_archive_transfer_t zlib = {0};
file_archive_transfer_t zlib;
struct archive_extract_userdata userdata = {{0}};
bool returnerr = true;
int ret = 0;
zlib.type = ARCHIVE_TRANSFER_INIT;
zlib.archive_size = 0;
zlib.start_delta = 0;
zlib.handle = NULL;
zlib.stream = NULL;
zlib.footer = NULL;
zlib.directory = NULL;
zlib.data = NULL;
zlib.backend = NULL;
userdata.decomp_state.needle = NULL;
userdata.decomp_state.opt_file = NULL;

View File

@ -39,8 +39,14 @@
#endif
#elif defined(__APPLE__)
#include <compat/apple_compat.h>
#if MAC_OS_X_VERSION_10_7
#include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h>
#else
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#endif
#elif defined(HAVE_PSGL)
#include <PSGL/psgl.h>
#include <GLES/glext.h>
@ -84,4 +90,12 @@
#define GL_RED_INTEGER 0x8D94
#endif
#ifndef GL_BGRA_EXT
#define GL_BGRA_EXT GL_BGRA
#endif
#ifndef GL_LUMINANCE_ALPHA
#define GL_LUMINANCE_ALPHA 0x190A
#endif
#endif

View File

@ -57,6 +57,7 @@
#include "../../defaults.h"
#include "../../managers/core_option_manager.h"
#include "../../managers/cheat_manager.h"
#include "../../tasks/task_audio_mixer.h"
#include "../../tasks/task_content.h"
#include "../../tasks/task_file_transfer.h"
#include "../../tasks/tasks_internal.h"
@ -4698,7 +4699,7 @@ static int action_ok_push_dropdown_item_resolution(const char *path,
refreshrate = strtoul(pch, NULL, 0);
if (video_display_server_set_resolution(width, height,
refreshrate, (float)refreshrate, 0, NULL))
refreshrate, (float)refreshrate, 0, 0))
{
settings_t *settings = config_get_ptr();

View File

@ -84,7 +84,7 @@ ozone_theme_t *ozone_themes[] = {
unsigned ozone_themes_count = sizeof(ozone_themes) / sizeof(ozone_themes[0]);
unsigned last_color_theme = 0;
bool last_use_preferred_system_color_theme = false;
ozone_theme_t *ozone_default_theme = &ozone_theme_light; /* also used as a tag for cursor animation */
ozone_theme_t *ozone_default_theme = &ozone_theme_dark; /* also used as a tag for cursor animation */
void ozone_set_color_theme(ozone_handle_t *ozone, unsigned color_theme)
{
@ -99,6 +99,8 @@ void ozone_set_color_theme(ozone_handle_t *ozone, unsigned color_theme)
theme = &ozone_theme_dark;
break;
case 0:
theme = &ozone_theme_light;
break;
default:
break;
}

310
menu/menu_defines.h Normal file
View File

@ -0,0 +1,310 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __MENU_DEFINES__H
#define __MENU_DEFINES__H
#include <retro_common_api.h>
#include "../audio/audio_defines.h"
RETRO_BEGIN_DECLS
#define MENU_SETTINGS_AUDIO_MIXER_MAX_STREAMS (AUDIO_MIXER_MAX_SYSTEM_STREAMS-1)
enum menu_image_type
{
MENU_IMAGE_NONE = 0,
MENU_IMAGE_WALLPAPER,
MENU_IMAGE_THUMBNAIL,
MENU_IMAGE_LEFT_THUMBNAIL,
MENU_IMAGE_SAVESTATE_THUMBNAIL
};
enum menu_environ_cb
{
MENU_ENVIRON_NONE = 0,
MENU_ENVIRON_RESET_HORIZONTAL_LIST,
MENU_ENVIRON_ENABLE_MOUSE_CURSOR,
MENU_ENVIRON_DISABLE_MOUSE_CURSOR,
MENU_ENVIRON_LAST
};
enum menu_state_changes
{
MENU_STATE_RENDER_FRAMEBUFFER = 0,
MENU_STATE_RENDER_MESSAGEBOX,
MENU_STATE_BLIT,
MENU_STATE_POP_STACK,
MENU_STATE_POST_ITERATE
};
enum rarch_menu_ctl_state
{
RARCH_MENU_CTL_NONE = 0,
RARCH_MENU_CTL_SET_PENDING_QUICK_MENU,
RARCH_MENU_CTL_SET_PENDING_QUIT,
RARCH_MENU_CTL_SET_PENDING_SHUTDOWN,
RARCH_MENU_CTL_DEINIT,
RARCH_MENU_CTL_SET_PREVENT_POPULATE,
RARCH_MENU_CTL_UNSET_PREVENT_POPULATE,
RARCH_MENU_CTL_IS_PREVENT_POPULATE,
RARCH_MENU_CTL_IS_TOGGLE,
RARCH_MENU_CTL_SET_TOGGLE,
RARCH_MENU_CTL_UNSET_TOGGLE,
RARCH_MENU_CTL_SET_OWN_DRIVER,
RARCH_MENU_CTL_UNSET_OWN_DRIVER,
RARCH_MENU_CTL_OWNS_DRIVER,
RARCH_MENU_CTL_FIND_DRIVER,
RARCH_MENU_CTL_LIST_FREE,
RARCH_MENU_CTL_ENVIRONMENT,
RARCH_MENU_CTL_DRIVER_DATA_GET,
RARCH_MENU_CTL_POINTER_TAP,
RARCH_MENU_CTL_POINTER_DOWN,
RARCH_MENU_CTL_POINTER_UP,
RARCH_MENU_CTL_OSK_PTR_AT_POS,
RARCH_MENU_CTL_BIND_INIT,
RARCH_MENU_CTL_UPDATE_THUMBNAIL_PATH,
RARCH_MENU_CTL_UPDATE_THUMBNAIL_IMAGE,
RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_PATH,
RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_IMAGE,
MENU_NAVIGATION_CTL_CLEAR,
MENU_NAVIGATION_CTL_INCREMENT,
MENU_NAVIGATION_CTL_DECREMENT,
MENU_NAVIGATION_CTL_SET_LAST,
MENU_NAVIGATION_CTL_DESCEND_ALPHABET,
MENU_NAVIGATION_CTL_ASCEND_ALPHABET,
MENU_NAVIGATION_CTL_CLEAR_SCROLL_INDICES,
MENU_NAVIGATION_CTL_ADD_SCROLL_INDEX,
MENU_NAVIGATION_CTL_SET_SCROLL_ACCEL,
MENU_NAVIGATION_CTL_GET_SCROLL_ACCEL
};
enum rgui_color_theme
{
RGUI_THEME_CUSTOM = 0,
RGUI_THEME_CLASSIC_RED,
RGUI_THEME_CLASSIC_ORANGE,
RGUI_THEME_CLASSIC_YELLOW,
RGUI_THEME_CLASSIC_GREEN,
RGUI_THEME_CLASSIC_BLUE,
RGUI_THEME_CLASSIC_VIOLET,
RGUI_THEME_CLASSIC_GREY,
RGUI_THEME_LEGACY_RED,
RGUI_THEME_DARK_PURPLE,
RGUI_THEME_MIDNIGHT_BLUE,
RGUI_THEME_GOLDEN,
RGUI_THEME_ELECTRIC_BLUE,
RGUI_THEME_APPLE_GREEN,
RGUI_THEME_VOLCANIC_RED,
RGUI_THEME_LAGOON,
RGUI_THEME_BROGRAMMER,
RGUI_THEME_DRACULA,
RGUI_THEME_FAIRYFLOSS,
RGUI_THEME_FLATUI,
RGUI_THEME_GRUVBOX_DARK,
RGUI_THEME_GRUVBOX_LIGHT,
RGUI_THEME_HACKING_THE_KERNEL,
RGUI_THEME_NORD,
RGUI_THEME_NOVA,
RGUI_THEME_ONE_DARK,
RGUI_THEME_PALENIGHT,
RGUI_THEME_SOLARIZED_DARK,
RGUI_THEME_SOLARIZED_LIGHT,
RGUI_THEME_TANGO_DARK,
RGUI_THEME_TANGO_LIGHT,
RGUI_THEME_ZENBURN,
RGUI_THEME_ANTI_ZENBURN,
RGUI_THEME_LAST
};
enum materialui_color_theme
{
MATERIALUI_THEME_BLUE = 0,
MATERIALUI_THEME_BLUE_GREY,
MATERIALUI_THEME_DARK_BLUE,
MATERIALUI_THEME_GREEN,
MATERIALUI_THEME_RED,
MATERIALUI_THEME_YELLOW,
MATERIALUI_THEME_NVIDIA_SHIELD,
MATERIALUI_THEME_LAST
};
enum xmb_color_theme
{
XMB_THEME_LEGACY_RED = 0,
XMB_THEME_DARK_PURPLE,
XMB_THEME_MIDNIGHT_BLUE,
XMB_THEME_GOLDEN,
XMB_THEME_ELECTRIC_BLUE,
XMB_THEME_APPLE_GREEN,
XMB_THEME_UNDERSEA,
XMB_THEME_VOLCANIC_RED,
XMB_THEME_DARK,
XMB_THEME_LIGHT,
XMB_THEME_WALLPAPER,
XMB_THEME_MORNING_BLUE,
XMB_THEME_LAST
};
enum xmb_icon_theme
{
XMB_ICON_THEME_MONOCHROME = 0,
XMB_ICON_THEME_FLATUI,
XMB_ICON_THEME_RETROACTIVE,
XMB_ICON_THEME_PIXEL,
XMB_ICON_THEME_NEOACTIVE,
XMB_ICON_THEME_SYSTEMATIC,
XMB_ICON_THEME_DOTART,
XMB_ICON_THEME_CUSTOM,
XMB_ICON_THEME_RETROSYSTEM,
XMB_ICON_THEME_MONOCHROME_INVERTED,
XMB_ICON_THEME_AUTOMATIC,
XMB_ICON_THEME_LAST
};
enum xmb_shader_pipeline
{
XMB_SHADER_PIPELINE_WALLPAPER = 0,
XMB_SHADER_PIPELINE_SIMPLE_RIBBON,
XMB_SHADER_PIPELINE_RIBBON,
XMB_SHADER_PIPELINE_SIMPLE_SNOW,
XMB_SHADER_PIPELINE_SNOW,
XMB_SHADER_PIPELINE_BOKEH,
XMB_SHADER_PIPELINE_SNOWFLAKE,
XMB_SHADER_PIPELINE_LAST
};
enum menu_display_prim_type
{
MENU_DISPLAY_PRIM_NONE = 0,
MENU_DISPLAY_PRIM_TRIANGLESTRIP,
MENU_DISPLAY_PRIM_TRIANGLES
};
enum menu_display_driver_type
{
MENU_VIDEO_DRIVER_GENERIC = 0,
MENU_VIDEO_DRIVER_OPENGL,
MENU_VIDEO_DRIVER_VULKAN,
MENU_VIDEO_DRIVER_METAL,
MENU_VIDEO_DRIVER_DIRECT3D8,
MENU_VIDEO_DRIVER_DIRECT3D9,
MENU_VIDEO_DRIVER_DIRECT3D10,
MENU_VIDEO_DRIVER_DIRECT3D11,
MENU_VIDEO_DRIVER_DIRECT3D12,
MENU_VIDEO_DRIVER_VITA2D,
MENU_VIDEO_DRIVER_CTR,
MENU_VIDEO_DRIVER_WIIU,
MENU_VIDEO_DRIVER_CACA,
MENU_VIDEO_DRIVER_SIXEL,
MENU_VIDEO_DRIVER_GDI,
MENU_VIDEO_DRIVER_SWITCH,
MENU_VIDEO_DRIVER_VGA
};
enum menu_toggle_reason
{
MENU_TOGGLE_REASON_NONE = 0,
MENU_TOGGLE_REASON_USER,
MENU_TOGGLE_REASON_MESSAGE
};
enum rgui_thumbnail_scaler
{
RGUI_THUMB_SCALE_POINT = 0,
RGUI_THUMB_SCALE_BILINEAR,
RGUI_THUMB_SCALE_SINC,
RGUI_THUMB_SCALE_LAST
};
enum menu_action
{
MENU_ACTION_NOOP = 0,
MENU_ACTION_UP,
MENU_ACTION_DOWN,
MENU_ACTION_LEFT,
MENU_ACTION_RIGHT,
MENU_ACTION_OK,
MENU_ACTION_SEARCH,
MENU_ACTION_SCAN,
MENU_ACTION_CANCEL,
MENU_ACTION_INFO,
MENU_ACTION_SELECT,
MENU_ACTION_START,
MENU_ACTION_SCROLL_DOWN,
MENU_ACTION_SCROLL_UP,
MENU_ACTION_TOGGLE,
MENU_ACTION_POINTER_MOVED,
MENU_ACTION_POINTER_PRESSED,
MENU_ACTION_QUIT
};
enum menu_input_pointer_state
{
MENU_POINTER_X_AXIS = 0,
MENU_POINTER_Y_AXIS,
MENU_POINTER_DELTA_X_AXIS,
MENU_POINTER_DELTA_Y_AXIS,
MENU_POINTER_PRESSED
};
enum menu_input_mouse_state
{
MENU_MOUSE_X_AXIS = 0,
MENU_MOUSE_Y_AXIS,
MENU_MOUSE_LEFT_BUTTON,
MENU_MOUSE_RIGHT_BUTTON,
MENU_MOUSE_WHEEL_UP,
MENU_MOUSE_WHEEL_DOWN,
MENU_MOUSE_HORIZ_WHEEL_UP,
MENU_MOUSE_HORIZ_WHEEL_DOWN
};
enum menu_input_ctl_state
{
MENU_INPUT_CTL_NONE = 0,
MENU_INPUT_CTL_MOUSE_PTR,
MENU_INPUT_CTL_POINTER_PTR,
MENU_INPUT_CTL_POINTER_ACCEL_READ,
MENU_INPUT_CTL_POINTER_ACCEL_WRITE,
MENU_INPUT_CTL_IS_POINTER_DRAGGED,
MENU_INPUT_CTL_SET_POINTER_DRAGGED,
MENU_INPUT_CTL_UNSET_POINTER_DRAGGED,
MENU_INPUT_CTL_DEINIT
};
typedef uintptr_t menu_texture_item;
typedef struct menu_display_ctx_clearcolor
{
float r;
float g;
float b;
float a;
} menu_display_ctx_clearcolor_t;
typedef struct menu_display_frame_info
{
bool shadows_enable;
} menu_display_frame_info_t;
typedef struct menu_display_ctx_draw menu_display_ctx_draw_t;
RETRO_END_DECLS
#endif

View File

@ -1089,6 +1089,24 @@ static int menu_displaylist_parse_system_info(menu_displaylist_info_t *info)
menu_entries_append_enum(info->list, feat_str, "",
MENU_ENUM_LABEL_SYSTEM_INFO_ENTRY, MENU_SETTINGS_CORE_INFO_NONE, 0, 0);
snprintf(feat_str, sizeof(feat_str),
"%s: %s",
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT),
_coreaudio_supp ?
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_YES) :
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO));
menu_entries_append_enum(info->list, feat_str, "",
MENU_ENUM_LABEL_SYSTEM_INFO_ENTRY, MENU_SETTINGS_CORE_INFO_NONE, 0, 0);
snprintf(feat_str, sizeof(feat_str),
"%s: %s",
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT),
_coreaudio3_supp ?
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_YES) :
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO));
menu_entries_append_enum(info->list, feat_str, "",
MENU_ENUM_LABEL_SYSTEM_INFO_ENTRY, MENU_SETTINGS_CORE_INFO_NONE, 0, 0);
snprintf(feat_str, sizeof(feat_str),
"%s: %s",
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_DSOUND_SUPPORT),
@ -4207,7 +4225,7 @@ bool menu_displaylist_process(menu_displaylist_info_t *info)
if (info->push_builtin_cores)
{
#if defined(HAVE_VIDEO_PROCESSOR)
#if defined(HAVE_VIDEOPROCESSOR)
menu_entries_append_enum(info->list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_START_VIDEO_PROCESSOR),
msg_hash_to_str(MENU_ENUM_LABEL_START_VIDEO_PROCESSOR),

View File

@ -28,12 +28,13 @@
#include <retro_common_api.h>
#include <gfx/math/matrix_4x4.h>
#include "widgets/menu_entry.h"
#include "menu_defines.h"
#include "menu_input.h"
#include "menu_entries.h"
#include "widgets/menu_entry.h"
#include "../audio/audio_driver.h"
#include "../gfx/video_driver.h"
#include "../file_path_special.h"
#include "../gfx/font_driver.h"
#include "../gfx/video_coord_array.h"
@ -59,76 +60,6 @@ RETRO_BEGIN_DECLS
extern float osk_dark[16];
enum menu_image_type
{
MENU_IMAGE_NONE = 0,
MENU_IMAGE_WALLPAPER,
MENU_IMAGE_THUMBNAIL,
MENU_IMAGE_LEFT_THUMBNAIL,
MENU_IMAGE_SAVESTATE_THUMBNAIL
};
enum menu_environ_cb
{
MENU_ENVIRON_NONE = 0,
MENU_ENVIRON_RESET_HORIZONTAL_LIST,
MENU_ENVIRON_ENABLE_MOUSE_CURSOR,
MENU_ENVIRON_DISABLE_MOUSE_CURSOR,
MENU_ENVIRON_LAST
};
enum menu_state_changes
{
MENU_STATE_RENDER_FRAMEBUFFER = 0,
MENU_STATE_RENDER_MESSAGEBOX,
MENU_STATE_BLIT,
MENU_STATE_POP_STACK,
MENU_STATE_POST_ITERATE
};
enum rarch_menu_ctl_state
{
RARCH_MENU_CTL_NONE = 0,
RARCH_MENU_CTL_SET_PENDING_QUICK_MENU,
RARCH_MENU_CTL_SET_PENDING_QUIT,
RARCH_MENU_CTL_SET_PENDING_SHUTDOWN,
RARCH_MENU_CTL_DEINIT,
RARCH_MENU_CTL_SET_PREVENT_POPULATE,
RARCH_MENU_CTL_UNSET_PREVENT_POPULATE,
RARCH_MENU_CTL_IS_PREVENT_POPULATE,
RARCH_MENU_CTL_IS_TOGGLE,
RARCH_MENU_CTL_SET_TOGGLE,
RARCH_MENU_CTL_UNSET_TOGGLE,
RARCH_MENU_CTL_SET_OWN_DRIVER,
RARCH_MENU_CTL_UNSET_OWN_DRIVER,
RARCH_MENU_CTL_OWNS_DRIVER,
RARCH_MENU_CTL_FIND_DRIVER,
RARCH_MENU_CTL_LIST_FREE,
RARCH_MENU_CTL_ENVIRONMENT,
RARCH_MENU_CTL_DRIVER_DATA_GET,
RARCH_MENU_CTL_POINTER_TAP,
RARCH_MENU_CTL_POINTER_DOWN,
RARCH_MENU_CTL_POINTER_UP,
RARCH_MENU_CTL_OSK_PTR_AT_POS,
RARCH_MENU_CTL_BIND_INIT,
RARCH_MENU_CTL_UPDATE_THUMBNAIL_PATH,
RARCH_MENU_CTL_UPDATE_THUMBNAIL_IMAGE,
RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_PATH,
RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_IMAGE,
MENU_NAVIGATION_CTL_CLEAR,
MENU_NAVIGATION_CTL_INCREMENT,
MENU_NAVIGATION_CTL_DECREMENT,
MENU_NAVIGATION_CTL_SET_LAST,
MENU_NAVIGATION_CTL_DESCEND_ALPHABET,
MENU_NAVIGATION_CTL_ASCEND_ALPHABET,
MENU_NAVIGATION_CTL_CLEAR_SCROLL_INDICES,
MENU_NAVIGATION_CTL_ADD_SCROLL_INDEX,
MENU_NAVIGATION_CTL_SET_SCROLL_ACCEL,
MENU_NAVIGATION_CTL_GET_SCROLL_ACCEL
};
#define MENU_SETTINGS_AUDIO_MIXER_MAX_STREAMS (AUDIO_MIXER_MAX_SYSTEM_STREAMS-1)
enum menu_settings_type
{
MENU_SETTINGS_NONE = FILE_TYPE_LAST + 1,
@ -253,161 +184,6 @@ enum menu_settings_type
MENU_SETTINGS_LAST
};
enum rgui_color_theme
{
RGUI_THEME_CUSTOM = 0,
RGUI_THEME_CLASSIC_RED,
RGUI_THEME_CLASSIC_ORANGE,
RGUI_THEME_CLASSIC_YELLOW,
RGUI_THEME_CLASSIC_GREEN,
RGUI_THEME_CLASSIC_BLUE,
RGUI_THEME_CLASSIC_VIOLET,
RGUI_THEME_CLASSIC_GREY,
RGUI_THEME_LEGACY_RED,
RGUI_THEME_DARK_PURPLE,
RGUI_THEME_MIDNIGHT_BLUE,
RGUI_THEME_GOLDEN,
RGUI_THEME_ELECTRIC_BLUE,
RGUI_THEME_APPLE_GREEN,
RGUI_THEME_VOLCANIC_RED,
RGUI_THEME_LAGOON,
RGUI_THEME_BROGRAMMER,
RGUI_THEME_DRACULA,
RGUI_THEME_FAIRYFLOSS,
RGUI_THEME_FLATUI,
RGUI_THEME_GRUVBOX_DARK,
RGUI_THEME_GRUVBOX_LIGHT,
RGUI_THEME_HACKING_THE_KERNEL,
RGUI_THEME_NORD,
RGUI_THEME_NOVA,
RGUI_THEME_ONE_DARK,
RGUI_THEME_PALENIGHT,
RGUI_THEME_SOLARIZED_DARK,
RGUI_THEME_SOLARIZED_LIGHT,
RGUI_THEME_TANGO_DARK,
RGUI_THEME_TANGO_LIGHT,
RGUI_THEME_ZENBURN,
RGUI_THEME_ANTI_ZENBURN,
RGUI_THEME_LAST
};
enum materialui_color_theme
{
MATERIALUI_THEME_BLUE = 0,
MATERIALUI_THEME_BLUE_GREY,
MATERIALUI_THEME_DARK_BLUE,
MATERIALUI_THEME_GREEN,
MATERIALUI_THEME_RED,
MATERIALUI_THEME_YELLOW,
MATERIALUI_THEME_NVIDIA_SHIELD,
MATERIALUI_THEME_LAST
};
enum xmb_color_theme
{
XMB_THEME_LEGACY_RED = 0,
XMB_THEME_DARK_PURPLE,
XMB_THEME_MIDNIGHT_BLUE,
XMB_THEME_GOLDEN,
XMB_THEME_ELECTRIC_BLUE,
XMB_THEME_APPLE_GREEN,
XMB_THEME_UNDERSEA,
XMB_THEME_VOLCANIC_RED,
XMB_THEME_DARK,
XMB_THEME_LIGHT,
XMB_THEME_WALLPAPER,
XMB_THEME_MORNING_BLUE,
XMB_THEME_LAST
};
enum xmb_icon_theme
{
XMB_ICON_THEME_MONOCHROME = 0,
XMB_ICON_THEME_FLATUI,
XMB_ICON_THEME_RETROACTIVE,
XMB_ICON_THEME_PIXEL,
XMB_ICON_THEME_NEOACTIVE,
XMB_ICON_THEME_SYSTEMATIC,
XMB_ICON_THEME_DOTART,
XMB_ICON_THEME_CUSTOM,
XMB_ICON_THEME_RETROSYSTEM,
XMB_ICON_THEME_MONOCHROME_INVERTED,
XMB_ICON_THEME_AUTOMATIC,
XMB_ICON_THEME_LAST
};
enum xmb_shader_pipeline
{
XMB_SHADER_PIPELINE_WALLPAPER = 0,
XMB_SHADER_PIPELINE_SIMPLE_RIBBON,
XMB_SHADER_PIPELINE_RIBBON,
XMB_SHADER_PIPELINE_SIMPLE_SNOW,
XMB_SHADER_PIPELINE_SNOW,
XMB_SHADER_PIPELINE_BOKEH,
XMB_SHADER_PIPELINE_SNOWFLAKE,
XMB_SHADER_PIPELINE_LAST
};
enum menu_display_prim_type
{
MENU_DISPLAY_PRIM_NONE = 0,
MENU_DISPLAY_PRIM_TRIANGLESTRIP,
MENU_DISPLAY_PRIM_TRIANGLES
};
enum menu_display_driver_type
{
MENU_VIDEO_DRIVER_GENERIC = 0,
MENU_VIDEO_DRIVER_OPENGL,
MENU_VIDEO_DRIVER_VULKAN,
MENU_VIDEO_DRIVER_METAL,
MENU_VIDEO_DRIVER_DIRECT3D8,
MENU_VIDEO_DRIVER_DIRECT3D9,
MENU_VIDEO_DRIVER_DIRECT3D10,
MENU_VIDEO_DRIVER_DIRECT3D11,
MENU_VIDEO_DRIVER_DIRECT3D12,
MENU_VIDEO_DRIVER_VITA2D,
MENU_VIDEO_DRIVER_CTR,
MENU_VIDEO_DRIVER_WIIU,
MENU_VIDEO_DRIVER_CACA,
MENU_VIDEO_DRIVER_SIXEL,
MENU_VIDEO_DRIVER_GDI,
MENU_VIDEO_DRIVER_SWITCH,
MENU_VIDEO_DRIVER_VGA
};
enum menu_toggle_reason
{
MENU_TOGGLE_REASON_NONE = 0,
MENU_TOGGLE_REASON_USER,
MENU_TOGGLE_REASON_MESSAGE
};
enum rgui_thumbnail_scaler
{
RGUI_THUMB_SCALE_POINT = 0,
RGUI_THUMB_SCALE_BILINEAR,
RGUI_THUMB_SCALE_SINC,
RGUI_THUMB_SCALE_LAST
};
typedef uintptr_t menu_texture_item;
typedef struct menu_display_ctx_clearcolor
{
float r;
float g;
float b;
float a;
} menu_display_ctx_clearcolor_t;
typedef struct menu_display_frame_info
{
bool shadows_enable;
} menu_display_frame_info_t;
typedef struct menu_display_ctx_draw menu_display_ctx_draw_t;
typedef struct menu_display_ctx_driver
{
/* Draw graphics to the screen. */

View File

@ -24,66 +24,11 @@
#include <retro_common_api.h>
#include <libretro.h>
#include "menu_defines.h"
#include "../input/input_types.h"
RETRO_BEGIN_DECLS
enum menu_action
{
MENU_ACTION_NOOP = 0,
MENU_ACTION_UP,
MENU_ACTION_DOWN,
MENU_ACTION_LEFT,
MENU_ACTION_RIGHT,
MENU_ACTION_OK,
MENU_ACTION_SEARCH,
MENU_ACTION_SCAN,
MENU_ACTION_CANCEL,
MENU_ACTION_INFO,
MENU_ACTION_SELECT,
MENU_ACTION_START,
MENU_ACTION_SCROLL_DOWN,
MENU_ACTION_SCROLL_UP,
MENU_ACTION_TOGGLE,
MENU_ACTION_POINTER_MOVED,
MENU_ACTION_POINTER_PRESSED,
MENU_ACTION_QUIT
};
enum menu_input_pointer_state
{
MENU_POINTER_X_AXIS = 0,
MENU_POINTER_Y_AXIS,
MENU_POINTER_DELTA_X_AXIS,
MENU_POINTER_DELTA_Y_AXIS,
MENU_POINTER_PRESSED
};
enum menu_input_mouse_state
{
MENU_MOUSE_X_AXIS = 0,
MENU_MOUSE_Y_AXIS,
MENU_MOUSE_LEFT_BUTTON,
MENU_MOUSE_RIGHT_BUTTON,
MENU_MOUSE_WHEEL_UP,
MENU_MOUSE_WHEEL_DOWN,
MENU_MOUSE_HORIZ_WHEEL_UP,
MENU_MOUSE_HORIZ_WHEEL_DOWN
};
enum menu_input_ctl_state
{
MENU_INPUT_CTL_NONE = 0,
MENU_INPUT_CTL_MOUSE_PTR,
MENU_INPUT_CTL_POINTER_PTR,
MENU_INPUT_CTL_POINTER_ACCEL_READ,
MENU_INPUT_CTL_POINTER_ACCEL_WRITE,
MENU_INPUT_CTL_IS_POINTER_DRAGGED,
MENU_INPUT_CTL_SET_POINTER_DRAGGED,
MENU_INPUT_CTL_UNSET_POINTER_DRAGGED,
MENU_INPUT_CTL_DEINIT
};
typedef struct menu_input
{
struct

View File

@ -3746,7 +3746,7 @@ static bool setting_append_list(
&subgroup_info,
parent_group);
#if defined(HAVE_VIDEO_PROCESSOR)
#if defined(HAVE_VIDEOPROCESSOR)
CONFIG_ACTION(
list, list_info,
MENU_ENUM_LABEL_START_VIDEO_PROCESSOR,

View File

@ -1835,6 +1835,8 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_ALSA_SUPPORT,
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_OSS_SUPPORT,
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_OPENAL_SUPPORT,
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO_SUPPORT,
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_COREAUDIO3_SUPPORT,
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_OPENSL_SUPPORT,
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_RSOUND_SUPPORT,
MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_ROARAUDIO_SUPPORT,
@ -2131,6 +2133,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_QT_DOWNLOAD_PLAYLIST_THUMBNAIL_PROGRESS,
MENU_ENUM_LABEL_VALUE_QT_CORE_OPTIONS,
MENU_ENUM_LABEL_VALUE_QT_ITEMS_COUNT,
MENU_ENUM_LABEL_VALUE_QT_DROP_IMAGE_HERE,
MENU_LABEL(MIDI_INPUT),
MENU_LABEL(MIDI_OUTPUT),
@ -2229,7 +2232,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_SWITCH_BACKLIGHT_CONTROL,
MENU_ENUM_SUBLABEL_SWITCH_BACKLIGHT_CONTROL,
#endif
#if defined(HAVE_LAKKA_SWITCH) || defined(HAVE_LIBNX)
#if defined(HAVE_LAKKA_SWITCH) || defined(HAVE_LIBNX)
MENU_ENUM_LABEL_SWITCH_CPU_PROFILE,
MENU_ENUM_LABEL_VALUE_SWITCH_CPU_PROFILE,
MENU_ENUM_SUBLABEL_SWITCH_CPU_PROFILE,
@ -2243,6 +2246,7 @@ enum msg_hash_enums
MENU_LABEL(MENU_SOUND_CANCEL),
MENU_LABEL(MENU_SOUND_NOTICE),
MENU_LABEL(MENU_SOUND_BGM),
MENU_ENUM_LABEL_VALUE_VIDEO_DRIVER_FALLBACK,
MSG_LAST
};

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.retroarch"
android:versionCode="74"
android:versionName="1.7.5"
android:versionCode="75"
android:versionName="1.7.6"
android:installLocation="internalOnly">
<uses-feature android:glEsVersion="0x00020000" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>

View File

@ -1,8 +1,8 @@
<!-- <!DOCTYPE manifest [ <!ENTITY % versionDTD SYSTEM "../../../version.dtd"> %versionDTD; ]> !-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.retroarch"
android:versionCode="74"
android:versionName="1.7.5"
android:versionCode="75"
android:versionName="1.7.6"
android:installLocation="internalOnly">
<uses-feature android:glEsVersion="0x00020000" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>

View File

@ -1,8 +1,8 @@
<!-- <!DOCTYPE manifest [ <!ENTITY % versionDTD SYSTEM "../../../version.dtd"> %versionDTD; ]> !-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.retroarch.aarch64"
android:versionCode="74"
android:versionName="1.7.5"
android:versionCode="75"
android:versionName="1.7.6"
android:installLocation="internalOnly">
<uses-feature android:glEsVersion="0x00020000" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>

View File

@ -30,17 +30,17 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.7.5</string>
<string>1.7.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.7.5</string>
<string>1.7.6</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 RetroArch. All rights reserved.</string>
<string>Copyright © 2019 RetroArch. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

View File

@ -30,17 +30,17 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.7.5</string>
<string>1.7.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.7.5</string>
<string>1.7.6</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 RetroArch. All rights reserved.</string>
<string>Copyright © 2019 RetroArch. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu_Metal</string>
<key>NSPrincipalClass</key>

Binary file not shown.

View File

@ -26,17 +26,55 @@
9204BE201D319EF300BD49DB /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96AFAE3116C1D4EA009DE44C /* OpenGLES.framework */; };
9204BE221D319EF300BD49DB /* iOS/Resources/ic_pause.png in Resources */ = {isa = PBXBuildFile; fileRef = 83D632DB19ECFCC4009E3161 /* iOS/Resources/ic_pause.png */; };
9204BE231D319EF300BD49DB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 967894611788EBD800D6CA69 /* InfoPlist.strings */; };
9204BE241D319EF300BD49DB /* iOS/Resources/Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69D31DE31A547EC800EF4C92 /* iOS/Resources/Media.xcassets */; };
9204BE251D319EF300BD49DB /* iOS/Resources/PauseIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83D632DE19ECFCC4009E3161 /* iOS/Resources/PauseIndicatorView.xib */; };
9204BE261D319EF300BD49DB /* iOS/modules in Resources */ = {isa = PBXBuildFile; fileRef = 83EB675F19EEAF050096F441 /* iOS/modules */; };
926C77E321FD1E6700103EDE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 926C77E221FD1E6700103EDE /* Assets.xcassets */; };
926C77EA21FD20C100103EDE /* griffin_objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 50521A431AA23BF500185CC9 /* griffin_objc.m */; };
926C77EB21FD20C400103EDE /* griffin.c in Sources */ = {isa = PBXBuildFile; fileRef = 501232C9192E5FC40063A359 /* griffin.c */; };
926C77EF21FD263800103EDE /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 926C77EE21FD263800103EDE /* AudioToolbox.framework */; };
926C77F121FD26E800103EDE /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 926C77F021FD26E800103EDE /* GameController.framework */; };
927BD1A42203DA3A00ECF6C9 /* iOS/modules in Resources */ = {isa = PBXBuildFile; fileRef = 83EB675F19EEAF050096F441 /* iOS/modules */; };
929784502200EEE400989A60 /* iOS/Resources/Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69D31DE31A547EC800EF4C92 /* iOS/Resources/Media.xcassets */; };
92CC05A221FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */; };
92CC05A321FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */; };
92CC05A421FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */; };
92CC05A521FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */; };
92CC05A621FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */; };
92CC05A721FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */; };
92CC05A821FE3C1700FF79F0 /* GCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058921FE3C1700FF79F0 /* GCDWebServer.m */; };
92CC05A921FE3C1700FF79F0 /* GCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058921FE3C1700FF79F0 /* GCDWebServer.m */; };
92CC05AA21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */; };
92CC05AB21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */; };
92CC05AC21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */; };
92CC05AD21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */; };
92CC05AE21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */; };
92CC05AF21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */; };
92CC05B021FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */; };
92CC05B121FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */; };
92CC05B221FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */; };
92CC05B321FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */; };
92CC05B421FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */; };
92CC05B521FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */; };
92CC05B621FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */; };
92CC05B721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */; };
92CC05B821FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */; };
92CC05B921FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */; };
92CC05BA21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */; };
92CC05BB21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */; };
92CC05BC21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */; };
92CC05BD21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */; };
92CC05BE21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */; };
92CC05BF21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */; };
92CC05C221FE3C6D00FF79F0 /* WebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05C121FE3C6D00FF79F0 /* WebServer.m */; };
92CC05C321FE3C6D00FF79F0 /* WebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05C121FE3C6D00FF79F0 /* WebServer.m */; };
92CC05C521FEDC9F00FF79F0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */; };
92CC05C721FEDD0B00FF79F0 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0FDA2A921BE1AFA800F2B5DA /* RetroArch_iOS9-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "RetroArch_iOS9-Info.plist"; path = "/Users/buildbot/buildbot/ios/retroarch/pkg/apple/RetroArch_iOS9-Info.plist"; sourceTree = "<absolute>"; };
501232C9192E5FC40063A359 /* griffin.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = griffin.c; path = ../../griffin/griffin.c; sourceTree = SOURCE_ROOT; };
501881EB184BAD6D006F665D /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
501881ED184BB54C006F665D /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
503700AE1ACA18E400A51A37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = iOS/Info.plist; sourceTree = SOURCE_ROOT; };
5040F04F1AE47ED4006F6972 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
50521A431AA23BF500185CC9 /* griffin_objc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = griffin_objc.m; path = ../../griffin/griffin_objc.m; sourceTree = SOURCE_ROOT; };
50C3B1AD1AB1107100F478D3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
@ -48,6 +86,47 @@
83D632DE19ECFCC4009E3161 /* iOS/Resources/PauseIndicatorView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = iOS/Resources/PauseIndicatorView.xib; sourceTree = SOURCE_ROOT; };
83EB675F19EEAF050096F441 /* iOS/modules */ = {isa = PBXFileReference; lastKnownFileType = folder; path = iOS/modules; sourceTree = SOURCE_ROOT; };
9204BE2B1D319EF300BD49DB /* RetroArchiOS11.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArchiOS11.app; sourceTree = BUILT_PRODUCTS_DIR; };
926C77D721FD1E6500103EDE /* RetroArchTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArchTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
926C77E221FD1E6700103EDE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
926C77E421FD1E6700103EDE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
926C77EC21FD261600103EDE /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.1.sdk/System/Library/Frameworks/CoreAudio.framework; sourceTree = DEVELOPER_DIR; };
926C77EE21FD263800103EDE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.1.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; };
926C77F021FD26E800103EDE /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.1.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
92CC058021FE3C1700FF79F0 /* GCDWebServerFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFunctions.h; sourceTree = "<group>"; };
92CC058121FE3C1700FF79F0 /* GCDWebServerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerPrivate.h; sourceTree = "<group>"; };
92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerResponse.m; sourceTree = "<group>"; };
92CC058321FE3C1700FF79F0 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerConnection.h; sourceTree = "<group>"; };
92CC058421FE3C1700FF79F0 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = "<group>"; };
92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerRequest.m; sourceTree = "<group>"; };
92CC058621FE3C1700FF79F0 /* GCDWebServerHTTPStatusCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerHTTPStatusCodes.h; sourceTree = "<group>"; };
92CC058721FE3C1700FF79F0 /* GCDWebServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerResponse.h; sourceTree = "<group>"; };
92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFunctions.m; sourceTree = "<group>"; };
92CC058921FE3C1700FF79F0 /* GCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServer.m; sourceTree = "<group>"; };
92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerConnection.m; sourceTree = "<group>"; };
92CC058B21FE3C1700FF79F0 /* GCDWebServerRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerRequest.h; sourceTree = "<group>"; };
92CC058D21FE3C1700FF79F0 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = "<group>"; };
92CC058E21FE3C1700FF79F0 /* GCDWebServerStreamedResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamedResponse.h; sourceTree = "<group>"; };
92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerErrorResponse.m; sourceTree = "<group>"; };
92CC059021FE3C1700FF79F0 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataResponse.h; sourceTree = "<group>"; };
92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileResponse.m; sourceTree = "<group>"; };
92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = "<group>"; };
92CC059321FE3C1700FF79F0 /* GCDWebServerErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerErrorResponse.h; sourceTree = "<group>"; };
92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamedResponse.m; sourceTree = "<group>"; };
92CC059621FE3C1700FF79F0 /* GCDWebServerDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataRequest.h; sourceTree = "<group>"; };
92CC059721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerMultiPartFormRequest.h; sourceTree = "<group>"; };
92CC059821FE3C1700FF79F0 /* GCDWebServerFileRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileRequest.h; sourceTree = "<group>"; };
92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerURLEncodedFormRequest.m; sourceTree = "<group>"; };
92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerMultiPartFormRequest.m; sourceTree = "<group>"; };
92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataRequest.m; sourceTree = "<group>"; };
92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileRequest.m; sourceTree = "<group>"; };
92CC059D21FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerURLEncodedFormRequest.h; sourceTree = "<group>"; };
92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = GCDWebUploader.bundle; sourceTree = "<group>"; };
92CC05A021FE3C1700FF79F0 /* GCDWebUploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebUploader.h; sourceTree = "<group>"; };
92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebUploader.m; sourceTree = "<group>"; };
92CC05C021FE3C6D00FF79F0 /* WebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebServer.h; sourceTree = "<group>"; };
92CC05C121FE3C6D00FF79F0 /* WebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebServer.m; sourceTree = "<group>"; };
92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
96366C5416C9AC3300D64A22 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
96366C5816C9ACF500D64A22 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
963C3C33186E3DED00A6EB1E /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
@ -58,8 +137,6 @@
96AFAE2D16C1D4EA009DE44C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
96AFAE2F16C1D4EA009DE44C /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; };
96AFAE3116C1D4EA009DE44C /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; };
96AFAF4516C1E00A009DE44C /* bitmap.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = bitmap.bin; sourceTree = "<group>"; };
96AFAF4616C1E00A009DE44C /* bitmap.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = bitmap.bmp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -67,6 +144,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
92CC05C721FEDD0B00FF79F0 /* MobileCoreServices.framework in Frameworks */,
92CC05C521FEDC9F00FF79F0 /* CFNetwork.framework in Frameworks */,
9204BE121D319EF300BD49DB /* libz.dylib in Frameworks */,
9204BE131D319EF300BD49DB /* QuartzCore.framework in Frameworks */,
9204BE141D319EF300BD49DB /* GameController.framework in Frameworks */,
@ -85,10 +164,19 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
926C77D421FD1E6500103EDE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
926C77F121FD26E800103EDE /* GameController.framework in Frameworks */,
926C77EF21FD263800103EDE /* AudioToolbox.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
83D632D719ECFCC4009E3161 /* Assets */ = {
83D632D719ECFCC4009E3161 /* iOS */ = {
isa = PBXGroup;
children = (
83EB675F19EEAF050096F441 /* iOS/modules */,
@ -96,20 +184,109 @@
83D632DE19ECFCC4009E3161 /* iOS/Resources/PauseIndicatorView.xib */,
69D31DE31A547EC800EF4C92 /* iOS/Resources/Media.xcassets */,
);
name = Assets;
name = iOS;
path = Resources;
sourceTree = "<group>";
};
926C77D821FD1E6500103EDE /* tvOS */ = {
isa = PBXGroup;
children = (
926C77E221FD1E6700103EDE /* Assets.xcassets */,
926C77E421FD1E6700103EDE /* Info.plist */,
);
path = tvOS;
sourceTree = "<group>";
};
92CC057E21FE3C1700FF79F0 /* GCDWebServer */ = {
isa = PBXGroup;
children = (
92CC057F21FE3C1700FF79F0 /* Core */,
92CC058C21FE3C1700FF79F0 /* Responses */,
92CC059521FE3C1700FF79F0 /* Requests */,
);
path = GCDWebServer;
sourceTree = "<group>";
};
92CC057F21FE3C1700FF79F0 /* Core */ = {
isa = PBXGroup;
children = (
92CC058021FE3C1700FF79F0 /* GCDWebServerFunctions.h */,
92CC058121FE3C1700FF79F0 /* GCDWebServerPrivate.h */,
92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */,
92CC058321FE3C1700FF79F0 /* GCDWebServerConnection.h */,
92CC058421FE3C1700FF79F0 /* GCDWebServer.h */,
92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */,
92CC058621FE3C1700FF79F0 /* GCDWebServerHTTPStatusCodes.h */,
92CC058721FE3C1700FF79F0 /* GCDWebServerResponse.h */,
92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */,
92CC058921FE3C1700FF79F0 /* GCDWebServer.m */,
92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */,
92CC058B21FE3C1700FF79F0 /* GCDWebServerRequest.h */,
);
path = Core;
sourceTree = "<group>";
};
92CC058C21FE3C1700FF79F0 /* Responses */ = {
isa = PBXGroup;
children = (
92CC058D21FE3C1700FF79F0 /* GCDWebServerFileResponse.h */,
92CC058E21FE3C1700FF79F0 /* GCDWebServerStreamedResponse.h */,
92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */,
92CC059021FE3C1700FF79F0 /* GCDWebServerDataResponse.h */,
92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */,
92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */,
92CC059321FE3C1700FF79F0 /* GCDWebServerErrorResponse.h */,
92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */,
);
path = Responses;
sourceTree = "<group>";
};
92CC059521FE3C1700FF79F0 /* Requests */ = {
isa = PBXGroup;
children = (
92CC059621FE3C1700FF79F0 /* GCDWebServerDataRequest.h */,
92CC059721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.h */,
92CC059821FE3C1700FF79F0 /* GCDWebServerFileRequest.h */,
92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */,
92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */,
92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */,
92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */,
92CC059D21FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.h */,
);
path = Requests;
sourceTree = "<group>";
};
92CC059E21FE3C1700FF79F0 /* GCDWebUploader */ = {
isa = PBXGroup;
children = (
92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */,
92CC05A021FE3C1700FF79F0 /* GCDWebUploader.h */,
92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */,
);
path = GCDWebUploader;
sourceTree = "<group>";
};
92CC05CC21FF782C00FF79F0 /* WebServer */ = {
isa = PBXGroup;
children = (
92CC05C021FE3C6D00FF79F0 /* WebServer.h */,
92CC05C121FE3C6D00FF79F0 /* WebServer.m */,
92CC057E21FE3C1700FF79F0 /* GCDWebServer */,
92CC059E21FE3C1700FF79F0 /* GCDWebUploader */,
);
path = WebServer;
sourceTree = "<group>";
};
96AFAE1A16C1D4EA009DE44C = {
isa = PBXGroup;
children = (
83D632D719ECFCC4009E3161 /* Assets */,
96AFAE9C16C1D976009DE44C /* core */,
83D632D719ECFCC4009E3161 /* iOS */,
926C77D821FD1E6500103EDE /* tvOS */,
92CC05CC21FF782C00FF79F0 /* WebServer */,
96AFAE2816C1D4EA009DE44C /* Frameworks */,
96AFAE2616C1D4EA009DE44C /* Products */,
96AFAE3416C1D4EA009DE44C /* Supporting Files */,
503700AE1ACA18E400A51A37 /* Info.plist */,
0FDA2A921BE1AFA800F2B5DA /* RetroArch_iOS9-Info.plist */,
);
indentWidth = 3;
sourceTree = "<group>";
@ -119,6 +296,7 @@
isa = PBXGroup;
children = (
9204BE2B1D319EF300BD49DB /* RetroArchiOS11.app */,
926C77D721FD1E6500103EDE /* RetroArchTV.app */,
);
name = Products;
sourceTree = "<group>";
@ -126,6 +304,11 @@
96AFAE2816C1D4EA009DE44C /* Frameworks */ = {
isa = PBXGroup;
children = (
92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */,
92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */,
926C77F021FD26E800103EDE /* GameController.framework */,
926C77EE21FD263800103EDE /* AudioToolbox.framework */,
926C77EC21FD261600103EDE /* CoreAudio.framework */,
5040F04F1AE47ED4006F6972 /* libz.dylib */,
50C3B1AD1AB1107100F478D3 /* QuartzCore.framework */,
696012F119F3389A006A1088 /* CoreText.framework */,
@ -152,36 +335,16 @@
9678945F1788EBD000D6CA69 /* Info.plist */,
);
name = "Supporting Files";
path = RetroArch;
sourceTree = "<group>";
};
96AFAE9C16C1D976009DE44C /* core */ = {
isa = PBXGroup;
children = (
D48581DC16F823E2004BEB17 /* griffin */,
96AFAF3116C1E00A009DE44C /* gfx */,
);
name = core;
sourceTree = "<group>";
};
96AFAF3116C1E00A009DE44C /* gfx */ = {
isa = PBXGroup;
children = (
96AFAF4416C1E00A009DE44C /* fonts */,
);
name = gfx;
path = ../gfx;
sourceTree = "<group>";
};
96AFAF4416C1E00A009DE44C /* fonts */ = {
isa = PBXGroup;
children = (
96AFAF4516C1E00A009DE44C /* bitmap.bin */,
96AFAF4616C1E00A009DE44C /* bitmap.bmp */,
);
path = fonts;
sourceTree = "<group>";
};
D48581DC16F823E2004BEB17 /* griffin */ = {
isa = PBXGroup;
children = (
@ -212,6 +375,24 @@
productReference = 9204BE2B1D319EF300BD49DB /* RetroArchiOS11.app */;
productType = "com.apple.product-type.application";
};
926C77D621FD1E6500103EDE /* RetroArchTV */ = {
isa = PBXNativeTarget;
buildConfigurationList = 926C77E921FD1E6700103EDE /* Build configuration list for PBXNativeTarget "RetroArchTV" */;
buildPhases = (
926C77D321FD1E6500103EDE /* Sources */,
926C77D421FD1E6500103EDE /* Frameworks */,
92CC057521FE2D4900FF79F0 /* ShellScript */,
926C77D521FD1E6500103EDE /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = RetroArchTV;
productName = RetroArchTV;
productReference = 926C77D721FD1E6500103EDE /* RetroArchTV.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -222,8 +403,13 @@
ORGANIZATIONNAME = RetroArch;
TargetAttributes = {
9204BE091D319EF300BD49DB = {
DevelopmentTeam = UK699V5ZS8;
DevelopmentTeamName = "RetroArch";
DevelopmentTeam = R72X3BF4KE;
DevelopmentTeamName = RetroArch;
};
926C77D621FD1E6500103EDE = {
CreatedOnToolsVersion = 10.1;
DevelopmentTeam = R72X3BF4KE;
ProvisioningStyle = Automatic;
};
};
};
@ -233,6 +419,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 96AFAE1A16C1D4EA009DE44C;
productRefGroup = 96AFAE2616C1D4EA009DE44C /* Products */;
@ -240,6 +427,7 @@
projectRoot = "";
targets = (
9204BE091D319EF300BD49DB /* RetroArchiOS11 */,
926C77D621FD1E6500103EDE /* RetroArchTV */,
);
};
/* End PBXProject section */
@ -251,12 +439,23 @@
files = (
9204BE221D319EF300BD49DB /* iOS/Resources/ic_pause.png in Resources */,
9204BE231D319EF300BD49DB /* InfoPlist.strings in Resources */,
9204BE241D319EF300BD49DB /* iOS/Resources/Media.xcassets in Resources */,
92CC05BC21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */,
9204BE251D319EF300BD49DB /* iOS/Resources/PauseIndicatorView.xib in Resources */,
929784502200EEE400989A60 /* iOS/Resources/Media.xcassets in Resources */,
9204BE261D319EF300BD49DB /* iOS/modules in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
926C77D521FD1E6500103EDE /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
927BD1A42203DA3A00ECF6C9 /* iOS/modules in Resources */,
92CC05BD21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */,
926C77E321FD1E6700103EDE /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@ -271,7 +470,24 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "./code-sign-cores.sh";
shellScript = "./code-sign-cores.sh iOS\n";
};
92CC057521FE2D4900FF79F0 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "./code-sign-cores.sh tvOS\n";
};
/* End PBXShellScriptBuildPhase section */
@ -280,8 +496,47 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
92CC05B821FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */,
92CC05A421FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */,
92CC05AA21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */,
92CC05BE21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */,
92CC05B621FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */,
92CC05AC21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */,
92CC05A621FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */,
92CC05A821FE3C1700FF79F0 /* GCDWebServer.m in Sources */,
9204BE0D1D319EF300BD49DB /* griffin_objc.m in Sources */,
9204BE101D319EF300BD49DB /* griffin.c in Sources */,
92CC05B421FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */,
92CC05B021FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */,
92CC05C221FE3C6D00FF79F0 /* WebServer.m in Sources */,
92CC05BA21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */,
92CC05AE21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */,
92CC05B221FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */,
92CC05A221FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
926C77D321FD1E6500103EDE /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
92CC05B921FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */,
92CC05A521FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */,
92CC05AB21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */,
92CC05BF21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */,
92CC05B721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */,
92CC05AD21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */,
92CC05A721FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */,
92CC05A921FE3C1700FF79F0 /* GCDWebServer.m in Sources */,
926C77EA21FD20C100103EDE /* griffin_objc.m in Sources */,
926C77EB21FD20C400103EDE /* griffin.c in Sources */,
92CC05B521FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */,
92CC05B121FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */,
92CC05C321FE3C6D00FF79F0 /* WebServer.m in Sources */,
92CC05BB21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */,
92CC05AF21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */,
92CC05B321FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */,
92CC05A321FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -309,7 +564,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist";
DEVELOPMENT_TEAM = UK699V5ZS8;
DEVELOPMENT_TEAM = R72X3BF4KE;
ENABLE_BITCODE = NO;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
@ -334,7 +589,7 @@
"-DHAVE_NETWORKING",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_AVFOUNDATION",
"-DHAVE_RUNAHEAD",
"-DHAVE_RUNAHEAD",
"-DHAVE_GRIFFIN",
"-DHAVE_STB_VORBIS",
"-DHAVE_MINIUPNPC",
@ -399,7 +654,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist";
DEVELOPMENT_TEAM = UK699V5ZS8;
DEVELOPMENT_TEAM = R72X3BF4KE;
ENABLE_BITCODE = NO;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
@ -414,63 +669,6 @@
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_NO_PIE = YES;
LIBRARY_SEARCH_PATHS = "";
OTHER_CFLAGS = (
"-DNS_BLOCK_ASSERTIONS=1",
"-DNDEBUG",
"-DDONT_WANT_ARM_OPTIMIZATIONS",
"-DHAVE_NETWORKGAMEPAD",
"-DHAVE_CORETEXT",
"-DHAVE_HID",
"-DHAVE_NETWORKING",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_AVFOUNDATION",
"-DHAVE_RUNAHEAD",
"-DHAVE_GRIFFIN",
"-DHAVE_STB_VORBIS",
"-DHAVE_MINIUPNPC",
"-DHAVE_BUILTINMINIUPNPC",
"-DHAVE_UPDATE_ASSETS",
"-DHAVE_LANGEXTRA",
"-DHAVE_CHEEVOS",
"-DRC_DISABLE_LUA",
"-DHAVE_IMAGEVIEWER",
"-DHAVE_CORELOCATION",
"-DHAVE_RGUI",
"-DHAVE_MENU",
"-DHAVE_LIBRETRODB",
"-DIOS",
"-DHAVE_OPENGL",
"-DHAVE_OPENGLES",
"-DHAVE_OPENGLES2",
"-DHAVE_CC_RESAMPLER",
"-DHAVE_GLSL",
"-DINLINE=inline",
"-D__LIBRETRO__",
"-DRARCH_MOBILE",
"-DHAVE_COREAUDIO",
"-DHAVE_DYNAMIC",
"-DHAVE_OVERLAY",
"-DHAVE_ZLIB",
"-DHAVE_RPNG",
"-DHAVE_RJPEG",
"-DHAVE_RBMP",
"-DHAVE_RTGA",
"-DHAVE_COCOATOUCH",
"-DHAVE_MAIN",
"-DRARCH_INTERNAL",
"-DHAVE_THREADS",
"-DHAVE_FILTERS_BUILTIN",
"-DHAVE_7ZIP",
"-DHAVE_MATERIALUI",
"-DHAVE_XMB",
"-DHAVE_OZONE",
"-DHAVE_SHADERPIPELINE",
"-D_LZMA_UINT32_IS_ULONG",
"-DHAVE_MFI",
"-DHAVE_BTSTACK",
"-DHAVE_AVFOUNDATION",
"-DHAVE_KEYMAPPER",
);
"OTHER_CFLAGS[arch=*]" = (
"-DNS_BLOCK_ASSERTIONS=1",
"-DNDEBUG",
@ -481,7 +679,7 @@
"-DHAVE_NETWORKING",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_AVFOUNDATION",
"-DHAVE_RUNAHEAD",
"-DHAVE_RUNAHEAD",
"-DHAVE_GRIFFIN",
"-DHAVE_STB_VORBIS",
"-DHAVE_MINIUPNPC",
@ -537,6 +735,235 @@
};
name = Release;
};
926C77E721FD1E6700103EDE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
HEADER_SEARCH_PATHS = (
../../,
"../../libretro-common/include",
../../deps/stb,
../../deps/rcheevos/include,
../../deps/libz,
../../deps,
);
INFOPLIST_FILE = "$(SRCROOT)/tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"-DDONT_WANT_ARM_OPTIMIZATIONS",
"-DHAVE_NETWORKGAMEPAD",
"-DHAVE_CORETEXT",
"-DHAVE_HID",
"-DHAVE_NETWORKING",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_RUNAHEAD",
"-DHAVE_GRIFFIN",
"-DHAVE_STB_VORBIS",
"-DHAVE_MINIUPNPC",
"-DHAVE_BUILTINMINIUPNPC",
"-DHAVE_UPDATE_ASSETS",
"-DHAVE_LANGEXTRA",
"-DHAVE_CHEEVOS",
"-DRC_DISABLE_LUA",
"-DHAVE_IMAGEVIEWER",
"-DHAVE_RGUI",
"-DHAVE_MENU",
"-DHAVE_LIBRETRODB",
"-DIOS",
"-DHAVE_OPENGL",
"-DHAVE_OPENGLES",
"-DHAVE_OPENGLES2",
"-DHAVE_CC_RESAMPLER",
"-DHAVE_GLSL",
"-DINLINE=inline",
"-D__LIBRETRO__",
"-DRARCH_MOBILE",
"-DHAVE_COREAUDIO",
"-DHAVE_DYNAMIC",
"-DHAVE_OVERLAY",
"-DHAVE_ZLIB",
"-DHAVE_RPNG",
"-DHAVE_RJPEG",
"-DHAVE_RBMP",
"-DHAVE_RTGA",
"-DHAVE_COCOATOUCH",
"-DHAVE_MAIN",
"-DRARCH_INTERNAL",
"-DHAVE_THREADS",
"-DHAVE_FILTERS_BUILTIN",
"-DHAVE_XMB",
"-DHAVE_OZONE",
"-DHAVE_SHADERPIPELINE",
"-D_LZMA_UINT32_IS_ULONG",
"-DHAVE_MFI",
"-DHAVE_BTSTACK",
"-DHAVE_KEYMAPPER",
);
PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchTV;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = appletvos;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.1;
};
name = Debug;
};
926C77E821FD1E6700103EDE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
HEADER_SEARCH_PATHS = (
../../,
"../../libretro-common/include",
../../deps/stb,
../../deps/libz,
../../deps,
);
INFOPLIST_FILE = "$(SRCROOT)/tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CFLAGS = (
"-DNS_BLOCK_ASSERTIONS=1",
"-DNDEBUG",
"-DDONT_WANT_ARM_OPTIMIZATIONS",
"-DHAVE_NETWORKGAMEPAD",
"-DHAVE_CORETEXT",
"-DHAVE_HID",
"-DHAVE_NETWORKING",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_RUNAHEAD",
"-DHAVE_GRIFFIN",
"-DHAVE_STB_VORBIS",
"-DHAVE_MINIUPNPC",
"-DHAVE_BUILTINMINIUPNPC",
"-DHAVE_UPDATE_ASSETS",
"-DHAVE_LANGEXTRA",
"-DHAVE_CHEEVOS",
"-DRC_DISABLE_LUA",
"-DHAVE_IMAGEVIEWER",
"-DHAVE_RGUI",
"-DHAVE_MENU",
"-DHAVE_LIBRETRODB",
"-DIOS",
"-DHAVE_OPENGL",
"-DHAVE_OPENGLES",
"-DHAVE_OPENGLES2",
"-DHAVE_CC_RESAMPLER",
"-DHAVE_GLSL",
"-DINLINE=inline",
"-D__LIBRETRO__",
"-DRARCH_MOBILE",
"-DHAVE_COREAUDIO",
"-DHAVE_DYNAMIC",
"-DHAVE_OVERLAY",
"-DHAVE_ZLIB",
"-DHAVE_RPNG",
"-DHAVE_RJPEG",
"-DHAVE_RBMP",
"-DHAVE_RTGA",
"-DHAVE_COCOATOUCH",
"-DHAVE_MAIN",
"-DRARCH_INTERNAL",
"-DHAVE_THREADS",
"-DHAVE_FILTERS_BUILTIN",
"-DHAVE_7ZIP",
"-DHAVE_XMB",
"-DHAVE_OZONE",
"-DHAVE_SHADERPIPELINE",
"-D_LZMA_UINT32_IS_ULONG",
"-DHAVE_MFI",
"-DHAVE_BTSTACK",
"-DHAVE_KEYMAPPER",
);
PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchTV;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = appletvos;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.1;
};
name = Release;
};
96AFAE5216C1D4EA009DE44C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -572,7 +999,7 @@
"-DHAVE_NETWORKING",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_AVFOUNDATION",
"-DHAVE_RUNAHEAD",
"-DHAVE_RUNAHEAD",
"-DHAVE_GRIFFIN",
"-DHAVE_STB_VORBIS",
"-DHAVE_MINIUPNPC",
@ -645,7 +1072,7 @@
"-DHAVE_NETWORKING",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_AVFOUNDATION",
"-DHAVE_RUNAHEAD",
"-DHAVE_RUNAHEAD",
"-DHAVE_GRIFFIN",
"-DHAVE_STB_VORBIS",
"-DHAVE_MINIUPNPC",
@ -701,6 +1128,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
926C77E921FD1E6700103EDE /* Build configuration list for PBXNativeTarget "RetroArchTV" */ = {
isa = XCConfigurationList;
buildConfigurations = (
926C77E721FD1E6700103EDE /* Debug */,
926C77E821FD1E6700103EDE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
96AFAE1F16C1D4EA009DE44C /* Build configuration list for PBXProject "RetroArch_iOS11" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9204BE091D319EF300BD49DB"
BuildableName = "RetroArchiOS11.app"
BlueprintName = "RetroArchiOS11"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "926C77D621FD1E6500103EDE"
BuildableName = "RetroArchTV.app"
BlueprintName = "RetroArchTV"
ReferencedContainer = "container:RetroArch_iOS11.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,622 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <TargetConditionals.h>
#import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerMatchBlock is called for every handler added to the
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
* been received). The block is passed the basic info for the request (HTTP method,
* URL, headers...) and must decide if it wants to handle it or not.
*
* If the handler can handle the request, the block must return a new
* GCDWebServerRequest instance created with the same basic info.
* Otherwise, it simply returns nil.
*/
typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery);
/**
* The GCDWebServerProcessBlock is called after the HTTP request has been fully
* received (i.e. the entire HTTP body has been read). The block is passed the
* GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock.
*
* The block must return a GCDWebServerResponse or nil on error, which will
* result in a 500 HTTP status code returned to the client. It's however
* recommended to return a GCDWebServerErrorResponse on error so more useful
* information can be returned to the client.
*/
typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);
/**
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
* except the GCDWebServerResponse can be returned to the server at a later time
* allowing for asynchronous generation of the response.
*
* The block must eventually call "completionBlock" passing a GCDWebServerResponse
* or nil on error, which will result in a 500 HTTP status code returned to the client.
* It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client.
*/
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response);
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
/**
* The port used by the GCDWebServer (NSNumber / NSUInteger).
*
* The default value is 0 i.e. let the OS pick a random port.
*/
extern NSString* const GCDWebServerOption_Port;
/**
* The Bonjour name used by the GCDWebServer (NSString). If set to an empty string,
* the name will automatically take the value of the GCDWebServerOption_ServerName
* option. If this option is set to nil, Bonjour will be disabled.
*
* The default value is nil.
*/
extern NSString* const GCDWebServerOption_BonjourName;
/**
* The Bonjour service type used by the GCDWebServer (NSString).
*
* The default value is "_http._tcp", the service type for HTTP web servers.
*/
extern NSString* const GCDWebServerOption_BonjourType;
/**
* Request a port mapping in the NAT gateway (NSNumber / BOOL).
*
* This uses the DNSService API under the hood which supports IPv4 mappings only.
*
* The default value is NO.
*
* @warning The external port set up by the NAT gateway may be different than
* the one used by the GCDWebServer.
*/
extern NSString* const GCDWebServerOption_RequestNATPortMapping;
/**
* Only accept HTTP requests coming from localhost i.e. not from the outside
* network (NSNumber / BOOL).
*
* The default value is NO.
*
* @warning Bonjour and NAT port mapping should be disabled if using this option
* since the server will not be reachable from the outside network anyway.
*/
extern NSString* const GCDWebServerOption_BindToLocalhost;
/**
* The maximum number of incoming HTTP requests that can be queued waiting to
* be handled before new ones are dropped (NSNumber / NSUInteger).
*
* The default value is 16.
*/
extern NSString* const GCDWebServerOption_MaxPendingConnections;
/**
* The value for "Server" HTTP header used by the GCDWebServer (NSString).
*
* The default value is the GCDWebServer class name.
*/
extern NSString* const GCDWebServerOption_ServerName;
/**
* The authentication method used by the GCDWebServer
* (one of "GCDWebServerAuthenticationMethod_...").
*
* The default value is nil i.e. authentication is disabled.
*/
extern NSString* const GCDWebServerOption_AuthenticationMethod;
/**
* The authentication realm used by the GCDWebServer (NSString).
*
* The default value is the same as the GCDWebServerOption_ServerName option.
*/
extern NSString* const GCDWebServerOption_AuthenticationRealm;
/**
* The authentication accounts used by the GCDWebServer
* (NSDictionary of username / password pairs).
*
* The default value is nil i.e. no accounts.
*/
extern NSString* const GCDWebServerOption_AuthenticationAccounts;
/**
* The class used by the GCDWebServer when instantiating GCDWebServerConnection
* (subclass of GCDWebServerConnection).
*
* The default value is the GCDWebServerConnection class.
*/
extern NSString* const GCDWebServerOption_ConnectionClass;
/**
* Allow the GCDWebServer to pretend "HEAD" requests are actually "GET" ones
* and automatically discard the HTTP body of the response (NSNumber / BOOL).
*
* The default value is YES.
*/
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
/**
* The interval expressed in seconds used by the GCDWebServer to decide how to
* coalesce calls to -webServerDidConnect: and -webServerDidDisconnect:
* (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0.
*
* The default value is 1.0 second.
*/
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;
/**
* Set the dispatch queue priority on which server connection will be
* run (NSNumber / long).
*
*
* The default value is DISPATCH_QUEUE_PRIORITY_DEFAULT.
*/
extern NSString* const GCDWebServerOption_DispatchQueuePriority;
#if TARGET_OS_IPHONE
/**
* Enables the GCDWebServer to automatically suspend itself (as if -stop was
* called) when the iOS app goes into the background and the last
* GCDWebServerConnection is closed, then resume itself (as if -start was called)
* when the iOS app comes back to the foreground (NSNumber / BOOL).
*
* See the README.md file for more information about this option.
*
* The default value is YES.
*
* @warning The running property will be NO while the GCDWebServer is suspended.
*/
extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground;
#endif
/**
* HTTP Basic Authentication scheme (see https://tools.ietf.org/html/rfc2617).
*
* @warning Use of this authentication scheme is not recommended as the
* passwords are sent in clear.
*/
extern NSString* const GCDWebServerAuthenticationMethod_Basic;
/**
* HTTP Digest Access Authentication scheme (see https://tools.ietf.org/html/rfc2617).
*/
extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
@class GCDWebServer;
/**
* Delegate methods for GCDWebServer.
*
* @warning These methods are always called on the main thread in a serialized way.
*/
@protocol GCDWebServerDelegate <NSObject>
@optional
/**
* This method is called after the server has successfully started.
*/
- (void)webServerDidStart:(GCDWebServer*)server;
/**
* This method is called after the Bonjour registration for the server has
* successfully completed.
*
* Use the "bonjourServerURL" property to retrieve the Bonjour address of the
* server.
*/
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;
/**
* This method is called after the NAT port mapping for the server has been
* updated.
*
* Use the "publicServerURL" property to retrieve the public address of the
* server.
*/
- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server;
/**
* This method is called when the first GCDWebServerConnection is opened by the
* server to serve a series of HTTP requests.
*
* A series of HTTP requests is considered ongoing as long as new HTTP requests
* keep coming (and new GCDWebServerConnection instances keep being opened),
* until before the last HTTP request has been responded to (and the
* corresponding last GCDWebServerConnection closed).
*/
- (void)webServerDidConnect:(GCDWebServer*)server;
/**
* This method is called when the last GCDWebServerConnection is closed after
* the server has served a series of HTTP requests.
*
* The GCDWebServerOption_ConnectedStateCoalescingInterval option can be used
* to have the server wait some extra delay before considering that the series
* of HTTP requests has ended (in case there some latency between consecutive
* requests). This effectively coalesces the calls to -webServerDidConnect:
* and -webServerDidDisconnect:.
*/
- (void)webServerDidDisconnect:(GCDWebServer*)server;
/**
* This method is called after the server has stopped.
*/
- (void)webServerDidStop:(GCDWebServer*)server;
@end
/**
* The GCDWebServer class listens for incoming HTTP requests on a given port,
* then passes each one to a "handler" capable of generating an HTTP response
* for it, which is then sent back to the client.
*
* GCDWebServer instances can be created and used from any thread but it's
* recommended to have the main thread's runloop be running so internal callbacks
* can be handled e.g. for Bonjour registration.
*
* See the README.md file for more information about the architecture of GCDWebServer.
*/
@interface GCDWebServer : NSObject
/**
* Sets the delegate for the server.
*/
@property(nonatomic, weak, nullable) id<GCDWebServerDelegate> delegate;
/**
* Returns YES if the server is currently running.
*/
@property(nonatomic, readonly, getter=isRunning) BOOL running;
/**
* Returns the port used by the server.
*
* @warning This property is only valid if the server is running.
*/
@property(nonatomic, readonly) NSUInteger port;
/**
* Returns the Bonjour name used by the server.
*
* @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds.
*/
@property(nonatomic, readonly, nullable) NSString* bonjourName;
/**
* Returns the Bonjour service type used by the server.
*
* @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds.
*/
@property(nonatomic, readonly, nullable) NSString* bonjourType;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)init;
/**
* Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
*
* @warning Addling handlers while the server is running is not allowed.
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
/**
* Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
*
* @warning Addling handlers while the server is running is not allowed.
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock;
/**
* Removes all handlers previously added to the server.
*
* @warning Removing handlers while the server is running is not allowed.
*/
- (void)removeAllHandlers;
/**
* Starts the server with explicit options. This method is the designated way
* to start the server.
*
* Returns NO if the server failed to start and sets "error" argument if not NULL.
*/
- (BOOL)startWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
/**
* Stops the server and prevents it to accepts new HTTP requests.
*
* @warning Stopping the server does not abort GCDWebServerConnection instances
* currently handling already received HTTP requests. These connections will
* continue to execute normally until completion.
*/
- (void)stop;
@end
@interface GCDWebServer (Extensions)
/**
* Returns the server's URL.
*
* @warning This property is only valid if the server is running.
*/
@property(nonatomic, readonly, nullable) NSURL* serverURL;
/**
* Returns the server's Bonjour URL.
*
* @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds.
* Also be aware this property will not automatically update if the Bonjour hostname
* has been dynamically changed after the server started running (this should be rare).
*/
@property(nonatomic, readonly, nullable) NSURL* bonjourServerURL;
/**
* Returns the server's public URL.
*
* @warning This property is only valid if the server is running and NAT port
* mapping is active.
*/
@property(nonatomic, readonly, nullable) NSURL* publicServerURL;
/**
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
* using the default Bonjour name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)start;
/**
* Starts the server on a given port and with a specific Bonjour name.
* Pass a nil Bonjour name to disable Bonjour entirely or an empty string to
* use the default name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
#if !TARGET_OS_IPHONE
/**
* Runs the server synchronously using -startWithPort:bonjourName: until a
* SIGINT signal is received i.e. Ctrl-C. This method is intended to be used
* by command line tools.
*
* Returns NO if the server failed to start.
*
* @warning This method must be used from the main thread only.
*/
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
/**
* Runs the server synchronously using -startWithOptions: until a SIGTERM or
* SIGINT signal is received i.e. Ctrl-C in Terminal. This method is intended to
* be used by command line tools.
*
* Returns NO if the server failed to start and sets "error" argument if not NULL.
*
* @warning This method must be used from the main thread only.
*/
- (BOOL)runWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
#endif
@end
@interface GCDWebServer (Handlers)
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method and generate responses synchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method and generate responses asynchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a specific case-insensitive path and generate responses
* synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a specific case-insensitive path and generate responses
* asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
@end
@interface GCDWebServer (GETHandlers)
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a specific case-insensitive path with in-memory data.
*/
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(nullable NSString*)contentType cacheAge:(NSUInteger)cacheAge;
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a specific case-insensitive path with a file.
*/
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a case-insensitive path inside a base path with the corresponding file
* inside a local directory. If no local file matches the request path, a 401
* HTTP status code is returned to the client.
*
* The "indexFilename" argument allows to specify an "index" file name to use
* when the request path corresponds to a directory.
*/
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(nullable NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
@end
/**
* GCDWebServer provides its own built-in logging facility which is used by
* default. It simply sends log messages to stderr assuming it is connected
* to a terminal type device.
*
* GCDWebServer is also compatible with a limited set of third-party logging
* facilities. If one of them is available at compile time, GCDWebServer will
* automatically use it in place of the built-in one.
*
* Currently supported third-party logging facilities are:
* - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility
*
* For the built-in logging facility, the default logging level is INFO
* (or DEBUG if the preprocessor constant "DEBUG" evaluates to non-zero at
* compile time).
*
* It's possible to have GCDWebServer use a custom logging facility by defining
* the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build
* settings to the name of a custom header file (escaped like \"MyLogging.h\").
* This header file must define the following set of macros:
*
* GWS_LOG_DEBUG(...)
* GWS_LOG_VERBOSE(...)
* GWS_LOG_INFO(...)
* GWS_LOG_WARNING(...)
* GWS_LOG_ERROR(...)
*
* IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG()
* macro should not do anything unless the preprocessor constant "DEBUG" evaluates
* to non-zero.
*
* The logging methods below send log messages to the same logging facility
* used by GCDWebServer. They can be used for consistency wherever you interact
* with GCDWebServer in your code (e.g. in the implementation of handlers).
*/
@interface GCDWebServer (Logging)
/**
* Sets the log level of the logging facility below which log messages are discarded.
*
* @warning The interpretation of the "level" argument depends on the logging
* facility used at compile time.
*
* If using the built-in logging facility, the log levels are as follow:
* DEBUG = 0
* VERBOSE = 1
* INFO = 2
* WARNING = 3
* ERROR = 4
*/
+ (void)setLogLevel:(int)level;
/**
* Logs a message to the logging facility at the VERBOSE level.
*/
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/**
* Logs a message to the logging facility at the INFO level.
*/
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/**
* Logs a message to the logging facility at the WARNING level.
*/
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/**
* Logs a message to the logging facility at the ERROR level.
*/
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
@end
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
@interface GCDWebServer (Testing)
/**
* Activates recording of HTTP requests and responses which create files in the
* current directory containing the raw data for all requests and responses.
*
* @warning The current directory must not contain any prior recording files.
*/
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled;
/**
* Runs tests by playing back pre-recorded HTTP requests in the given directory
* and comparing the generated responses with the pre-recorded ones.
*
* Returns the number of failed tests or -1 if server failed to start.
*/
- (NSInteger)runTestsWithOptions:(nullable NSDictionary<NSString*, id>*)options inDirectory:(NSString*)path;
@end
#endif
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,183 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebServerHandler;
/**
* The GCDWebServerConnection class is instantiated by GCDWebServer to handle
* each new HTTP connection. Each instance stays alive until the connection is
* closed.
*
* You cannot use this class directly, but it is made public so you can
* subclass it to override some hooks. Use the GCDWebServerOption_ConnectionClass
* option for GCDWebServer to install your custom subclass.
*
* @warning The GCDWebServerConnection retains the GCDWebServer until the
* connection is closed.
*/
@interface GCDWebServerConnection : NSObject
/**
* Returns the GCDWebServer that owns the connection.
*/
@property(nonatomic, readonly) GCDWebServer* server;
/**
* Returns YES if the connection is using IPv6.
*/
@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6;
/**
* Returns the address of the local peer (i.e. server) of the connection
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* localAddressData;
/**
* Returns the address of the local peer (i.e. server) of the connection
* as a string.
*/
@property(nonatomic, readonly) NSString* localAddressString;
/**
* Returns the address of the remote peer (i.e. client) of the connection
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* remoteAddressData;
/**
* Returns the address of the remote peer (i.e. client) of the connection
* as a string.
*/
@property(nonatomic, readonly) NSString* remoteAddressString;
/**
* Returns the total number of bytes received from the remote peer (i.e. client)
* so far.
*/
@property(nonatomic, readonly) NSUInteger totalBytesRead;
/**
* Returns the total number of bytes sent to the remote peer (i.e. client) so far.
*/
@property(nonatomic, readonly) NSUInteger totalBytesWritten;
@end
/**
* Hooks to customize the behavior of GCDWebServer HTTP connections.
*
* @warning These methods can be called on any GCD thread.
* Be sure to also call "super" when overriding them.
*/
@interface GCDWebServerConnection (Subclassing)
/**
* This method is called when the connection is opened.
*
* Return NO to reject the connection e.g. after validating the local
* or remote address.
*/
- (BOOL)open;
/**
* This method is called whenever data has been received
* from the remote peer (i.e. client).
*
* @warning Do not attempt to modify this data.
*/
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length;
/**
* This method is called whenever data has been sent
* to the remote peer (i.e. client).
*
* @warning Do not attempt to modify this data.
*/
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length;
/**
* This method is called after the HTTP headers have been received to
* allow replacing the request URL by another one.
*
* The default implementation returns the original URL.
*/
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary<NSString*, NSString*>*)headers;
/**
* Assuming a valid HTTP request was received, this method is called before
* the request is processed.
*
* Return a non-nil GCDWebServerResponse to bypass the request processing entirely.
*
* The default implementation checks for HTTP authentication if applicable
* and returns a barebone 401 status code response if authentication failed.
*/
- (nullable GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;
/**
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
* this method is called to process the request by executing the handler's
* process block.
*/
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion;
/**
* Assuming a valid HTTP request was received and either -preflightRequest:
* or -processRequest:completion: returned a non-nil GCDWebServerResponse,
* this method is called to override the response.
*
* You can either modify the current response and return it, or return a
* completely new one.
*
* The default implementation replaces any response matching the "ETag" or
* "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304)
* one.
*/
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request;
/**
* This method is called if any error happens while validing or processing
* the request or if no GCDWebServerResponse was generated during processing.
*
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
* the "request" argument will be nil.
*/
- (void)abortRequest:(nullable GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
/**
* Called when the connection is closed.
*/
- (void)close;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,843 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <TargetConditionals.h>
#import <netdb.h>
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
#import <libkern/OSAtomic.h>
#endif
#import "GCDWebServerPrivate.h"
#define kHeadersReadCapacity (1 * 1024)
#define kBodyReadCapacity (256 * 1024)
typedef void (^ReadDataCompletionBlock)(BOOL success);
typedef void (^ReadHeadersCompletionBlock)(NSData* extraData);
typedef void (^ReadBodyCompletionBlock)(BOOL success);
typedef void (^WriteDataCompletionBlock)(BOOL success);
typedef void (^WriteHeadersCompletionBlock)(BOOL success);
typedef void (^WriteBodyCompletionBlock)(BOOL success);
static NSData* _CRLFData = nil;
static NSData* _CRLFCRLFData = nil;
static NSData* _continueData = nil;
static NSData* _lastChunkData = nil;
static NSString* _digestAuthenticationNonce = nil;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
static int32_t _connectionCounter = 0;
#endif
NS_ASSUME_NONNULL_BEGIN
@interface GCDWebServerConnection (Read)
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block;
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block;
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block;
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block;
@end
@interface GCDWebServerConnection (Write)
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block;
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block;
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block;
@end
NS_ASSUME_NONNULL_END
@implementation GCDWebServerConnection {
CFSocketNativeHandle _socket;
BOOL _virtualHEAD;
CFHTTPMessageRef _requestMessage;
GCDWebServerRequest* _request;
GCDWebServerHandler* _handler;
CFHTTPMessageRef _responseMessage;
GCDWebServerResponse* _response;
NSInteger _statusCode;
BOOL _opened;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSUInteger _connectionIndex;
NSString* _requestPath;
int _requestFD;
NSString* _responsePath;
int _responseFD;
#endif
}
+ (void)initialize {
if (_CRLFData == nil) {
_CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2];
GWS_DCHECK(_CRLFData);
}
if (_CRLFCRLFData == nil) {
_CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
GWS_DCHECK(_CRLFCRLFData);
}
if (_continueData == nil) {
CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
_continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message));
CFRelease(message);
GWS_DCHECK(_continueData);
}
if (_lastChunkData == nil) {
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
}
if (_digestAuthenticationNonce == nil) {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
_digestAuthenticationNonce = GCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)));
CFRelease(uuid);
}
}
- (BOOL)isUsingIPv6 {
const struct sockaddr* localSockAddr = _localAddressData.bytes;
return (localSockAddr->sa_family == AF_INET6);
}
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
_statusCode = statusCode;
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (__bridge CFStringRef)_server.serverName);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (__bridge CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
}
- (void)_startProcessingRequest {
GWS_DCHECK(_responseMessage == NULL);
GCDWebServerResponse* preflightResponse = [self preflightRequest:_request];
if (preflightResponse) {
[self _finishProcessingRequest:preflightResponse];
} else {
[self processRequest:_request
completion:^(GCDWebServerResponse* processResponse) {
[self _finishProcessingRequest:processResponse];
}];
}
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
GWS_DCHECK(_responseMessage == NULL);
BOOL hasBody = NO;
if (response) {
response = [self overrideResponse:response forRequest:_request];
}
if (response) {
if ([response hasBody]) {
[response prepareForReading];
hasBody = !_virtualHEAD;
}
NSError* error = nil;
if (hasBody && ![response performOpen:&error]) {
GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
} else {
_response = response;
}
}
if (_response) {
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
if (_response.lastModifiedDate) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822((NSDate*)_response.lastModifiedDate));
}
if (_response.eTag) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
}
if ((_response.statusCode >= 200) && (_response.statusCode < 300)) {
if (_response.cacheControlMaxAge > 0) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]);
} else {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
}
}
if (_response.contentType != nil) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType));
}
if (_response.contentLength != NSUIntegerMax) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
}
if (_response.usesChunkedTransferEncoding) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
}
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
CFHTTPMessageSetHeaderFieldValue(self->_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
}];
[self writeHeadersWithCompletionBlock:^(BOOL success) {
if (success) {
if (hasBody) {
[self writeBodyWithCompletionBlock:^(BOOL successInner) {
[self->_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent
}];
}
} else if (hasBody) {
[self->_response performClose];
}
}];
} else {
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
NSError* error = nil;
if (![_request performOpen:&error]) {
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
return;
}
if (initialData.length) {
if (![_request performWriteData:initialData error:&error]) {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
if (![_request performClose:&error]) {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
}
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
return;
}
length -= initialData.length;
}
if (length) {
[self readBodyWithRemainingLength:length
completionBlock:^(BOOL success) {
NSError* localError = nil;
if ([self->_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
} else {
if ([_request performClose:&error]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}
}
- (void)_readChunkedBodyWithInitialData:(NSData*)initialData {
NSError* error = nil;
if (![_request performOpen:&error]) {
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
return;
}
NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
[self readNextBodyChunk:chunkData
completionBlock:^(BOOL success) {
NSError* localError = nil;
if ([self->_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
}
- (void)_readRequestHeaders {
_requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
[self readHeaders:headersData
withCompletionBlock:^(NSData* extraData) {
if (extraData) {
NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(self->_requestMessage)); // Method verbs are case-sensitive and uppercase
if (self->_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
requestMethod = @"GET";
self->_virtualHEAD = YES;
}
NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self->_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(self->_requestMessage));
if (requestURL) {
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
GWS_DCHECK(requestURL);
}
NSString* urlPath = requestURL ? CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL)) : nil; // Don't use -[NSURL path] which strips the ending slash
if (urlPath == nil) {
urlPath = @"/"; // CFURLCopyPath() returns NULL for a relative URL with path "//" contrary to -[NSURL path] which returns "/"
}
NSString* requestPath = urlPath ? GCDWebServerUnescapeURLString(urlPath) : nil;
NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
for (self->_handler in self->_server.handlers) {
self->_request = self->_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery);
if (self->_request) {
break;
}
}
if (self->_request) {
self->_request.localAddressData = self.localAddressData;
self->_request.remoteAddressData = self.remoteAddressData;
if ([self->_request hasBody]) {
[self->_request prepareForWriting];
if (self->_request.usesChunkedTransferEncoding || (extraData.length <= self->_request.contentLength)) {
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
if (expectHeader) {
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
[self writeData:_continueData
withCompletionBlock:^(BOOL success) {
if (success) {
if (self->_request.usesChunkedTransferEncoding) {
[self _readChunkedBodyWithInitialData:extraData];
} else {
[self _readBodyWithLength:self->_request.contentLength initialData:extraData];
}
}
}];
} else {
GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", self->_socket);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
}
} else {
if (self->_request.usesChunkedTransferEncoding) {
[self _readChunkedBodyWithInitialData:extraData];
} else {
[self _readBodyWithLength:self->_request.contentLength initialData:extraData];
}
}
} else {
GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", self->_socket);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
}
} else {
[self _startProcessingRequest];
}
} else {
self->_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
GWS_DCHECK(self->_request);
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_NotImplemented];
}
} else {
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
GWS_DNOT_REACHED();
}
} else {
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
}
- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket {
if ((self = [super init])) {
_server = server;
_localAddressData = localAddress;
_remoteAddressData = remoteAddress;
_socket = socket;
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
[_server willStartConnection:self];
if (![self open]) {
close(_socket);
return nil;
}
_opened = YES;
[self _readRequestHeaders];
}
return self;
}
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
}
- (void)dealloc {
int result = close(_socket);
if (result != 0) {
GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno);
} else {
GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket);
}
if (_opened) {
[self close];
}
[_server didEndConnection:self];
if (_requestMessage) {
CFRelease(_requestMessage);
}
if (_responseMessage) {
CFRelease(_responseMessage);
}
}
@end
@implementation GCDWebServerConnection (Read)
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
@autoreleasepool {
if (error == 0) {
size_t size = dispatch_data_get_size(buffer);
if (size > 0) {
NSUInteger originalLength = data.length;
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
[data appendBytes:chunkBytes length:chunkSize];
return true;
});
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
block(YES);
} else {
if (self->_totalBytesRead > 0) {
GWS_LOG_ERROR(@"No more data available on socket %i", self->_socket);
} else {
GWS_LOG_WARNING(@"No data received from socket %i", self->_socket);
}
block(NO);
}
} else {
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", self->_socket, strerror(error), error);
block(NO);
}
}
});
}
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
GWS_DCHECK(_requestMessage);
[self readData:headersData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
if (range.location == NSNotFound) {
[self readHeaders:headersData withCompletionBlock:block];
} else {
NSUInteger length = range.location + range.length;
if (CFHTTPMessageAppendBytes(self->_requestMessage, headersData.bytes, length)) {
if (CFHTTPMessageIsHeaderComplete(self->_requestMessage)) {
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
} else {
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", self->_socket);
block(nil);
}
} else {
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", self->_socket);
block(nil);
}
}
} else {
block(nil);
}
}];
}
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
[self readData:bodyData
withLength:length
completionBlock:^(BOOL success) {
if (success) {
if (bodyData.length <= length) {
NSError* error = nil;
if ([self->_request performWriteData:bodyData error:&error]) {
NSUInteger remainingLength = length - bodyData.length;
if (remainingLength) {
[self readBodyWithRemainingLength:remainingLength completionBlock:block];
} else {
block(YES);
}
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", self->_socket, error);
block(NO);
}
} else {
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", self->_socket);
block(NO);
GWS_DNOT_REACHED();
}
} else {
block(NO);
}
}];
}
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
char buffer[size + 1];
bcopy(bytes, buffer, size);
buffer[size] = 0;
char* end = NULL;
long result = strtol(buffer, &end, 16);
return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
}
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
while (1) {
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
if (range.location == NSNotFound) {
break;
}
NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions
NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
if (length != NSNotFound) {
if (length) {
if (chunkData.length < range.location + range.length + length + 2) {
break;
}
const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
NSError* error = nil;
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
block(NO);
return;
}
} else {
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
block(NO);
return;
}
} else {
NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers
if (trailerRange.location != NSNotFound) {
block(YES);
return;
}
}
} else {
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
block(NO);
return;
}
}
[self readData:chunkData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
[self readNextBodyChunk:chunkData completionBlock:block];
} else {
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Write)
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
[data self]; // Keeps ARC from releasing data too early
});
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
@autoreleasepool {
if (error == 0) {
GWS_DCHECK(remainingData == NULL);
[self didWriteBytes:data.bytes length:data.length];
block(YES);
} else {
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", self->_socket, strerror(error), error);
block(NO);
}
}
});
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
dispatch_release(buffer);
#endif
}
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
GWS_DCHECK(_responseMessage);
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
[self writeData:(__bridge NSData*)data withCompletionBlock:block];
CFRelease(data);
}
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
GWS_DCHECK([_response hasBody]);
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
if (data) {
if (data.length) {
if (self->_response.usesChunkedTransferEncoding) {
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
size_t hexLength = strlen(hexString);
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
if (chunk == nil) {
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", self->_socket, error);
block(NO);
return;
}
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
bcopy(hexString, ptr, hexLength);
ptr += hexLength;
*ptr++ = '\r';
*ptr++ = '\n';
bcopy(data.bytes, ptr, data.length);
ptr += data.length;
*ptr++ = '\r';
*ptr = '\n';
data = chunk;
}
[self writeData:data
withCompletionBlock:^(BOOL success) {
if (success) {
[self writeBodyWithCompletionBlock:block];
} else {
block(NO);
}
}];
} else {
if (self->_response.usesChunkedTransferEncoding) {
[self writeData:_lastChunkData
withCompletionBlock:^(BOOL success) {
block(success);
}];
} else {
block(YES);
}
}
} else {
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", self->_socket, error);
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Subclassing)
- (BOOL)open {
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if (_server.recordingEnabled) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
#pragma clang diagnostic pop
_requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
GWS_DCHECK(_requestFD > 0);
_responsePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
GWS_DCHECK(_responseFD > 0);
}
#endif
return YES;
}
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
_totalBytesRead += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno);
close(_requestFD);
_requestFD = 0;
}
#endif
}
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
_totalBytesWritten += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno);
close(_responseFD);
_responseFD = 0;
}
#endif
}
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary<NSString*, NSString*>*)headers {
return url;
}
// https://tools.ietf.org/html/rfc2617
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
GCDWebServerResponse* response = nil;
if (_server.authenticationBasicAccounts) {
__block BOOL authenticated = NO;
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
if ([authorizationHeader hasPrefix:@"Basic "]) {
NSString* basicAccount = [authorizationHeader substringFromIndex:6];
[_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) {
if ([basicAccount isEqualToString:digest]) {
authenticated = YES;
*stop = YES;
}
}];
}
if (!authenticated) {
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
[response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"];
}
} else if (_server.authenticationDigestAccounts) {
BOOL authenticated = NO;
BOOL isStaled = NO;
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
if ([authorizationHeader hasPrefix:@"Digest "]) {
NSString* realm = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm");
if (realm && [_server.authenticationRealm isEqualToString:realm]) {
NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce");
if ([nonce isEqualToString:_digestAuthenticationNonce]) {
NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username");
NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri");
NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response");
NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username];
NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required
NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2);
if ([actualResponse isEqualToString:expectedResponse]) {
authenticated = YES;
}
} else if (nonce.length) {
isStaled = YES;
}
}
}
if (!authenticated) {
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
[response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop")
}
}
return response;
}
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
_handler.asyncProcessBlock(request, [completion copy]);
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) {
if (requestLastModified && responseLastModified) {
if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) {
return YES;
}
}
if (requestETag && responseETag) { // Per the specs "If-None-Match" must be checked after "If-Modified-Since"
if ([requestETag isEqualToString:@"*"]) {
return YES;
}
if ([responseETag isEqualToString:requestETag]) {
return YES;
}
}
return NO;
}
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request {
if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) {
NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed;
GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code];
newResponse.cacheControlMaxAge = response.cacheControlMaxAge;
newResponse.lastModifiedDate = response.lastModifiedDate;
newResponse.eTag = response.eTag;
GWS_DCHECK(newResponse);
return newResponse;
}
return response;
}
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode {
GWS_DCHECK(_responseMessage == NULL);
GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
[self _initializeResponseHeadersWithStatusCode:statusCode];
[self writeHeadersWithCompletionBlock:^(BOOL success) {
; // Nothing more to do
}];
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
}
- (void)close {
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if (_requestPath) {
BOOL success = NO;
NSError* error = nil;
if (_requestFD > 0) {
close(_requestFD);
NSString* name = [NSString stringWithFormat:@"%03lu-%@.request", (unsigned long)_connectionIndex, _virtualHEAD ? @"HEAD" : _request.method];
success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
}
if (!success) {
GWS_LOG_ERROR(@"Failed saving recorded request: %@", error);
GWS_DNOT_REACHED();
}
unlink([_requestPath fileSystemRepresentation]);
}
if (_responsePath) {
BOOL success = NO;
NSError* error = nil;
if (_responseFD > 0) {
close(_responseFD);
NSString* name = [NSString stringWithFormat:@"%03lu-%i.response", (unsigned long)_connectionIndex, (int)_statusCode];
success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
}
if (!success) {
GWS_LOG_ERROR(@"Failed saving recorded response: %@", error);
GWS_DNOT_REACHED();
}
unlink([_responsePath fileSystemRepresentation]);
}
#endif
if (_request) {
GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
} else {
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
}
}
@end

View File

@ -0,0 +1,114 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifdef __cplusplus
extern "C" {
#endif
/**
* Converts a file extension to the corresponding MIME type.
* If there is no match, "application/octet-stream" is returned.
*
* Overrides allow to customize the built-in mapping from extensions to MIME
* types. Keys of the dictionary must be lowercased file extensions without
* the period, and the values must be the corresponding MIME types.
*/
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* _Nullable overrides);
/**
* Add percent-escapes to a string so it can be used in a URL.
* The legal characters ":@/?&=+" are also escaped to ensure compatibility
* with URL encoded forms and URL queries.
*/
NSString* _Nullable GCDWebServerEscapeURLString(NSString* string);
/**
* Unescapes a URL percent-encoded string.
*/
NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string);
/**
* Extracts the unescaped names and values from an
* "application/x-www-form-urlencoded" form.
* http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
*/
NSDictionary<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form);
/**
* On OS X, returns the IPv4 or IPv6 address as a string of the primary
* connected service or nil if not available.
*
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
* interface if connected or nil otherwise.
*/
NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
/**
* Converts a date into a string using RFC822 formatting.
* https://tools.ietf.org/html/rfc822#section-5
* https://tools.ietf.org/html/rfc1123#section-5.2.14
*/
NSString* GCDWebServerFormatRFC822(NSDate* date);
/**
* Converts a RFC822 formatted string into a date.
* https://tools.ietf.org/html/rfc822#section-5
* https://tools.ietf.org/html/rfc1123#section-5.2.14
*
* @warning Timezones other than GMT are not supported by this function.
*/
NSDate* _Nullable GCDWebServerParseRFC822(NSString* string);
/**
* Converts a date into a string using IOS 8601 formatting.
* http://tools.ietf.org/html/rfc3339#section-5.6
*/
NSString* GCDWebServerFormatISO8601(NSDate* date);
/**
* Converts a ISO 8601 formatted string into a date.
* http://tools.ietf.org/html/rfc3339#section-5.6
*
* @warning Only "calendar" variant is supported at this time and timezones
* other than GMT are not supported either.
*/
NSDate* _Nullable GCDWebServerParseISO8601(NSString* string);
/**
* Removes "//", "/./" and "/../" components from path as well as any trailing slash.
*/
NSString* GCDWebServerNormalizePath(NSString* path);
#ifdef __cplusplus
}
#endif
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,331 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <TargetConditionals.h>
#if TARGET_OS_IPHONE
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <SystemConfiguration/SystemConfiguration.h>
#endif
#import <CommonCrypto/CommonDigest.h>
#import <ifaddrs.h>
#import <net/if.h>
#import <netdb.h>
#import "GCDWebServerPrivate.h"
static NSDateFormatter* _dateFormatterRFC822 = nil;
static NSDateFormatter* _dateFormatterISO8601 = nil;
static dispatch_queue_t _dateFormatterQueue = NULL;
// TODO: Handle RFC 850 and ANSI C's asctime() format
void GCDWebServerInitializeFunctions() {
GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
if (_dateFormatterRFC822 == nil) {
_dateFormatterRFC822 = [[NSDateFormatter alloc] init];
_dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
_dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
_dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
GWS_DCHECK(_dateFormatterRFC822);
}
if (_dateFormatterISO8601 == nil) {
_dateFormatterISO8601 = [[NSDateFormatter alloc] init];
_dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
_dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
_dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
GWS_DCHECK(_dateFormatterISO8601);
}
if (_dateFormatterQueue == NULL) {
_dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
GWS_DCHECK(_dateFormatterQueue);
}
}
NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
if (value) {
NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive
if (range.location != NSNotFound) {
value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]];
} else {
value = [value lowercaseString];
}
}
return value;
}
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
if (value) {
NSRange range = [value rangeOfString:@";"];
if (range.location != NSNotFound) {
return [value substringToIndex:range.location];
}
}
return value;
}
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
NSString* parameter = nil;
if (value) {
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
NSString* string = [NSString stringWithFormat:@"%@=", name];
if ([scanner scanUpToString:string intoString:NULL]) {
[scanner scanString:string intoString:NULL];
if ([scanner scanString:@"\"" intoString:NULL]) {
[scanner scanUpToString:@"\"" intoString:&parameter];
} else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
}
}
}
return parameter;
}
// http://www.w3schools.com/tags/ref_charactersets.asp
NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) {
NSStringEncoding encoding = kCFStringEncodingInvalidId;
if (charset) {
encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
}
return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
}
NSString* GCDWebServerFormatRFC822(NSDate* date) {
__block NSString* string;
dispatch_sync(_dateFormatterQueue, ^{
string = [_dateFormatterRFC822 stringFromDate:date];
});
return string;
}
NSDate* GCDWebServerParseRFC822(NSString* string) {
__block NSDate* date;
dispatch_sync(_dateFormatterQueue, ^{
date = [_dateFormatterRFC822 dateFromString:string];
});
return date;
}
NSString* GCDWebServerFormatISO8601(NSDate* date) {
__block NSString* string;
dispatch_sync(_dateFormatterQueue, ^{
string = [_dateFormatterISO8601 stringFromDate:date];
});
return string;
}
NSDate* GCDWebServerParseISO8601(NSString* string) {
__block NSDate* date;
dispatch_sync(_dateFormatterQueue, ^{
date = [_dateFormatterISO8601 dateFromString:string];
});
return date;
}
BOOL GCDWebServerIsTextContentType(NSString* type) {
return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]);
}
NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
if (GCDWebServerIsTextContentType(type)) {
NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
if (string) {
return string;
}
}
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
}
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* overrides) {
NSDictionary* builtInOverrides = @{@"css" : @"text/css"};
NSString* mimeType = nil;
extension = [extension lowercaseString];
if (extension.length) {
mimeType = [overrides objectForKey:extension];
if (mimeType == nil) {
mimeType = [builtInOverrides objectForKey:extension];
}
if (mimeType == nil) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
if (uti) {
mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
CFRelease(uti);
}
}
}
return mimeType ? mimeType : kGCDWebServerDefaultMimeType;
}
NSString* GCDWebServerEscapeURLString(NSString* string) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
#pragma clang diagnostic pop
}
NSString* GCDWebServerUnescapeURLString(NSString* string) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
#pragma clang diagnostic pop
}
NSDictionary<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form) {
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
NSScanner* scanner = [[NSScanner alloc] initWithString:form];
[scanner setCharactersToBeSkipped:nil];
while (1) {
NSString* key = nil;
if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
break;
}
[scanner setScanLocation:([scanner scanLocation] + 1)];
NSString* value = nil;
[scanner scanUpToString:@"&" intoString:&value];
if (value == nil) {
value = @"";
}
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil;
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil;
if (unescapedKey && unescapedValue) {
[parameters setObject:unescapedValue forKey:unescapedKey];
} else {
GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
GWS_DNOT_REACHED();
}
if ([scanner isAtEnd]) {
break;
}
[scanner setScanLocation:([scanner scanLocation] + 1)];
}
return parameters;
}
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
char hostBuffer[NI_MAXHOST];
char serviceBuffer[NI_MAXSERV];
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) {
#if DEBUG
GWS_DNOT_REACHED();
#else
return @"";
#endif
}
return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer];
}
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
NSString* address = nil;
#if TARGET_OS_IPHONE
#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV
const char* primaryInterface = "en0"; // WiFi interface on iOS
#endif
#else
const char* primaryInterface = NULL;
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
if (store) {
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
if (info) {
NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"];
if (interface) {
primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool
}
CFRelease(info);
}
CFRelease(store);
}
if (primaryInterface == NULL) {
primaryInterface = "lo0";
}
#endif
struct ifaddrs* list;
if (getifaddrs(&list) >= 0) {
for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV
// Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
// Assumption holds for Apple TV running tvOS
if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))
#else
if (strcmp(ifap->ifa_name, primaryInterface))
#endif
{
continue;
}
if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) {
address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
break;
}
}
freeifaddrs(list);
}
return address;
}
NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
va_list arguments;
va_start(arguments, format);
const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String];
va_end(arguments);
unsigned char md5[CC_MD5_DIGEST_LENGTH];
CC_MD5(string, (CC_LONG)strlen(string), md5);
char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
unsigned char byte = md5[i];
unsigned char byteHi = (byte & 0xF0) >> 4;
buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
unsigned char byteLo = byte & 0x0F;
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
}
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
return (NSString*)[NSString stringWithUTF8String:buffer];
}
NSString* GCDWebServerNormalizePath(NSString* path) {
NSMutableArray* components = [[NSMutableArray alloc] init];
for (NSString* component in [path componentsSeparatedByString:@"/"]) {
if ([component isEqualToString:@".."]) {
[components removeLastObject];
} else if (component.length && ![component isEqualToString:@"."]) {
[components addObject:component];
}
}
if (path.length && ([path characterAtIndex:0] == '/')) {
return [@"/" stringByAppendingString:[components componentsJoinedByString:@"/"]]; // Preserve initial slash
}
return [components componentsJoinedByString:@"/"];
}

View File

@ -0,0 +1,116 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
#import <Foundation/Foundation.h>
/**
* Convenience constants for "informational" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_Continue = 100,
kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101,
kGCDWebServerHTTPStatusCode_Processing = 102
};
/**
* Convenience constants for "successful" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_OK = 200,
kGCDWebServerHTTPStatusCode_Created = 201,
kGCDWebServerHTTPStatusCode_Accepted = 202,
kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203,
kGCDWebServerHTTPStatusCode_NoContent = 204,
kGCDWebServerHTTPStatusCode_ResetContent = 205,
kGCDWebServerHTTPStatusCode_PartialContent = 206,
kGCDWebServerHTTPStatusCode_MultiStatus = 207,
kGCDWebServerHTTPStatusCode_AlreadyReported = 208
};
/**
* Convenience constants for "redirection" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_MultipleChoices = 300,
kGCDWebServerHTTPStatusCode_MovedPermanently = 301,
kGCDWebServerHTTPStatusCode_Found = 302,
kGCDWebServerHTTPStatusCode_SeeOther = 303,
kGCDWebServerHTTPStatusCode_NotModified = 304,
kGCDWebServerHTTPStatusCode_UseProxy = 305,
kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307,
kGCDWebServerHTTPStatusCode_PermanentRedirect = 308
};
/**
* Convenience constants for "client error" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_BadRequest = 400,
kGCDWebServerHTTPStatusCode_Unauthorized = 401,
kGCDWebServerHTTPStatusCode_PaymentRequired = 402,
kGCDWebServerHTTPStatusCode_Forbidden = 403,
kGCDWebServerHTTPStatusCode_NotFound = 404,
kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405,
kGCDWebServerHTTPStatusCode_NotAcceptable = 406,
kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407,
kGCDWebServerHTTPStatusCode_RequestTimeout = 408,
kGCDWebServerHTTPStatusCode_Conflict = 409,
kGCDWebServerHTTPStatusCode_Gone = 410,
kGCDWebServerHTTPStatusCode_LengthRequired = 411,
kGCDWebServerHTTPStatusCode_PreconditionFailed = 412,
kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413,
kGCDWebServerHTTPStatusCode_RequestURITooLong = 414,
kGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415,
kGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416,
kGCDWebServerHTTPStatusCode_ExpectationFailed = 417,
kGCDWebServerHTTPStatusCode_UnprocessableEntity = 422,
kGCDWebServerHTTPStatusCode_Locked = 423,
kGCDWebServerHTTPStatusCode_FailedDependency = 424,
kGCDWebServerHTTPStatusCode_UpgradeRequired = 426,
kGCDWebServerHTTPStatusCode_PreconditionRequired = 428,
kGCDWebServerHTTPStatusCode_TooManyRequests = 429,
kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431
};
/**
* Convenience constants for "server error" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_InternalServerError = 500,
kGCDWebServerHTTPStatusCode_NotImplemented = 501,
kGCDWebServerHTTPStatusCode_BadGateway = 502,
kGCDWebServerHTTPStatusCode_ServiceUnavailable = 503,
kGCDWebServerHTTPStatusCode_GatewayTimeout = 504,
kGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505,
kGCDWebServerHTTPStatusCode_InsufficientStorage = 507,
kGCDWebServerHTTPStatusCode_LoopDetected = 508,
kGCDWebServerHTTPStatusCode_NotExtended = 510,
kGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511
};

View File

@ -0,0 +1,224 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <os/object.h>
#import <sys/socket.h>
/**
* All GCDWebServer headers.
*/
#import "GCDWebServerHTTPStatusCodes.h"
#import "GCDWebServerFunctions.h"
#import "GCDWebServer.h"
#import "GCDWebServerConnection.h"
#import "GCDWebServerDataRequest.h"
#import "GCDWebServerFileRequest.h"
#import "GCDWebServerMultiPartFormRequest.h"
#import "GCDWebServerURLEncodedFormRequest.h"
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h"
#import "GCDWebServerStreamedResponse.h"
/**
* Check if a custom logging facility should be used instead.
*/
#if defined(__GCDWEBSERVER_LOGGING_HEADER__)
#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__
#import __GCDWEBSERVER_LOGGING_HEADER__
/**
* Automatically detect if XLFacility is available and if so use it as a
* logging facility.
*/
#elif defined(__has_include) && __has_include("XLFacilityMacros.h")
#define __GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__
#undef XLOG_TAG
#define XLOG_TAG @"gcdwebserver.internal"
#import "XLFacilityMacros.h"
#define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__)
#define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__)
#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__)
#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__)
#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__)
#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__)
#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE()
/**
* If all of the above fail, then use GCDWebServer built-in
* logging facility.
*/
#else
#define __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
kGCDWebServerLoggingLevel_Debug = 0,
kGCDWebServerLoggingLevel_Verbose,
kGCDWebServerLoggingLevel_Info,
kGCDWebServerLoggingLevel_Warning,
kGCDWebServerLoggingLevel_Error
};
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* _Nonnull format, ...) NS_FORMAT_FUNCTION(2, 3);
#if DEBUG
#define GWS_LOG_DEBUG(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); \
} while (0)
#else
#define GWS_LOG_DEBUG(...)
#endif
#define GWS_LOG_VERBOSE(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); \
} while (0)
#define GWS_LOG_INFO(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); \
} while (0)
#define GWS_LOG_WARNING(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); \
} while (0)
#define GWS_LOG_ERROR(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); \
} while (0)
#endif
/**
* Consistency check macros used when building Debug only.
*/
#if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED)
#if DEBUG
#define GWS_DCHECK(__CONDITION__) \
do { \
if (!(__CONDITION__)) { \
abort(); \
} \
} while (0)
#define GWS_DNOT_REACHED() abort()
#else
#define GWS_DCHECK(__CONDITION__)
#define GWS_DNOT_REACHED()
#endif
#endif
NS_ASSUME_NONNULL_BEGIN
/**
* GCDWebServer internal constants and APIs.
*/
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
return ((range.location != NSUIntegerMax) || (range.length > 0));
}
static inline NSError* GCDWebServerMakePosixError(int code) {
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : (NSString*)[NSString stringWithUTF8String:strerror(code)]}];
}
extern void GCDWebServerInitializeFunctions(void);
extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
extern BOOL GCDWebServerIsTextContentType(NSString* type);
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2);
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
@interface GCDWebServerConnection ()
- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
@end
@interface GCDWebServer ()
@property(nonatomic, readonly) NSMutableArray<GCDWebServerHandler*>* handlers;
@property(nonatomic, readonly, nullable) NSString* serverName;
@property(nonatomic, readonly, nullable) NSString* authenticationRealm;
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationBasicAccounts;
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationDigestAccounts;
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
- (void)willStartConnection:(GCDWebServerConnection*)connection;
- (void)didEndConnection:(GCDWebServerConnection*)connection;
@end
@interface GCDWebServerHandler : NSObject
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock;
@end
@interface GCDWebServerRequest ()
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
@property(nonatomic) NSData* localAddressData;
@property(nonatomic) NSData* remoteAddressData;
- (void)prepareForWriting;
- (BOOL)performOpen:(NSError**)error;
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
- (BOOL)performClose:(NSError**)error;
- (void)setAttribute:(nullable id)attribute forKey:(NSString*)key;
@end
@interface GCDWebServerResponse ()
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* additionalHeaders;
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
- (void)prepareForReading;
- (BOOL)performOpen:(NSError**)error;
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
- (void)performClose;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,210 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
* with the contents of any regular expression captures done on the request path.
*
* @warning This attribute will only be set on the request if adding a handler using
* -addHandlerForMethod:pathRegex:requestClass:processBlock:.
*/
extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
/**
* This protocol is used by the GCDWebServerConnection to communicate with
* the GCDWebServerRequest and write the received HTTP body data.
*
* Note that multiple GCDWebServerBodyWriter objects can be chained together
* internally e.g. to automatically decode gzip encoded content before
* passing it on to the GCDWebServerRequest.
*
* @warning These methods can be called on any GCD thread.
*/
@protocol GCDWebServerBodyWriter <NSObject>
/**
* This method is called before any body data is received.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)open:(NSError**)error;
/**
* This method is called whenever body data has been received.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)writeData:(NSData*)data error:(NSError**)error;
/**
* This method is called after all body data has been received.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)close:(NSError**)error;
@end
/**
* The GCDWebServerRequest class is instantiated by the GCDWebServerConnection
* after the HTTP headers have been received. Each instance wraps a single HTTP
* request. If a body is present, the methods from the GCDWebServerBodyWriter
* protocol will be called by the GCDWebServerConnection to receive it.
*
* The default implementation of the GCDWebServerBodyWriter protocol on the class
* simply ignores the body data.
*
* @warning GCDWebServerRequest instances can be created and used on any GCD thread.
*/
@interface GCDWebServerRequest : NSObject <GCDWebServerBodyWriter>
/**
* Returns the HTTP method for the request.
*/
@property(nonatomic, readonly) NSString* method;
/**
* Returns the URL for the request.
*/
@property(nonatomic, readonly) NSURL* URL;
/**
* Returns the HTTP headers for the request.
*/
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* headers;
/**
* Returns the path component of the URL for the request.
*/
@property(nonatomic, readonly) NSString* path;
/**
* Returns the parsed and unescaped query component of the URL for the request.
*
* @warning This property will be nil if there is no query in the URL.
*/
@property(nonatomic, readonly, nullable) NSDictionary<NSString*, NSString*>* query;
/**
* Returns the content type for the body of the request parsed from the
* "Content-Type" header.
*
* This property will be nil if the request has no body or set to
* "application/octet-stream" if a body is present but there was no
* "Content-Type" header.
*/
@property(nonatomic, readonly, nullable) NSString* contentType;
/**
* Returns the content length for the body of the request parsed from the
* "Content-Length" header.
*
* This property will be set to "NSUIntegerMax" if the request has no body or
* if there is a body but no "Content-Length" header, typically because
* chunked transfer encoding is used.
*/
@property(nonatomic, readonly) NSUInteger contentLength;
/**
* Returns the parsed "If-Modified-Since" header or nil if absent or malformed.
*/
@property(nonatomic, readonly, nullable) NSDate* ifModifiedSince;
/**
* Returns the parsed "If-None-Match" header or nil if absent or malformed.
*/
@property(nonatomic, readonly, nullable) NSString* ifNoneMatch;
/**
* Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed.
* The range will be set to (offset, length) if expressed from the beginning
* of the entity body, or (NSUIntegerMax, length) if expressed from its end.
*/
@property(nonatomic, readonly) NSRange byteRange;
/**
* Returns YES if the client supports gzip content encoding according to the
* "Accept-Encoding" header.
*/
@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding;
/**
* Returns the address of the local peer (i.e. server) for the request
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* localAddressData;
/**
* Returns the address of the local peer (i.e. server) for the request
* as a string.
*/
@property(nonatomic, readonly) NSString* localAddressString;
/**
* Returns the address of the remote peer (i.e. client) for the request
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* remoteAddressData;
/**
* Returns the address of the remote peer (i.e. client) for the request
* as a string.
*/
@property(nonatomic, readonly) NSString* remoteAddressString;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(nullable NSDictionary<NSString*, NSString*>*)query;
/**
* Convenience method that checks if the contentType property is defined.
*/
- (BOOL)hasBody;
/**
* Convenience method that checks if the byteRange property is defined.
*/
- (BOOL)hasByteRange;
/**
* Retrieves an attribute associated with this request using the given key.
*
* @return The attribute value for the key.
*/
- (nullable id)attributeForKey:(NSString*)key;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,303 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <zlib.h>
#import "GCDWebServerPrivate.h"
NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerRequestAttribute_RegexCaptures";
#define kZlibErrorDomain @"ZlibErrorDomain"
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
@end
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
@end
@implementation GCDWebServerBodyDecoder {
GCDWebServerRequest* __unsafe_unretained _request;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
}
- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id<GCDWebServerBodyWriter> _Nonnull)writer {
if ((self = [super init])) {
_request = request;
_writer = writer;
}
return self;
}
- (BOOL)open:(NSError**)error {
return [_writer open:error];
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
return [_writer writeData:data error:error];
}
- (BOOL)close:(NSError**)error {
return [_writer close:error];
}
@end
@implementation GCDWebServerGZipDecoder {
z_stream _stream;
BOOL _finished;
}
- (BOOL)open:(NSError**)error {
int result = inflateInit2(&_stream, 15 + 16);
if (result != Z_OK) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO;
}
if (![super open:error]) {
inflateEnd(&_stream);
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
GWS_DCHECK(!_finished);
_stream.next_in = (Bytef*)data.bytes;
_stream.avail_in = (uInt)data.length;
NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
if (decodedData == nil) {
GWS_DNOT_REACHED();
return NO;
}
NSUInteger length = 0;
while (1) {
NSUInteger maxLength = decodedData.length - length;
_stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length);
_stream.avail_out = (uInt)maxLength;
int result = inflate(&_stream, Z_NO_FLUSH);
if ((result != Z_OK) && (result != Z_STREAM_END)) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO;
}
length += maxLength - _stream.avail_out;
if (_stream.avail_out > 0) {
if (result == Z_STREAM_END) {
_finished = YES;
}
break;
}
decodedData.length = 2 * decodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
}
decodedData.length = length;
BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet
return success;
}
- (BOOL)close:(NSError**)error {
GWS_DCHECK(_finished);
inflateEnd(&_stream);
return [super close:error];
}
@end
@implementation GCDWebServerRequest {
BOOL _opened;
NSMutableArray<GCDWebServerBodyDecoder*>* _decoders;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
NSMutableDictionary<NSString*, id>* _attributes;
}
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
if ((self = [super init])) {
_method = [method copy];
_URL = url;
_headers = headers;
_path = [path copy];
_query = query;
_contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
_usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
if (lengthHeader) {
NSInteger length = [lengthHeader integerValue];
if (_usesChunkedTransferEncoding || (length < 0)) {
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL);
GWS_DNOT_REACHED();
return nil;
}
_contentLength = length;
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
} else if (_usesChunkedTransferEncoding) {
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
_contentLength = NSUIntegerMax;
} else {
if (_contentType) {
GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL);
_contentType = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense
}
_contentLength = NSUIntegerMax;
}
NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
if (modifiedHeader) {
_ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
}
_ifNoneMatch = [_headers objectForKey:@"If-None-Match"];
_byteRange = NSMakeRange(NSUIntegerMax, 0);
NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]);
if (rangeHeader) {
if ([rangeHeader hasPrefix:@"bytes="]) {
NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
if (components.count == 1) {
components = [(NSString*)[components firstObject] componentsSeparatedByString:@"-"];
if (components.count == 2) {
NSString* startString = [components objectAtIndex:0];
NSInteger startValue = [startString integerValue];
NSString* endString = [components objectAtIndex:1];
NSInteger endValue = [endString integerValue];
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
_byteRange.location = startValue;
_byteRange.length = endValue - startValue + 1;
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
_byteRange.location = startValue;
_byteRange.length = NSUIntegerMax;
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
_byteRange.location = NSUIntegerMax;
_byteRange.length = endValue;
}
}
}
}
if ((_byteRange.location == NSUIntegerMax) && (_byteRange.length == 0)) { // Ignore "Range" header if syntactically invalid
GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
}
}
if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
_acceptsGzipContentEncoding = YES;
}
_decoders = [[NSMutableArray alloc] init];
_attributes = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)hasBody {
return _contentType ? YES : NO;
}
- (BOOL)hasByteRange {
return GCDWebServerIsValidByteRange(_byteRange);
}
- (id)attributeForKey:(NSString*)key {
return [_attributes objectForKey:key];
}
- (BOOL)open:(NSError**)error {
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
return YES;
}
- (BOOL)close:(NSError**)error {
return YES;
}
- (void)prepareForWriting {
_writer = self;
if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) {
GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer];
[_decoders addObject:decoder];
_writer = decoder;
}
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_contentType);
GWS_DCHECK(_writer);
if (_opened) {
GWS_DNOT_REACHED();
return NO;
}
_opened = YES;
return [_writer open:error];
}
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
GWS_DCHECK(_opened);
return [_writer writeData:data error:error];
}
- (BOOL)performClose:(NSError**)error {
GWS_DCHECK(_opened);
return [_writer close:error];
}
- (void)setAttribute:(id)attribute forKey:(NSString*)key {
[_attributes setValue:attribute forKey:key];
}
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n %@ = %@", argument, [_query objectForKey:argument]];
}
[description appendString:@"\n"];
for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]];
}
return description;
}
@end

View File

@ -0,0 +1,212 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
* GCDWebServerBodyReader object when reading data from it asynchronously.
*/
typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* _Nullable error);
/**
* This protocol is used by the GCDWebServerConnection to communicate with
* the GCDWebServerResponse and read the HTTP body data to send.
*
* Note that multiple GCDWebServerBodyReader objects can be chained together
* internally e.g. to automatically apply gzip encoding to the content before
* passing it on to the GCDWebServerResponse.
*
* @warning These methods can be called on any GCD thread.
*/
@protocol GCDWebServerBodyReader <NSObject>
@required
/**
* This method is called before any body data is sent.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)open:(NSError**)error;
/**
* This method is called whenever body data is sent.
*
* It should return a non-empty NSData if there is body data available,
* or an empty NSData there is no more body data, or nil on error and set
* the "error" argument which is guaranteed to be non-NULL.
*/
- (nullable NSData*)readData:(NSError**)error;
/**
* This method is called after all body data has been sent.
*/
- (void)close;
@optional
/**
* If this method is implemented, it will be preferred over -readData:.
*
* It must call the passed block when data is available, passing a non-empty
* NSData if there is body data available, or an empty NSData there is no more
* body data, or nil on error and pass an NSError along.
*/
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
@end
/**
* The GCDWebServerResponse class is used to wrap a single HTTP response.
* It is instantiated by the handler of the GCDWebServer that handled the request.
* If a body is present, the methods from the GCDWebServerBodyReader protocol
* will be called by the GCDWebServerConnection to send it.
*
* The default implementation of the GCDWebServerBodyReader protocol
* on the class simply returns an empty body.
*
* @warning GCDWebServerResponse instances can be created and used on any GCD thread.
*/
@interface GCDWebServerResponse : NSObject <GCDWebServerBodyReader>
/**
* Sets the content type for the body of the response.
*
* The default value is nil i.e. the response has no body.
*
* @warning This property must be set if a body is present.
*/
@property(nonatomic, copy, nullable) NSString* contentType;
/**
* Sets the content length for the body of the response. If a body is present
* but this property is set to "NSUIntegerMax", this means the length of the body
* cannot be known ahead of time. Chunked transfer encoding will be
* automatically enabled by the GCDWebServerConnection to comply with HTTP/1.1
* specifications.
*
* The default value is "NSUIntegerMax" i.e. the response has no body or its length
* is undefined.
*/
@property(nonatomic) NSUInteger contentLength;
/**
* Sets the HTTP status code for the response.
*
* The default value is 200 i.e. "OK".
*/
@property(nonatomic) NSInteger statusCode;
/**
* Sets the caching hint for the response using the "Cache-Control" header.
* This value is expressed in seconds.
*
* The default value is 0 i.e. "no-cache".
*/
@property(nonatomic) NSUInteger cacheControlMaxAge;
/**
* Sets the last modified date for the response using the "Last-Modified" header.
*
* The default value is nil.
*/
@property(nonatomic, nullable) NSDate* lastModifiedDate;
/**
* Sets the ETag for the response using the "ETag" header.
*
* The default value is nil.
*/
@property(nonatomic, copy, nullable) NSString* eTag;
/**
* Enables gzip encoding for the response body.
*
* The default value is NO.
*
* @warning Enabling gzip encoding will remove any "Content-Length" header
* since the length of the body is not known anymore. The client will still
* be able to determine the body length when connection is closed per
* HTTP/1.1 specifications.
*/
@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled;
/**
* Creates an empty response.
*/
+ (instancetype)response;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)init;
/**
* Sets an additional HTTP header on the response.
* Pass a nil value to remove an additional header.
*
* @warning Do not attempt to override the primary headers used
* by GCDWebServerResponse like "Content-Type", "ETag", etc...
*/
- (void)setValue:(nullable NSString*)value forAdditionalHeader:(NSString*)header;
/**
* Convenience method that checks if the contentType property is defined.
*/
- (BOOL)hasBody;
@end
@interface GCDWebServerResponse (Extensions)
/**
* Creates a empty response with a specific HTTP status code.
*/
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode;
/**
* Creates an HTTP redirect response to a new URL.
*/
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
/**
* Initializes an empty response with a specific HTTP status code.
*/
- (instancetype)initWithStatusCode:(NSInteger)statusCode;
/**
* Initializes an HTTP redirect response to a new URL.
*/
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,284 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import <zlib.h>
#import "GCDWebServerPrivate.h"
#define kZlibErrorDomain @"ZlibErrorDomain"
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
@end
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
@end
@implementation GCDWebServerBodyEncoder {
GCDWebServerResponse* __unsafe_unretained _response;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
if ((self = [super init])) {
_response = response;
_reader = reader;
}
return self;
}
- (BOOL)open:(NSError**)error {
return [_reader open:error];
}
- (NSData*)readData:(NSError**)error {
return [_reader readData:error];
}
- (void)close {
[_reader close];
}
@end
@implementation GCDWebServerGZipEncoder {
z_stream _stream;
BOOL _finished;
}
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
if ((self = [super initWithResponse:response reader:reader])) {
response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
}
return self;
}
- (BOOL)open:(NSError**)error {
int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
if (result != Z_OK) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO;
}
if (![super open:error]) {
deflateEnd(&_stream);
return NO;
}
return YES;
}
- (NSData*)readData:(NSError**)error {
NSMutableData* encodedData;
if (_finished) {
encodedData = [[NSMutableData alloc] init];
} else {
encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
if (encodedData == nil) {
GWS_DNOT_REACHED();
return nil;
}
NSUInteger length = 0;
do {
NSData* data = [super readData:error];
if (data == nil) {
return nil;
}
_stream.next_in = (Bytef*)data.bytes;
_stream.avail_in = (uInt)data.length;
while (1) {
NSUInteger maxLength = encodedData.length - length;
_stream.next_out = (Bytef*)((char*)encodedData.mutableBytes + length);
_stream.avail_out = (uInt)maxLength;
int result = deflate(&_stream, data.length ? Z_NO_FLUSH : Z_FINISH);
if (result == Z_STREAM_END) {
_finished = YES;
} else if (result != Z_OK) {
if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return nil;
}
length += maxLength - _stream.avail_out;
if (_stream.avail_out > 0) {
break;
}
encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
}
GWS_DCHECK(_stream.avail_in == 0);
} while (length == 0); // Make sure we don't return an empty NSData if not in finished state
encodedData.length = length;
}
return encodedData;
}
- (void)close {
deflateEnd(&_stream);
[super close];
}
@end
@implementation GCDWebServerResponse {
BOOL _opened;
NSMutableArray<GCDWebServerBodyEncoder*>* _encoders;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
+ (instancetype)response {
return [(GCDWebServerResponse*)[[self class] alloc] init];
}
- (instancetype)init {
if ((self = [super init])) {
_contentType = nil;
_contentLength = NSUIntegerMax;
_statusCode = kGCDWebServerHTTPStatusCode_OK;
_cacheControlMaxAge = 0;
_additionalHeaders = [[NSMutableDictionary alloc] init];
_encoders = [[NSMutableArray alloc] init];
}
return self;
}
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
[_additionalHeaders setValue:value forKey:header];
}
- (BOOL)hasBody {
return _contentType ? YES : NO;
}
- (BOOL)usesChunkedTransferEncoding {
return (_contentType != nil) && (_contentLength == NSUIntegerMax);
}
- (BOOL)open:(NSError**)error {
return YES;
}
- (NSData*)readData:(NSError**)error {
return [NSData data];
}
- (void)close {
;
}
- (void)prepareForReading {
_reader = self;
if (_gzipContentEncodingEnabled) {
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
[_encoders addObject:encoder];
_reader = encoder;
}
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_contentType);
GWS_DCHECK(_reader);
if (_opened) {
GWS_DNOT_REACHED();
return NO;
}
_opened = YES;
return [_reader open:error];
}
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
GWS_DCHECK(_opened);
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
[_reader asyncReadDataWithCompletion:[block copy]];
} else {
NSError* error = nil;
NSData* data = [_reader readData:&error];
block(data, error);
}
}
- (void)performClose {
GWS_DCHECK(_opened);
[_reader close];
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode];
if (_contentType) {
[description appendFormat:@"\nContent Type = %@", _contentType];
}
if (_contentLength != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength];
}
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge];
if (_lastModifiedDate) {
[description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate];
}
if (_eTag) {
[description appendFormat:@"\nETag = %@", _eTag];
}
if (_additionalHeaders.count) {
[description appendString:@"\n"];
for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]];
}
}
return description;
}
@end
@implementation GCDWebServerResponse (Extensions)
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
return [(GCDWebServerResponse*)[self alloc] initWithStatusCode:statusCode];
}
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
return [(GCDWebServerResponse*)[self alloc] initWithRedirect:location permanent:permanent];
}
- (instancetype)initWithStatusCode:(NSInteger)statusCode {
if ((self = [self init])) {
self.statusCode = statusCode;
}
return self;
}
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
if ((self = [self init])) {
self.statusCode = permanent ? kGCDWebServerHTTPStatusCode_MovedPermanently : kGCDWebServerHTTPStatusCode_TemporaryRedirect;
[self setValue:[location absoluteString] forAdditionalHeader:@"Location"];
}
return self;
}
@end

View File

@ -0,0 +1,64 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request in memory.
*/
@interface GCDWebServerDataRequest : GCDWebServerRequest
/**
* Returns the data for the request body.
*/
@property(nonatomic, readonly) NSData* data;
@end
@interface GCDWebServerDataRequest (Extensions)
/**
* Returns the data for the request body interpreted as text. If the content
* type of the body is not a text one, or if an error occurs, nil is returned.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly, nullable) NSString* text;
/**
* Returns the data for the request body interpreted as a JSON object. If the
* content type of the body is not JSON, or if an error occurs, nil is returned.
*/
@property(nonatomic, readonly, nullable) id jsonObject;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,104 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@interface GCDWebServerDataRequest ()
@property(nonatomic) NSMutableData* data;
@end
@implementation GCDWebServerDataRequest {
NSString* _text;
id _jsonObject;
}
- (BOOL)open:(NSError**)error {
if (self.contentLength != NSUIntegerMax) {
_data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
} else {
_data = [[NSMutableData alloc] init];
}
if (_data == nil) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed allocating memory"}];
}
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
[_data appendData:data];
return YES;
}
- (BOOL)close:(NSError**)error {
return YES;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_data) {
[description appendString:@"\n\n"];
[description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)];
}
return description;
}
@end
@implementation GCDWebServerDataRequest (Extensions)
- (NSString*)text {
if (_text == nil) {
if ([self.contentType hasPrefix:@"text/"]) {
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
_text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
} else {
GWS_DNOT_REACHED();
}
}
return _text;
}
- (id)jsonObject {
if (_jsonObject == nil) {
NSString* mimeType = GCDWebServerTruncateHeaderValue(self.contentType);
if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) {
_jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL];
} else {
GWS_DNOT_REACHED();
}
}
return _jsonObject;
}
@end

View File

@ -0,0 +1,49 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request to a file on disk.
*/
@interface GCDWebServerFileRequest : GCDWebServerRequest
/**
* Returns the path to the temporary file containing the request body.
*
* @warning This temporary file will be automatically deleted when the
* GCDWebServerFileRequest is deallocated. If you want to preserve this file,
* you must move it to a different location beforehand.
*/
@property(nonatomic, readonly) NSString* temporaryPath;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,102 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
@implementation GCDWebServerFileRequest {
int _file;
}
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
_temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
}
return self;
}
- (void)dealloc {
unlink([_temporaryPath fileSystemRepresentation]);
}
- (BOOL)open:(NSError**)error {
_file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_file <= 0) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (write(_file, data.bytes, data.length) != (ssize_t)data.length) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO;
}
return YES;
}
- (BOOL)close:(NSError**)error {
if (close(_file) < 0) {
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO;
}
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"];
if (creationDateHeader) {
NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) {
return NO;
}
}
NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"];
if (modifiedDateHeader) {
NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) {
return NO;
}
}
#endif
return YES;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
[description appendFormat:@"\n\n{%@}", _temporaryPath];
return description;
}
@end

View File

@ -0,0 +1,136 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerMultiPart class is an abstract class that wraps the content
* of a part.
*/
@interface GCDWebServerMultiPart : NSObject
/**
* Returns the control name retrieved from the part headers.
*/
@property(nonatomic, readonly) NSString* controlName;
/**
* Returns the content type retrieved from the part headers or "text/plain"
* if not available (per HTTP specifications).
*/
@property(nonatomic, readonly) NSString* contentType;
/**
* Returns the MIME type component of the content type for the part.
*/
@property(nonatomic, readonly) NSString* mimeType;
@end
/**
* The GCDWebServerMultiPartArgument subclass of GCDWebServerMultiPart wraps
* the content of a part as data in memory.
*/
@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart
/**
* Returns the data for the part.
*/
@property(nonatomic, readonly) NSData* data;
/**
* Returns the data for the part interpreted as text. If the content
* type of the part is not a text one, or if an error occurs, nil is returned.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly, nullable) NSString* string;
@end
/**
* The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps
* the content of a part as a file on disk.
*/
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
/**
* Returns the file name retrieved from the part headers.
*/
@property(nonatomic, readonly) NSString* fileName;
/**
* Returns the path to the temporary file containing the part data.
*
* @warning This temporary file will be automatically deleted when the
* GCDWebServerMultiPartFile is deallocated. If you want to preserve this file,
* you must move it to a different location beforehand.
*/
@property(nonatomic, readonly) NSString* temporaryPath;
@end
/**
* The GCDWebServerMultiPartFormRequest subclass of GCDWebServerRequest
* parses the body of the HTTP request as a multipart encoded form.
*/
@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest
/**
* Returns the argument parts from the multipart encoded form as
* name / GCDWebServerMultiPartArgument pairs.
*/
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartArgument*>* arguments;
/**
* Returns the files parts from the multipart encoded form as
* name / GCDWebServerMultiPartFile pairs.
*/
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartFile*>* files;
/**
* Returns the MIME type for multipart encoded forms
* i.e. "multipart/form-data".
*/
+ (NSString*)mimeType;
/**
* Returns the first argument for a given control name or nil if not found.
*/
- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
/**
* Returns the first file for a given control name or nil if not found.
*/
- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,405 @@
/*
Copyright (c) 2012-2019, Pierre-Olivier Latour
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The name of Pierre-Olivier Latour may not be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !__has_feature(objc_arc)
#error GCDWebServer requires ARC
#endif
#import "GCDWebServerPrivate.h"
#define kMultiPartBufferSize (256 * 1024)
typedef enum {
kParserState_Undefined = 0,
kParserState_Start,
kParserState_Headers,
kParserState_Content,
kParserState_End
} ParserState;
@interface GCDWebServerMIMEStreamParser : NSObject
@end
static NSData* _newlineData = nil;
static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil;
@implementation GCDWebServerMultiPart
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
if ((self = [super init])) {
_controlName = [name copy];
_contentType = [type copy];
_mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
}
return self;
}
@end
@implementation GCDWebServerMultiPartArgument
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data {
if ((self = [super initWithControlName:name contentType:type])) {
_data = data;
if ([self.contentType hasPrefix:@"text/"]) {
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
_string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)];
}
}
return self;
}
- (NSString*)description {
return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length];
}
@end
@implementation GCDWebServerMultiPartFile
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath {
if ((self = [super initWithControlName:name contentType:type])) {
_fileName = [fileName copy];
_temporaryPath = [temporaryPath copy];
}
return self;
}
- (void)dealloc {
unlink([_temporaryPath fileSystemRepresentation]);
}
- (NSString*)description {
return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
}
@end
@implementation GCDWebServerMIMEStreamParser {
NSData* _boundary;
NSString* _defaultcontrolName;
ParserState _state;
NSMutableData* _data;
NSMutableArray<GCDWebServerMultiPartArgument*>* _arguments;
NSMutableArray<GCDWebServerMultiPartFile*>* _files;
NSString* _controlName;
NSString* _fileName;
NSString* _contentType;
NSString* _tmpPath;
int _tmpFile;
GCDWebServerMIMEStreamParser* _subParser;
}
+ (void)initialize {
if (_newlineData == nil) {
_newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
GWS_DCHECK(_newlineData);
}
if (_newlinesData == nil) {
_newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
GWS_DCHECK(_newlinesData);
}
if (_dashNewlineData == nil) {
_dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
GWS_DCHECK(_dashNewlineData);
}
}
- (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray<GCDWebServerMultiPartArgument*>* _Nonnull)arguments files:(NSMutableArray<GCDWebServerMultiPartFile*>* _Nonnull)files {
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
if ((self = [super init])) {
_boundary = data;
_defaultcontrolName = name;
_arguments = arguments;
_files = files;
_data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
_state = kParserState_Start;
}
return self;
}
- (void)dealloc {
if (_tmpFile > 0) {
close(_tmpFile);
unlink([_tmpPath fileSystemRepresentation]);
}
}
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
- (BOOL)_parseData {
BOOL success = YES;
if (_state == kParserState_Headers) {
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) {
_controlName = nil;
_fileName = nil;
_contentType = nil;
_tmpPath = nil;
_subParser = nil;
NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding];
if (headers) {
for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) {
NSRange subRange = [header rangeOfString:@":"];
if (subRange.location != NSNotFound) {
NSString* name = [header substringToIndex:subRange.location];
NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) {
_contentType = GCDWebServerNormalizeHeaderValue(value);
} else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) {
NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value);
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
_controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name");
_fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
} else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) {
_controlName = _defaultcontrolName;
_fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
}
}
} else {
GWS_DNOT_REACHED();
}
}
if (_contentType == nil) {
_contentType = @"text/plain";
}
} else {
GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'");
GWS_DNOT_REACHED();
}
if (_controlName) {
if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) {
NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary");
_subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files];
if (_subParser == nil) {
GWS_DNOT_REACHED();
success = NO;
}
} else if (_fileName) {
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_tmpFile > 0) {
_tmpPath = [path copy];
} else {
GWS_DNOT_REACHED();
success = NO;
}
}
} else {
GWS_DNOT_REACHED();
success = NO;
}
[_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
_state = kParserState_Content;
}
}
if ((_state == kParserState_Start) || (_state == kParserState_Content)) {
NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) {
NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length);
NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
if (_state == kParserState_Content) {
const void* dataBytes = _data.bytes;
NSUInteger dataLength = range.location - 2;
if (_subParser) {
if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) {
GWS_DNOT_REACHED();
success = NO;
}
_subParser = nil;
} else if (_tmpPath) {
ssize_t result = write(_tmpFile, dataBytes, dataLength);
if (result == (ssize_t)dataLength) {
if (close(_tmpFile) == 0) {
_tmpFile = 0;
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
[_files addObject:file];
} else {
GWS_DNOT_REACHED();
success = NO;
}
} else {
GWS_DNOT_REACHED();
success = NO;
}
_tmpPath = nil;
} else {
NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength];
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data];
[_arguments addObject:argument];
}
}
if (subRange1.location != NSNotFound) {
[_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
_state = kParserState_Headers;
success = [self _parseData];
} else {
_state = kParserState_End;
}
}
} else {
NSUInteger margin = 2 * _boundary.length;
if (_data.length > margin) {
NSUInteger length = _data.length - margin;
if (_subParser) {
if ([_subParser appendBytes:_data.bytes length:length]) {
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
GWS_DNOT_REACHED();
success = NO;
}
} else if (_tmpPath) {
ssize_t result = write(_tmpFile, _data.bytes, length);
if (result == (ssize_t)length) {
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
GWS_DNOT_REACHED();
success = NO;
}
}
}
}
}
return success;
}
- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length {
[_data appendBytes:bytes length:length];
return [self _parseData];
}
- (BOOL)isAtEnd {
return (_state == kParserState_End);
}
@end
@interface GCDWebServerMultiPartFormRequest ()
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartArgument*>* arguments;
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartFile*>* files;
@end
@implementation GCDWebServerMultiPartFormRequest {
GCDWebServerMIMEStreamParser* _parser;
}
+ (NSString*)mimeType {
return @"multipart/form-data";
}
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
_arguments = [[NSMutableArray alloc] init];
_files = [[NSMutableArray alloc] init];
}
return self;
}
- (BOOL)open:(NSError**)error {
NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
if (_parser == nil) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed starting to parse multipart form data"}];
}
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (![_parser appendBytes:data.bytes length:data.length]) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed continuing to parse multipart form data"}];
}
return NO;
}
return YES;
}
- (BOOL)close:(NSError**)error {
BOOL atEnd = [_parser isAtEnd];
_parser = nil;
if (!atEnd) {
if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed finishing to parse multipart form data"}];
}
return NO;
}
return YES;
}
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name {
for (GCDWebServerMultiPartArgument* argument in _arguments) {
if ([argument.controlName isEqualToString:name]) {
return argument;
}
}
return nil;
}
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name {
for (GCDWebServerMultiPartFile* file in _files) {
if ([file.controlName isEqualToString:name]) {
return file;
}
}
return nil;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_arguments.count) {
[description appendString:@"\n"];
for (GCDWebServerMultiPartArgument* argument in _arguments) {
[description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType];
[description appendString:GCDWebServerDescribeData(argument.data, argument.contentType)];
}
}
if (_files.count) {
[description appendString:@"\n"];
for (GCDWebServerMultiPartFile* file in _files) {
[description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath];
}
}
return description;
}
@end

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