commit
b1a20b6ea7
|
@ -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_*
|
||||
|
|
61
CHANGES.md
61
CHANGES.md
|
@ -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.
|
||||
|
|
1
Makefile
1
Makefile
|
@ -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)"'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
2
driver.c
2
driver.c
|
@ -22,6 +22,8 @@
|
|||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <audio/audio_resampler.h>
|
||||
|
||||
#ifdef HAVE_MENU
|
||||
#include "menu/menu_driver.h"
|
||||
#endif
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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))))
|
||||
|
|
|
@ -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",
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <linux/parport.h>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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 },
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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),
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
@ -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 = (
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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:¶meter];
|
||||
} else {
|
||||
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
||||
}
|
||||
}
|
||||
}
|
||||
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:@"/"];
|
||||
}
|
|
@ -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
|
||||
};
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue