harmonyos: Port SDL3 to HarmonyOS/OpenHarmony.

This is my port of SDL3 to HarmonyOS/OpenHarmony. It has been tested on real
hardware from Huawei, and commercial games are being prepped to ship with it
already.

There is _heavy_ documentation in docs/README-harmonyos.md, including how to
set up a development environment and project, build, and debug, for the
completely uninitiated.

This work was sponsored by Outfit7: https://outfit7.com/

Fixes #9837.
This commit is contained in:
Ryan C. Gordon
2026-06-09 00:52:31 -04:00
parent 570f72297b
commit a24429c192
115 changed files with 7518 additions and 91 deletions

10
.gitignore vendored
View File

@@ -99,6 +99,16 @@ out/
android-project/local.properties
android-project/.gradle/
# for OpenHarmony/HarmonyOS
openharmony-project/entry/src/main/cpp/CMakeLists.txt
openharmony-project/entry/src/main/cpp/*.c
openharmony-project/entry/src/main/cpp/*.h
openharmony-project/entry/src/main/cpp/SDL
openharmony-project/entry/src/main/resources/rawfile
openharmony-project/AppScope/app.json5
openharmony-project/AppScope/resources/base/element/string.json
openharmony-project/build-profile.json5
test/checkkeys
test/checkkeysthreads
test/gamepadmap

View File

@@ -78,6 +78,7 @@ include("${SDL3_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake")
include("${SDL3_SOURCE_DIR}/cmake/3rdparty.cmake")
include("${SDL3_SOURCE_DIR}/cmake/PreseedMSVCCache.cmake")
include("${SDL3_SOURCE_DIR}/cmake/PreseedEmscriptenCache.cmake")
include("${SDL3_SOURCE_DIR}/cmake/PreseedOpenHarmonyCache.cmake")
include("${SDL3_SOURCE_DIR}/cmake/PreseedHaikuCache.cmake")
include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake")
include("${SDL3_SOURCE_DIR}/cmake/PreseedDOSCache.cmake")
@@ -322,7 +323,7 @@ dep_option(SDL_SSE4_2 "Use SSE4.2 assembly routines" ON "SDL_ASSEMB
dep_option(SDL_MMX "Use MMX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_X86 OR SDL_CPU_X64" OFF)
dep_option(SDL_ALTIVEC "Use Altivec assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_POWERPC32 OR SDL_CPU_POWERPC64" OFF)
dep_option(SDL_ARMNEON "Use NEON assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_ARM32 OR SDL_CPU_ARM64" OFF)
dep_option(SDL_ARMSVE2 "Use SVE2 assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_ARM64" OFF)
dep_option(SDL_ARMSVE2 "Use SVE2 assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_ARM64 AND NOT OHOS" OFF)
dep_option(SDL_LSX "Use LSX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF)
dep_option(SDL_LASX "Use LASX assembly routines" ON "SDL_ASSEMBLY;SDL_CPU_LOONGARCH64" OFF)
@@ -385,7 +386,7 @@ dep_option(SDL_RENDER_D3D12 "Enable the Direct3D 12 render driver" ON "SD
dep_option(SDL_RENDER_METAL "Enable the Metal render driver" ON "SDL_RENDER;APPLE" OFF)
dep_option(SDL_RENDER_GPU "Enable the SDL_GPU render driver" ON "SDL_RENDER;SDL_GPU" OFF)
dep_option(SDL_VIVANTE "Use Vivante EGL video driver" ON "${UNIX_SYS};SDL_CPU_ARM32" OFF)
dep_option(SDL_VULKAN "Enable Vulkan support" "${SDL_VULKAN_DEFAULT}" "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR OPENBSD OR WINDOWS OR CYGWIN" OFF)
dep_option(SDL_VULKAN "Enable Vulkan support" "${SDL_VULKAN_DEFAULT}" "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR OPENBSD OR WINDOWS OR CYGWIN OR OHOS" OFF)
dep_option(SDL_RENDER_VULKAN "Enable the Vulkan render driver" ON "SDL_RENDER;SDL_VULKAN" OFF)
dep_option(SDL_METAL "Enable Metal support" ON "APPLE" OFF)
set_option(SDL_OPENVR "Use OpenVR video driver" OFF)
@@ -1724,6 +1725,170 @@ if(ANDROID)
target_link_options(SDL3-static INTERFACE "-Wl,-u,JNI_OnLoad")
endif()
elseif(OHOS) # OpenHarmony/HarmonyOS
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/core/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/core/openharmony/*.h"
)
sdl_link_dependency(openharmonycore LIBS libace_napi.z.so libace_ndk.z.so deviceinfo_ndk.z rawfile.z hilog_ndk.z ohcommonevent)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/main/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/main/openharmony/*.h"
)
set(HAVE_SDL_MAIN_CALLBACKS TRUE)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/misc/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/misc/openharmony/*.h"
)
set(HAVE_SDL_MISC TRUE)
if(SDL_AUDIO)
set(SDL_AUDIO_DRIVER_OHAUDIO 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/audio/ohaudio/*.c"
"${SDL3_SOURCE_DIR}/src/audio/ohaudio/*.h"
)
set(HAVE_SDL_AUDIO TRUE)
endif()
set(SDL_FILESYSTEM_OPENHARMONY 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/filesystem/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/filesystem/openharmony/*.h"
)
sdl_link_dependency(openharmonyfs LIBS ability_runtime)
set(HAVE_SDL_FILESYSTEM TRUE)
set(SDL_FSOPS_POSIX 1)
sdl_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/SDL_sysfsops.c")
set(HAVE_SDL_FSOPS TRUE)
if (False) # !!! FIXME
if(SDL_HAPTIC)
set(SDL_HAPTIC_OPENHARMONY 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/haptic/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/haptic/openharmony/*.h"
)
set(HAVE_SDL_HAPTIC TRUE)
endif()
# !!! FIXME: any chance HIDAPI would work here?
CheckHIDAPI()
if(SDL_JOYSTICK)
set(SDL_JOYSTICK_OPENHARMONY 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/joystick/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/joystick/openharmony/*.h"
)
set(HAVE_SDL_JOYSTICK TRUE)
endif()
endif()
set(SDL_LOADSO_DLOPEN 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.c"
"${SDL3_SOURCE_DIR}/src/loadso/dlopen/*.h"
)
set(HAVE_SDL_LOADSO TRUE)
if(SDL_POWER)
set(SDL_POWER_OPENHARMONY 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/power/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/power/openharmony/*.h"
)
sdl_link_dependency(openharmonypower LIBS ohbattery_info)
set(HAVE_SDL_POWER TRUE)
endif()
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/locale/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/locale/openharmony/*.h"
)
set(HAVE_SDL_LOCALE TRUE)
set(SDL_TIME_UNIX 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/time/unix/*.c"
"${SDL3_SOURCE_DIR}/src/time/unix/*.h"
)
set(HAVE_SDL_TIME TRUE)
set(SDL_TIMER_UNIX 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/timer/unix/*.c"
"${SDL3_SOURCE_DIR}/src/timer/unix/*.h"
)
set(HAVE_SDL_TIMERS TRUE)
if(False)
if(SDL_SENSOR)
set(SDL_SENSOR_OPENHARMONY 1)
set(HAVE_SDL_SENSORS TRUE)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/sensor/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/sensor/openharmony/*.h"
)
endif()
endif()
if(SDL_CAMERA)
set(SDL_CAMERA_DRIVER_OPENHARMONY 1)
set(HAVE_CAMERA TRUE)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/camera/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/camera/openharmony/*.h"
)
sdl_link_dependency(openharmonycamera LIBS ohcamera ohimage image_receiver native_image native_buffer)
endif()
if(SDL_VIDEO)
set(SDL_VIDEO_DRIVER_OPENHARMONY 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/video/openharmony/*.c"
"${SDL3_SOURCE_DIR}/src/video/openharmony/*.h"
)
set(HAVE_SDL_VIDEO TRUE)
sdl_link_dependency(openharmonyvideo LIBS native_display_manager pasteboard udmf)
# Core stuff
# find_library(ANDROID_DL_LIBRARY dl)
# FIXME failing dlopen https://github.com/android-ndk/ndk/issues/929
# !!! FIXME
# sdl_link_dependency(android_video LIBS dl log android)
sdl_compile_definitions(PRIVATE "GL_GLEXT_PROTOTYPES")
#enable gles
if(SDL_OPENGLES)
set(SDL_VIDEO_OPENGL_EGL 1)
set(HAVE_OPENGLES TRUE)
set(SDL_VIDEO_OPENGL_ES2 1)
set(SDL_VIDEO_RENDER_OGL_ES2 1)
#sdl_link_dependency(opengles LIBS GLESv3)
endif()
if(SDL_VULKAN)
set(SDL_VIDEO_VULKAN 1)
set(HAVE_VULKAN TRUE)
if(SDL_RENDER_VULKAN)
set(SDL_VIDEO_RENDER_VULKAN 1)
set(HAVE_RENDER_VULKAN TRUE)
endif()
endif()
endif()
CheckPTHREAD()
if(SDL_CLOCK_GETTIME)
set(HAVE_CLOCK_GETTIME 1)
endif()
elseif(EMSCRIPTEN)
# Hide noisy warnings that intend to aid mostly during initial stages of porting a new
# project. Uncomment at will for verbose cross-compiling -I/../ path info.

209
build-scripts/harmonyos-tool.sh Executable file
View File

@@ -0,0 +1,209 @@
#!/usr/bin/bash
HVIGORWPATH=`which hvigorw 2>/dev/null`
if [ "x$HVIGORWPATH" == "x" ]; then
echo "Couldn't find hvigorw!"
echo "Please make sure the HarmonyOS command line tools are in your PATH." 1>&2
exit 1
fi
HDCPATH=`which hdc 2>/dev/null`
if [ "x$HDCPATH" == "x" ]; then
echo "Couldn't find hdc!"
echo "Please make sure the HarmonyOS command line tools are in your PATH." 1>&2
exit 1
fi
CLTBINPATH=`dirname "$HVIGORWPATH"`
CLTPATH=`dirname "$CLTBINPATH"`
CLT=`realpath "$CLTPATH"`
#echo "CLT is '$CLT'"
need_usage=0
APPJSON=""
PROJDIR="$1"
if [ "x$PROJDIR" == "x" ]; then
need_usage=1
else
PROJDIR=`realpath "$PROJDIR"`
APPJSON="$PROJDIR/AppScope/app.json5"
if [ ! -f "$APPJSON" ]; then
echo "Can't find '$APPJSON'. Is project directory correct?" 1>&2
echo "" 1>&2
need_usage=1
fi
fi
do_clean=0
do_build=0
do_uninstall=0
do_install=0
do_kill=0
do_launch=0
do_log=0
do_debug=0
do_anything=0
shift # dump projectdir
while [ $# -gt 0 ]; do
do_anything=1
case $1 in
--clean)
do_clean=1
shift
;;
--build)
do_build=1
shift
;;
--install)
do_install=1
shift
;;
--uninstall)
do_uninstall=1
shift
;;
--kill)
do_kill=1
shift
;;
--launch)
do_launch=1
shift
;;
--log)
do_log=1
shift
;;
--debug)
do_launch=1
do_debug=1
shift
;;
*)
echo "Unknown option '$1'" 1>&2
need_usage=1
shift
;;
esac
done
if [ "$do_anything" == "0" ]; then
need_usage=1
fi
if [ "$need_usage" == "1" ]; then
echo "USAGE: $0 <project_dir> [--kill] [--uninstall] [--build] [--install] [--launch] [--debug] [--log]" 1>&2
echo "" 1>&2
exit 1
fi
if [ "$do_build" == "1" ]; then
if [ "$do_debug" == "1" ]; then
do_install=1
elif [ "$do_launch" == "1" ]; then
do_install=1
fi
fi
# Here we go!
BUNDLE=`grep -F bundleName "$PROJDIR/AppScope/app.json5" |perl -w -p -e 's/\A\s*\"bundleName\"\s*\:\s*\"(.*?)\".*?\Z/$1/i;'`
if [ "x$BUNDLE" == "x" ]; then
echo "Failed to determine bundle name! Aborting!" 1>&2
exit 1
fi
echo "BUNDLE is '$BUNDLE'"
#set -x
cd "$PROJDIR"
if [ "$do_kill" == "1" ]; then
echo "KILLING..."
hdc shell aa force-stop $BUNDLE || exit 1
fi
if [ "$do_uninstall" == "1" ]; then
echo "UNINSTALLING..."
hdc uninstall $BUNDLE || exit 1
fi
if [ "$do_clean" == "1" ]; then
echo "CLEANING..."
hvigorw clean || exit 1
fi
if [ "$do_build" == "1" ]; then
echo "BUILDING..."
hvigorw assembleHap || exit 1
fi
if [ "$do_debug" == "1" ]; then
hdc shell mkdir -p data/local/tmp/debugserver/$BUNDLE
hdc file send "$CLT/sdk/default/hms/native/lldb/aarch64-linux-ohos/lldb-server" data/local/tmp/debugserver/$BUNDLE
hdc shell chmod 755 data/local/tmp/debugserver/$BUNDLE/lldb-server
fi
if [ "$do_install" == "1" ]; then
echo "INSTALLING..."
hdc install "$PROJDIR/entry/build/default/outputs/default/entry-default-signed.hap" || exit 1
fi
# Catch sigint, kill the backgrounded `hdc hilog` then exit.
trap_sigint() {
if [ "$HILOGPID" != "" ]; then
kill -9 "$HILOGPID"
fi
wait "$HILOGPID" 2>/dev/null
echo ""
#echo "Killed $HILOGPID"
exit 0
}
HILOGPID=""
if [ "$do_log" == "1" ]; then
trap trap_sigint INT
# This wild bash syntax puts `hdc` in the background, and pipes its output through `grep`, but `$!` will have hdc's process ID.
# The ridiculous perl regular expression basically filters out a ton of garbage from each line of the log. You can remove it entirely and just grep for the bundle name, though!
hdc hilog > >(grep -F --line-buffered "/$BUNDLE/" |perl -w -p -e 's/\A\d+\-\d+\s+(\d+\:\d+\:\d+)\.\d+\s+\d+\s+\d+\s+(.)\s+.*?\/.*?\/(.*?)\:\s+/$1 $2 $3: /;') & HILOGPID="$!"
fi
if [ "$do_launch" == "1" ]; then
echo "LAUNCHING..."
# !!! FIXME: maybe this makes the process wait on launch until debugger is attached?
#hdc shell aa appdebug -b $BUNDLE
hdc shell aa start -a EntryAbility -b $BUNDLE || exit 1
fi
if [ "$do_debug" == "1" ]; then
echo "DEBUGGING..."
hdc shell aa attach -b $BUNDLE
hdc shell aa process -a EntryAbility -b $BUNDLE -D "/data/local/tmp/debugserver/$BUNDLE/lldb-server platform --listen unix-abstract:///lldb-server/platform.sock"
PID=`hdc shell ps -w -o PID,ARGS |grep -F "$BUNDLE" |head -n 1 |awk '{print $1}'`
if [ "x$PID" == "x" ]; then
echo "Failed to determine PID! Aborting!" 1>&2
exit 1
fi
echo "PID is $PID"
trap - INT
"$CLT/sdk/default/openharmony/native/llvm/bin/lldb" \
-O "platform select remote-ohos" \
-O "platform connect unix-abstract-connect:///lldb-server/platform.sock" \
-O "settings append target.exec-search-paths \"$PROJDIR/entry/build/default/intermediates/cmake/default/obj/arm64-v8a\"" \
-O "attach $PID"
trap trap_sigint INT
fi
if [ "$HILOGPID" != "" ]; then
#echo "$0: WAITING ON $HILOGPID"
wait "$HILOGPID"
fi

View File

@@ -0,0 +1,188 @@
if(OHOS)
function(SDL_Preseed_CMakeCache)
set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS")
set(COMPILER_SUPPORTS_GCC_ATOMICS "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_GCC_ATOMICS")
set(COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET "1" CACHE INTERNAL "Test COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET")
set(HAVE_CLANG_COMMENT_BLOCK_COMMANDS "1" CACHE INTERNAL "Test HAVE_CLANG_COMMENT_BLOCK_COMMANDS")
set(HAVE_ALLOCA_H "1" CACHE INTERNAL "Have include alloca.h")
set(HAVE_LIBM "1" CACHE INTERNAL "Have library m")
set(HAVE_POSIX_SPAWN "1" CACHE INTERNAL "Have symbol posix_spawn")
set(HAVE_FSEEKO "1" CACHE INTERNAL "Have symbol fseeko")
set(HAVE_OFF64_T "1" CACHE INTERNAL "Have symbol off64_t")
set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs")
set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos")
set(LIBC_HAS_ACOSF "1" CACHE INTERNAL "Have symbol acosf")
set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin")
set(LIBC_HAS_ASINF "1" CACHE INTERNAL "Have symbol asinf")
set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan")
set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2")
set(LIBC_HAS_ATAN2F "1" CACHE INTERNAL "Have symbol atan2f")
set(LIBC_HAS_ATANF "1" CACHE INTERNAL "Have symbol atanf")
set(LIBC_HAS_ATOF "1" CACHE INTERNAL "Have symbol atof")
set(LIBC_HAS_ATOI "1" CACHE INTERNAL "Have symbol atoi")
set(LIBC_HAS_BCOPY "1" CACHE INTERNAL "Have symbol bcopy")
set(LIBC_HAS_CALLOC "1" CACHE INTERNAL "Have symbol calloc")
set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil")
set(LIBC_HAS_CEILF "1" CACHE INTERNAL "Have symbol ceilf")
set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign")
set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf")
set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos")
set(LIBC_HAS_COSF "1" CACHE INTERNAL "Have symbol cosf")
set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp")
set(LIBC_HAS_EXPF "1" CACHE INTERNAL "Have symbol expf")
set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs")
set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf")
set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h")
set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor")
set(LIBC_HAS_FLOORF "1" CACHE INTERNAL "Have symbol floorf")
set(LIBC_HAS_FMOD "1" CACHE INTERNAL "Have symbol fmod")
set(LIBC_HAS_FMODF "1" CACHE INTERNAL "Have symbol fmodf")
set(LIBC_HAS_FOPEN64 "1" CACHE INTERNAL "Have symbol fopen64")
set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free")
set(LIBC_HAS_FSEEKO "1" CACHE INTERNAL "Have symbol fseeko")
set(LIBC_HAS_FSEEKO64 "1" CACHE INTERNAL "Have symbol fseeko64 (broken in DJGPP)")
set(LIBC_HAS_GETENV "1" CACHE INTERNAL "Have symbol getenv")
set(LIBC_HAS_ICONV_H "1" CACHE INTERNAL "Have include iconv.h")
set(LIBC_HAS_INDEX "1" CACHE INTERNAL "Have symbol index")
set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h")
set(LIBC_HAS_ISINF "" CACHE INTERNAL "Have include isinf(double)")
set(LIBC_ISINF_HANDLES_FLOAT "" CACHE INTERNAL "Have include isinf(float)")
set(LIBC_HAS_ISINFF "" CACHE INTERNAL "Have include isinff(float)")
set(LIBC_HAS_ISNAN "" CACHE INTERNAL "Have include isnan(double)")
set(LIBC_ISNAN_HANDLES_FLOAT "" CACHE INTERNAL "Have include isnan(float)")
set(LIBC_HAS_ISNANF "" CACHE INTERNAL "Have include isnanf(float)")
set(LIBC_HAS_ITOA "" CACHE INTERNAL "Have symbol itoa")
set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h")
set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log")
set(LIBC_HAS_LOG10 "1" CACHE INTERNAL "Have symbol log10")
set(LIBC_HAS_LOG10F "1" CACHE INTERNAL "Have symbol log10f")
set(LIBC_HAS_LOGF "1" CACHE INTERNAL "Have symbol logf")
set(LIBC_HAS_LROUND "1" CACHE INTERNAL "Have symbol lround")
set(LIBC_HAS_LROUNDF "1" CACHE INTERNAL "Have symbol lroundf")
set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc")
set(LIBC_HAS_MALLOC_H "1" CACHE INTERNAL "Have include malloc.h")
set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h")
set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp")
set(LIBC_HAS_MEMCPY "1" CACHE INTERNAL "Have symbol memcpy")
set(LIBC_HAS_MEMMOVE "1" CACHE INTERNAL "Have symbol memmove")
set(LIBC_HAS_MEMORY_H "1" CACHE INTERNAL "Have include memory.h")
set(LIBC_HAS_MEMSET "1" CACHE INTERNAL "Have symbol memset")
set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf")
set(LIBC_HAS_MODFF "1" CACHE INTERNAL "Have symbol modff")
set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow")
set(LIBC_HAS_POWF "1" CACHE INTERNAL "Have symbol powf")
set(LIBC_HAS_PUTENV "1" CACHE INTERNAL "Have symbol putenv")
set(LIBC_HAS_REALLOC "1" CACHE INTERNAL "Have symbol realloc")
set(LIBC_HAS_RINDEX "" CACHE INTERNAL "Have symbol rindex")
set(LIBC_HAS_ROUND "1" CACHE INTERNAL "Have symbol round")
set(LIBC_HAS_ROUNDF "1" CACHE INTERNAL "Have symbol roundf")
set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn")
set(LIBC_HAS_SCALBNF "1" CACHE INTERNAL "Have symbol scalbnf")
set(LIBC_HAS_SETENV "1" CACHE INTERNAL "Have symbol setenv")
set(LIBC_HAS_SIGNAL_H "1" CACHE INTERNAL "Have include signal.h")
set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin")
set(LIBC_HAS_SINF "1" CACHE INTERNAL "Have symbol sinf")
set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr")
set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt")
set(LIBC_HAS_SQRTF "1" CACHE INTERNAL "Have symbol sqrtf")
set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf")
set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h")
set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h")
set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h")
set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h")
set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h")
set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h")
set(LIBC_HAS_STRCASESTR "1" CACHE INTERNAL "Have symbol strcasestr")
set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr")
set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp")
set(LIBC_HAS_STRINGS_H "1" CACHE INTERNAL "Have include strings.h")
set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h")
set(LIBC_HAS_STRLCAT "1" CACHE INTERNAL "Have symbol strlcat")
set(LIBC_HAS_STRLCPY "1" CACHE INTERNAL "Have symbol strlcpy")
set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen")
set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp")
set(LIBC_HAS_STRNLEN "1" CACHE INTERNAL "Have symbol strnlen")
set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr")
set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk")
set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr")
set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr")
set(LIBC_HAS_STRTOD "1" CACHE INTERNAL "Have symbol strtod")
set(LIBC_HAS_STRTOK_R "1" CACHE INTERNAL "Have symbol strtok_r")
set(LIBC_HAS_STRTOL "1" CACHE INTERNAL "Have symbol strtol")
set(LIBC_HAS_STRTOLL "1" CACHE INTERNAL "Have symbol strtoll")
set(LIBC_HAS_STRTOUL "1" CACHE INTERNAL "Have symbol strtoul")
set(LIBC_HAS_STRTOULL "1" CACHE INTERNAL "Have symbol strtoull")
set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h")
set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan")
set(LIBC_HAS_TANF "1" CACHE INTERNAL "Have symbol tanf")
set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h")
set(LIBC_HAS_TRUNC "1" CACHE INTERNAL "Have symbol trunc")
set(LIBC_HAS_TRUNCF "1" CACHE INTERNAL "Have symbol truncf")
set(LIBC_HAS_UNSETENV "1" CACHE INTERNAL "Have symbol unsetenv")
set(LIBC_HAS_VFORK "1" CACHE INTERNAL "Have symbol vfork")
set(LIBC_HAS_VSNPRINTF "1" CACHE INTERNAL "Have symbol vsnprintf")
set(LIBC_HAS_VSSCANF "1" CACHE INTERNAL "Have symbol vsscanf")
set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h")
set(LIBC_HAS_WCSCMP "1" CACHE INTERNAL "Have symbol wcscmp")
set(LIBC_HAS_WCSDUP "1" CACHE INTERNAL "Have symbol wcsdup")
set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat")
set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy")
set(LIBC_HAS_WCSLEN "1" CACHE INTERNAL "Have symbol wcslen")
set(LIBC_HAS_WCSNCMP "1" CACHE INTERNAL "Have symbol wcsncmp")
set(LIBC_HAS_WCSNLEN "1" CACHE INTERNAL "Have symbol wcsnlen")
set(LIBC_HAS_WCSSTR "1" CACHE INTERNAL "Have symbol wcsstr")
set(LIBC_HAS_WCSTOL "1" CACHE INTERNAL "Have symbol wcstol")
set(LIBC_HAS_WCSTOLL "1" CACHE INTERNAL "Have symbol wcstoll")
set(LIBC_HAS_WCSTOUL "1" CACHE INTERNAL "Have symbol wcstoul")
set(LIBC_HAS_WCSTOULL "1" CACHE INTERNAL "Have symbol wcstoull")
set(LIBC_HAS__EXIT "1" CACHE INTERNAL "Have symbol _Exit")
set(LIBC_HAS__I64TOA "" CACHE INTERNAL "Have symbol _i64toa")
set(LIBC_HAS__LTOA "" CACHE INTERNAL "Have symbol _ltoa")
set(LIBC_HAS__STRREV "" CACHE INTERNAL "Have symbol _strrev")
set(LIBC_HAS__ULTOA "" CACHE INTERNAL "Have symbol _ultoa")
set(LIBC_HAS__WCSDUP "1" CACHE INTERNAL "Have symbol _wcsdup")
set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__")
set(HAVE_GCC_WALL "1" CACHE INTERNAL "Test HAVE_GCC_WALL")
set(HAVE_GCC_WUNDEF "1" CACHE INTERNAL "Test HAVE_GCC_WUNDEF")
set(HAVE_GCC_WFLOAT_CONVERSION "1" CACHE INTERNAL "Test HAVE_GCC_WFLOAT_CONVERSION")
set(HAVE_GCC_NO_STRICT_ALIASING "1" CACHE INTERNAL "Test HAVE_GCC_NO_STRICT_ALIASING")
set(HAVE_GCC_WDOCUMENTATION "1" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION")
set(HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND "1" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND")
set(HAVE_GCC_COMMENT_BLOCK_COMMANDS "1" CACHE INTERNAL "Test HAVE_GCC_COMMENT_BLOCK_COMMANDS")
set(HAVE_GCC_WSHADOW "1" CACHE INTERNAL "Test HAVE_GCC_WSHADOW")
set(HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS "1" CACHE INTERNAL "Test HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS")
set(HAVE_GCC_WIMPLICIT_FALLTHROUGH "1" CACHE INTERNAL "Test HAVE_GCC_WIMPLICIT_FALLTHROUGH")
set(HAVE_GCC_FVISIBILITY "" CACHE INTERNAL "Test HAVE_GCC_FVISIBILITY")
set(HAVE_ST_MTIM "1" CACHE INTERNAL "Test HAVE_ST_MTIM")
set(HAVE_LD_VERSION_SCRIPT "1" CACHE INTERNAL "Test HAVE_LD_VERSION_SCRIPT")
set(HAVE_WL_VERSION_SCRIPT "1" CACHE INTERNAL "Test HAVE_WL_VERSION_SCRIPT")
set(LINKER_SUPPORTS_VERSION_SCRIPT "1" CACHE INTERNAL "Test LINKER_SUPPORTS_VERSION_SCRIPT")
set(LINKER_SUPPORTS_WL_NO_UNDEFINED "1" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED")
set(ICONV_IN_LIBC "1" CACHE INTERNAL "Test ICONV_IN_LIBC")
set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV")
set(HAVE_GETPAGESIZE "1" CACHE INTERNAL "Have symbol getpagesize")
set(HAVE_SIGACTION "1" CACHE INTERNAL "Have symbol sigaction")
set(HAVE_SA_SIGACTION "1" CACHE INTERNAL "Have symbol sa_sigaction")
set(HAVE_SETJMP "1" CACHE INTERNAL "Have symbol setjmp")
set(HAVE_NANOSLEEP "1" CACHE INTERNAL "Have symbol nanosleep")
set(HAVE_GMTIME_R "1" CACHE INTERNAL "Have symbol gmtime_r")
set(HAVE_LOCALTIME_R "1" CACHE INTERNAL "Have symbol localtime_r")
set(HAVE_NL_LANGINFO "1" CACHE INTERNAL "Have symbol nl_langinfo")
set(HAVE_SYSCONF "1" CACHE INTERNAL "Have symbol sysconf")
set(HAVE_SYSCTLBYNAME "" CACHE INTERNAL "Have symbol sysctlbyname")
set(HAVE_GETAUXVAL "1" CACHE INTERNAL "Have symbol getauxval")
set(HAVE_ELF_AUX_INFO "" CACHE INTERNAL "Have symbol elf_aux_info")
set(HAVE_POLL "" CACHE INTERNAL "Have symbol poll")
set(HAVE_MEMFD_CREATE "1" CACHE INTERNAL "Have symbol memfd_create")
set(HAVE_POSIX_FALLOCATE "1" CACHE INTERNAL "Have symbol posix_fallocate")
set(HAVE_DLOPEN_IN_LIBC "1" CACHE INTERNAL "Have symbol dlopen")
set(HAVE_GETHOSTNAME "1" CACHE INTERNAL "Have symbol gethostname")
set(HAVE_SIGTIMEDWAIT "1" CACHE INTERNAL "Have symbol sigtimedwait")
set(HAVE_PPOLL "1" CACHE INTERNAL "Have symbol ppoll")
set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR "" CACHE INTERNAL "Have symbol addchdir")
set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP "" CACHE INTERNAL "Have symbol addchdir_np")
set(HAVE_FDATASYNC "1" CACHE INTERNAL "Have symbol fdatasync")
set(HAVE_GETRESUID "1" CACHE INTERNAL "Have symbol getresuid")
set(HAVE_GETRESGID "1" CACHE INTERNAL "Have symbol getresgid")
endfunction()
endif()

View File

@@ -425,7 +425,7 @@ function(SDL_PrintSummary)
message(STATUS "")
endif()
if(UNIX AND NOT (ANDROID OR APPLE OR EMSCRIPTEN OR HAIKU OR RISCOS OR DJGPP OR CYGWIN))
if(UNIX AND NOT (ANDROID OR APPLE OR EMSCRIPTEN OR HAIKU OR RISCOS OR DJGPP OR CYGWIN OR OHOS))
if(NOT (HAVE_X11 OR HAVE_WAYLAND))
if(NOT SDL_UNIX_CONSOLE_BUILD)
message(FATAL_ERROR

View File

@@ -184,6 +184,11 @@ function(SDL_AddCommonCompilerFlags TARGET)
if(USE_TCC)
sdl_target_compile_option_all_languages(${TARGET} "-DSTBI_NO_SIMD")
endif()
if(OHOS) # OpenHarmony/HarmonyOS
# Stop the `clang: warning: argument unused during compilation: '--gcc-toolchain=$X/sdk/default/openharmony/native/llvm' [-Wunused-command-line-argument]` on every file compiled. I think this is a toolchain file issue...?
sdl_target_compile_option_all_languages(${TARGET} "-Wno-unused-command-line-argument")
endif()
endfunction()
function(check_x86_source_compiles BODY VAR)

872
docs/README-harmonyos.md Normal file
View File

@@ -0,0 +1,872 @@
# HarmonyOS
(And also OpenHarmony.)
## What is HarmonyOS?
(This is my current understanding, please correct these docs if incorrect.)
HarmonyOS is an operating system for tablets and phones. It is developed by
[Huawei](https://www.huawei.com/en/). It is competitive with platforms like
Android and iOS, capable of powering high-end phones. Unlike Google and Apple,
Huawei is a Chinese company, and HarmonyOS is largely serving the Chinese
market at this time.
The system uses some custom pieces, but also relies on open source software
from outside of the project, such as NodeJS, musl, etc.
Much of the platform is open source, under the name "OpenHarmony," and Huawei's
proprietary spin of OpenHarmony is called HarmonyOS. Huawei is the primary
contributor to OpenHarmony.
So it is probably best said: HarmonyOS is to Android what OpenHarmony is to
AOSP. But, to be clear, OpenHarmony is not derived from Android.
This document will generally refer to HarmonyOS, but from a development
viewpoint, you are actually targeting OpenHarmony, even if you're building
with intent to only target HarmonyOS on Huawei-produced phones.
## Supported versions
All testing to date has been done on a real phone, a Huawei Mate 80. SDL
currently supports HarmonyOS 5.0.4 (API level 16) and later, although low-end
testing hasn't been done so there might be little changes needed for extreme
compatibility. Report bugs or send patches if this becomes a problem.
## Some basic target platform truths
More or less, an app on HarmonyOS is a NodeJS process, in the same way that
an app on Android is a Java virtual machine process. While one generally
writes apps in Java or Kotlin for Android, HarmonyOS uses a TypeScript
variant called ArkTS. However, like SDL on Android, we will be focusing on
native code in C or C++, so SDL makes moves similar to its Android strategy,
migrating from ArkTS to native code as quickly as possible and handing control
over to a (presumably C or C++) native code app, and making it so the app can
entirely opt out of interacting with the ArkTS layer directly.
The equivalent of the Android "JNI" layer is NodeJS's NAPI interfaces. A large
portion of the system APIs are accessible directly from OS-provided native
libraries, so there is a lot less reason to dip back down into the ArkTS layer
for most tasks, whereas lots of things on Android need to talk to Java-only
interfaces to accomplish common and important tasks, even in an
otherwise-entirely native program.
The C/C++ compiler is based on Clang (you might end up using a compiler called
"BiSheng", which is Clang with some custom optimization techniques developed
by Huawei, but the binary is still called "clang").
The platform triplet is "aarch64-linux-ohos" (other CPU architectures are
possible, but this is likely the most common. The
devtools mention 32-bit ARM and x86-64, too). The CMake toolchain file sets
the variable `OHOS` (OpenHarmony OS) to "OHOS", so `if(OHOS)` works. Further,
`CMAKE_SYSTEM_NAME` is set to "OHOS". The C preprocessor defines `__OHOS__`.
## Getting hardware to run HarmonyOS
(This information is accurate as of September 2026, but may be out of date at
any moment, so please check other sources before assuming this is still
correct.)
HarmonyOS phones work in America (at least on WiFi; presumably one could get a
working (e)SIM card for phone calls and cell network access, but I haven't
tried). It may be difficult to obtain a phone: I couldn't find one for
purchase on Amazon or eBay from the United States. You might have an easier
time in other countries. I assume it is super-easy in China. :)
One can run OpenHarmony (not HarmonyOS) on a Raspberry Pi 4b, compliments of
the Oniro project, which provides both source code and prebuilt images for
download:
https://docs.oniroproject.org/device-development/developer-boards/raspberry-pi-4model-b/
This lets you run the open source variant of the OS on real hardware, but
obviously a Raspberry Pi isn't a tablet or a phone, so the experience is
somewhat tangential to the the most common configuration one might expect in
the field, but it _does_ work.
DevEco Studio offers phone, tablet, and smartwatch emulators for Huawei
hardware, and these apparently run on Windows, macOS and Linux, but will not
let you download them outside of mainland China (except for smartwatch
emulators, for some reason). Comments on Huawei's forums suggest relaxing this
restriction is planned, but no timeline has been announced.
## Get HarmonyOS development tools
You will need to download Huawei's developer tools. They offer a full IDE,
called "DevEco Studio," but this is not strictly necessary for compiling SDL
itself.
There are also command line tools available for Windows, macOS, and Linux, and
these _are_ sufficient to compile SDL. For building full applications, you
likely need DevEco Studio to at least set up your project and manage signing
keys, etc. Some things (but not all) are doable from the command line tools,
and a handful of things are (currently) not.
DevEco Studio, the GUI, is only available for Windows and macOS. It appears to
be a fork of IntelliJ IDEA. It does not appear to be open source, afaict.
To download the developer tools, you'll need to register an account at
[the Huawei developer site](https://developer.huawei.com/en/). Then visit
[the tool download page](https://developer.huawei.com/consumer/en/download/)
For Linux, scroll down to "Command Line Tools." Download for your platform,
which should be a simple .zip file, and unpack it. If you're on Windows or
macOS, download and install DevEco Studio itself while you're here, too.
## Don't fight against DevEco Studio
I'm a Linux user, and I avoid programming IDEs in general, so my instinct is
to just get everything done with my text editor and the command line tools,
and this _feels_ possible, but I haven't quite figured it out. The system is
friendly to things like CMake, clang, etc, and the documentation, even in
English, is pretty thorough, but I still couldn't get there without using
DevEco Studio a little.
The system appears designed to at least hand off a working project from
DevEco Studio to a Linux machine for building and deploying from the command
line tools, for Continuous Integration if nothing else. Figuring out how to
eliminate the Windows/Mac GUI dependency entirely is an ongoing and unresolved
concern. I definitely needed it to manage signing keys. Once I got that far, I
was able to copy the appropriate directories to a Linux system and use the
`hvigorw` command line tool to build and sign an app, and `hdc` to install it
on a real phone. Please see "The Helper Script" section, below, to automate
a lot of the Linux command line pieces, too.
## Install command line tools.
(If you're entirely focused on DevEco Studio, you can probably skip this.)
I unzipped the downloaded command line tools so they're sitting in
`$HOME/HarmonyOS-SDK/command-line-tools`, but anywhere you want to put it is
fine.
When this document refers to this directory, it'll call it `$CLT` for short.
Add some things to your PATH, so you can find `hdc` and `hvigorw`, etc.
```bash
export PATH=$PATH:$CLT/sdk/default/openharmony/toolchains:$CLT/bin
```
## Putting a HarmonyOS phone in developer mode.
If you want to test and debug your SDL apps natively on a real phone directly,
you have to put the device in Developer Mode. As the name implies, this is not
necessary to install published apps, but only apps under development locally.
Any retail phone running HarmonyOS should be able to do this. This is almost
identical to how one would do this for local Android development.
On the phone, go to Settings, Device Name (it's "Mate 80" on mine, in a box by
itself near the top of the main settings page). This will bring up an "About"
page. Scroll down to "Software version" and tap the version number seven times
quickly. The phone will pop up a dialog asking if you want to enable developer
mode and reboot. Say yes. If you've already done this, it will tell you so.
After reboot, go into Settings, System. There will be a new "Developer options"
section at the bottom. Turn on USB debugging. Plug in a USB cable to your
computer, and choose to trust the connection (and choose "use USB to transfer
files" if given the option, otherwise it will only allow charging over USB).
At this point, you should be able to use "hdc" somewhat like you would use
"adc" on an Android device:
See if the phone is visible/accessible:
```bash
hdc list targets -v
```
You can also connect the phone over Wifi on the same LAN and not use USB. If
you want to try, turn on "Wireless debugging" in Developer Options, note the
IP address and port, and run:
```bash
hdc tconn 192.168.1.104:40317
```
Where the IP address and port match the phone.
(DevEco Studio: add the device by IP address and port by going to "Tools" ->
"IP Connection" on the menu bar. This appears to be IPv4 only at the moment,
but it successfully connected to the phone. This was a good option, because
for whatever reason the phone's USB connection would continually drop and
reconnect on my VirtualBox Windows 10 VM. But YMMV!)
## HarmonyOS App Development with SDL
### Create a new project
Copy the "openharmony-project" directory out of the root of SDL's source tree
to where you want your project to live, and name it appropriately
("mygame-openharmony" or whatever). Then, in that new directory:
- in AppScope/app.json5, change at least "bundleName" and "vendor" to match
your project.
- in AppScope/resources/base/element/string.json, change "app_name"'s value to
the name of your app.
- in build-profile.json5, you need to add a signingConfig (this is probably
doable without the GUI, but I have not entirely solved how to do this outside
of DevEco Studio yet).
Do not create a new project in DevEco Studio, since there are pieces of ArkTS
scripting that need to be set up correctly for SDL, and that code is already
present in openharmony-project. You _should_ be able to open the project in
DevEco Studio once you've set it up.
### Add source code to your new project.
Native code building uses CMake, whether you use DevEco Studio or not. This
makes integrating SDL into your project easy!
Edit the text file entry/src/main/cpp/CMakeLists.txt inside your new project
directory and change this line:
```cmake
add_library(main SHARED myapp.c)
```
"myapp.c" should be replaced with any source files that comprise your app.
This is a standard CMake project file, so you can get as fancy as you want
or just have a list of files separated by spaces.
These files do not have to be in this directory; they can live outside of
the project directory entirely, as long as you specify the correct directory
to reach them.
You must either copy the SDL source tree into entry/src/main/cpp, or make a
symlink to it, or edit the `add_subdirectory("SDL")` line to point to it.
This line makes SDL's source compile with the rest of the project. The
directory you point to must be the root of an SDL clone, the one that holds
CMakeLists.txt, src, and include.
Your app _must_ compile to a shared library named "libmain.so" ... the
"add_library" line does this. Do not build an executable! It must be a
shared library, due to how HarmonyOS deals with apps. When you're done
building, you should have a libmain.so and a libSDL3.so file.
### Customize your project
If you need some specific permissions (microphone/camera access, internet
access, etc), you must list them in
openharmony-project/entry/src/main/module.json5 ...some of these are
automatically granted on installation, some will require the user to
approve them on first use at runtime, but all of them must be listed. See
the "requestPermissions" section of this file for examples. There are more
permissions available than are listed there!
You must set up some basic strings, before publishing your app, or you will
either have an app icon with name "label" or fail certification because you
didn't explain clearly why you need various permissions.
These are in a JSON file in:
openharmony-project/entry/src/main/resources/base/element/string.json
You can do localizations of these strings by making a second copy of the
file, where "base" is replaced with the proper localization ("en_GB" or
whatever). See the documentation at:
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/resource-categories-and-access
Static data files that ship with your app go in the directory:
openharmony-project/entry/src/main/resources/rawfile/
The file tree under this directory will be packaged up with the app and made
available through SDL APIs (see "Filesystem" and "IOStream" sections, below).
### Get a HarmonyOS Debug Certificate
You need to sign apps to run them on a real device, even for local debugging
and testing and not just deployment and release.
For now, just open your project in DevEco Studio, then in the menu bar, go
to "File", "Project Structure", "Signing Configs", "Automatically generate
signature". Log in to your Huawei developer account if needed. Let it do the
work. Later, I copied the project's directory and ".ohos" dir in my home
directory to a Linux system, adjusted a few Windows absolute paths in
build-profile.json5, and that was good enough.
### Build your app from the command line
Building your project from DevEco Studio is just a single click, but you
can also build from the command line on macOS, Windows, or Linux.
If you have everything (signing certs, the entire project structure, etc) in
place, you can build an app from the command line. The HarmonyOS equivalent of
Android's `gradlew` command is `hvigorw`. Go to the root folder of the project
and run:
```bash
hvigorw assembleHap
```
If everything went well (AND IT OFTEN DOES NOT, READ THE OUTPUT!), you'll have
a signed HarmonyOS app bundle (a ".hap" file) you can install to a device.
There is a Bash shell script to automate this as much as possible: see the
"The Helper Script" section, below.
### Install on real hardware from the command line.
Make sure the phone is connected (run `hdc list targets -v` to verify).
```bash
# (or whatever the directory and .hap file are named.)
hdc install ./entry/build/default/outputs/default/entry-default-signed.hap
```
There is a Bash shell script to automate this as much as possible: see the
"The Helper Script" section, below.
### Debugging
If you just want to do "printf debugging," then SDL_Log() will write to the
system's "hilog" output, and you can use `hdc hilog |grep -F "APPBUNDLENAME"`
to find your app's output in the absolute firehose of text that is produced
through that interface.
One can use a real GUI debugger through DevEco Studio without a lot of drama,
and one can also use lldb from the command line with "remote debugging." The
latter option takes a lot of manual setup, and additionally you have to be
familiar with LLDB's text interface to find this useful, but it _can_ be done
from Linux, where DevEco Studio isn't available (and also Windows and macOS,
if all you have are the command line tools).
There is a Bash shell script to automate this as much as possible: see the
"The Helper Script" section, below.
The instructions on setup from scratch, without the shell script, are here:
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/debug-lldb#remote-debugging
The only thing that document fails to mention is that the PID number you need
for LLDB's `attach` command can be obtained by running `hdc shell ps` and
looking for your app's bundle ID.
### The Helper Script
There is a Bash shell script to simplify several tasks, if you're using the
command line tools. This is not necessary from DevEco Studio. This has only
been tested on Linux.
Make sure your PATH is set up (see "Install Command Line Tools," above), and
run this command:
```bash
SDL/build-scripts/harmonyos-tool.sh openharmony-project --build --debug
```
"openharmony-project" is literally the path to your project file. It can be a
relative path, including "." if you are sitting in the directory.
All the other command line arguments are optional, and can be used in any
combination (for example, you can --launch without --build'ing, or --install
and --debug in one run, etc.)
If you specify --log, the script will fire up `hdc hilog` in the background
and filter out everything but your app bundle's log calls, so you can watch
the log while other tasks complete. It will start this once passed the
--install stage (even if you didn't install), so you are watching it while
launching/debugging begins. After all other tasks are done, harmonyos-tool.sh
won't terminate until either the `hdc` process terminates or the user hits
CTRL-C (rather, until SIGINT is delivered, which CTRL-C does). The script will
kill the `hdc` process in that case before terminating itself.
If you specify --clean, the script will clean up any build artifacts from
previous compiles, etc. This will run before --build, so you can specify
`--clean --build` for a complete rebuild.
If you specify --build, the script will compile C/C++ code and assemble the
.hap file to be run on a device or emulator.
If you specify --install, the script will (re)install the .hap on a device
(which will kill the process if it's currently running).
If you specify --uninstall, the script will remove the existing app on a
device (which will kill the process if it's currently running).
If you specify --launch, the script will launch the app on a device, if it
isn't already running.
If you specify --debug, the script will prepare remote debugging via lldb,
and attach to the running app.
If you specify --kill, the script will force-stop any existing process.
(--debug implies --launch, "--build --launch" and "--build --debug" imply
--install.)
Most of these tasks obviously need a phone that hdc can talk to.
Note that debugging always attaches to a process, so if you need to debug
something at startup, plan to add a sleep to the start of the program, as it
can take several seconds for the attachment to complete. It isn't clear to me
how to launch an app from the debugger so you can break at startup, but there
is probably a way.
Also note that this script is fragile in general; be gentle, report bugs, send
patches.
## SDL/HarmonyOS subsystem details
Specifics of actually using SDL on HarmonyOS follow.
### SDL platform defines
SDL will define SDL_PLATFORM_OPENHARMONY and SDL_PLATFORM_UNIX. Do be cautioned
that while HarmonyOS has some Linux kernel compatibility interfaces, it is
_not_ comparable to a "normal" Linux system in almost any way, so
SDL_PLATFORM_LINUX is not defined (SDL for Android, which literally uses the
Linux kernel, follows this same convention inside SDL). HarmonyOS devices
don't use the actual Linux kernel (they use a microkernel named "HongMeng"),
but OpenHarmony devices that aren't specifically HarmonyOS _might_ use a Linux
kernel. Don't make assumptions.
### Dynamic API
Presumably users cannot override the SDL build in their phone's apps, so the
Dynamic API is disabled on this platform.
### Log
SDL_Log() and friends will push through hilog, so you can view the output
from the `hdc hilog` command. Note that logs are preformatted strings by the
time they hit hilog, so things like "%s" are already processed, and thus
there is no `{public}` and `{private}` format modifiers available like there
would be if calling into hilog's APIs directly.
Output from `hdc hilog` for `SDL_Log("Hello world!");` will look something like:
```
06-29 22:22:02.953 25275 25275 I A00000/org.libsdl.loopwave/SDL/APP: Hello world!
```
This is kind of chatty, but not unlike Android's `adc logcat` output.
These logs are viewable to end-users, even in release builds, if they have a
phone in Developer Mode that can talk to `hdc`, so be careful what you log!
Also plan to use "grep" to find relevant logs; `hdc hilog` is an endless
waterfall of unrelated data that you _will_ get lost in immediately.
### Main
HarmonyOS has a fairly complicated startup sequence (described later in this
document). Apps for this platform should use the "main callbacks" (with
`#define SDL_USE_MAIN_CALLBACKS`) instead of an ANSI C style "main" entry
point, as that fits the app design paradigm of OpenHarmony.
However, if you build an app with a standard "main" function, SDL will notice
this, spin a thread, and attempt to call that function from the new background
thread, and call exit() when it returns. This _happens_ to work, at least for
simple test cases that do rendering and touch input, but one uses this path at
their own risk, as several important things might result in unexpected race
conditions and unexpected behavior, possibly in a later version of the OS. It
is strongly recommended that you migrate to the Main Callbacks!
### Filesystem
HarmonyOS uses '/' as a path separator, like Unix-style (etc) systems.
SDL_GetBasePath() returns "assets://" unconditionally (see IOStream section,
below). SDL_GetPrefPath() returns a path under the string returned from
OH_AbilityRuntime_ApplicationContextGetFilesDir(), which in practice looks
like "/data/storage/el2/base/files/APPNAME" ... this looks like an absolute
path, but HarmonyOS maps this to an app-and-user-specific path behind the
scenes. The prefpath is readable/writable with both SDL_IOStream and "normal"
APIs like fopen().
There is also SDL_GetOpenHarmonyInternalStoragePath(), which returns the base
directory used for SDL_GetPrefPath() without extra subdirs appended to it or
mkdir() calls issued, but generally SDL_GetPrefPath() is preferred as the
more-portable option.
### IOStream
This works like Android: some files are accessible like normal filesystem
things, and can be accessed with fopen(), open(), etc. Other things are
installed with the application (the "HAP"), and they need special APIs to
access, that sort of map to Android's AAssetManager, but a little more
powerful (these are called "RawFile" APIs on HarmonyOS).
Both types of files can be accessed as SDL_IOStreams through SDL_IOFromFile().
SDL uses the same conventions as Android here: if the file path is a relative
path or prefixed with "assets://", it'll use the RawFile APIs. Absolute paths
will use "normal" filesystem APIs. SDL_GetBasePath() returns "assets://", so
one can access files installed with the app with SDL_IOFromFile().
"assets://" is entirely an SDL3 construct; it will not work with fopen(), and
is not understood by the system itself.
### Async I/O
SDL uses the "generic" asyncio backend here (a thread pool that uses
SDL_IOStreams). Not only is there no sort of io_uring-style interface (afaik),
but this also means you can async-load stuff from "assets://" paths without
problems.
### Video
One SDL_Window is allowed at a time and it takes the entire available display
(like Android). Currently we only report a single display (the phone/tablet's
screen).
Setting an SDL_Window to be fullscreen means it will turn off the system
status and navigation bars, allowing the window to use those extra pixels. A
non-fullscreen window leaves those bars in place, letting users see the
current time, battery/network/etc status, and other icon things.
Touch events work, and multitouch is supported. Mouse events (from an external,
physical mouse) are supported. Physical keyboard input works, and enabling
text input will pop up a screen keyboard.
Clipboard is supported for text. SDL_SetClipboardText() works as expected
and other apps will see the text. However, an app needs the
`ohos.permission.READ_PASTEBOARD` permission to obtain data from the system
clipboard, and this is a _restricted_ permission: you must list it in
modules.json5, prompt the user to approve it beforehand by calling
SDL_RequestOpenHarmonyPermission(), and also the app developer must
explicitly apply for permission from Huawei through AppGallery Connect!
Consider if you actually need to read clipboard data into your app that badly.
SDL_GetSystemTheme() and SDL_EVENT_SYSTEM_THEME_CHANGED both work.
### Render/GPU
OpenGL ES 3 is supported, as is Vulkan. Desktop OpenGL is supported by
the platform, too, but for now support is disabled within SDL itself as likely
superfluous (until it is not, open an issue if you need it). EGL is used to
manage GL contexts internally, and a HarmonyOS-specific Vulkan extension is
provided to get a surface.
Like other platforms, we favor OpenGL ES 2 for the 2D renderer; Vulkan and "GPU"
also works, but SDL's GLES is assumed to be more trusted at the current moment,
and historically GLES has been more solid on mobile devices than Vulkan, but
HarmonyOS-based phones might have significantly better Vulkan support than
other comparable smartphones; this is yet to be determined.
Since Vulkan works, the SDL3 GPU API is available and operational, in addition
to the hardware-accelerated 2D Renderer API.
### Audio
HarmonyOS support OpenSL ES, but like on Android, it is deprecated. As our
OpenSL ES backend is heavy with Android-specific code, it is not used on
HarmonyOS. There is a new backend using HarmonyOS's OHAudio API. This is
almost exactly like Android's AAudio API.
Recording works, but you must request `ohos.permission.MICROPHONE` permission
before it will work. You must list it in modules.json5, and prompt the user to
approve it beforehand by calling SDL_RequestOpenHarmonyPermission(). Once
permission is granted, recording audio devices can be opened as usual, and
this permission remains for future runs, unless the user revokes in in the
system preferences later. On HarmonyOS, if a user denies permission, they will
never get the popup request dialog again, and must go and grant permission
through the system preferences. Plan accordingly if you want your UI to
explain this to users.
Note that SDL for Android currently requests permission from the user on
behalf of the app when opening a recording device, and blocks until the user
approves or denies the request, but on HarmonyOS this is not done. This may
change in the future, but for now the app should handle requesting permission
before opening the device. If a future revision of SDL tries to manage the
permission, it won't hurt anything if the app has already taken care of it.
### Power
Battery percent, and whether the device is plugged in, are reported.
### Thread
OpenHarmony uses pthreads, and are fully supported.
### LoadSO
HarmonyOS uses ELF shared libraries and has a POSIX-style dlopen() mechanism,
so SDL_LoadObject() works as expected.
### Tray
The Tray API uses the "dummy" backend as this functionality doesn't make sense
on a phone.
### Time
The Time and Timer subsystems use the usual Unix implementations and work fine.
### Locale
SDL_GetPreferredLocales() returns a single locale, as specified in System
Preferences -> "System" -> "Language & Region". In my case, I have it set to
English/United States, so SDL reports "en_US".
SDL will send SDL_EVENT_LOCALE_CHANGED events if the user changes their locale
in the system preferences while the app is running.
### Camera
Camera access works. You must request the `ohos.permission.CAMERA` permission
in modules.json5. SDL will handle the UI to prompt the user for permission
during SDL_OpenCamera() if necessary, so the app doesn't have to manage this
further (unlike using a microphone in the audio API, SDL's camera API was
built on the idea that the user's permission or refusal might be coming at a
later time).
### Misc
SDL_OpenURL() works, and will launch the appropriate app ("ability") for
whatever protocol is specified: "https://", etc, URLs will bring up a web
browser, custom URL schemes can launch specific apps, etc.
### STILL TODO
- Message boxes
- Assert
- Dialog
- Haptic
- HIDAPI
- Joystick
- Process
- Sensor
- Storage
- Pen (tablets work over USB-C on the phone, but this isn't wired up in SDL at all)
- SDL_EVENT_DISPLAY_ORIENTATION (window resizes when phone orientation changes, this event doesn't fire yet, though)
- Can external displays be used over USB-C => HDMI? (yes, at least for mirroring)
- SDL_HINT_ORIENTATION (for runtime control; but you _can_ explicitly set the "orientation" setting in module.json5 for now if you need a specific global, unchanging setting.)
## Building SDL for HarmonyOS
(Skip this if you are developing an SDL-based app and not SDL itself. There's
a different section of this document for you, later.)
SDL for HarmonyOS is built with CMake, like other platforms. Please note that
you _must_ use the CMake that comes with HarmonyOS's development tools, or
building will fail, as standard CMake does not recognize this platform at all
currently.
Generate a project file for building SDL. We use Ninja, because it's a fast
command line tool that will maximize multi-CPU compiling, but it's probably
possible to generate a real Visual Studio, Xcode, etc, project.
```bash
cd SDL
mkdir buildbot-ohos # or whatever you want to call the build directory.
cd buildbot-ohos
$CLT/sdk/default/openharmony/native/build-tools/cmake/bin/cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=$CLT/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DOHOS_COMPATIBLE_SDK_VERSION=16.0.0 ..
```
(One can also add -DOHOS_ARCH=cputype, where "cputype" is "arm64-v8a" for ARM64, "armeabi-v7a" for ARM32, and "x86_64" Intel 64-bit. It defaults to ARM64 if unspecified.)
If all went well, you should be able to build SDL for HarmonyOS now. In our
case, build with Ninja:
```bash
ninja
```
Or use whatever instructs Xcode/Visual Studio/etc to build things.
## Startup sequence
(You can probably skip this section if you just want to target HarmonyOS with
an SDL3-based app and not delve into the low-level details.)
Starting an SDL app on HarmonyOS works differently than any other platform,
but efforts have been made to _hide_ most of this from the C programmer, so
this should mostly feel like any other SDL app they have built in C before.
This is how startup works:
(Exact paths might vary, and maybe this will simplify later.)
- The actual application entry point is in ArkTS, in the source file
`$PROJECT/entry/src/main/ets/entryability/EntryAbility.ets`.
- This file defines a class, EntryAbility, that extends UIAbility, and
also imports the native SDL library.
- By now, libSDL3.so has been loaded. In a shared library constructor named
SDL_RegisterNativeInterfaces, it uses NAPI to register a native module. Once
this module initializes, we'll be able to call into SDL's C code from ArkTS
through interfaces we defined in SDL_Init_Native_Interfaces(). This is in
SDL/src/core/openharmony/SDL_openharmony.c. This will _also_ hook into
XComponent's API, to register some event callbacks.
- EntryAbility has an overridden method named onCreate. When this
runs, the app ("Ability" in HarmonyOS terms) is starting up. This function
calls into the sdl module we registered to hand it a few objects (including
the EntryAbility instance, and some small things that are trivial to create
in ArkTS.
- SDL, now in native code, will save off those objects as appropriate, and
also monkey-patch EntryAbility, so that it now has several overridden
methods that only exist in native code. This lets us deal with ArkTS-only
interfaces on EntryAbility that the system intends to interact with, without
providing any ArkTS scripting for them.
- EntryAbility now has an overridden method named onWindowStageCreate. When this
runs, the system is ready for the app to display to the screen. This
method, in C, calls `windowStage.loadContent('pages/Index', ...)`.
- This causes the app to load `$PROJECT/entry/src/main/ets/pages/Index.ets`.
- Index.ets specifies a layout for the window/view/whatever. We simply have a
single row and column containing a single "XComponent" which is more or
less a UI widget backed by native code.
- When the XComponent's OnSurfaceCreated callback fires (landing in
SDL_XComponent_OnSurfaceCreatedCallback()), we are then ready to hand control
to the actual C application. This starts in
SDL_OpenHarmonyMainSurfaceCreated(), which eventually lands in
RunAppOpenHarmonyMain().
- Here we load libmain.so, which is where the app's actual C code lives.
If we see if libmain contains a symbol named "SDL_main", we spin a thread
and call into that symbol from the new thread as a standard ANSI C "main"
entry point, and exit() the process when it returns (which looks like a
crash on HarmonyOS, so please don't return.)
- If there was no symbol called "SDL_main", we look for the Main Callbacks
symbols in libmain.so: SDL_AppInit, SDL_AppIterate, etc. If found, we will
call libmain.so's SDL_AppInit() and respond appropriately. If not found, we
panic and terminate the process, since we're out of options and something
was obviously built incorrectly.
- Later, whenever the XComponent fires its OnFrame callback, we will call
SDL_OpenHarmonyOnFrameCallback(), which calls libmain.so's SDL_AppIterate(),
if we ended up using the Main Callbacks.
## Incomplete notes
Everything below here is incomplete, in-progress, and maybe incomprehensible.
You can stop reading now, if you like.
- A lot of how to get started with native code and rendering was gleaned from
https://gitee.com/harmonyos_samples/ndk-opengl and then trying to find
various symbols used in there in the HarmonyOS documentation.
- https://github.com/openharmony/app_samples/ETSUI/XComponent, seems to do
a lot of the native startup without the windowStage nonsense. Not clear if
this still works, or is safe to do, but it _would_ simplify things a little.
### Android mappings
These are not direct mappings (the command lines are not identical, etc).
- "adc" command line tool: "hdc"
- "adc logcat": "hdc hilog"
- "gradlew": "hvigorw"
### Get a HarmonyOS Debug Certificate without DevEco Studio
This is a giant dump of notes for doing this manually, which I didn't _quite_
manage to get working. Huawei has docs on this, which I've sort of half limped
through.
For now, just create your project in DevEco Studio,
then in the menu bar, got to "File" -> "Project Structure" ->
"Signing Configs" -> "Automatically generate signature". Log in to your Huawei
developer account if needed. Let it do the work. Later, I copied the project's
directory and ".ohos" dir in my home directory to a Linux system, adjusted a
few Windows absolute paths in build-profile.json5, and that was good enough.
(Ignore these notes if you aren't improving them.)
If you aren't using DevEco Studio to automatically generate signatures:
You need a signing certificate before you can install apps on a real HarmonyOS
device.
You get a certificate by generating a Certificate Signing Request (a "CSR")
and submitting it to Huawei.
Right now this needs DevEco Studio to generate the CSR file, as a one-time
step. Go into the IDE, click the hamburger menu icon on the top left of the
window, choose the "Build" menu item, and then click "Generate Key and CSR".
In the dialog that pops up, you'll first make a signing key in ".p12" format.
Enter a filename and save path, choose a password for the key. Fill in the
"Alias" which is any short, unique, possibly-ASCII, human readable string.
Ignore the Advanced section.
Click "Next". Now you'll generate the CSR file. The same .p12 file, password
and alias you just generated will be prefilled in the new dialog. Just tell it
where to save the new .csr file. Click "Finish" and you're done with DevEco
Studio for this task.
The Common Name ("CN=" field) on the Certificate Request is what DevEco Studio
refers as an "alias." I put this in as "icculus.org" for mine, but it might
just need to be unique. It's required, all the other fields are not (indeed,
DevEco Studio does not ask for them by default and leaves them blank).
*TODO IN THE FUTURE:* the CSR files are in a standard format, so OpenSSL's
command line utility can _probably_ generate them without having to go through
DevEco Studio, but anything OpenSSL is complicated and needs research.
Dumping the .csr file that DevEco Studio generated (using the command line:
`openssl req -in MyCSRFile.csr -noout -text`) reports the following, so there
are probably OpenSSL incantations to generate your own .p12 and .csr file
directly.
```
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C=, ST=, L=, O=, OU=, CN=[the "Alias" that I entered is here]
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
[hex values for public key are here]
ASN1 OID: prime256v1
NIST CURVE: P-256
Attributes:
Requested Extensions:
X509v3 Subject Key Identifier:
[hex values for key identifier are here]
Signature Algorithm: ecdsa-with-SHA256
Signature Value:
[hex values for signature are here]
```
Once you have a .csr file, go to Huawei's AppGallery Connect portal, and find
the "Certificates, app IDs, and profiles" tab at the top. Click the "New
certificate" button, and upload the .csr file you generated. Give it a name,
set the "Certificate type" to "Debug certificate" and point to your .csr file.
Click "Submit".
If everything went okay, you'll have a certificate listed for download. This
should be an immediate thing, it doesn't need approval on Huawei's side.
Download the .cer file and save it for later.

View File

@@ -9,6 +9,7 @@ SDL3 has been known to work on the following platforms at some point:
- [Emscripten](README-emscripten.md) (Web browsers)
- [FreeBSD](README-bsd.md)
- [Haiku OS](README-haiku.md)
- [HarmonyOS/OpenHarmony](README-harmonyos.md)
- [iOS](README-ios.md)
- [Linux](README-linux.md)
- [macOS](README-macos.md) (10.14 and later)

View File

@@ -257,6 +257,10 @@ typedef struct SDL_IOStream SDL_IOStream;
* to an Android NDK `AAsset *`, that this SDL_IOStream is using to access
* the filesystem. If SDL used some other method to access the filesystem,
* this property will not be set.
* - `SDL_PROP_IOSTREAM_OPENHARMONY_RAWFILE64_POINTER`: a pointer, that can be
* cast to an OpenHarmony/HarmonyOS `RawFile64 *`, that this SDL_IOStream is
* using to access the filesystem. If SDL used some other method to access
* the filesystem, this property will not be set.
*
* \param file a UTF-8 string representing the filename to open.
* \param mode an ASCII string representing the mode to be used for opening
@@ -277,10 +281,11 @@ typedef struct SDL_IOStream SDL_IOStream;
*/
extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, const char *mode);
#define SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER "SDL.iostream.windows.handle"
#define SDL_PROP_IOSTREAM_STDIO_FILE_POINTER "SDL.iostream.stdio.file"
#define SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER "SDL.iostream.file_descriptor"
#define SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER "SDL.iostream.android.aasset"
#define SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER "SDL.iostream.windows.handle"
#define SDL_PROP_IOSTREAM_STDIO_FILE_POINTER "SDL.iostream.stdio.file"
#define SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER "SDL.iostream.file_descriptor"
#define SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER "SDL.iostream.android.aasset"
#define SDL_PROP_IOSTREAM_OPENHARMONY_RAWFILE64_POINTER "SDL.iostream.openharmony.rawfile64"
/**
* Use this function to prepare a read-write memory buffer for use with

View File

@@ -464,6 +464,12 @@ typedef enum SDL_Capitalization
* use, overriding other properties. This is documented at
* https://developer.android.com/reference/android/text/InputType
*
* On HarmonyOS/OpenHarmony you can directly specify the input type:
*
* - `SDL_PROP_TEXTINPUT_OPENHARMONY_INPUTTYPE_NUMBER` - the text input type to
* use, overriding other properties. This is documented at
* https://developer.android.com/reference/android/text/InputType
*
* \param window the window to enable text input.
* \param props the properties to use.
* \returns true on success or false on failure; call SDL_GetError() for more
@@ -489,6 +495,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_StartTextInputWithProperties(SDL_Window *wi
#define SDL_PROP_TEXTINPUT_DEFAULT_TEXT_STRING "SDL.textinput.default_text"
#define SDL_PROP_TEXTINPUT_MAX_LENGTH_NUMBER "SDL.textinput.max_length"
#define SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER "SDL.textinput.android.inputtype"
#define SDL_PROP_TEXTINPUT_OPENHARMONY_INPUTTYPE_NUMBER "SDL.textinput.openharmony.inputtype"
/**
* Check whether or not Unicode text input events are enabled for a window.

View File

@@ -193,6 +193,19 @@
*/
#define SDL_MAIN_EXPORTED
#elif defined(SDL_PLATFORM_OPENHARMONY)
/* On HarmonyOS/OpenHarmony SDL does a lot of work to gain control of the app. */
#define SDL_MAIN_NEEDED
/* As this is launched from ArkTS, the real entry point (main() function)
is outside of the the binary built from this code.
This define makes sure that, unlike on other platforms, SDL_main.h
and SDL_main_impl.h export an `SDL_main()` function (to be called
from ArkTS, sort of), but don't implement a native
`int main(int argc, char* argv[])` or similar.
*/
#define SDL_MAIN_EXPORTED
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
/* On Emscripten, SDL provides a main function that converts URL
parameters that start with "SDL_" to environment variables, so

View File

@@ -49,6 +49,13 @@
/* currently there are no platforms that _need_ a magic entry point here
for callbacks, but if one shows up, implement it here. */
#elif defined(SDL_PLATFORM_OPENHARMONY)
/* HarmonyOS/OpenHarmony: we look up SDL_AppInit/SDL_AppIterate/etc in libmain.so and call them directly.
Since we don't need a `main` entry point, we don't generate an SDL_main implementation here _at all_. */
#ifndef SDL_MAIN_USE_CALLBACKS /* !!! FIXME, remove this later? */
#error SDL3 on OpenHarmony currently requires the SDL Main Callbacks to operate.
#endif
#else /* use a standard SDL_main, which the app SHOULD NOT ALSO SUPPLY. */
/* this define makes the normal SDL_main entry point stuff work...we just provide SDL_main() instead of the app. */

View File

@@ -112,6 +112,17 @@
#undef SDL_PLATFORM_LINUX
#endif
#if defined(__OHOS__)
/**
* A preprocessor macro that is only defined if compiling for OpenHarmony/HarmonyOS.
*
* \since This macro is available since SDL 3.6.0.
*/
#define SDL_PLATFORM_OPENHARMONY 1
#undef SDL_PLATFORM_LINUX
#endif
#if defined(__unix__) || defined(__unix) || defined(unix)
/**

View File

@@ -522,7 +522,7 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void)
extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void);
/**
* Callback that presents a response from a SDL_RequestAndroidPermission call.
* Callback that presents SDL_RequestAndroidPermission() results.
*
* \param userdata an app-controlled pointer that is passed to the callback.
* \param permission the Android-specific permission name that was requested.
@@ -616,6 +616,99 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int para
#endif /* SDL_PLATFORM_ANDROID */
#ifdef SDL_PLATFORM_OPENHARMONY
/**
* Query OpenHarmony API level of the current device.
*
* - API level 20: OpenHarmony 6.0.0
* - API level 18: OpenHarmony 5.1.0
* - API level 16: OpenHarmony 5.0.4
* - API level 15: OpenHarmony 5.0.3
* - API level 14: OpenHarmony 5.0.2
* - API level 13: OpenHarmony 5.0.1
* - API level 12: OpenHarmony 5.0.0
* - API level 11: OpenHarmony 4.1.0
* - API level 10: OpenHarmony 4.0.0
* - API level 9: OpenHarmony 3.2.0
*
* \returns the OpenHarmony API level.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.6.0.
*/
extern SDL_DECLSPEC int SDLCALL SDL_GetOpenHarmonySDKVersion(void);
/**
* Get the path used for internal storage for this OpenHarmony application.
*
* This path is unique to your application and cannot be written to by other
* applications.
*
* Your internal storage path is typically:
* `/data/storage/el2/base/files`.
*
* \returns the path used for internal storage or NULL on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.6.0.
*/
extern SDL_DECLSPEC const char * SDLCALL SDL_GetOpenHarmonyInternalStoragePath(void);
/**
* Callback that presents SDL_RequestOpenHarmonyPermission() results.
*
* \param userdata an app-controlled pointer that is passed to the callback.
* \param permission the OpenHarmony-specific permission name that was requested.
* \param granted true if permission is granted, false if denied.
*
* \since This datatype is available since SDL 3.6.0.
*
* \sa SDL_RequestOpenHarmonyPermission
*/
typedef void (SDLCALL *SDL_RequestOpenHarmonyPermissionCallback)(void *userdata, const char *permission, bool granted);
/**
* Request permissions at runtime, asynchronously.
*
* You do not need to call this for built-in functionality of SDL; recording
* from a microphone or reading images from a camera, using standard SDL APIs,
* will manage permission requests for you.
*
* This function never blocks. Instead, the app-supplied callback will be
* called when a decision has been made. This callback may happen on a
* different thread, and possibly much later, as it might wait on a user to
* respond to a system dialog. If permission has already been granted for a
* specific entitlement, the callback will still fire, probably on the current
* thread and before this function returns.
*
* If the request submission fails, this function returns false and the
* callback will NOT be called, but this should only happen in catastrophic
* conditions, like memory running out. Normally there will be a yes or no to
* the request through the callback.
*
* For the `permission` parameter, choose a value from here:
*
* https://developer.huawei.com/consumer/en/doc/harmonyos-guides/app-permissions
*
* Strings should be in the form of "ohos.permission.PERMISSION_NAME".
*
* \param permission the permission to request.
* \param cb the callback to trigger when the request has a response.
* \param userdata an app-controlled pointer that is passed to the callback.
* \returns true if the request was submitted, false if there was an error
* submitting. The result of the request is only ever reported
* through the callback, not this return value.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.6.0.
*/
extern SDL_DECLSPEC bool SDLCALL SDL_RequestOpenHarmonyPermission(const char *permission, SDL_RequestOpenHarmonyPermissionCallback cb, void *userdata);
#endif /* SDL_PLATFORM_OPENHARMONY */
/**
* Query if the current device is a phone.
*

View File

@@ -1579,6 +1579,15 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window)
* - `SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER`: the EGLSurface associated with
* the window
*
* On OpenHarmony/HarmonyOS:
*
* - `SDL_PROP_WINDOW_OPENHARMONY_XCOMPONENT_POINTER`: the OH_NativeXComponent
* associated with the window
* - `SDL_PROP_WINDOW_OPENHARMONY_WINDOW_POINTER`: the OHNativeWindow associated
* with the window
* - `SDL_PROP_WINDOW_OPENHARMONY_SURFACE_POINTER`: the EGLSurface associated with
* the window
*
* On iOS:
*
* - `SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER`: the `(__unsafe_unretained)`
@@ -1706,6 +1715,9 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window
#define SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT "SDL.window.HDR_headroom"
#define SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER "SDL.window.android.window"
#define SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER "SDL.window.android.surface"
#define SDL_PROP_WINDOW_OPENHARMONY_XCOMPONENT_POINTER "SDL.window.openharmony.xcomponent"
#define SDL_PROP_WINDOW_OPENHARMONY_WINDOW_POINTER "SDL.window.openharmony.window"
#define SDL_PROP_WINDOW_OPENHARMONY_SURFACE_POINTER "SDL.window.openharmony.surface"
#define SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER "SDL.window.uikit.window"
#define SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER "SDL.window.uikit.metal_view_tag"
#define SDL_PROP_WINDOW_UIKIT_OPENGL_FRAMEBUFFER_NUMBER "SDL.window.uikit.opengl.framebuffer"

View File

@@ -297,6 +297,7 @@
#cmakedefine SDL_AUDIO_DRIVER_NGAGE 1
#cmakedefine SDL_AUDIO_DRIVER_QNX 1
#cmakedefine SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER 1
#cmakedefine SDL_AUDIO_DRIVER_OHAUDIO 1
#cmakedefine SDL_AUDIO_DRIVER_PRIVATE 1
/* Enable various input drivers */
@@ -404,6 +405,7 @@
/* Enable various video drivers */
#cmakedefine SDL_VIDEO_DRIVER_ANDROID 1
#cmakedefine SDL_VIDEO_DRIVER_OPENHARMONY 1
#cmakedefine SDL_VIDEO_DRIVER_COCOA 1
#cmakedefine SDL_VIDEO_DRIVER_DUMMY 1
#cmakedefine SDL_VIDEO_DRIVER_EMSCRIPTEN 1
@@ -513,6 +515,7 @@
#cmakedefine SDL_POWER_VITA 1
#cmakedefine SDL_POWER_PSP 1
#cmakedefine SDL_POWER_N3DS 1
#cmakedefine SDL_POWER_OPENHARMONY 1
#cmakedefine SDL_POWER_PRIVATE 1
@@ -530,6 +533,7 @@
#cmakedefine SDL_FILESYSTEM_PS2 1
#cmakedefine SDL_FILESYSTEM_N3DS 1
#cmakedefine SDL_FILESYSTEM_DOS 1
#cmakedefine SDL_FILESYSTEM_OPENHARMONY 1
#cmakedefine SDL_FILESYSTEM_PRIVATE 1
@@ -551,6 +555,7 @@
#cmakedefine SDL_CAMERA_DRIVER_V4L2 1
#cmakedefine SDL_CAMERA_DRIVER_COREMEDIA 1
#cmakedefine SDL_CAMERA_DRIVER_ANDROID 1
#cmakedefine SDL_CAMERA_DRIVER_OPENHARMONY 1
#cmakedefine SDL_CAMERA_DRIVER_EMSCRIPTEN 1
#cmakedefine SDL_CAMERA_DRIVER_MEDIAFOUNDATION 1
#cmakedefine SDL_CAMERA_DRIVER_PIPEWIRE 1

12
openharmony-project/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test
/.appanalyzer

View File

@@ -0,0 +1,11 @@
{
"app": {
"bundleName": "com.example.app",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"buildVersion": "1",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}

View File

@@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "app"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,7 @@
{
"layered-image":
{
"background" : "$media:background",
"foreground" : "$media:foreground"
}
}

View File

@@ -0,0 +1,42 @@
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "5.0.4(16)",
"compatibleSdkVersion": "5.0.4(16)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"nativeCompiler": "BiSheng",
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}

View File

@@ -0,0 +1,32 @@
{
"files": [
"**/*.ets"
],
"ignore": [
"**/src/ohosTest/**/*",
"**/src/test/**/*",
"**/src/mock/**/*",
"**/node_modules/**/*",
"**/oh_modules/**/*",
"**/build/**/*",
"**/.preview/**/*"
],
"ruleSet": [
"plugin:@performance/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@security/no-unsafe-aes": "error",
"@security/no-unsafe-hash": "error",
"@security/no-unsafe-mac": "warn",
"@security/no-unsafe-dh": "error",
"@security/no-unsafe-dsa": "error",
"@security/no-unsafe-ecdsa": "error",
"@security/no-unsafe-rsa-encrypt": "error",
"@security/no-unsafe-rsa-sign": "error",
"@security/no-unsafe-rsa-key": "error",
"@security/no-unsafe-dsa-key": "error",
"@security/no-unsafe-dh-key": "error",
"@security/no-unsafe-3des": "error"
}
}

6
openharmony-project/entry/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

View File

@@ -0,0 +1,45 @@
{
"apiType": "stageMode",
"buildOption": {
"resOptions": {
"copyCodeResource": {
"enable": false
}
},
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "-DCMAKE_PLATFORM_NO_VERSIONED_SONAME=1", // Otherwise, we load SDL3's data segment twice. :(
"cppFlags": "",
"abiFilters": [
"arm64-v8a",
"x86_64"
]
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
},
"nativeLib": {
"debugSymbol": {
"strip": true,
"exclude": []
}
}
},
],
"targets": [
{
"name": "default"
},
]
}

View File

@@ -0,0 +1,6 @@
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@@ -0,0 +1,23 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://developer.huawei.com/consumer/en/doc/harmonyos-guides/source-obfuscation
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation

View File

@@ -0,0 +1,19 @@
{
"meta": {
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"libentry.so@src/main/cpp/types/libentry": "libentry.so@src/main/cpp/types/libentry"
},
"packages": {
"libentry.so@src/main/cpp/types/libentry": {
"name": "libentry.so",
"version": "1.0.0",
"resolved": "src/main/cpp/types/libentry",
"registryType": "local"
}
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
//"libSDL3.so": "file:./src/main/cpp/types/libSDL3",
}
}

View File

@@ -0,0 +1,18 @@
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(loopwave)
set(NATIVEAPP_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
add_subdirectory("SDL")
include_directories(${NATIVEAPP_ROOT_PATH}
${NATIVEAPP_ROOT_PATH}/include
${NATIVEAPP_ROOT_PATH}/SDL/include)
add_library(main SHARED myapp.c)
target_link_libraries(main PUBLIC SDL3::SDL3)

View File

@@ -0,0 +1 @@
export const provideArkTSObjects: (ability: UIAbility, atManager: abilityAccessCtrl.AtManager, locale: intl.Locale) => void;

View File

@@ -0,0 +1,7 @@
{
"name": "libSDL3.so",
"types": "./Index.d.ts",
"version": "1.0.0",
"description": "SDL3 interfaces callable from ArkTS."
}

View File

@@ -0,0 +1,28 @@
import { abilityAccessCtrl, AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { i18n } from '@kit.LocalizationKit';
import { inputMethod } from '@kit.IMEKit';
import pointer from "@ohos.multimodalInput.pointer"
import sdl from 'libSDL3.so';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN, 'SDL/STARTUP', '%{public}s', 'Handing control from ArkTS to native SDL.');
try {
// This hands stuff to SDL's C code that it needs to deal with ArkTS interfaces. We are trying not to write scripting code, if we can
// handle it awkwardly in C through NAPI calls, so we have a _tiny_ surface area of risk where the script and the native code can go
// out of sync. This function call will add methods to `this`, such as onWindowStageCreate(), so we can drive everything from C and
// have almost no ArkTS code at all.
// Which is to say: if this function call fails, your app won't actually start.
//
// ONLY EVER ADD PARAMETERS TO THIS FUNCTION CALL. THE C CODE WILL ABORT IF IT DOESN'T GET THE EXACT NUMBER IT EXPECTS.
sdl.provideArkTSObjects(this, abilityAccessCtrl.createAtManager(), i18n.System, pointer.setPointerVisibleSync, inputMethod.getController());
} catch (e) {
hilog.info(DOMAIN, 'SDL/STARTUP', '%{public}s', 'Exception thrown! App will not start.');
}
}
}

View File

@@ -0,0 +1,16 @@
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
const DOMAIN = 0x0000;
export default class EntryBackupAbility extends BackupExtensionAbility {
async onBackup() {
hilog.info(DOMAIN, 'testTag', 'onBackup ok');
await Promise.resolve();
}
async onRestore(bundleVersion: BundleVersion) {
hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
await Promise.resolve();
}
}

View File

@@ -0,0 +1,15 @@
const DOMAIN = 0x0000;
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
XComponent({ id: 'SDL_XComponent', type: XComponentType.SURFACE, libraryname: 'SDL3' }).id('SDL_XComponent')
}
.width('100%')
}
.height('100%')
}
}

View File

@@ -0,0 +1,67 @@
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
// !!! FIXME: we can maybe put "car" and "wearable" in here, too, if SDL will run on them and we could test it at some point.
"deviceTypes": [
"phone",
"tablet",
"2in1",
"tv"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
// PERMISSIONS: Uncomment if your app needs any of these things. Please edit the "reason" text to more-clearly explain why you need access to the feature.
// Documentation on this section: https://developer.huawei.com/consumer/en/doc/harmonyos-guides/declare-permissions
// Lists of specific permissions: https://developer.huawei.com/consumer/en/doc/harmonyos-guides/app-permissions
// Note that READ_PASTEBOARD is a restricted permission, you need to take serious extra steps to use it. https://developer.huawei.com/consumer/en/doc/harmonyos-guides/restricted-permissions#ohospermissionread_pasteboard
"requestPermissions": [
//{ "name": "ohos.permission.MICROPHONE", "reason": "$string:perm_reason_MICROPHONE", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } },
//{ "name": "ohos.permission.CAMERA", "reason": "$string:perm_reason_CAMERA", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } },
//{ "name": "ohos.permission.INTERNET", "reason": "$string:perm_reason_INTERNET", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } },
//{ "name": "ohos.permission.READ_PASTEBOARD", "reason": "$string:perm_reason_READ_PASTEBOARD", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } },
],
"abilities": [
{
"name": "EntryAbility",
"orientation": "auto_rotation_restricted",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"ohos.want.action.home"
]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
],
}
]
}
}

View File

@@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"float": [
{
"name": "page_text_font_size",
"value": "50fp"
}
]
}

View File

@@ -0,0 +1,33 @@
{
"string": [
{
"name": "module_desc",
"value": "SDL Application"
},
{
"name": "EntryAbility_desc",
"value": "SDL Application"
},
{
"name": "EntryAbility_label",
"value": "SDL App"
},
{
"name": "perm_reason_MICROPHONE",
"value": "Used to record audio"
},
{
"name": "perm_reason_CAMERA",
"value": "Used to record video"
},
{
"name": "perm_reason_INTERNET",
"value": "Used to download additional content"
},
{
"name": "perm_reason_READ_PASTEBOARD",
"value": "Used to read from the pasteboard"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -0,0 +1,7 @@
{
"layered-image":
{
"background" : "$media:background",
"foreground" : "$media:foreground"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,3 @@
{
"allowToBackupRestore": true
}

View File

@@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#000000"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"modelVersion": "6.1.1",
"dependencies": {
},
"execution": {
// "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */
// "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
// "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
// "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
// "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
// "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
},
"nodeOptions": {
// "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
// "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
}
}

View File

@@ -0,0 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@@ -0,0 +1,28 @@
{
"meta": {
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0",
"@ohos/hypium@1.0.25": "@ohos/hypium@1.0.25"
},
"packages": {
"@ohos/hamock@1.0.0": {
"name": "@ohos/hamock",
"version": "1.0.0",
"integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har",
"registryType": "ohpm"
},
"@ohos/hypium@1.0.25": {
"name": "@ohos/hypium",
"version": "1.0.25",
"integrity": "sha512-l6uO2pjl8HyEKdekLqQt7tUpWbDqX/42zoAzkagtUVZAW9jT6lMvbe54MVjoLxq/RwQGygRvi6j4GpypSMFSHw==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.25.har",
"registryType": "ohpm"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"modelVersion": "6.1.1",
"description": "Please describe the basic information.",
"dependencies": {
},
"devDependencies": {
"@ohos/hypium": "1.0.25",
"@ohos/hamock": "1.0.0"
}
}

View File

@@ -61,6 +61,9 @@
#ifdef SDL_PLATFORM_ANDROID
#include "core/android/SDL_android.h"
#endif
#ifdef SDL_PLATFORM_OPENHARMONY
#include "core/openharmony/SDL_openharmony.h"
#endif
#define SDL_ALL_SUBSYSTEM_FLAGS ~0U
@@ -803,6 +806,8 @@ const char *SDL_GetPlatform(void)
return "Nokia N-Gage";
#elif defined(SDL_PLATFORM_OPENBSD)
return "OpenBSD";
#elif defined(SDL_PLATFORM_OPENHARMONY)
return "OpenHarmony";
#elif defined(SDL_PLATFORM_OS2)
return "OS/2";
#elif defined(SDL_PLATFORM_OSF)
@@ -867,6 +872,8 @@ SDL_FormFactor SDL_GetDeviceFormFactor(void)
return SDL_FORMFACTOR_PRIVATE;
#elif defined(SDL_PLATFORM_ANDROID)
return SDL_GetAndroidDeviceFormFactor();
#elif defined(SDL_PLATFORM_OPENHARMONY)
return SDL_GetOpenHarmonyDeviceFormFactor();
#elif defined(SDL_PLATFORM_IOS)
extern SDL_FormFactor SDL_GetUIKitDeviceFormFactor(void);
return SDL_GetUIKitDeviceFormFactor();

View File

@@ -40,6 +40,10 @@
#include <android/log.h>
#endif
#ifdef SDL_PLATFORM_OPENHARMONY
#include <hilog/log.h>
#endif
#include "stdlib/SDL_vacopy.h"
// The size of the stack buffer to use for rendering log messages.
@@ -122,7 +126,19 @@ static int SDL_android_priority[] = {
ANDROID_LOG_FATAL
};
SDL_COMPILE_TIME_ASSERT(android_priority, SDL_arraysize(SDL_android_priority) == SDL_LOG_PRIORITY_COUNT);
#endif // SDL_PLATFORM_ANDROID
#elif defined(SDL_PLATFORM_OPENHARMONY)
static int SDL_openharmony_priority[] = {
LOG_DEBUG,
LOG_DEBUG,
LOG_DEBUG,
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR,
LOG_FATAL
};
SDL_COMPILE_TIME_ASSERT(openharmony_priority, SDL_arraysize(SDL_openharmony_priority) == SDL_LOG_PRIORITY_COUNT);
#endif
static void SDLCALL SDL_LoggingChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
@@ -583,7 +599,7 @@ void SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_ST
va_end(ap);
}
#ifdef SDL_PLATFORM_ANDROID
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
static const char *GetCategoryPrefix(int category)
{
if (category < SDL_LOG_CATEGORY_RESERVED2) {
@@ -594,7 +610,7 @@ static const char *GetCategoryPrefix(int category)
}
return "CUSTOM";
}
#endif // SDL_PLATFORM_ANDROID
#endif // SDL_PLATFORM_ANDROID || SDL_PLATFORM_OPENHARMONY
void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
{
@@ -758,12 +774,15 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority
SDL_free(tstr);
SDL_small_free(output, isstack);
}
#elif defined(SDL_PLATFORM_ANDROID)
#elif defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
{
char tag[32];
SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s", GetCategoryPrefix(category));
#if defined(SDL_PLATFORM_ANDROID)
__android_log_write(SDL_android_priority[priority], tag, message);
#elif defined(SDL_PLATFORM_OPENHARMONY)
OH_LOG_Print(LOG_APP, SDL_openharmony_priority[priority], LOG_DOMAIN, tag, "%{public}s", message);
#endif
}
#elif defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))
/* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now.

View File

@@ -95,6 +95,9 @@ static const AudioBootStrap *const bootstrap[] = {
#ifdef SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER
&DOSSOUNDBLASTER_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_OHAUDIO
&OHAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_DISK
&DISKAUDIO_bootstrap,
#endif

View File

@@ -395,5 +395,6 @@ extern AudioBootStrap NGAGEAUDIO_bootstrap;
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
extern AudioBootStrap QSAAUDIO_bootstrap;
extern AudioBootStrap DOSSOUNDBLASTER_bootstrap;
extern AudioBootStrap OHAUDIO_bootstrap;
#endif // SDL_sysaudio_h_

View File

@@ -0,0 +1,648 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// OpenHarmony OHAudio driver.
// This code is based on the Android AAudio backend, since the two APIs are almost identical.
#ifdef SDL_AUDIO_DRIVER_OHAUDIO
#include "../SDL_sysaudio.h"
#include "SDL_ohaudio.h"
#include <ohaudio/native_audiorenderer.h>
#include <ohaudio/native_audiostreambuilder.h>
struct SDL_PrivateAudioData
{
union {
OH_AudioRenderer *renderer;
OH_AudioCapturer *capturer;
} stream;
int num_buffers;
Uint8 *mixbuf; // Raw mixing buffer
size_t mixbuf_bytes; // num_buffers * device->buffer_size
size_t callback_bytes;
size_t processed_bytes;
SDL_Semaphore *semaphore;
SDL_AtomicInt error_callback_triggered;
};
// !!! FIXME: Remove this LOGI macro.
// Debug
#if 0
#define LOGI(...) SDL_Log(__VA_ARGS__);
#else
#define LOGI(...)
#endif
#define LIB_OHAUDIO_SO "libohaudio.so"
SDL_ELF_NOTE_DLOPEN(
"audio-ohaudio",
"Support for audio through OHAudio",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
LIB_OHAUDIO_SO
)
typedef struct OHAUDIO_Data
{
SDL_SharedObject *handle;
#define SDL_PROC(ret, func, params) ret (*func) params;
#include "SDL_ohaudiofuncs.h"
} OHAUDIO_Data;
static OHAUDIO_Data ctx;
static bool OHAUDIO_LoadFunctions(OHAUDIO_Data *data)
{
#define SDL_PROC(ret, func, params) \
do { \
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
if (!data->func) { \
return SDL_SetError("Couldn't load OHAudio function %s: %s", #func, SDL_GetError()); \
} \
} while (0);
#define SDL_PROC_OPTIONAL(ret, func, params) \
do { \
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); /* if it fails, okay. */ \
} while (0);
#include "SDL_ohaudiofuncs.h"
return true;
}
static const char *AudioStreamResultToString(OH_AudioStream_Result res)
{
switch (res) {
#define ERRCASE(nam) case AUDIOSTREAM_##nam: return #nam
ERRCASE(SUCCESS);
ERRCASE(ERROR_INVALID_PARAM);
ERRCASE(ERROR_ILLEGAL_STATE);
ERRCASE(ERROR_SYSTEM);
ERRCASE(ERROR_UNSUPPORTED_FORMAT);
#undef ERRCASE
default: break;
}
return "[unknown]";
}
static void ErrorCallback(void *userData, OH_AudioStream_Result error)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
LOGI("SDL ErrorCallback%s: %d - %s", device->recording ? "Capturer" : "Renderer", error, AudioStreamResultToString(error));
// You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
// Just flag the device so we can kill it in PlayDevice instead.
SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); // AUDIOSTREAM_SUCCESS is zero, so !triggered means no error.
SDL_SignalSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
}
static void ErrorCallbackCapturer(OH_AudioCapturer *capturer, void *userData, OH_AudioStream_Result error)
{
ErrorCallback(userData, error);
}
static void ErrorCallbackRenderer(OH_AudioRenderer *renderer, void *userData, OH_AudioStream_Result error)
{
ErrorCallback(userData, error);
}
static void ReadDataCallback(OH_AudioCapturer *capturer, void *userData, void *audioData, int32_t numBytes)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
struct SDL_PrivateAudioData *hidden = device->hidden;
const size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
const size_t callback_bytes = (size_t) numBytes;
size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
const Uint8 *input = (const Uint8 *)audioData;
const size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes);
const size_t size = SDL_min(available_bytes, callback_bytes);
const size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
const size_t end = (offset + size) % hidden->mixbuf_bytes;
SDL_assert(size <= hidden->mixbuf_bytes);
(void) framesize; // only used in macros that bubble out of release code, etc.
//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize);
if (offset <= end) {
SDL_memcpy(&hidden->mixbuf[offset], input, size);
} else {
size_t partial = (hidden->mixbuf_bytes - offset);
SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial);
SDL_memcpy(&hidden->mixbuf[0], &input[partial], end);
}
SDL_MemoryBarrierRelease(); // the other half of this is in OHAUDIO_RecordDevice().
hidden->callback_bytes += size;
if (size < callback_bytes) {
LOGI("Audio recording overflow, dropped %zu frames", (callback_bytes - size) / framesize);
}
// tell the audio thread to pull this data in.
const size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
while (old_buffer_index < new_buffer_index) {
// Trigger audio processing
SDL_SignalSemaphore(hidden->semaphore);
++old_buffer_index;
}
}
static OH_AudioData_Callback_Result WriteDataCallback(OH_AudioRenderer *renderer, void *userData, void *audioData, int32_t numBytes)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
struct SDL_PrivateAudioData *hidden = device->hidden;
const size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
const size_t callback_bytes = (size_t) numBytes;
size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
Uint8 *output = (Uint8 *)audioData;
const size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes);
const size_t size = SDL_min(available_bytes, callback_bytes);
const size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
const size_t end = (offset + size) % hidden->mixbuf_bytes;
SDL_assert(size <= hidden->mixbuf_bytes);
(void) framesize; // only used in macros that bubble out of release code, etc.
//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize);
SDL_MemoryBarrierAcquire(); // the other half of this is in OHAUDIO_PlayDevice().
if (offset <= end) {
SDL_memcpy(output, &hidden->mixbuf[offset], size);
} else {
size_t partial = (hidden->mixbuf_bytes - offset);
SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial);
SDL_memcpy(&output[partial], &hidden->mixbuf[0], end);
}
hidden->callback_bytes += size;
if (size < callback_bytes) {
LOGI("Audio playback underflow, missed %zu frames", (callback_bytes - size) / framesize);
SDL_memset(&output[size], device->silence_value, (callback_bytes - size));
}
// tell the audio thread to generate more data, so we're ready to memcpy it when this callback runs again.
const size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
while (old_buffer_index < new_buffer_index) {
// Trigger audio processing
SDL_SignalSemaphore(hidden->semaphore);
++old_buffer_index;
}
return AUDIO_DATA_CALLBACK_RESULT_VALID;
}
static Uint8 *OHAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
return &hidden->mixbuf[offset];
}
static bool OHAUDIO_WaitDevice(SDL_AudioDevice *device)
{
while (!SDL_GetAtomicInt(&device->shutdown)) {
// this semaphore won't fire when the app is in the background (OHAUDIO_PauseDevices was called).
if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) {
return true; // semaphore was signaled, let's go!
}
// Still waiting on the semaphore (or the system), check other things then wait again.
}
return true;
}
static bool BuildOHAudioStream(SDL_AudioDevice *device);
static bool RecoverOHAudioDevice(SDL_AudioDevice *device)
{
SDL_assert(!device->recording); // !!! FIXME: we (currently) only do this for playback devices. This is true in AAudio, too.
// attempt to build a new stream, in case there's a new default device.
struct SDL_PrivateAudioData *hidden = device->hidden;
ctx.OH_AudioRenderer_Stop(hidden->stream.renderer);
ctx.OH_AudioRenderer_Release(hidden->stream.renderer);
hidden->stream.renderer = NULL;
SDL_aligned_free(hidden->mixbuf);
hidden->mixbuf = NULL;
SDL_DestroySemaphore(hidden->semaphore);
hidden->semaphore = NULL;
const int prev_sample_frames = device->sample_frames;
SDL_AudioSpec prevspec;
SDL_copyp(&prevspec, &device->spec);
if (!BuildOHAudioStream(device)) {
return false; // oh well, we tried.
}
// we don't know the new device spec until we open the new device, so we saved off the old one and force it back
// so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec.
const int new_sample_frames = device->sample_frames;
SDL_AudioSpec newspec;
SDL_copyp(&newspec, &device->spec);
device->sample_frames = prev_sample_frames;
SDL_copyp(&device->spec, &prevspec);
if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
return false; // ugh
}
return true;
}
static bool OHAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// WriteDataCallback picks up our work and unblocks OHAUDIO_WaitDevice. But make sure we didn't fail here.
const OH_AudioStream_Result err = (OH_AudioStream_Result) SDL_GetAtomicInt(&hidden->error_callback_triggered);
if (err) {
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ohaudio: Audio device triggered error %d (%s)", (int) err, AudioStreamResultToString(err));
if (!RecoverOHAudioDevice(device)) {
return false; // oh well, we went down hard.
}
} else {
SDL_MemoryBarrierRelease();
hidden->processed_bytes += buflen;
}
return true;
}
static int OHAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// ReadDataCallback picks up our work and unblocks OHAUDIO_WaitDevice. But make sure we didn't fail here.
if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) {
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
return -1;
}
SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
SDL_MemoryBarrierAcquire();
SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen);
hidden->processed_bytes += buflen;
return buflen;
}
static void OHAUDIO_CloseDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
if (hidden) {
if ((device->recording) && (hidden->stream.capturer)) {
ctx.OH_AudioCapturer_Stop(hidden->stream.capturer);
ctx.OH_AudioCapturer_Release(hidden->stream.capturer);
} else if ((!device->recording) && (hidden->stream.renderer)) {
ctx.OH_AudioRenderer_Stop(hidden->stream.renderer);
// !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)?
// !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again?
ctx.OH_AudioRenderer_Release(hidden->stream.renderer);
}
if (hidden->semaphore) {
SDL_DestroySemaphore(hidden->semaphore);
}
SDL_aligned_free(hidden->mixbuf);
SDL_free(hidden);
device->hidden = NULL;
}
}
static void SetOptionalStreamUsage(OH_AudioStreamBuilder *builder)
{
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE);
if (hint) {
OH_AudioStream_Usage usage = AUDIOSTREAM_USAGE_MOVIE; // !!! FIXME: I don't know if this is a good default.
if ((SDL_strcasecmp(hint, "Communications") == 0) || (SDL_strcasecmp(hint, "GameChat") == 0)) {
usage = AUDIOSTREAM_USAGE_VOICE_COMMUNICATION;
} else if (SDL_strcasecmp(hint, "Game") == 0) {
usage = AUDIOSTREAM_USAGE_GAME;
}
ctx.OH_AudioStreamBuilder_SetRendererInfo(builder, usage);
}
}
static bool BuildOHAudioStream(SDL_AudioDevice *device)
{
const char *streamtypestr = device->recording ? "Capturer" : "Renderer";
SDL_assert(SDL_GetOpenHarmonySDKVersion() >= 12); // if not, we need to use a different, deprecated way to assign callbacks. It's doable, but messy if not required.
// !!! FIXME: make sure these all actually settle out into the say channel order SDL expects.
// !!! FIXME: 4POINT1 doesn't match SDL's layout in any case. If you can specify bits instead of prechosen enums, we want CH_LAYOUT_QUAD | CH_SET_LOW_FREQUENCY
static const OH_AudioChannelLayout sdl_layout_map[] = { CH_LAYOUT_MONO, CH_LAYOUT_STEREO, CH_LAYOUT_2POINT1, CH_LAYOUT_QUAD, CH_LAYOUT_4POINT1, CH_LAYOUT_5POINT1_BACK, CH_LAYOUT_6POINT1, CH_LAYOUT_7POINT1 };
SDL_assert(device->spec.channels <= SDL_arraysize(sdl_layout_map)); // update this if we add support for more channels later.
device->spec.channels = SDL_clamp(device->spec.channels, 1, SDL_arraysize(sdl_layout_map));
struct SDL_PrivateAudioData *hidden = device->hidden;
const bool recording = device->recording;
OH_AudioStream_Result res;
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
OH_AudioStreamBuilder *builder = NULL;
res = ctx.OH_AudioStreamBuilder_Create(&builder, recording ? AUDIOSTREAM_TYPE_CAPTURER : AUDIOSTREAM_TYPE_RENDERER);
if (res != AUDIOSTREAM_SUCCESS) {
LOGI("SDL Failed OH_AudioStreamBuilder_Create %d", (int) res);
return SDL_SetError("SDL Failed OH_AudioStreamBuilder_Create %d", (int) res);
} else if (!builder) {
LOGI("SDL Failed OH_AudioStreamBuilder_Create - builder NULL");
return SDL_SetError("SDL Failed OH_AudioStreamBuilder_Create - builder NULL");
}
OH_AudioStream_SampleFormat format;
if ((device->spec.format == SDL_AUDIO_F32LE) && (SDL_GetOpenHarmonySDKVersion() >= 17)) {
format = AUDIOSTREAM_SAMPLE_F32LE;
} else if (device->spec.format == SDL_AUDIO_S32LE) {
format = AUDIOSTREAM_SAMPLE_S32LE;
} else if (device->spec.format == SDL_AUDIO_S16LE) {
format = AUDIOSTREAM_SAMPLE_S16LE;
} else if (device->spec.format == SDL_AUDIO_U8) {
format = AUDIOSTREAM_SAMPLE_U8;
} else {
format = AUDIOSTREAM_SAMPLE_S16LE; // sint16 is a safe bet for everything else.
}
ctx.OH_AudioStreamBuilder_SetSampleFormat(builder, format);
ctx.OH_AudioStreamBuilder_SetSamplingRate(builder, device->spec.freq);
ctx.OH_AudioStreamBuilder_SetChannelCount(builder, device->spec.channels);
#if 0 // !!! FIXME: maybe we don't mess with this. (this is from the AAudio backend.)
int32_t sample_frames;
if (SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) {
sample_frames = device->sample_frames;
} else {
// Use 20 ms for the default audio buffer size
sample_frames = (device->spec.freq / 50);
}
ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * sample_frames); // AAudio requires that the buffer capacity is at least
ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, sample_frames); // twice the size of the data callback buffer size
#endif
ctx.OH_AudioStreamBuilder_SetLatencyMode(builder, AUDIOSTREAM_LATENCY_MODE_FAST);
ctx.OH_AudioStreamBuilder_SetChannelLayout(builder, sdl_layout_map[device->spec.channels-1]);
LOGI("OHAudio Try to generate %s stream %d hz %s %u channels samples %u",
device->recording ? "recording" : "playback", device->spec.freq,
SDL_GetAudioFormatName(device->spec.format), device->spec.channels,
device->sample_frames);
if (device->recording) {
// try to use a microphone that is for recording external audio, otherwise you might end up with one used for telephone calls right when holding the device near your face.
ctx.OH_AudioStreamBuilder_SetCapturerInfo(builder, AUDIOSTREAM_SOURCE_TYPE_CAMCORDER);
ctx.OH_AudioStreamBuilder_SetCapturerReadDataCallback(builder, ReadDataCallback, device);
// !!! FIXME: we can't seem to bind a stream to a specific device, so I don't see a lot of value in knowing when routing changes.
//OH_AudioStream_Result OH_AudioStreamBuilder_SetCapturerDeviceChangeCallback(OH_AudioStreamBuilder* builder, OH_AudioCapturer_OnDeviceChangeCallback callback, void* userData);
ctx.OH_AudioStreamBuilder_SetCapturerErrorCallback(builder, ErrorCallbackCapturer, device);
res = ctx.OH_AudioStreamBuilder_GenerateCapturer(builder, &hidden->stream.capturer);
} else {
SetOptionalStreamUsage(builder);
ctx.OH_AudioStreamBuilder_SetRendererErrorCallback(builder, ErrorCallbackRenderer, device);
ctx.OH_AudioStreamBuilder_SetRendererWriteDataCallback(builder, WriteDataCallback, device);
// !!! FIXME: we can't seem to bind a stream to a specific device, so I don't see a lot of value in knowing when routing changes.
//OH_AudioStream_Result OH_AudioStreamBuilder_SetRendererOutputDeviceChangeCallback(OH_AudioStreamBuilder* builder, OH_AudioRenderer_OutputDeviceChangeCallback callback, void* userData);
res = ctx.OH_AudioStreamBuilder_GenerateRenderer(builder, &hidden->stream.renderer);
}
ctx.OH_AudioStreamBuilder_Destroy(builder);
if (res != AUDIOSTREAM_SUCCESS) {
LOGI("SDL Failed OH_AudioStreamBuilder_Generate%s %d %s", streamtypestr, res, AudioStreamResultToString(res));
return SDL_SetError("%s : %s", SDL_FUNCTION, AudioStreamResultToString(res));
}
// !!! FIXME: it's not clear to me if OHAudio streams will promise you the format you requested, converting if necessary, if Generate(Renderer|Capturer) succeeded.
// !!! FIXME: AAudio goes and queries to see what it _actually_ got from the stream, so we'll do that too.
// I assume all of these succeed...why would it fail to tell you the sampling rate of the stream, etc?
int32_t sample_frames = 0, freq = 0, channels = 0;
if (device->recording) {
ctx.OH_AudioCapturer_GetFrameSizeInCallback(hidden->stream.capturer, &sample_frames);
ctx.OH_AudioCapturer_GetSamplingRate(hidden->stream.capturer, &freq);
ctx.OH_AudioCapturer_GetChannelCount(hidden->stream.capturer, &channels);
ctx.OH_AudioCapturer_GetSampleFormat(hidden->stream.capturer, &format);
} else {
ctx.OH_AudioRenderer_GetFrameSizeInCallback(hidden->stream.renderer, &sample_frames);
ctx.OH_AudioRenderer_GetSamplingRate(hidden->stream.renderer, &freq);
ctx.OH_AudioRenderer_GetChannelCount(hidden->stream.renderer, &channels);
ctx.OH_AudioRenderer_GetSampleFormat(hidden->stream.renderer, &format);
}
device->sample_frames = (int) sample_frames; // OHAudio docs claim this is always consistent, never returns something like AAUDIO_UNSPECIFIED like AAudio.
device->spec.freq = (int) freq;
device->spec.channels = (int) channels;
if (format == AUDIOSTREAM_SAMPLE_U8) {
device->spec.format = SDL_AUDIO_U8;
} else if (format == AUDIOSTREAM_SAMPLE_S16LE) {
device->spec.format = SDL_AUDIO_S16LE;
} else if (format == AUDIOSTREAM_SAMPLE_S32LE) {
device->spec.format = SDL_AUDIO_S32LE;
} else if (format == AUDIOSTREAM_SAMPLE_F32LE) {
device->spec.format = SDL_AUDIO_F32LE;
} else {
return SDL_SetError("Got unexpected audio format %d from OH_Audio%s_GetSampleFormat", (int) format, streamtypestr);
}
SDL_UpdatedAudioDeviceFormat(device);
// Allocate a triple buffered mixing buffer
// Two buffers can be in the process of being filled while the third is being read
hidden->num_buffers = 3;
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), hidden->mixbuf_bytes);
if (!hidden->mixbuf) {
return false;
}
hidden->processed_bytes = 0;
hidden->callback_bytes = 0;
hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers - 1);
if (!hidden->semaphore) {
LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording);
return false;
}
LOGI("OHAudio Actually generate %s stream %d hz %s %u channels samples %u, buffers %d",
device->recording ? "recording" : "playback",
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
device->spec.channels, device->sample_frames, hidden->num_buffers);
return true;
}
// !!! FIXME: make this non-blocking!
#if 0
static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
{
SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
}
#endif
static bool OHAUDIO_OpenDevice(SDL_AudioDevice *device)
{
#if 0 // !!! FIXME: look up how to do this in HarmonyOS.
if (device->recording) {
// !!! FIXME: make this non-blocking!
SDL_AtomicInt permission_response;
SDL_SetAtomicInt(&permission_response, 0);
if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
return false;
}
while (SDL_GetAtomicInt(&permission_response) == 0) {
SDL_Delay(10);
}
if (SDL_GetAtomicInt(&permission_response) < 0) {
LOGI("This app doesn't have RECORD_AUDIO permission");
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
}
}
#endif
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
return BuildOHAudioStream(device);
}
static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
{
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
if (hidden) {
if (hidden->stream.renderer) { // this is a union, so a valid capture stream will still have a non-NULL `renderer`.
OH_AudioStream_Result res;
if (device->recording) {
res = ctx.OH_AudioCapturer_Pause(hidden->stream.capturer);
} else {
res = ctx.OH_AudioRenderer_Pause(hidden->stream.renderer);
}
if (res != AUDIOSTREAM_SUCCESS) {
LOGI("SDL Failed OH_Audio%s_Pause %d", device->recording ? "Capturer" : "Renderer", res);
SDL_SetError("%s : %s", SDL_FUNCTION, AudioStreamResultToString(res));
}
}
}
return false; // keep enumerating.
}
// Pause (block) all non already paused audio devices by taking their mixer lock
void OHAUDIO_PauseDevices(void)
{
if (ctx.handle) { // OHAudio driver is used?
(void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
}
}
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
if (hidden) {
OH_AudioStream_Result res = AUDIOSTREAM_SUCCESS;
if (device->recording && hidden->stream.capturer) {
res = ctx.OH_AudioCapturer_Start(hidden->stream.capturer);
} else if (!device->recording && hidden->stream.renderer) {
res = ctx.OH_AudioRenderer_Start(hidden->stream.renderer);
}
if (res != AUDIOSTREAM_SUCCESS) {
LOGI("SDL Failed OH_Audio%s_Start %d", device->recording ? "Capturer" : "Renderer", res);
SDL_SetError("%s : %s", SDL_FUNCTION, AudioStreamResultToString(res));
}
}
return false; // keep enumerating.
}
void OHAUDIO_ResumeDevices(void)
{
if (ctx.handle) { // AAUDIO driver is used?
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL);
}
}
static void OHAUDIO_Deinitialize(void)
{
if (ctx.handle) {
SDL_UnloadObject(ctx.handle);
}
SDL_zero(ctx);
}
static void OHAUDIO_ThreadInit(SDL_AudioDevice *device)
{
SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
// Start the audio stream here; OHAudio's docs say that starting and stopping might take significant time and shouldn't be done from the main thread.
if (device->recording) {
ctx.OH_AudioCapturer_Start(device->hidden->stream.capturer);
} else {
ctx.OH_AudioRenderer_Start(device->hidden->stream.renderer);
}
}
static bool OHAUDIO_Init(SDL_AudioDriverImpl *impl)
{
SDL_zero(ctx);
ctx.handle = SDL_LoadObject(LIB_OHAUDIO_SO);
if (!ctx.handle) {
LOGI("SDL couldn't find " LIB_OHAUDIO_SO);
return false;
}
if (!OHAUDIO_LoadFunctions(&ctx)) {
SDL_UnloadObject(ctx.handle);
SDL_zero(ctx);
return false;
}
impl->ThreadInit = OHAUDIO_ThreadInit;
impl->Deinitialize = OHAUDIO_Deinitialize;
impl->OpenDevice = OHAUDIO_OpenDevice;
impl->CloseDevice = OHAUDIO_CloseDevice;
impl->WaitDevice = OHAUDIO_WaitDevice;
impl->PlayDevice = OHAUDIO_PlayDevice;
impl->GetDeviceBuf = OHAUDIO_GetDeviceBuf;
impl->WaitRecordingDevice = OHAUDIO_WaitDevice;
impl->RecordDevice = OHAUDIO_RecordDevice;
impl->HasRecordingSupport = true;
impl->OnlyHasDefaultPlaybackDevice = true;
impl->OnlyHasDefaultRecordingDevice = true;
LOGI("SDL OHAUDIO_Init OK");
return true;
}
AudioBootStrap OHAUDIO_bootstrap = {
"OHAudio", "OpenHarmony OHAudio audio driver", OHAUDIO_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_OHAUDIO

View File

@@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_ohaudio_h_
#define SDL_ohaudio_h_
#ifdef SDL_AUDIO_DRIVER_OHAUDIO
extern void OHAUDIO_ResumeDevices(void);
extern void OHAUDIO_PauseDevices(void);
#else
#define OHAUDIO_ResumeDevices()
#define OHAUDIO_PauseDevices()
#endif
#endif // SDL_ohaudio_h_

View File

@@ -0,0 +1,62 @@
/*
Simple DirectMedia Layer
Copyright , (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_PROC_OPTIONAL
#define SDL_PROC_OPTIONAL(ret, func, params) SDL_PROC(ret, func, params)
#endif
#define SDL_PROC_UNUSED(ret, func, params)
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_Create, (OH_AudioStreamBuilder** builder, OH_AudioStream_Type type))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_Destroy, (OH_AudioStreamBuilder* builder))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetSamplingRate, (OH_AudioStreamBuilder* builder, int32_t rate))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetChannelCount, (OH_AudioStreamBuilder* builder, int32_t channelCount))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetSampleFormat, (OH_AudioStreamBuilder* builder, OH_AudioStream_SampleFormat format))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetLatencyMode, (OH_AudioStreamBuilder* builder, OH_AudioStream_LatencyMode latencyMode))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetChannelLayout, (OH_AudioStreamBuilder* builder, OH_AudioChannelLayout channelLayout))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetRendererInfo, (OH_AudioStreamBuilder* builder, OH_AudioStream_Usage usage))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetCapturerInfo, (OH_AudioStreamBuilder* builder, OH_AudioStream_SourceType sourceType))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_GenerateRenderer, (OH_AudioStreamBuilder* builder, OH_AudioRenderer** audioRenderer))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_GenerateCapturer, (OH_AudioStreamBuilder* builder, OH_AudioCapturer** audioCapturer))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetRendererWriteDataCallback, (OH_AudioStreamBuilder* builder, OH_AudioRenderer_OnWriteDataCallback callback, void* userData))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetRendererErrorCallback, (OH_AudioStreamBuilder* builder, OH_AudioRenderer_OnErrorCallback callback, void* userData))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetCapturerReadDataCallback, (OH_AudioStreamBuilder* builder, OH_AudioCapturer_OnReadDataCallback callback, void* userData))
SDL_PROC(OH_AudioStream_Result, OH_AudioStreamBuilder_SetCapturerErrorCallback, (OH_AudioStreamBuilder* builder, OH_AudioCapturer_OnErrorCallback callback, void* userData))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_Release, (OH_AudioRenderer* renderer))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_Start, (OH_AudioRenderer* renderer))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_Pause, (OH_AudioRenderer* renderer))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_Stop, (OH_AudioRenderer* renderer))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_GetSamplingRate, (OH_AudioRenderer* renderer, int32_t* rate))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_GetChannelCount, (OH_AudioRenderer* renderer, int32_t* channelCount))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_GetSampleFormat, (OH_AudioRenderer* renderer, OH_AudioStream_SampleFormat* sampleFormat))
SDL_PROC(OH_AudioStream_Result, OH_AudioRenderer_GetFrameSizeInCallback, (OH_AudioRenderer* renderer, int32_t* frameSize))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_Release, (OH_AudioCapturer* capturer))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_Start, (OH_AudioCapturer* capturer))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_Pause, (OH_AudioCapturer* capturer))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_Stop, (OH_AudioCapturer* capturer))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_GetSamplingRate, (OH_AudioCapturer* capturer, int32_t* rate))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_GetChannelCount, (OH_AudioCapturer* capturer, int32_t* channelCount))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_GetSampleFormat, (OH_AudioCapturer* capturer, OH_AudioStream_SampleFormat* sampleFormat))
SDL_PROC(OH_AudioStream_Result, OH_AudioCapturer_GetFrameSizeInCallback, (OH_AudioCapturer* capturer, int32_t* frameSize))
#undef SDL_PROC
#undef SDL_PROC_UNUSED
#undef SDL_PROC_OPTIONAL

View File

@@ -44,6 +44,9 @@ static const CameraBootStrap *const bootstrap[] = {
#ifdef SDL_CAMERA_DRIVER_ANDROID
&ANDROIDCAMERA_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_OPENHARMONY
&OPENHARMONYCAMERA_bootstrap,
#endif
#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
&EMSCRIPTENCAMERA_bootstrap,
#endif

View File

@@ -222,6 +222,7 @@ extern CameraBootStrap PIPEWIRECAMERA_bootstrap;
extern CameraBootStrap V4L2_bootstrap;
extern CameraBootStrap COREMEDIA_bootstrap;
extern CameraBootStrap ANDROIDCAMERA_bootstrap;
extern CameraBootStrap OPENHARMONYCAMERA_bootstrap;
extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
extern CameraBootStrap MEDIAFOUNDATION_bootstrap;
extern CameraBootStrap VITACAMERA_bootstrap;

View File

@@ -0,0 +1,775 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_surface_c.h"
#include "../../core/openharmony/SDL_openharmony.h"
#ifdef SDL_CAMERA_DRIVER_OPENHARMONY
/*
* module.json5:
* { "name": "ohos.permission.CAMERA", "reason": "$string:perm_reason_CAMERA", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } },
*
* Very likely SDL must be build with YUV support (done by default)
*/
// !!! FIXME: Using a "preview output" is the only way I could find to read pixels on the CPU. If I
// !!! FIXME: try to use a "video output" with an image receiver, it doesn't work: I get data from
// !!! FIXME: the camera and I can see motion in the output if I wave my hand in front of the camera,
// !!! FIXME: but it's _definitely_ not the NV21 format it claims to be, and the image is some weird
// !!! FIXME: scrambled thing split into rows.
// !!! FIXME: While preview output is "fine," it doesn't give us access to various camera framerates
// !!! FIXME: (we currently lie and say 60fps), nor the maximum resolution of the camera.
// !!! FIXME: I think VideoOutput wants the system to control the whole pipeline, from lens to encoder
// !!! FIXME: to disk, and preview output wants to add the GPU into the mix but will accept CPU access
// !!! FIXME: if you want to do things like Snapchat filters or whatever on the data. I don't know.
// prevent unneeded rawfile headers (which contain C++ code) from getting included by other system headers.
typedef struct RawFileDescriptor RawFileDescriptor;
#define GLOBAL_RAW_FILE_H 1
#include <multimedia/image_framework/image/image_native.h>
#include <multimedia/image_framework/image/image_receiver_native.h>
#include <ohcamera/camera.h>
#include <ohcamera/camera_device.h>
#include <ohcamera/camera_input.h>
#include <ohcamera/capture_session.h>
#include <ohcamera/video_output.h>
#include <ohcamera/camera_manager.h>
#include "../../core/openharmony/SDL_openharmony.h"
struct SDL_PrivateCameraData
{
Camera_Input *input;
OH_ImageReceiverOptions *options;
OH_ImageReceiverNative *image_receiver;
Camera_PreviewOutput *output;
//Camera_VideoOutput *output;
Camera_CaptureSession *session;
int rotation; // degrees to rotate clockwise to get from camera's static orientation to device's native orientation. Apply this plus current phone rotation to get upright image!
SDL_CameraSpec requested_spec;
};
static bool SetErrorStr(const char *what, const char *errstr, const int rc)
{
char errbuf[128];
if (!errstr) {
SDL_snprintf(errbuf, sizeof (errbuf), "Unknown error #%d", rc);
errstr = errbuf;
}
return SDL_SetError("%s: %s", what, errstr);
}
static const char *CameraErrorStr(const Camera_ErrorCode rc)
{
switch (rc) {
case CAMERA_OK: return "no error";
case CAMERA_INVALID_ARGUMENT: return "invalid argument";
case CAMERA_OPERATION_NOT_ALLOWED: return "operation not allowed";
case CAMERA_SESSION_NOT_CONFIG: return "session not configured";
case CAMERA_SESSION_NOT_RUNNING: return "session not running";
case CAMERA_SESSION_CONFIG_LOCKED: return "session config locked";
case CAMERA_DEVICE_SETTING_LOCKED: return "device setting locked";
case CAMERA_CONFLICT_CAMERA: return "camera conflict";
case CAMERA_DEVICE_DISABLED: return "device disabled";
case CAMERA_DEVICE_PREEMPTED: return "device preempted";
case CAMERA_UNRESOLVED_CONFLICTS_WITH_CURRENT_CONFIGURATIONS: return "unresolved conflicts with current configuration";
case CAMERA_SERVICE_FATAL_ERROR: return "fatal error";
default: break;
}
return NULL; // unknown error
}
static bool SetCameraError(const char *what, const Camera_ErrorCode rc)
{
return SetErrorStr(what, CameraErrorStr(rc), (int) rc);
}
static const char *ImageErrorStr(const Image_ErrorCode rc)
{
switch (rc) {
case IMAGE_SUCCESS: return "no error";
case IMAGE_BAD_PARAMETER: return "bad parameter";
case IMAGE_UNSUPPORTED_MIME_TYPE: return "unsupported mimetype";
case IMAGE_UNKNOWN_MIME_TYPE: return "unknown mimetype";
case IMAGE_TOO_LARGE: return "too large";
case IMAGE_GET_IMAGE_DATA_FAILED: return "get image data failed";
case IMAGE_DMA_NOT_EXIST: return "DMA doesn't exist";
case IMAGE_DMA_OPERATION_FAILED: return "DMA operation failed";
case IMAGE_UNSUPPORTED_OPERATION: return "unsupported operation";
case IMAGE_UNSUPPORTED_METADATA: return "unsupported metadata";
case IMAGE_UNSUPPORTED_CONVERSION: return "unsupported conversion";
case IMAGE_INVALID_REGION: return "invalid region";
case IMAGE_UNSUPPORTED_MEMORY_FORMAT: return "unsupported memory format";
case IMAGE_INVALID_PARAMETER: return "invalid parameter";
case IMAGE_UNSUPPORTED_DATA_FORMAT: return "unsupported data format";
case IMAGE_ALLOC_FAILED: return "allocation failed";
case IMAGE_COPY_FAILED: return "copy failed";
case IMAGE_LOCK_UNLOCK_FAILED: return "unlock failed";
case IMAGE_INIT_FAILED: return "init failed";
case IMAGE_CREATE_PIXELMAP_FAILED: return "create pixelmap failed";
case IMAGE_ALLOCATOR_MODE_UNSUPPORTED: return "allocator mode unsupported";
case IMAGE_UNKNOWN_ERROR: return "unknown error";
case IMAGE_BAD_SOURCE: return "bad source";
case IMAGE_SOURCE_UNSUPPORTED_MIME_TYPE: return "unsupported source mimetype";
case IMAGE_SOURCE_TOO_LARGE: return "source too large";
case IMAGE_SOURCE_UNSUPPORTED_ALLOCATOR_TYPE: return "unsupported source allocator type";
case IMAGE_SOURCE_UNSUPPORTED_METADATA: return "unsupported source metadata";
case IMAGE_SOURCE_UNSUPPORTED_OPTIONS: return "unsupported source options";
case IMAGE_SOURCE_INVALID_PARAMETER: return "invalid source parameter";
case IMAGE_DECODE_FAILED: return "decode failed";
case IMAGE_SOURCE_ALLOC_FAILED: return "source allocation failed";
case IMAGE_PACKER_INVALID_PARAMETER: return "invalid packer parameter";
case IMAGE_ENCODE_FAILED: return "encode failed";
case IMAGE_RECEIVER_INVALID_PARAMETER: return "invalid receiver parameter";
default: break;
}
return NULL; // unknown error
}
static bool SetImageError(const char *what, const Image_ErrorCode rc)
{
return SetErrorStr(what, ImageErrorStr(rc), (int) rc);
}
static Camera_Manager *camera_manager = NULL;
static bool CreateCameraManager(void)
{
SDL_assert(camera_manager == NULL);
const Camera_ErrorCode rc = OH_Camera_GetCameraManager(&camera_manager);
if (rc != CAMERA_OK) {
return SetCameraError("Error creating Camera_Manager", rc);
}
return true;
}
static void DestroyCameraManager(void)
{
if (camera_manager) {
OH_Camera_DeleteCameraManager(camera_manager);
camera_manager = NULL;
}
}
static void ConvertOpenHarmonyFormatToSDL(Camera_Format fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
{
switch (fmt) {
#define CASE(x, y, z) case x: *format = y; *colorspace = z; return
CASE(CAMERA_FORMAT_YUV_420_SP, SDL_PIXELFORMAT_NV21, SDL_COLORSPACE_BT709_LIMITED);
CASE(CAMERA_FORMAT_JPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB);
CASE(CAMERA_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888, SDL_COLORSPACE_SRGB);
#undef CASE
default: break;
}
#if DEBUG_CAMERA
SDL_Log("CAMERA: Unknown format Camera_Format '%d'", fmt);
#endif
*format = SDL_PIXELFORMAT_UNKNOWN;
*colorspace = SDL_COLORSPACE_UNKNOWN;
}
static Camera_Format ConvertSDLFormatToOpenHarmony(SDL_PixelFormat fmt)
{
switch (fmt) {
#define CASE(x, y) case y: return x
CASE(CAMERA_FORMAT_YUV_420_SP, SDL_PIXELFORMAT_NV21);
CASE(CAMERA_FORMAT_JPEG, SDL_PIXELFORMAT_MJPG);
CASE(CAMERA_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
#undef CASE
default: return 0;
}
}
static bool FindOpenHarmonyCameraByDevicePtr(SDL_Camera *device, void *userdata)
{
const Camera_Device *ohdevice = (const Camera_Device *) userdata;
return (SDL_strcmp(ohdevice->cameraId, ((const Camera_Device *) device->handle)->cameraId) == 0);
}
static bool FindOpenHarmonyCameraByImageReceiver(SDL_Camera *device, void *userdata)
{
OH_ImageReceiverNative *receiver = (OH_ImageReceiverNative *) userdata;
struct SDL_PrivateCameraData *hidden = device->hidden;
return (hidden && (hidden->image_receiver == receiver));
}
static bool FindOpenHarmonyCameraByInput(SDL_Camera *device, void *userdata)
{
Camera_Input *input = (Camera_Input *) userdata;
struct SDL_PrivateCameraData *hidden = device->hidden;
return (hidden && (hidden->input == input));
}
static bool OPENHARMONYCAMERA_WaitDevice(SDL_Camera *device)
{
return true; // this isn't used atm, since we run our own thread via onImageAvailable callbacks.
}
static SDL_CameraFrameResult OPENHARMONYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
{
struct SDL_PrivateCameraData *hidden = device->hidden;
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
Image_ErrorCode imgrc = IMAGE_SUCCESS;
OH_ImageNative *image = NULL;
if ((imgrc = OH_ImageReceiverNative_ReadNextImage(hidden->image_receiver, &image)) != IMAGE_SUCCESS) {
SetImageError("Failed to read next image", imgrc);
return SDL_CAMERA_FRAME_ERROR;
}
Image_Size size;
if ((imgrc = OH_ImageNative_GetImageSize(image, &size)) != IMAGE_SUCCESS) {
SetImageError("Failed to get image size", imgrc);
OH_ImageNative_Release(image);
return SDL_CAMERA_FRAME_ERROR;
}
int64_t ohtimestamp = 0;
if (OH_ImageNative_GetTimestamp(image, &ohtimestamp) == IMAGE_SUCCESS) {
*timestampNS = (Uint64) ohtimestamp;
} else {
*timestampNS = 0;
}
uint32_t components_buf[16];
uint32_t *components = components_buf;
size_t num_components = SDL_arraysize(components_buf);
if ((imgrc = OH_ImageNative_GetComponentTypes(image, &components, &num_components)) != IMAGE_SUCCESS) {
SetImageError("Failed to get image component types", imgrc);
OH_ImageNative_Release(image);
return SDL_CAMERA_FRAME_ERROR;
}
SDL_assert(num_components <= SDL_arraysize(components_buf)); // just in case.
int num_planes = (int) num_components;
#if 0
SDL_Log("CAMERA: %d planes:", (int) num_components);
for (size_t i = 0; i < num_components; i++) { SDL_Log("CAMERA: - %d: %d", (int) i, (int) components[i]); }
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
if ((num_planes == 3) && (device->spec.format == SDL_PIXELFORMAT_NV21)) {
SDL_Log("THIS IS AN NV21 IMAGE, ALLEGEDLY.");
num_planes--; // treat the interleaved planes as one.
}
#endif
int32_t stride = 0;
// !!! FIXME: this works because of luck of how YUV420 and NV21 are layed out, but we _should_ check this for each component.
if ((imgrc = OH_ImageNative_GetRowStride(image, components[0], &stride)) != IMAGE_SUCCESS) {
SetImageError("Failed to get image row stride", imgrc);
OH_ImageNative_Release(image);
return SDL_CAMERA_FRAME_ERROR;
}
frame->pitch = (int) stride;
size_t buflen = 0;
for (int i = 0; (i < num_planes) && (i < 3); i++) {
size_t datalen = 0;
OH_ImageNative_GetBufferSize(image, components[i], &datalen);
buflen += datalen;
}
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (frame->pixels == NULL) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
Uint8 *dst = frame->pixels;
for (int i = 0; i < num_planes; i++) {
OH_NativeBuffer *imagebuf = NULL;
OH_ImageNative_GetByteBuffer(image, components[i], &imagebuf);
size_t datalen = 0;
OH_ImageNative_GetBufferSize(image, components[i], &datalen);
void *data = NULL;
if (OH_NativeBuffer_Map(imagebuf, &data) != 0) {
SDL_memset(dst, 0, datalen); // oh well.
} else {
SDL_memcpy(dst, data, datalen);
OH_NativeBuffer_Unmap(imagebuf);
}
dst += datalen;
}
}
OH_ImageNative_Release(image);
int dev_rotation = 0;
switch (SDL_GetOpenHarmonyDeviceCurrentOrientation()) {
case SDL_ORIENTATION_PORTRAIT: dev_rotation = 0; break;
case SDL_ORIENTATION_LANDSCAPE: dev_rotation = 90; break;
case SDL_ORIENTATION_PORTRAIT_FLIPPED: dev_rotation = 180; break;
case SDL_ORIENTATION_LANDSCAPE_FLIPPED: dev_rotation = 270; break;
default: SDL_assert(!"Unexpected device rotation!"); dev_rotation = 0; break;
}
if (device->position == SDL_CAMERA_POSITION_BACK_FACING) {
dev_rotation = -dev_rotation; // we want to subtract this value, instead of add, if back-facing.
}
*rotation = (float) (dev_rotation + device->hidden->rotation); // current phone orientation, static camera orientation in relation to phone.
return result;
}
static void OPENHARMONYCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep the OH_ImageNative until ReleaseFrame...
SDL_aligned_free(frame->pixels);
}
static void OnImageReceiverFrame(OH_ImageReceiverNative *receiver)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB OnImageReceiverFrame");
#endif
SDL_Camera *device = SDL_FindPhysicalCameraByCallback(FindOpenHarmonyCameraByImageReceiver, receiver);
if (device) {
SDL_CameraThreadIterate(device);
}
}
static void OnVideoOutputFrameStart(Camera_PreviewOutput *output)
//static void OnVideoOutputFrameStart(Camera_VideoOutput *output)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB OnVideoOutputFrameStart");
#endif
}
static void OnVideoOutputFrameEnd(Camera_PreviewOutput *output, int32_t frame_count)
//static void OnVideoOutputFrameEnd(Camera_VideoOutput *output, int32_t frame_count)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB OnVideoOutputFrameEnd");
#endif
}
static void OnVideoOutputError(Camera_PreviewOutput *output, Camera_ErrorCode err)
//static void OnVideoOutputError(Camera_VideoOutput *output, Camera_ErrorCode err)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB OnVideoOutputError");
#endif
}
static void OnCameraInputError(const Camera_Input *input, Camera_ErrorCode err)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB OnCameraInputError");
#endif
SDL_Camera *device = SDL_FindPhysicalCameraByCallback(FindOpenHarmonyCameraByInput, (void *) input);
if (device) {
SDL_CameraDisconnected(device);
}
}
static void OPENHARMONYCAMERA_CloseDevice(SDL_Camera *device)
{
if (device && device->hidden) {
struct SDL_PrivateCameraData *hidden = device->hidden;
if (hidden->session) {
OH_CaptureSession_Stop(hidden->session);
OH_CaptureSession_Release(hidden->session);
}
if (hidden->input) {
OH_CameraInput_Close(hidden->input);
OH_CameraInput_Release(hidden->input);
}
if (hidden->output) {
OH_PreviewOutput_Stop(hidden->output);
OH_PreviewOutput_Release(hidden->output);
//OH_VideoOutput_Stop(hidden->output);
//OH_VideoOutput_Release(hidden->output);
}
if (hidden->image_receiver) {
OH_ImageReceiverNative_Release(hidden->image_receiver);
}
if (hidden->options) {
OH_ImageReceiverOptions_Release(hidden->options);
}
device->hidden = NULL;
SDL_free(hidden);
}
}
// this is where the "opening" of the camera happens, after permission is granted.
static bool PrepareCamera(SDL_Camera *device)
{
struct SDL_PrivateCameraData *hidden = device->hidden;
SDL_assert(hidden != NULL);
// just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
const SDL_CameraSpec *spec = &hidden->requested_spec;
Camera_ErrorCode rc = CAMERA_OK;
Image_ErrorCode imgrc = IMAGE_SUCCESS;
Camera_Device *ohdevice = (Camera_Device *) device->handle;
const Camera_Profile video_profile = { ConvertSDLFormatToOpenHarmony(spec->format), { (uint32_t) spec->width, (uint32_t) spec->height } };
//const Camera_VideoProfile video_profile = { ConvertSDLFormatToOpenHarmony(spec->format), { (uint32_t) spec->width, (uint32_t) spec->height }, { spec->framerate_numerator, spec->framerate_numerator } };
const Image_Size imgsize = { (uint32_t) spec->width, (uint32_t) spec->height };
const int32_t imgcapacity = 8; // image buffer queue size. docs recommend this be 8: https://developer.huawei.com/consumer/en/doc/harmonyos-guides/native-camera-preview-imagereceiver
uint64_t receiver_surface_id = 0;
char receiver_surface_id_str[64];
uint32_t orientation = 0;
CameraInput_Callbacks camera_input_callbacks = { OnCameraInputError };
PreviewOutput_Callbacks video_output_callbacks = { OnVideoOutputFrameStart, OnVideoOutputFrameEnd, OnVideoOutputError };
//VideoOutput_Callbacks video_output_callbacks = { OnVideoOutputFrameStart, OnVideoOutputFrameEnd, OnVideoOutputError };
if ((imgrc = OH_ImageReceiverOptions_Create(&hidden->options)) != IMAGE_SUCCESS) {
return SetImageError("Failed to create image receiver options", imgrc);
} else if ((imgrc = OH_ImageReceiverOptions_SetSize(hidden->options, imgsize)) != IMAGE_SUCCESS) {
return SetImageError("Failed to set image receiver size option", imgrc);
} else if ((imgrc = OH_ImageReceiverOptions_SetCapacity(hidden->options, imgcapacity)) != IMAGE_SUCCESS) {
return SetImageError("Failed to set image receiver capacity option", imgrc);
} else if ((imgrc = OH_ImageReceiverNative_Create(hidden->options, &hidden->image_receiver)) != IMAGE_SUCCESS) {
return SetImageError("Failed to create native image receiver", imgrc);
} else if ((imgrc = OH_ImageReceiverNative_On(hidden->image_receiver, OnImageReceiverFrame)) != IMAGE_SUCCESS) {
return SetImageError("Failed to set native image receiver callback", imgrc);
} else if ((imgrc = OH_ImageReceiverNative_GetReceivingSurfaceId(hidden->image_receiver, &receiver_surface_id)) != IMAGE_SUCCESS) {
return SetImageError("Failed to create native image receiver", imgrc);
} else if (SDL_snprintf(receiver_surface_id_str, sizeof (receiver_surface_id_str), "%" SDL_PRIu64, receiver_surface_id) >= sizeof (receiver_surface_id_str)) {
return SDL_OutOfMemory(); // shouldn't happen.
} else if ((rc = OH_CameraDevice_GetCameraOrientation(ohdevice, &orientation)) != CAMERA_OK) {
return SetCameraError("Failed to determine camera orientation", rc);
} else if ((rc = OH_CameraManager_CreatePreviewOutput(camera_manager, &video_profile, receiver_surface_id_str, &hidden->output)) != CAMERA_OK) {
//} else if ((rc = OH_CameraManager_CreateVideoOutput(camera_manager, &video_profile, receiver_surface_id_str, &hidden->output)) != CAMERA_OK) {
return SetCameraError("Failed to create video output", rc);
} else if ((rc = OH_PreviewOutput_RegisterCallback(hidden->output, &video_output_callbacks)) != CAMERA_OK) {
//} else if ((rc = OH_VideoOutput_RegisterCallback(hidden->output, &video_output_callbacks)) != CAMERA_OK) {
return SetCameraError("Failed to set video output callbacks", rc);
} else if ((rc = OH_CameraManager_CreateCameraInput(camera_manager, ohdevice, &hidden->input)) != CAMERA_OK) {
return SetCameraError("Failed to create camera input", rc);
} else if ((rc = OH_CameraInput_RegisterCallback(hidden->input, &camera_input_callbacks)) != CAMERA_OK) {
return SetCameraError("Failed to register camera input callbacks", rc);
} else if ((rc = OH_CameraInput_Open(hidden->input)) != CAMERA_OK) {
return SetCameraError("Failed to open camera input", rc);
} else if ((rc = OH_CameraManager_CreateCaptureSession(camera_manager, &hidden->session)) != CAMERA_OK) {
return SetCameraError("Failed to create camera capture session", rc);
} else if ((rc = OH_CaptureSession_SetSessionMode(hidden->session, NORMAL_VIDEO)) != CAMERA_OK) {
return SetCameraError("Failed to set camera capture session mode", rc);
} else if ((rc = OH_CaptureSession_BeginConfig(hidden->session)) != CAMERA_OK) {
return SetCameraError("Failed to begin camera capture session config", rc);
// !!! FIXME: maybe? } else if ((rc = OH_CaptureSession_SetActiveColorSpace(hidden->session, colorspace)) != CAMERA_OK) {
// !!! FIXME: maybe? return SetCameraError("Failed to set active colorspace in session config", rc);
} else if ((rc = OH_CaptureSession_AddInput(hidden->session, hidden->input)) != CAMERA_OK) {
return SetCameraError("Failed to add device input to camera capture session", rc);
} else if ((rc = OH_CaptureSession_AddPreviewOutput(hidden->session, hidden->output)) != CAMERA_OK) {
//} else if ((rc = OH_CaptureSession_AddVideoOutput(hidden->session, hidden->output)) != CAMERA_OK) {
return SetCameraError("Failed to add video output to camera capture session", rc);
} else if ((rc = OH_CaptureSession_CommitConfig(hidden->session)) != CAMERA_OK) {
return SetCameraError("Failed to commit camera capture session config", rc);
} else if ((rc = OH_CaptureSession_Start(hidden->session)) != CAMERA_OK) {
return SetCameraError("Failed to start camera capture session", rc);
//} else if ((rc = OH_VideoOutput_Start(hidden->output)) != CAMERA_OK) {
// return SetCameraError("Failed to start video output", rc);
}
// we don't need this, it's only in `hidden` so it'll clean up if we fail halfway through this.
OH_ImageReceiverOptions_Release(hidden->options);
hidden->options = NULL;
hidden->rotation = (int) (orientation % 360);
return true;
}
static void SDLCALL CameraPermissionCallback(void *userdata, const char *permission, bool granted)
{
SDL_Camera *device = (SDL_Camera *) userdata;
if (device->hidden != NULL) { // if device was already closed, don't send an event.
if (!granted) {
SDL_CameraPermissionOutcome(device, false); // sorry, permission denied.
} else if (!PrepareCamera(device)) { // permission given? Actually open the camera now.
// uhoh, setup failed; since the app thinks we already "opened" the device, mark it as disconnected and don't report the permission.
SDL_CameraDisconnected(device);
} else {
// okay! We have permission to use the camera _and_ opening the hardware worked out, report that the camera is usable!
SDL_CameraPermissionOutcome(device, true); // go go go!
}
}
UnrefPhysicalCamera(device); // we ref'd this in OpenDevice, release the extra reference.
}
static bool OPENHARMONYCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
{
#if 0
// !!! FIXME: Android requires all concurrent cameras to be opened before assigning them all to a session.
// !!! FIXME: OpenHarmony, in API level 18, added OH_CameraInput_OpenConcurrentCameras() and some other support APIs,
// !!! FIXME: which is probably the way to handle this, but we're adopting SDL's current Android attitude for now: if it fails, okay.
if (CheckDevicePlaying()) {
return SDL_SetError("A camera is already playing");
}
#endif
if (spec->framerate_denominator != 1) {
return SDL_SetError("Invalid framerate"); // we force these to fps/1
}
device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
if (device->hidden == NULL) {
return false;
}
RefPhysicalCamera(device); // ref'd until permission callback fires.
// just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
SDL_copyp(&device->hidden->requested_spec, spec);
if (!SDL_RequestOpenHarmonyPermission("ohos.permission.CAMERA", CameraPermissionCallback, device)) {
UnrefPhysicalCamera(device);
return false;
}
return true; // we don't open the camera until permission is granted, so always succeed for now.
}
static void OPENHARMONYCAMERA_FreeDeviceHandle(SDL_Camera *device)
{
if (device) {
Camera_Device *ohdevice = (Camera_Device *) device->handle;
SDL_free(ohdevice->cameraId);
SDL_free(ohdevice);
}
}
static void GatherCameraSpecs(Camera_Device *ohdevice, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
{
Camera_ErrorCode rc = CAMERA_OK;
SDL_zerop(add_data);
*fullname = NULL; // !!! FIXME: there doesn't appear to be a "get device's name" API for obtaining something like "Logitech WebCam".
if (ohdevice->cameraPosition == CAMERA_POSITION_FRONT) {
*position = SDL_CAMERA_POSITION_FRONT_FACING;
if (!*fullname) {
*fullname = SDL_strdup("Front-facing camera");
}
} else if (ohdevice->cameraPosition == CAMERA_POSITION_BACK) {
*position = SDL_CAMERA_POSITION_BACK_FACING;
if (!*fullname) {
*fullname = SDL_strdup("Back-facing camera");
}
} else {
*position = SDL_CAMERA_POSITION_UNKNOWN;
}
if (!*fullname) {
*fullname = SDL_strdup("Generic camera"); // we tried.
}
Camera_OutputCapability *output_caps = NULL;
rc = OH_CameraManager_GetSupportedCameraOutputCapabilityWithSceneMode(camera_manager, ohdevice, NORMAL_VIDEO, &output_caps);
if ((rc != CAMERA_OK) || !output_caps) {
return;
}
const uint32_t total = output_caps->previewProfilesSize;
//const uint32_t total = output_caps->videoProfilesSize;
for (uint32_t i = 0; i < total; i++) {
const Camera_Profile *vidprof = output_caps->previewProfiles[i];
//const Camera_VideoProfile *vidprof = output_caps->videoProfiles[i];
SDL_PixelFormat device_format = SDL_PIXELFORMAT_UNKNOWN;
SDL_Colorspace device_colorspace = SDL_COLORSPACE_UNKNOWN;
const int w = (int) vidprof->size.width;
const int h = (int) vidprof->size.height;
if ((w <= 0) || (h <= 0)) {
continue;
} else {
ConvertOpenHarmonyFormatToSDL(vidprof->format, &device_format, &device_colorspace);
if (device_format == SDL_PIXELFORMAT_UNKNOWN) {
continue;
}
}
SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, 60, 1);
//SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, vidprof->range.min, 1);
//if (vidprof->range.min != vidprof->range.max) {
// SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, vidprof->range.max, 1);
//}
}
OH_CameraManager_DeleteSupportedCameraOutputCapability(camera_manager, output_caps);
}
static void MaybeAddDevice(Camera_Device *ohdevice)
{
const char *devid = ohdevice->cameraId;
Camera_ErrorCode rc;
#if DEBUG_CAMERA
SDL_Log("CAMERA: MaybeAddDevice('%s')", devid);
#endif
if (SDL_FindPhysicalCameraByCallback(FindOpenHarmonyCameraByDevicePtr, ohdevice)) {
return; // already have this one.
}
Camera_SceneMode *scene_modes = NULL;
uint32_t num_scene_modes = 0;
rc = OH_CameraManager_GetSupportedSceneModes(ohdevice, &scene_modes, &num_scene_modes);
if ((rc != CAMERA_OK) || !scene_modes) {
return;
}
bool okay = false;
for (uint32_t i = 0; i < num_scene_modes; i++) {
if (scene_modes[i] == NORMAL_VIDEO) {
okay = true;
break;
}
}
OH_CameraManager_DeleteSceneModes(camera_manager, scene_modes);
if (!okay) {
return; // uh...weird camera...?
}
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
char *fullname = NULL;
CameraFormatAddData add_data;
GatherCameraSpecs(ohdevice, &add_data, &fullname, &position);
if (add_data.num_specs > 0) {
SDL_Camera *device = NULL;
Camera_Device *devcpy = (Camera_Device *) SDL_malloc(sizeof (*devcpy));
if (devcpy) {
devcpy->cameraId = SDL_strdup(devid);
if (devcpy->cameraId) {
device = SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, devcpy);
}
}
if (!device) {
if (devcpy) {
SDL_free(devcpy->cameraId);
SDL_free(devcpy);
}
}
}
SDL_free(fullname);
SDL_free(add_data.specs);
}
// note that camera "availability" covers both hotplugging and whether another
// has the device opened, but for something like OpenHarmony, it's probably fine
// to treat both unplugging and loss of access as disconnection events. When
// the other app closes the camera, we get an available event as if it was
// just plugged back in.
static void OnCameraDeviceStatus(Camera_Manager *cameraManager, Camera_StatusInfo *status)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB OnCameraDeviceStatus('%s', %d)", status->camera->cameraId, (int) status->status);
#endif
SDL_assert(status != NULL);
SDL_assert(status->camera != NULL);
SDL_assert(status->camera->cameraId != NULL);
if (status->status == CAMERA_STATUS_AVAILABLE) {
MaybeAddDevice(status->camera);
} else if (status->status == CAMERA_STATUS_UNAVAILABLE) {
// !!! FIXME: this comment is from Android, is this true here, too?
// THIS CALLBACK FIRES WHEN YOU OPEN THE DEVICE YOURSELF. :(
// Make sure we don't have the device opened, in which case a different callback will fire instead if actually lost.
SDL_Camera *device = SDL_FindPhysicalCameraByCallback(FindOpenHarmonyCameraByDevicePtr, status->camera);
if (device && !device->hidden) {
SDL_CameraDisconnected(device);
}
}
}
static CameraManager_Callbacks camera_availability_listener = { OnCameraDeviceStatus };
static void OPENHARMONYCAMERA_DetectDevices(void)
{
Camera_ErrorCode rc = CAMERA_OK;
Camera_Device *cameras = NULL;
uint32_t total = 0;
rc = OH_CameraManager_GetSupportedCameras(camera_manager, &cameras, &total);
if ((rc == CAMERA_OK) && (total > 0)) { // if this fails, maybe the callback catches things later.
for (uint32_t i = 0; i < total; i++) {
MaybeAddDevice(&cameras[i]);
}
OH_CameraManager_DeleteSupportedCameras(camera_manager, cameras, total);
}
OH_CameraManager_RegisterCallback(camera_manager, &camera_availability_listener);
}
static void OPENHARMONYCAMERA_Deinitialize(void)
{
OH_CameraManager_UnregisterCallback(camera_manager, &camera_availability_listener);
DestroyCameraManager();
}
static bool OPENHARMONYCAMERA_Init(SDL_CameraDriverImpl *impl)
{
if (!CreateCameraManager()) {
return false;
}
impl->DetectDevices = OPENHARMONYCAMERA_DetectDevices;
impl->OpenDevice = OPENHARMONYCAMERA_OpenDevice;
impl->CloseDevice = OPENHARMONYCAMERA_CloseDevice;
impl->WaitDevice = OPENHARMONYCAMERA_WaitDevice;
impl->AcquireFrame = OPENHARMONYCAMERA_AcquireFrame;
impl->ReleaseFrame = OPENHARMONYCAMERA_ReleaseFrame;
impl->FreeDeviceHandle = OPENHARMONYCAMERA_FreeDeviceHandle;
impl->Deinitialize = OPENHARMONYCAMERA_Deinitialize;
impl->ProvidesOwnCallbackThread = true;
return true;
}
CameraBootStrap OPENHARMONYCAMERA_bootstrap = {
"ohcamera", "SDL OpenHarmony camera driver", OPENHARMONYCAMERA_Init, false
};
#endif

View File

@@ -204,3 +204,25 @@ bool SDL_IsUbuntuTouch(void)
}
#endif
#ifndef SDL_PLATFORM_OPENHARMONY
const char *SDL_GetOpenHarmonyInternalStoragePath(void)
{
SDL_Unsupported();
return NULL;
}
int SDL_GetOpenHarmonySDKVersion(void)
{
return SDL_Unsupported();
}
bool SDL_RequestOpenHarmonyPermission(const char *permission, SDL_RequestOpenHarmonyPermissionCallback cb, void *userdata)
{
(void)permission;
(void)cb;
(void)userdata;
return SDL_Unsupported();
}
#endif

View File

@@ -69,3 +69,11 @@ extern SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(JavaVM *vm, void *reserved);
#if !defined(SDL_PLATFORM_LINUX)
extern SDL_DECLSPEC bool SDLCALL SDL_IsUbuntuTouch(void);
#endif /* !SDL_PLATFORM_LINUX */
#if !defined(SDL_PLATFORM_OPENHARMONY)
typedef void *SDL_RequestOpenHarmonyPermissionCallback;
extern SDL_DECLSPEC const char * SDLCALL SDL_GetOpenHarmonyInternalStoragePath(void);
extern SDL_DECLSPEC int SDLCALL SDL_GetOpenHarmonySDKVersion(void);
extern SDL_DECLSPEC bool SDLCALL SDL_RequestOpenHarmonyPermission(const char *permission, SDL_RequestOpenHarmonyPermissionCallback cb, void *userdata);
#endif

View File

@@ -113,6 +113,10 @@ bool Android_JNI_FileClose(void *userdata);
bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata);
bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info);
#define SDL_PlatformEnumerateAssetDirectory Android_JNI_EnumerateAssetDirectory
#define SDL_GetPlatformInternalStoragePath SDL_GetAndroidInternalStoragePath
#define SDL_PlatformGetAssetPathInfo Android_JNI_GetAssetPathInfo
// Environment support
void Android_JNI_GetManifestEnvironmentVariables(void);
int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_openharmony_h
#define SDL_openharmony_h
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
/* *INDENT-OFF* */
extern "C" {
/* *INDENT-ON* */
#endif
void SDL_DebugLogOpenHarmonyInfo(void);
const char *SDL_GetOpenHarmonySystemLocale(void);
SDL_SystemTheme SDL_GetOpenHarmonySystemTheme(void);
SDL_DisplayOrientation SDL_GetOpenHarmonyDeviceCurrentOrientation(void);
SDL_FormFactor SDL_GetOpenHarmonyDeviceFormFactor(void);
bool SDL_OpenHarmonyOpenURL(const char *url);
bool SDL_OpenHarmonyToggleSystemBars(bool status_bar, bool navigation_bar);
bool SDL_OpenHarmonyChangeScreenSaver(bool enable);
bool SDL_OpenHarmonyShowScreenKeyboard(int input_type, int cap_type, const SDL_Rect *text_input_rect);
bool SDL_OpenHarmonyHideScreenKeyboard(void);
bool SDL_OpenHarmonyShowMousePointer(void);
bool SDL_OpenHarmonyHideMousePointer(void);
bool SDL_OpenHarmonyRawFileOpen(void **puserdata, const char *fileName, const char *mode);
Sint64 SDL_OpenHarmonyRawFileSize(void *userdata);
Sint64 SDL_OpenHarmonyRawFileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence);
size_t SDL_OpenHarmonyRawFileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status);
bool SDL_OpenHarmonyRawFileClose(void *userdata);
bool SDL_OpenHarmonyEnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata);
bool SDL_OpenHarmonyGetAssetPathInfo(const char *path, SDL_PathInfo *info);
#define SDL_PlatformEnumerateAssetDirectory SDL_OpenHarmonyEnumerateAssetDirectory
#define SDL_GetPlatformInternalStoragePath SDL_GetOpenHarmonyInternalStoragePath
#define SDL_PlatformGetAssetPathInfo SDL_OpenHarmonyGetAssetPathInfo
// Ends C function definitions when using C++
#ifdef __cplusplus
/* *INDENT-OFF* */
}
/* *INDENT-ON* */
#endif
#endif // SDL_openharmony_h

View File

@@ -493,7 +493,7 @@ static int CPU_haveNEON(void)
return 0;
}
return (hasneon & HWCAP_NEON) == HWCAP_NEON;
#elif (defined(SDL_PLATFORM_LINUX) && defined(HAVE_GETAUXVAL)) || defined(SDL_PLATFORM_ANDROID)
#elif (defined(SDL_PLATFORM_LINUX) && defined(HAVE_GETAUXVAL)) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
return (getauxval(AT_HWCAP) & HWCAP_NEON) == HWCAP_NEON;
#elif defined(SDL_PLATFORM_LINUX)
return readProcAuxvForNeon();

View File

@@ -1311,3 +1311,6 @@ _SDL_SetRenderViewportFloat
_SDL_GetRenderViewportFloat
_SDL_SetRenderClipRectFloat
_SDL_GetRenderClipRectFloat
_SDL_GetOpenHarmonySDKVersion
_SDL_GetOpenHarmonyInternalStoragePath
_SDL_RequestOpenHarmonyPermission

View File

@@ -49,6 +49,8 @@
#define SDL_DYNAMIC_API 0
#elif defined(SDL_PLATFORM_ANDROID) // probably not useful on Android.
#define SDL_DYNAMIC_API 0
#elif defined(SDL_PLATFORM_OPENHARMONY) // probably not useful on OpenHarmony.
#define SDL_DYNAMIC_API 0
#elif defined(SDL_PLATFORM_EMSCRIPTEN) // probably not useful on Emscripten.
#define SDL_DYNAMIC_API 0
#elif defined(SDL_PLATFORM_PS2) && SDL_PLATFORM_PS2

View File

@@ -1312,6 +1312,9 @@ SDL3_0.0.0 {
SDL_GetRenderViewportFloat;
SDL_SetRenderClipRectFloat;
SDL_GetRenderClipRectFloat;
SDL_GetOpenHarmonySDKVersion;
SDL_GetOpenHarmonyInternalStoragePath;
SDL_RequestOpenHarmonyPermission;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@@ -1338,3 +1338,6 @@
#define SDL_GetRenderViewportFloat SDL_GetRenderViewportFloat_REAL
#define SDL_SetRenderClipRectFloat SDL_SetRenderClipRectFloat_REAL
#define SDL_GetRenderClipRectFloat SDL_GetRenderClipRectFloat_REAL
#define SDL_GetOpenHarmonySDKVersion SDL_GetOpenHarmonySDKVersion_REAL
#define SDL_GetOpenHarmonyInternalStoragePath SDL_GetOpenHarmonyInternalStoragePath_REAL
#define SDL_RequestOpenHarmonyPermission SDL_RequestOpenHarmonyPermission_REAL

View File

@@ -1346,3 +1346,6 @@ SDL_DYNAPI_PROC(bool,SDL_SetRenderViewportFloat,(SDL_Renderer *a,const SDL_FRect
SDL_DYNAPI_PROC(bool,SDL_GetRenderViewportFloat,(SDL_Renderer *a,SDL_FRect *b),(a,b),return)
SDL_DYNAPI_PROC(bool,SDL_SetRenderClipRectFloat,(SDL_Renderer *a,const SDL_FRect *b),(a,b),return)
SDL_DYNAPI_PROC(bool,SDL_GetRenderClipRectFloat,(SDL_Renderer *a,SDL_FRect *b),(a,b),return)
SDL_DYNAPI_PROC(int,SDL_GetOpenHarmonySDKVersion,(void),(),return)
SDL_DYNAPI_PROC(const char*,SDL_GetOpenHarmonyInternalStoragePath,(void),(),return)
SDL_DYNAPI_PROC(bool,SDL_RequestOpenHarmonyPermission,(const char *a,SDL_RequestOpenHarmonyPermissionCallback b,void *c),(a,b,c),return)

View File

@@ -333,7 +333,7 @@ SDL_Window *SDL_GetKeyboardFocus(void)
bool SDL_SetKeyboardFocus(SDL_Window *window)
{
#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID)
#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_OPENHARMONY)
SDL_VideoDevice *video = SDL_GetVideoDevice();
#endif
SDL_Keyboard *keyboard = &SDL_keyboard;
@@ -354,7 +354,7 @@ bool SDL_SetKeyboardFocus(SDL_Window *window)
if (keyboard->focus && keyboard->focus != window) {
SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0);
#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID)
#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_OPENHARMONY)
// Ensures IME compositions are committed
if (SDL_TextInputActive(keyboard->focus)) {
if (video && video->StopTextInput) {
@@ -384,7 +384,7 @@ bool SDL_SetKeyboardFocus(SDL_Window *window)
if (keyboard->focus) {
SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_GAINED, 0, 0);
#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID)
#if !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_OPENHARMONY)
if (SDL_TextInputActive(keyboard->focus)) {
if (video && video->StartTextInput) {
video->StartTextInput(video, keyboard->focus, keyboard->focus->text_input_props);

View File

@@ -375,7 +375,7 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob
size_t pathlen = SDL_strlen(path);
// if path ends with any slash, chop them off, so we don't confuse the pattern matcher later.
#ifdef SDL_PLATFORM_ANDROID
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
if (SDL_strcmp(path, "assets://") == 0) { // don't chop '//' off this if we're looking for the root of the asset tree.
pathlen--; // we'll add a 1 again later.
} else

View File

@@ -0,0 +1,78 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_FILESYSTEM_OPENHARMONY
// !!! FIXME: HACK to prevent <AbilityKit/ability_runtime/start_options.h> from including. It has '&' instead of '*' for some args, which suggests it's only been tested with C++.
#define ABILITY_RUNTIME_START_OPTIONS_H
typedef enum AbilityRuntime_StartOptions AbilityRuntime_StartOptions;
#include <sys/stat.h>
#include <AbilityKit/ability_runtime/application_context.h>
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// System dependent filesystem routines
#include "../SDL_sysfilesystem.h"
char *SDL_SYS_GetBasePath(void)
{
return SDL_strdup("assets://");
}
char *SDL_SYS_GetExeName(void)
{
char buffer[128];
int32_t writelen = 0;
const AbilityRuntime_ErrorCode rc = OH_AbilityRuntime_ApplicationContextGetBundleName(buffer, (int32_t) sizeof (buffer), &writelen);
if (rc != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
SDL_SetError("OH_AbilityRuntime_ApplicationContextGetBundleName failed: %d", (int) rc);
return NULL;
}
return SDL_strdup(buffer);
}
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
{
const char *prefdir = SDL_GetOpenHarmonyInternalStoragePath();
char *retval = NULL;
if (prefdir) {
if (SDL_asprintf(&retval, "%s/%s/", prefdir, app) < 0) {
retval = NULL;
}
}
mkdir(prefdir, 0755);
mkdir(retval, 0755);
return retval;
}
char *SDL_SYS_GetUserFolder(SDL_Folder folder)
{
SDL_Unsupported(); // !!! FIXME: Can we support _any_ of this?
return NULL;
}
#endif // SDL_FILESYSTEM_OPENHARMONY

View File

@@ -39,24 +39,28 @@
#include "../../core/android/SDL_android.h"
#endif
#ifdef SDL_PLATFORM_OPENHARMONY
#include "../../core/openharmony/SDL_openharmony.h"
#endif
bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata)
{
char *apath = NULL; // absolute path (for Android, iOS, etc). Overrides `path`.
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS)
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_OPENHARMONY)
if (*path == '\0') {
return SDL_SetError("No such file or directory");
} else if (*path != '/') {
#ifdef SDL_PLATFORM_ANDROID
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
if (SDL_strncmp(path, "assets://", 9) == 0) {
char *pathwithsep = NULL;
SDL_asprintf(&pathwithsep, "%s%s", path, (path[SDL_strlen(path) - 1] != '/') ? "/" : "");
const bool retval = pathwithsep ? Android_JNI_EnumerateAssetDirectory(pathwithsep, cb, userdata) : false;
const bool retval = pathwithsep ? SDL_PlatformEnumerateAssetDirectory(pathwithsep, cb, userdata) : false;
SDL_free(pathwithsep);
return retval;
}
SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path);
SDL_asprintf(&apath, "%s/%s", SDL_GetPlatformInternalStoragePath(), path);
#elif defined(SDL_PLATFORM_IOS)
char *base = SDL_GetPrefPath("", "");
if (!base) {
@@ -71,7 +75,7 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
return false;
}
}
#elif 0 // this is just for testing that `apath` works when you aren't on iOS or Android.
#elif 0 // this is just for testing that `apath` works when you aren't on iOS or Android or HarmonyOS.
if (*path != '/') {
char *c = SDL_SYS_GetCurrentDirectory();
SDL_asprintf(&apath, "%s%s", c, path);
@@ -98,9 +102,9 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
DIR *dir = opendir(pathwithsep);
if (!dir) {
#ifdef SDL_PLATFORM_ANDROID // Maybe it's an asset... that didn't use an "assets://" URL?
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY) // Maybe it's an asset... that didn't use an "assets://" URL?
if (*pathwithsep != '/') { // don't fall back to asset tree for absolute paths, in case opendir() failed for other reasons, like opendir("/") returning EACCES.
const bool retval = Android_JNI_EnumerateAssetDirectory(pathwithsep + extralen, cb, userdata);
const bool retval = SDL_PlatformEnumerateAssetDirectory(pathwithsep + extralen, cb, userdata);
SDL_free(pathwithsep);
return retval;
}
@@ -130,12 +134,12 @@ bool SDL_SYS_RemovePath(const char *path)
{
int rc;
#ifdef SDL_PLATFORM_ANDROID
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
if (*path == '/') {
rc = remove(path);
} else {
char *apath = NULL;
SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path);
SDL_asprintf(&apath, "%s/%s", SDL_GetPlatformInternalStoragePath(), path);
if (!apath) {
return false;
}
@@ -177,18 +181,18 @@ bool SDL_SYS_RenamePath(const char *oldpath, const char *newpath)
{
int rc;
#ifdef SDL_PLATFORM_ANDROID
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
char *aoldpath = NULL;
char *anewpath = NULL;
if (*oldpath != '/') {
SDL_asprintf(&aoldpath, "%s/%s", SDL_GetAndroidInternalStoragePath(), oldpath);
SDL_asprintf(&aoldpath, "%s/%s", SDL_GetPlatformInternalStoragePath(), oldpath);
if (!aoldpath) {
return false;
}
oldpath = aoldpath;
}
if (*newpath != '/') {
SDL_asprintf(&anewpath, "%s/%s", SDL_GetAndroidInternalStoragePath(), newpath);
SDL_asprintf(&anewpath, "%s/%s", SDL_GetPlatformInternalStoragePath(), newpath);
if (!anewpath) {
SDL_free(aoldpath);
return false;
@@ -349,16 +353,16 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
struct stat statbuf;
int rc;
#ifdef SDL_PLATFORM_ANDROID
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
if (*path == '\0') {
return SDL_SetError("No such file or directory");
} else if (*path == '/') {
rc = stat(path, &statbuf);
} else if (SDL_strncmp(path, "assets://", 9) == 0) {
return Android_JNI_GetAssetPathInfo(path, info);
return SDL_PlatformGetAssetPathInfo(path, info);
} else {
char *apath = NULL;
SDL_asprintf(&apath, "%s/%s", SDL_GetAndroidInternalStoragePath(), path);
SDL_asprintf(&apath, "%s/%s", SDL_GetPlatformInternalStoragePath(), path);
if (!apath) {
return false;
}
@@ -366,7 +370,7 @@ bool SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info)
SDL_free(apath);
}
if (rc < 0) { // Maybe it's an asset... that didn't use an "assets://" URL?
return Android_JNI_GetAssetPathInfo(path, info);
return SDL_PlatformGetAssetPathInfo(path, info);
}
#elif defined(SDL_PLATFORM_IOS)
if (*path == '/') {

View File

@@ -69,6 +69,11 @@ struct SDL_IOStream
#include "../core/android/SDL_android.h"
#endif
#ifdef SDL_PLATFORM_OPENHARMONY
#include <unistd.h>
#include "../core/openharmony/SDL_openharmony.h"
#endif
#if defined(SDL_PLATFORM_WINDOWS) && !defined(SDL_PLATFORM_CYGWIN)
typedef struct IOStreamWindowsData
@@ -1024,7 +1029,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
return NULL;
}
#ifdef SDL_PLATFORM_ANDROID
#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
#ifdef HAVE_STDIO_H
// Try to open the file on the filesystem first
if (*file == '/') {
@@ -1037,6 +1042,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
}
return SDL_IOFromFP(fp, true);
}
#ifdef SDL_PLATFORM_ANDROID
} else if (SDL_strncmp(file, "content://", 10) == 0) {
// Try opening content:// URI
int fd = Android_JNI_OpenFileDescriptor(file, mode);
@@ -1053,10 +1059,11 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
}
return SDL_IOFromFP(fp, true);
#endif
} else if (SDL_strncmp(file, "assets://", 9) != 0) {
// Try opening it from internal storage if it's a relative path
char *path = NULL;
SDL_asprintf(&path, "%s/%s", SDL_GetAndroidInternalStoragePath(), file);
SDL_asprintf(&path, "%s/%s", SDL_GetPlatformInternalStoragePath(), file);
if (path) {
FILE *fp = fopen(path, mode);
SDL_free(path);
@@ -1072,6 +1079,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
}
#endif // HAVE_STDIO_H
#ifdef SDL_PLATFORM_ANDROID
// Try to open the file from the asset system?
void *iodata = NULL;
if (!Android_JNI_FileOpen(&iodata, file, mode)) {
@@ -1093,6 +1101,32 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
iostr->setioprops = android_setioprops;
}
#elif defined(SDL_PLATFORM_OPENHARMONY)
// Try to open the file from the asset system?
void *iodata = NULL;
if (!SDL_OpenHarmonyRawFileOpen(&iodata, file, mode)) {
return NULL;
}
SDL_IOStreamInterface iface;
SDL_INIT_INTERFACE(&iface);
iface.size = SDL_OpenHarmonyRawFileSize;
iface.seek = SDL_OpenHarmonyRawFileSeek;
iface.read = SDL_OpenHarmonyRawFileRead;
iface.write = NULL; // no write access via RawFile.
iface.close = SDL_OpenHarmonyRawFileClose;
iostr = SDL_OpenIO(&iface, iodata);
if (!iostr) {
iface.close(iodata);
} else {
const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
if (props) {
SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_OPENHARMONY_RAWFILE64_POINTER, iodata);
}
}
#endif
#elif defined(SDL_PLATFORM_IOS)
// Try to open the file on the filesystem first

View File

@@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_syslocale.h"
#include "../../core/openharmony/SDL_openharmony.h"
bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen)
{
const char *syslocale = SDL_GetOpenHarmonySystemLocale();
if (!syslocale) {
return SDL_SetError("Current locale couldn't be determined");
}
SDL_strlcpy(buf, syslocale, buflen);
return true;
}

View File

@@ -75,4 +75,3 @@ bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen)
return true;
}
/* vi: set ts=4 sw=4 expandtab: */

View File

@@ -88,10 +88,7 @@ static bool SDLCALL SDL_MainCallbackEventWatcher(void *userdata, SDL_Event *even
bool SDL_HasMainCallbacks(void)
{
if (SDL_main_iteration_callback) {
return true;
}
return false;
return (SDL_main_iteration_callback != NULL);
}
SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
@@ -107,9 +104,7 @@ SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func app
if (!SDL_InitSubSystem(SDL_INIT_EVENTS)) {
SDL_SetAtomicInt(&apprc, SDL_APP_FAILURE);
return SDL_APP_FAILURE;
}
if (!SDL_AddEventWatch(SDL_MainCallbackEventWatcher, NULL)) {
} else if (!SDL_AddEventWatch(SDL_MainCallbackEventWatcher, NULL)) {
SDL_SetAtomicInt(&apprc, SDL_APP_FAILURE);
return SDL_APP_FAILURE;
}

View File

@@ -23,6 +23,7 @@
#include "../SDL_main_callbacks.h"
#include "../../video/SDL_sysvideo.h"
// !!! FIXME: does this ifndef need to be here?
#ifndef SDL_PLATFORM_IOS
static Uint64 callback_rate_increment = 0;

View File

@@ -0,0 +1,60 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_PLATFORM_OPENHARMONY
#include "../SDL_main_callbacks.h"
void SDL_OpenHarmonyOnFrameCallback(void) // src/core/openharmony calls this when our XComponent is ready for a new frame, and we call SDL_AppIterate here
{
if (SDL_HasMainCallbacks()) { // ignore this if we're using SDL_main().
const SDL_AppResult rc = SDL_IterateMainCallbacks(true);
if (rc != SDL_APP_CONTINUE) {
SDL_Log("SDL_AppIterate said %s! Terminating!", (rc == SDL_APP_FAILURE) ? "SDL_APP_FAILURE" : "SDL_APP_SUCCESS");
SDL_QuitMainCallbacks(rc);
exit((rc == SDL_APP_FAILURE) ? 1 : 0);
}
}
}
// We hook into this deep into the actual OpenHarmony app lifecycle, see notes elsewhere. Once we return from this, we'll get OnFrame callbacks from our
// XComponent, which we'll use for SDL_AppIterate, etc.
int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
{
const SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit);
if (rc == SDL_APP_CONTINUE) {
SDL_Log("SDL_AppInit said continue! We are live!");
return 0; // this will fall all the way out of SDL_main (or whatever), where OpenHarmony will keep running the process, passing events through ArkTS, etc...
}
SDL_Log("SDL_AppInit said %s! Terminating!", (rc == SDL_APP_FAILURE) ? "SDL_APP_FAILURE" : "SDL_APP_SUCCESS");
// appinit requested quit, just bounce out now.
SDL_QuitMainCallbacks(rc);
exit((rc == SDL_APP_FAILURE) ? 1 : 0);
return 1; // just in case.
}
#endif

View File

@@ -0,0 +1,102 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_PLATFORM_OPENHARMONY
#include <hilog/log.h>
#include <dlfcn.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include "../SDL_main_callbacks.h"
static int SDL_main_argc = 0;
static char **SDL_main_argv = NULL;
static void *ThreadEntry__SDL_main(void *userdata)
{
SDL_main_func pMain = (SDL_main_func) userdata;
pthread_setname_np(pthread_self(), "SDL_main");
OH_LOG_Print(LOG_APP, LOG_FATAL, LOG_DOMAIN, "SDL/STARTUP", "Calling SDL_main!");
const int rc = pMain(SDL_main_argc, SDL_main_argv);
OH_LOG_Print(LOG_APP, LOG_FATAL, LOG_DOMAIN, "SDL/STARTUP", "SDL_main returned %{public}d! Now terminating the process...", rc);
exit(rc);
return NULL;
}
// This is passed to SDL_RunApp. It decides whether libmain.so has the main callbacks or SDL_main,
// and either calls SDL_EnterAppMainCallbacks or spins a thread and attempts to work with SDL_main().
// This will log something and attempt to terminate the process if things go wrong.
static int RunAppOpenHarmonyMain(int argc, char **argv)
{
// don't call SDL_Log() in here, we aren't necessarily set up yet! Use hilog directly!!
const char *sofile = "libmain.so";
void *lib = dlopen(sofile, RTLD_NOW | RTLD_LOCAL);
if (!lib) {
OH_LOG_Print(LOG_APP, LOG_FATAL, LOG_DOMAIN, "SDL/STARTUP", "Failed loading %{public}s, can't start app! (%{public}s)", sofile, dlerror());
exit(1);
}
SDL_main_func pMain = (SDL_main_func) dlsym(lib, "SDL_main");
if (pMain) {
SDL_main_argc = argc;
SDL_main_argv = argv;
pthread_t thread;
const int rc = pthread_create(&thread, NULL, ThreadEntry__SDL_main, pMain);
if (rc != 0) {
OH_LOG_Print(LOG_APP, LOG_FATAL, LOG_DOMAIN, "SDL/STARTUP", "Failed to create thread to run SDL_main from %{public}s, can't start app! (%{public}s)", sofile, strerror(rc));
exit(1);
}
pthread_detach(thread); // we won't be waiting on this.
return 0; // SDL_main is running in another thread, and this thread returns to the app's core loop thing.
}
#define FINDCBFN(nam) \
SDL_App##nam##_func pApp##nam = (SDL_App##nam##_func) dlsym(lib, "SDL_App" #nam); \
if (!pApp##nam) { \
OH_LOG_Print(LOG_APP, LOG_FATAL, LOG_DOMAIN, "SDL/STARTUP", "Couldn't find %{public}s in %{public}s, can't start app! (%{public}s)", "SDL_App" #nam, sofile, dlerror()); \
exit(1); \
}
FINDCBFN(Init);
FINDCBFN(Iterate);
FINDCBFN(Event);
FINDCBFN(Quit);
#undef FINDCBFN
// this returns after SDL_AppInit, and we'll deal with SDL_AppEvent/SDL_AppIterate in SDL_sysmain_callbacks.c, when we get OnFrame events from SDL's XComponent.
return SDL_EnterAppMainCallbacks(argc, argv, pAppInit, pAppIterate, pAppEvent, pAppQuit);
}
void SDL_OpenHarmonyMainSurfaceCreated(void) // src/core/openharmony calls this when our XComponent is ready to go, and we use this to get the actual native app code booted.
{
static bool booted = false;
if (!booted) { // just in case we make more XComponents later...?
booted = true;
SDL_RunApp(0, NULL, RunAppOpenHarmonyMain, NULL);
}
}
#endif

View File

@@ -20,6 +20,8 @@
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_ANDROID)
#include "../SDL_sysurl.h"
#include "../../core/android/SDL_android.h"
@@ -27,3 +29,6 @@ bool SDL_SYS_OpenURL(const char *url)
{
return Android_JNI_OpenURL(url);
}
#endif

View File

@@ -0,0 +1,34 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_OPENHARMONY)
#include "../SDL_sysurl.h"
#include "../../core/openharmony/SDL_openharmony.h"
bool SDL_SYS_OpenURL(const char *url)
{
return SDL_OpenHarmonyOpenURL(url);
}
#endif

View File

@@ -62,6 +62,9 @@ static SDL_GetPowerInfo_Impl implementations[] = {
#ifdef SDL_POWER_ANDROID // handles Android.
SDL_GetPowerInfo_Android,
#endif
#ifdef SDL_POWER_OPENHARMONY // handles HarmonyOS/OpenHarmony
SDL_GetPowerInfo_OpenHarmony,
#endif
#ifdef SDL_POWER_PSP // handles PSP.
SDL_GetPowerInfo_PSP,
#endif

View File

@@ -35,6 +35,7 @@ bool SDL_GetPowerInfo_UIKit(SDL_PowerState *, int *, int *);
bool SDL_GetPowerInfo_MacOSX(SDL_PowerState *, int *, int *);
bool SDL_GetPowerInfo_Haiku(SDL_PowerState *, int *, int *);
bool SDL_GetPowerInfo_Android(SDL_PowerState *, int *, int *);
bool SDL_GetPowerInfo_OpenHarmony(SDL_PowerState *, int *, int *);
bool SDL_GetPowerInfo_PSP(SDL_PowerState *, int *, int *);
bool SDL_GetPowerInfo_VITA(SDL_PowerState *, int *, int *);
bool SDL_GetPowerInfo_N3DS(SDL_PowerState *, int *, int *);

View File

@@ -0,0 +1,46 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_POWER_DISABLED
#ifdef SDL_POWER_OPENHARMONY
#include <BasicServicesKit/ohbattery_info.h>
bool SDL_GetPowerInfo_OpenHarmony(SDL_PowerState *state, int *seconds, int *percent)
{
*seconds = -1;
*percent = (int) OH_BatteryInfo_GetCapacity();
const BatteryInfo_BatteryPluggedType pluggedtype = OH_BatteryInfo_GetPluggedType();
if (pluggedtype == PLUGGED_TYPE_NONE) {
*state = SDL_POWERSTATE_ON_BATTERY;
} else {
*state = (*percent < 100) ? SDL_POWERSTATE_CHARGING : SDL_POWERSTATE_CHARGED;
}
return true;
}
#endif // SDL_POWER_OPENHARMONY
#endif // SDL_POWER_DISABLED

View File

@@ -40,7 +40,7 @@ SDL_AddEventWatch to catch SDL_EVENT_WILL_ENTER_BACKGROUND events and stopped
drawing themselves. Other platforms still draw, as the compositor can use it,
and more importantly: drawing to render targets isn't lost. But I still think
this should probably be removed at some point in the future. --ryan. */
#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) || defined(SDL_PLATFORM_ANDROID)
#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
#define DONT_DRAW_WHILE_HIDDEN 1
#else
#define DONT_DRAW_WHILE_HIDDEN 0

View File

@@ -40,7 +40,7 @@
#include "../../core/linux/SDL_dbus.h"
#endif // SDL_PLATFORM_LINUX
#if (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)) && defined(HAVE_DLOPEN)
#if (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)) && defined(HAVE_DLOPEN)
#include <dlfcn.h>
#ifndef RTLD_DEFAULT
#define RTLD_DEFAULT NULL
@@ -77,7 +77,7 @@ static void *RunThread(void *data)
#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)) && defined(HAVE_DLOPEN)
static bool checked_setname = false;
static int (*ppthread_setname_np)(const char *) = NULL;
#elif (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN)
#elif (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)) && defined(HAVE_DLOPEN)
static bool checked_setname = false;
static int (*ppthread_setname_np)(pthread_t, const char *) = NULL;
#endif
@@ -88,12 +88,12 @@ bool SDL_SYS_CreateThread(SDL_Thread *thread,
pthread_attr_t type;
// do this here before any threads exist, so there's no race condition.
#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN)
#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)) && defined(HAVE_DLOPEN)
if (!checked_setname) {
void *fn = dlsym(RTLD_DEFAULT, "pthread_setname_np");
#if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)
ppthread_setname_np = (int (*)(const char *))fn;
#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)
#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
ppthread_setname_np = (int (*)(pthread_t, const char *))fn;
#endif
checked_setname = true;
@@ -129,12 +129,12 @@ void SDL_SYS_SetupThread(const char *name)
#endif
if (name) {
#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN)
#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)) && defined(HAVE_DLOPEN)
SDL_assert(checked_setname);
if (ppthread_setname_np) {
#if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)
ppthread_setname_np(name);
#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)
#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_OPENHARMONY)
if (ppthread_setname_np(pthread_self(), name) == ERANGE) {
char namebuf[16]; // Limited to 16 char
SDL_strlcpy(namebuf, name, sizeof(namebuf));
@@ -171,6 +171,9 @@ void SDL_SYS_SetupThread(const char *name)
pthread_sigmask(SIG_BLOCK, &mask, NULL);
#endif
// HarmonyOS defines this, but doesn't offer pthread_setcanceltype.
// !!! FIXME: but it looks like we shouldn't be using this on any platform! https://clang.llvm.org/extra/clang-tidy/checks/concurrency/thread-canceltype-asynchronous.html
#ifndef SDL_PLATFORM_OPENHARMONY
#ifdef PTHREAD_CANCEL_ASYNCHRONOUS
// Allow ourselves to be asynchronously cancelled
{
@@ -178,6 +181,7 @@ void SDL_SYS_SetupThread(const char *name)
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldstate);
}
#endif
#endif
}
SDL_ThreadID SDL_GetCurrentThreadID(void)

View File

@@ -86,6 +86,10 @@
#define DEFAULT_OGL_ES_PVR "libGLES_CM.so"
#define DEFAULT_OGL_ES "libGLESv1_CM.so"
#elif defined(SDL_VIDEO_DRIVER_OPENHARMONY)
#define DEFAULT_EGL "libEGL.so"
#define DEFAULT_OGL_ES2 "libGLESv3.so"
#elif defined(SDL_VIDEO_DRIVER_WINDOWS)
// EGL AND OpenGL ES support via ANGLE
#define DEFAULT_EGL "libEGL.dll"

View File

@@ -529,6 +529,7 @@ extern VideoBootStrap WINDOWS_bootstrap;
extern VideoBootStrap HAIKU_bootstrap;
extern VideoBootStrap UIKIT_bootstrap;
extern VideoBootStrap Android_bootstrap;
extern VideoBootStrap OPENHARMONY_bootstrap;
extern VideoBootStrap PS2_bootstrap;
extern VideoBootStrap PSP_bootstrap;
extern VideoBootStrap VITA_bootstrap;

View File

@@ -110,6 +110,9 @@ static VideoBootStrap *bootstrap[] = {
#ifdef SDL_VIDEO_DRIVER_ANDROID
&Android_bootstrap,
#endif
#ifdef SDL_VIDEO_DRIVER_OPENHARMONY
&OPENHARMONY_bootstrap,
#endif
#ifdef SDL_VIDEO_DRIVER_PS2
&PS2_bootstrap,
#endif

View File

@@ -27,6 +27,9 @@
#ifdef SDL_VIDEO_DRIVER_ANDROID
#define VK_USE_PLATFORM_ANDROID_KHR
#endif
#ifdef SDL_VIDEO_DRIVER_OPENHARMONY
#define VK_USE_PLATFORM_OHOS
#endif
#ifdef SDL_VIDEO_DRIVER_COCOA
#define VK_USE_PLATFORM_METAL_EXT
#define VK_USE_PLATFORM_MACOS_MVK

View File

@@ -0,0 +1,220 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_OPENHARMONY
#include <database/pasteboard/oh_pasteboard.h>
#include <database/pasteboard/oh_pasteboard_err_code.h>
#include <database/udmf/udmf.h>
#include <database/udmf/uds.h>
#include "SDL_openharmonyvideo.h"
#include "SDL_openharmonyclipboard.h"
#include "../../core/openharmony/SDL_openharmony.h"
#include "../../events/SDL_events_c.h"
static void PasteboardNotifyCallback(void *userdata, Pasteboard_NotifyType type)
{
SDL_assert(userdata != NULL);
SDL_VideoDevice *_this = (SDL_VideoDevice *) userdata;
SDL_VideoData *data = _this->internal;
if (data->clipboard_set) {
data->clipboard_set = false; // this was us, ignore it.
return;
}
OH_Pasteboard *pasteboard = (OH_Pasteboard *) data->oh_pasteboard;
SDL_assert(pasteboard != NULL);
const char *mimetype = "text/plain";
if (OH_Pasteboard_HasType(pasteboard, mimetype)) {
const size_t slen = SDL_strlen(mimetype);
const size_t allocationsize = (slen + 1) + (sizeof (char *) * 2);
char **new_mime_types = SDL_AllocateTemporaryMemory(allocationsize);
if (new_mime_types) {
char *ptr = (char *)(new_mime_types + 2);
SDL_strlcpy(ptr, mimetype, slen + 1);
new_mime_types[0] = ptr;
new_mime_types[1] = NULL;
SDL_SendClipboardUpdate(false, new_mime_types, 1);
}
}
}
static void PasteboardFinalizeCallback(void *userdata)
{
// do nothing.
}
void OPENHARMONY_InitClipboard(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
data->clipboard_set = false;
OH_Pasteboard *pasteboard = OH_Pasteboard_Create();
data->oh_pasteboard = pasteboard;
OH_PasteboardObserver *observer = OH_PasteboardObserver_Create();
data->oh_pasteboard_observer = observer;
if (observer) {
OH_PasteboardObserver_SetData(observer, _this, PasteboardNotifyCallback, PasteboardFinalizeCallback);
}
if (pasteboard && observer) {
OH_Pasteboard_Subscribe(pasteboard, NOTIFY_LOCAL_DATA_CHANGE, observer);
}
}
void OPENHARMONY_QuitClipboard(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
OH_PasteboardObserver *observer = (OH_PasteboardObserver *) data->oh_pasteboard_observer;
OH_Pasteboard *pasteboard = (OH_Pasteboard *) data->oh_pasteboard;
if (observer && pasteboard) {
OH_Pasteboard_Unsubscribe(pasteboard, NOTIFY_LOCAL_DATA_CHANGE, observer);
}
if (observer) {
OH_PasteboardObserver_Destroy(observer);
data->oh_pasteboard_observer = NULL;
}
if (pasteboard) {
OH_Pasteboard_Destroy(pasteboard);
data->oh_pasteboard = NULL;
}
}
const char *const *OPENHARMONY_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types)
{
static const char *const text_mime_types[] = { "text/plain" };
*num_mime_types = SDL_arraysize(text_mime_types);
return text_mime_types;
}
bool OPENHARMONY_SetClipboardText(SDL_VideoDevice *_this, const char *text)
{
SDL_VideoData *data = _this->internal;
OH_Pasteboard *pasteboard = (OH_Pasteboard *) data->oh_pasteboard;
if (!pasteboard) {
return SDL_SetError("Pasteboard not initialized");
}
OH_UdsPlainText *udspt = OH_UdsPlainText_Create();
if (!udspt) {
return SDL_SetError("Could not create OH_UdsPlainText instance");
}
OH_UdsPlainText_SetContent(udspt, text);
OH_UdmfRecord *udmfrec = OH_UdmfRecord_Create();
if (!udmfrec) {
OH_UdsPlainText_Destroy(udspt);
return SDL_SetError("Could not create OH_UdmfRecord instance");
}
OH_UdmfRecord_AddPlainText(udmfrec, udspt);
OH_UdmfData *udmfdata = OH_UdmfData_Create();
if (!udmfdata) {
OH_UdmfRecord_Destroy(udmfrec);
OH_UdsPlainText_Destroy(udspt);
return SDL_SetError("Could not create OH_UdmfData instance");
}
OH_UdmfData_AddRecord(udmfdata, udmfrec);
data->clipboard_set = true;
const PASTEBOARD_ErrCode rc = OH_Pasteboard_SetData(pasteboard, udmfdata);
OH_UdmfData_Destroy(udmfdata);
OH_UdmfRecord_Destroy(udmfrec);
OH_UdsPlainText_Destroy(udspt);
if (rc != ERR_OK) {
data->clipboard_set = true;
return SDL_SetError("OH_Pasteboard_SetData failed: %d", (int) rc);
}
return true;
}
char *OPENHARMONY_GetClipboardText(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
OH_Pasteboard *pasteboard = (OH_Pasteboard *) data->oh_pasteboard;
if (!pasteboard) {
SDL_SetError("Pasteboard not initialized");
return NULL;
}
char *retval = NULL;
if (OH_Pasteboard_HasType(pasteboard, "text/plain")) {
int rc = (int) ERR_OK;
OH_UdmfData *udmfdata = OH_Pasteboard_GetData(pasteboard, &rc);
if (!udmfdata) {
SDL_SetError("Could not create OH_UdmfData instance %d", rc);
return NULL;
}
OH_UdmfRecord *udmfrec = OH_UdmfData_GetRecord(udmfdata, 0); // 0 == first record in udmfdata
if (!udmfrec) {
OH_UdmfData_Destroy(udmfdata);
SDL_SetError("Could not create OH_UdmfRecord instance");
return NULL;
}
OH_UdsPlainText *udspt = OH_UdsPlainText_Create();
if (!udspt) {
OH_UdmfRecord_Destroy(udmfrec);
OH_UdmfData_Destroy(udmfdata);
SDL_SetError("Could not create OH_UdsPlainText instance");
return NULL;
}
OH_UdmfRecord_GetPlainText(udmfrec, udspt);
const char *utf8 = OH_UdsPlainText_GetContent(udspt);
if (!utf8) {
SDL_SetError("Could not get plaintext content");
} else {
retval = SDL_strdup(utf8);
}
OH_UdsPlainText_Destroy(udspt);
OH_UdmfRecord_Destroy(udmfrec);
OH_UdmfData_Destroy(udmfdata);
}
return retval;
}
bool OPENHARMONY_HasClipboardText(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
OH_Pasteboard *pasteboard = (OH_Pasteboard *) data->oh_pasteboard;
if (!pasteboard) {
return SDL_SetError("Pasteboard not initialized");
}
return OH_Pasteboard_HasType(pasteboard, "text/plain");
}
#endif // SDL_VIDEO_DRIVER_OPENHARMONY

View File

@@ -0,0 +1,34 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_openharmonyclipboard_h_
#define SDL_openharmonyclipboard_h_
extern void OPENHARMONY_InitClipboard(SDL_VideoDevice *_this);
extern void OPENHARMONY_QuitClipboard(SDL_VideoDevice *_this);
extern const char *const *OPENHARMONY_GetTextMimeTypes(SDL_VideoDevice *_this, size_t *num_mime_types);
extern bool OPENHARMONY_SetClipboardText(SDL_VideoDevice *_this, const char *text);
extern char *OPENHARMONY_GetClipboardText(SDL_VideoDevice *_this);
extern bool OPENHARMONY_HasClipboardText(SDL_VideoDevice *_this);
#endif // SDL_openharmonyclipboard_h_

View File

@@ -0,0 +1,574 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_OPENHARMONY
// !!! FIXME: these are defined as "const uint32_t VARNAME = VALUE;" in native_interface_xcomponent.h, which becomes a global variable in _our_ C code! Maybe C++ handles this differently...?
#define OH_XCOMPONENT_ID_LEN_MAX sdl_ohosevents_OH_XCOMPONENT_ID_LEN_MAX
#define OH_MAX_TOUCH_POINTS_NUMBER sdl_ohosevents_OH_MAX_TOUCH_POINTS_NUMBER
#include <ace/xcomponent/native_interface_xcomponent.h>
#include "SDL_openharmonyevents.h"
//#include "SDL_openharmonykeyboard.h"
#include "SDL_openharmonywindow.h"
#include "../SDL_sysvideo.h"
#include "../../events/SDL_events_c.h"
static SDL_Scancode MapOHKeycodeToSDLScancode(const OH_NativeXComponent_KeyCode ohcode)
{
// !!! FIXME: this needs a serious looking-over.
switch (ohcode) {
#define KEYMAP(oh,sdl) case KEY_##oh: return SDL_SCANCODE_##sdl
KEYMAP(UNKNOWN, UNKNOWN);
//case KEY_FN = 0,
KEYMAP(HOME, AC_HOME);
KEYMAP(BACK, AC_BACK);
KEYMAP(MEDIA_PLAY_PAUSE, MEDIA_PLAY_PAUSE);
KEYMAP(MEDIA_STOP, MEDIA_STOP);
KEYMAP(MEDIA_NEXT, MEDIA_NEXT_TRACK);
KEYMAP(MEDIA_PREVIOUS, MEDIA_PREVIOUS_TRACK);
KEYMAP(MEDIA_REWIND, MEDIA_REWIND);
KEYMAP(MEDIA_FAST_FORWARD, MEDIA_FAST_FORWARD);
KEYMAP(VOLUME_UP, VOLUMEUP);
KEYMAP(VOLUME_DOWN, VOLUMEDOWN);
KEYMAP(POWER, POWER);
//KEYMAP(CAMERA, CAMERA);
KEYMAP(VOLUME_MUTE, MUTE);
KEYMAP(MUTE, MUTE);
//KEYMAP(BRIGHTNESS_UP = 40,
//KEYMAP(BRIGHTNESS_DOWN = 41,
KEYMAP(0, 0);
KEYMAP(1, 1);
KEYMAP(2, 2);
KEYMAP(3, 3);
KEYMAP(4, 4);
KEYMAP(5, 5);
KEYMAP(6, 6);
KEYMAP(7, 7);
KEYMAP(8, 8);
KEYMAP(9, 9);
//KEYMAP(STAR = 2010,
//KEYMAP(POUND = 2011,
KEYMAP(DPAD_UP, UP);
KEYMAP(DPAD_DOWN, DOWN);
KEYMAP(DPAD_LEFT, LEFT);
KEYMAP(DPAD_RIGHT, RIGHT);
//KEYMAP(DPAD_CENTER = 2016,
KEYMAP(A, A);
KEYMAP(B, B);
KEYMAP(C, C);
KEYMAP(D, D);
KEYMAP(E, E);
KEYMAP(F, F);
KEYMAP(G, G);
KEYMAP(H, H);
KEYMAP(I, I);
KEYMAP(J, J);
KEYMAP(K, K);
KEYMAP(L, L);
KEYMAP(M, M);
KEYMAP(N, N);
KEYMAP(O, O);
KEYMAP(P, P);
KEYMAP(Q, Q);
KEYMAP(R, R);
KEYMAP(S, S);
KEYMAP(T, T);
KEYMAP(U, U);
KEYMAP(V, V);
KEYMAP(W, W);
KEYMAP(X, X);
KEYMAP(Y, Y);
KEYMAP(Z, Z);
KEYMAP(COMMA, COMMA);
KEYMAP(PERIOD, PERIOD);
KEYMAP(ALT_LEFT, LALT);
KEYMAP(ALT_RIGHT, RALT);
KEYMAP(SHIFT_LEFT, LSHIFT);
KEYMAP(SHIFT_RIGHT, RSHIFT);
KEYMAP(TAB, TAB);
KEYMAP(SPACE, SPACE);
//KEYMAP(SYM = 2051,
//KEYMAP(EXPLORER = 2052,
//KEYMAP(ENVELOPE = 2053,
KEYMAP(ENTER, RETURN);
KEYMAP(DEL, DELETE);
KEYMAP(GRAVE, GRAVE);
KEYMAP(MINUS, MINUS);
KEYMAP(EQUALS, EQUALS);
KEYMAP(LEFT_BRACKET, LEFTBRACKET);
KEYMAP(RIGHT_BRACKET, RIGHTBRACKET);
KEYMAP(BACKSLASH, BACKSLASH);
KEYMAP(SEMICOLON, SEMICOLON);
KEYMAP(APOSTROPHE, APOSTROPHE);
KEYMAP(SLASH, SLASH);
KEYMAP(AT, KP_AT);
KEYMAP(PLUS, KP_PLUS);
KEYMAP(MENU, MENU);
KEYMAP(PAGE_UP, PAGEUP);
KEYMAP(PAGE_DOWN, PAGEDOWN);
KEYMAP(ESCAPE, ESCAPE);
//KEYMAP(FORWARD_DEL = 2071,
KEYMAP(CTRL_LEFT, LCTRL);
KEYMAP(CTRL_RIGHT, RCTRL);
KEYMAP(CAPS_LOCK, CAPSLOCK);
KEYMAP(SCROLL_LOCK, SCROLLLOCK);
KEYMAP(META_LEFT, LGUI);
KEYMAP(META_RIGHT, RGUI);
//KEYMAP(FUNCTION = 2078,
KEYMAP(SYSRQ, SYSREQ);
//KEYMAP(BREAK = 2080,
KEYMAP(MOVE_HOME, HOME);
KEYMAP(MOVE_END, END);
KEYMAP(INSERT, INSERT);
KEYMAP(FORWARD, AC_FORWARD);
KEYMAP(MEDIA_PLAY, MEDIA_PLAY);
KEYMAP(MEDIA_PAUSE, MEDIA_PAUSE);
//KEYMAP(MEDIA_CLOSE = 2087,
KEYMAP(MEDIA_EJECT, MEDIA_EJECT);
KEYMAP(MEDIA_RECORD, MEDIA_RECORD);
KEYMAP(F1, F1);
KEYMAP(F2, F2);
KEYMAP(F3, F3);
KEYMAP(F4, F4);
KEYMAP(F5, F5);
KEYMAP(F6, F6);
KEYMAP(F7, F7);
KEYMAP(F8, F8);
KEYMAP(F9, F9);
KEYMAP(F10, F10);
KEYMAP(F11, F11);
KEYMAP(F12, F12);
KEYMAP(NUM_LOCK, NUMLOCKCLEAR);
KEYMAP(NUMPAD_0, KP_0);
KEYMAP(NUMPAD_1, KP_1);
KEYMAP(NUMPAD_2, KP_2);
KEYMAP(NUMPAD_3, KP_3);
KEYMAP(NUMPAD_4, KP_4);
KEYMAP(NUMPAD_5, KP_5);
KEYMAP(NUMPAD_6, KP_6);
KEYMAP(NUMPAD_7, KP_7);
KEYMAP(NUMPAD_8, KP_8);
KEYMAP(NUMPAD_9, KP_9);
KEYMAP(NUMPAD_DIVIDE, KP_DIVIDE);
KEYMAP(NUMPAD_MULTIPLY, KP_MULTIPLY);
KEYMAP(NUMPAD_SUBTRACT, KP_MINUS);
KEYMAP(NUMPAD_ADD, KP_PLUS);
KEYMAP(NUMPAD_DOT, KP_PERIOD);
//KEYMAP(NUMPAD_COMMA = 2118,
KEYMAP(NUMPAD_ENTER, KP_ENTER);
//KEYMAP(NUMPAD_EQUALS = 2120,
KEYMAP(NUMPAD_LEFT_PAREN, KP_LEFTPAREN);
KEYMAP(NUMPAD_RIGHT_PAREN, KP_RIGHTPAREN);
//KEYMAP(VIRTUAL_MULTITASK = 2210,
KEYMAP(SLEEP, SLEEP);
KEYMAP(ZENKAKU_HANKAKU, LANG5);
//KEYMAP(102ND = 2602,
//KEYMAP(RO = 2603,
KEYMAP(KATAKANA, LANG3);
KEYMAP(HIRAGANA, LANG4);
//KEYMAP(HENKAN = 2606,
//KEYMAP(KATAKANA_HIRAGANA = 2607,
//KEYMAP(MUHENKAN = 2608,
//KEYMAP(LINEFEED = 2609,
//KEYMAP(MACRO = 2610,
KEYMAP(NUMPAD_PLUSMINUS, KP_PLUSMINUS);
//KEYMAP(SCALE = 2612,
//KEYMAP(HANGUEL = 2613,
//KEYMAP(HANJA = 2614,
//KEYMAP(YEN = 2615,
KEYMAP(STOP, STOP);
KEYMAP(AGAIN, AGAIN);
KEYMAP(PROPS, AC_PROPERTIES); // I think....?
KEYMAP(UNDO, UNDO);
KEYMAP(COPY, COPY);
KEYMAP(OPEN, AC_OPEN); /// I think...?
KEYMAP(PASTE, PASTE);
KEYMAP(FIND, FIND);
KEYMAP(CUT, CUT);
KEYMAP(HELP, HELP);
//KEYMAP(CALC = 2626,
//KEYMAP(FILE = 2627,
KEYMAP(BOOKMARKS, AC_BOOKMARKS);
KEYMAP(NEXT, MEDIA_NEXT_TRACK);
KEYMAP(PLAYPAUSE, MEDIA_PLAY_PAUSE);
KEYMAP(PREVIOUS, MEDIA_PREVIOUS_TRACK);
KEYMAP(STOPCD, MEDIA_STOP);
//KEYMAP(CONFIG = 2634,
KEYMAP(REFRESH, AC_REFRESH);
KEYMAP(EXIT, AC_EXIT);
//KEYMAP(EDIT = 2637,
//KEYMAP(SCROLLUP = 2638,
//KEYMAP(SCROLLDOWN = 2639,
KEYMAP(NEW, AC_NEW);
KEYMAP(REDO, AGAIN);
KEYMAP(CLOSE, AC_CLOSE);
KEYMAP(PLAY, MEDIA_PLAY);
//KEYMAP(BASSBOOST = 2644,
KEYMAP(PRINT, PRINTSCREEN);
//KEYMAP(CHAT = 2646,
//KEYMAP(FINANCE = 2647,
KEYMAP(CANCEL, CANCEL);
//KEYMAP(KBDILLUM_TOGGLE = 2649,
//KEYMAP(KBDILLUM_DOWN = 2650,
//KEYMAP(KBDILLUM_UP = 2651,
//KEYMAP(SEND = 2652,
//KEYMAP(REPLY = 2653,
//KEYMAP(FORWARDMAIL = 2654,
KEYMAP(SAVE, AC_SAVE);
//KEYMAP(DOCUMENTS = 2656,
//KEYMAP(VIDEO_NEXT = 2657,
//KEYMAP(VIDEO_PREV = 2658,
//KEYMAP(BRIGHTNESS_CYCLE = 2659,
//KEYMAP(BRIGHTNESS_ZERO = 2660,
//KEYMAP(DISPLAY_OFF = 2661,
//KEYMAP(BTN_MISC = 2662,
//KEYMAP(GOTO = 2663,
//KEYMAP(INFO = 2664,
//KEYMAP(PROGRAM = 2665,
//KEYMAP(PVR = 2666,
//KEYMAP(SUBTITLE = 2667,
//KEYMAP(FULL_SCREEN = 2668,
//KEYMAP(KEYBOARD = 2669,
//KEYMAP(ASPECT_RATIO = 2670,
//KEYMAP(PC = 2671,
//KEYMAP(TV = 2672,
//KEYMAP(TV2 = 2673,
//KEYMAP(VCR = 2674,
//KEYMAP(VCR2 = 2675,
//KEYMAP(SAT = 2676,
//KEYMAP(CD = 2677,
//KEYMAP(TAPE = 2678,
//KEYMAP(TUNER = 2679,
//KEYMAP(PLAYER = 2680,
//KEYMAP(DVD = 2681,
//KEYMAP(AUDIO = 2682,
//KEYMAP(VIDEO = 2683,
//KEYMAP(MEMO = 2684,
//KEYMAP(CALENDAR = 2685,
//KEYMAP(RED = 2686,
//KEYMAP(GREEN = 2687,
//KEYMAP(YELLOW = 2688,
//KEYMAP(BLUE = 2689,
//KEYMAP(CHANNELUP = 2690,
//KEYMAP(CHANNELDOWN = 2691,
//KEYMAP(LAST = 2692,
//KEYMAP(RESTART = 2693,
//KEYMAP(SLOW = 2694,
//KEYMAP(SHUFFLE = 2695,
//KEYMAP(VIDEOPHONE = 2696,
//KEYMAP(GAMES = 2697,
//KEYMAP(ZOOMIN = 2698,
//KEYMAP(ZOOMOUT = 2699,
//KEYMAP(ZOOMRESET = 2700,
//KEYMAP(WORDPROCESSOR = 2701,
//KEYMAP(EDITOR = 2702,
//KEYMAP(SPREADSHEET = 2703,
//KEYMAP(GRAPHICSEDITOR = 2704,
//KEYMAP(PRESENTATION = 2705,
//KEYMAP(DATABASE = 2706,
//KEYMAP(NEWS = 2707,
//KEYMAP(VOICEMAIL = 2708,
//KEYMAP(ADDRESSBOOK = 2709,
//KEYMAP(MESSENGER = 2710,
//KEYMAP(BRIGHTNESS_TOGGLE = 2711,
//KEYMAP(SPELLCHECK = 2712,
//KEYMAP(COFFEE = 2713,
//KEYMAP(MEDIA_REPEAT = 2714,
//KEYMAP(IMAGES = 2715,
//KEYMAP(BUTTONCONFIG = 2716,
//KEYMAP(TASKMANAGER = 2717,
//KEYMAP(JOURNAL = 2718,
//KEYMAP(CONTROLPANEL = 2719,
//KEYMAP(APPSELECT = 2720,
//KEYMAP(SCREENSAVER = 2721,
//KEYMAP(ASSISTANT = 2722,
//KEYMAP(KBD_LAYOUT_NEXT = 2723,
//KEYMAP(BRIGHTNESS_MIN = 2724,
//KEYMAP(BRIGHTNESS_MAX = 2725,
//KEYMAP(KBDINPUTASSIST_PREV = 2726,
//KEYMAP(KBDINPUTASSIST_NEXT = 2727,
//KEYMAP(KBDINPUTASSIST_PREVGROUP = 2728,
//KEYMAP(KBDINPUTASSIST_NEXTGROUP = 2729,
//KEYMAP(KBDINPUTASSIST_ACCEPT = 2730,
//KEYMAP(KBDINPUTASSIST_CANCEL = 2731,
//KEYMAP(FRONT = 2800,
//KEYMAP(SETUP = 2801,
//KEYMAP(WAKEUP = 2802,
//KEYMAP(SENDFILE = 2803,
//KEYMAP(DELETEFILE = 2804,
//KEYMAP(XFER = 2805,
//KEYMAP(PROG1 = 2806,
//KEYMAP(PROG2 = 2807,
//KEYMAP(MSDOS = 2808,
//KEYMAP(SCREENLOCK = 2809,
//KEYMAP(DIRECTION_ROTATE_DISPLAY = 2810,
//KEYMAP(CYCLEWINDOWS = 2811,
//KEYMAP(COMPUTER = 2812,
//KEYMAP(EJECTCLOSECD = 2813,
//KEYMAP(ISO = 2814,
//KEYMAP(MOVE = 2815,
KEYMAP(F13, F13);
KEYMAP(F14, F14);
KEYMAP(F15, F15);
KEYMAP(F16, F16);
KEYMAP(F17, F17);
KEYMAP(F18, F18);
KEYMAP(F19, F19);
KEYMAP(F20, F20);
KEYMAP(F21, F21);
KEYMAP(F22, F22);
KEYMAP(F23, F23);
KEYMAP(F24, F24);
//KEYMAP(PROG3 = 2828,
//KEYMAP(PROG4 = 2829,
//KEYMAP(DASHBOARD = 2830,
//KEYMAP(SUSPEND = 2831,
//KEYMAP(HP = 2832,
//KEYMAP(SOUND = 2833,
//KEYMAP(QUESTION = 2834,
//KEYMAP(CONNECT = 2836,
//KEYMAP(SPORT = 2837,
//KEYMAP(SHOP = 2838,
//KEYMAP(ALTERASE = 2839,
//KEYMAP(SWITCHVIDEOMODE = 2841,
//KEYMAP(BATTERY = 2842,
//KEYMAP(BLUETOOTH = 2843,
//KEYMAP(WLAN = 2844,
//KEYMAP(UWB = 2845,
//KEYMAP(WWAN_WIMAX = 2846,
//KEYMAP(RFKILL = 2847,
//KEYMAP(CHANNEL = 3001,
//KEYMAP(BTN_0 = 3100,
//KEYMAP(BTN_1 = 3101,
//KEYMAP(BTN_2 = 3102,
//KEYMAP(BTN_3 = 3103,
//KEYMAP(BTN_4 = 3104,
//KEYMAP(BTN_5 = 3105,
//KEYMAP(BTN_6 = 3106,
//KEYMAP(BTN_7 = 3107,
//KEYMAP(BTN_8 = 3108,
//KEYMAP(BTN_9 = 3109,
#undef KEYMAP
default: break;
}
return SDL_SCANCODE_UNKNOWN;
}
void SDL_OpenHarmonyDispatchTouchEvent(void *component, void *window)
{
OH_NativeXComponent *xcomponent = (OH_NativeXComponent *) component;
OH_NativeXComponent_TouchEvent event;
if (OH_NativeXComponent_GetTouchEvent(xcomponent, window, &event) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
return; // oh well.
}
#if 0
SDL_Log("TOUCH EVENT! id=%d screenX=%f screenY=%f x=%f y=%f type=%d size=%f force=%f devid=%lld timestamp=%lld numpoints=%d", (int) event.id, event.screenX, event.screenY, event.x, event.y, (int) event.type, (float) event.size, event.force, (long long) event.deviceId, (long long) event.timeStamp, (int) event.numPoints);
for (int i = 0; i < event.numPoints; i++) {
SDL_Log("TOUCH POINT #%d: id=%d screenX=%f screenY=%f x=%f y=%f type=%d size=%f force=%f timestamp=%lld ispressed=%s", i, (int) event.touchPoints[i].id, event.touchPoints[i].screenX, event.touchPoints[i].screenY, event.touchPoints[i].x, event.touchPoints[i].y, (int) event.touchPoints[i].type, (float) event.touchPoints[i].size, event.touchPoints[i].force, (long long) event.touchPoints[i].timeStamp, event.touchPoints[i].isPressed ? "true" : "false");
}
#endif
SDL_EventType sdleventtype = SDL_EVENT_FIRST;
switch (event.type) {
#define EVTYPEMAP(ohevent, sdlevent) case OH_NATIVEXCOMPONENT_##ohevent: sdleventtype = SDL_EVENT_FINGER_##sdlevent; break
EVTYPEMAP(DOWN, DOWN);
EVTYPEMAP(UP, UP);
EVTYPEMAP(MOVE, MOTION);
EVTYPEMAP(CANCEL, CANCELED);
default: break;
#undef EVTYPEMAP
}
if (sdleventtype == SDL_EVENT_FIRST) { // FIRST == unknown event type.
return; // nothing to do.
}
int touchidx = 0;
while (touchidx < event.numPoints) {
if (event.touchPoints[touchidx].id == event.id) {
break;
}
touchidx++;
}
if (touchidx >= event.numPoints) {
return; // uh...we don't have this touch...?
}
OH_NativeXComponent_TouchPointToolType tooltype;
if (OH_NativeXComponent_GetTouchPointToolType(xcomponent, touchidx, &tooltype) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
return; // bad bad bad
}
const float normalized_x = OPENHARMONY_Window->w ? (event.x / ((float) OPENHARMONY_Window->w)) : 0.0f;
const float normalized_y = OPENHARMONY_Window->h ? (event.y / ((float) OPENHARMONY_Window->h)) : 0.0f;
if (tooltype == OH_NATIVEXCOMPONENT_TOOL_TYPE_FINGER) {
const SDL_TouchID touchid = (SDL_TouchID)(((uintptr_t)event.deviceId) + 1);
SDL_AddTouch(touchid, SDL_TOUCH_DEVICE_DIRECT, NULL);
if (sdleventtype == SDL_EVENT_FINGER_MOTION) {
SDL_SendTouchMotion((Uint64) event.timeStamp, touchid, (SDL_FingerID)(((uintptr_t)event.id)+1), OPENHARMONY_Window, normalized_x, normalized_y, event.force);
} else {
SDL_SendTouch((Uint64) event.timeStamp, touchid, (SDL_FingerID)(((uintptr_t)event.id)+1), OPENHARMONY_Window, sdleventtype, normalized_x, normalized_y, event.force);
}
return;
}
#if 0 // !!! FIXME: see if Pen events are useful here.
switch (tooltype) {
/** Indicates invalid tool type. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN = 0,
/** Indicates a finger. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_FINGER,
/** Indicates a stylus. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_PEN,
/** Indicates an eraser. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_RUBBER,
/** Indicates a brush. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_BRUSH,
/** Indicates a pencil. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_PENCIL,
/** Indicates a brush. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_AIRBRUSH,
/** Indicates a mouse. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_MOUSE,
/** Indicates a lens. */
OH_NATIVEXCOMPONENT_TOOL_TYPE_LENS,
}
#endif
}
void SDL_OpenHarmonyDispatchMouseEvent(void *component, void *window)
{
OH_NativeXComponent *xcomponent = (OH_NativeXComponent *) component;
OH_NativeXComponent_MouseEvent event;
if (OH_NativeXComponent_GetMouseEvent(xcomponent, window, &event) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
return; // oh well.
}
#if 1
SDL_Log("MOUSE EVENT! screenX=%f screenY=%f x=%f y=%f timestamp=%lld action=%d button=0x%X", event.screenX, event.screenY, event.x, event.y, (long long) event.timestamp, (int) event.action, (unsigned int) event.button);
#endif
const SDL_MouseID mouseid = SDL_DEFAULT_MOUSE_ID; // !!! FIXME: should this be SDL_GLOBAL_MOUSE_ID or SDL_DEFAULT_MOUSE_ID?
bool down = false;
switch (event.action) {
case OH_NATIVEXCOMPONENT_MOUSE_PRESS:
down = true;
SDL_FALLTHROUGH;
case OH_NATIVEXCOMPONENT_MOUSE_RELEASE:
// these are bitmasks, so while I _assume_ you won't see more than one button per event, check them all separately, just in case.
#define CHECK_BUTTON(ohos, sdl) if (event.button & OH_NATIVEXCOMPONENT_##ohos##_BUTTON) { SDL_SendMouseButton((Uint64) event.timestamp, OPENHARMONY_Window, mouseid, SDL_BUTTON_##sdl, down); }
CHECK_BUTTON(LEFT, LEFT);
CHECK_BUTTON(RIGHT, RIGHT);
CHECK_BUTTON(MIDDLE, MIDDLE);
CHECK_BUTTON(BACK, X1);
CHECK_BUTTON(FORWARD, X2);
#undef CHECK_BUTTON
break;
case OH_NATIVEXCOMPONENT_MOUSE_MOVE:
SDL_SendMouseMotion((Uint64) event.timestamp, OPENHARMONY_Window, mouseid, false, event.x, event.y);
break;
default: break; // nothing to do?
}
}
void SDL_OpenHarmonyDispatchUIInputEvent(void *vcomponent, void *vevent, int32_t vtype)
{
// OH_NativeXComponent *component = (OH_NativeXComponent *) vcomponent;
ArkUI_UIInputEvent *event = (ArkUI_UIInputEvent *) vevent;
const ArkUI_UIInputEvent_Type type = (const ArkUI_UIInputEvent_Type) vtype;
if (type != ARKUI_UIINPUTEVENT_TYPE_AXIS) {
return; // this is all we handle here for now. Maybe more in the future!
} else if (OH_ArkUI_UIInputEvent_GetSourceType(event) != UI_INPUT_EVENT_SOURCE_TYPE_MOUSE) {
return; // skip two-finger scrolling (...for now...?)
}
const SDL_MouseID mouseid = SDL_DEFAULT_MOUSE_ID; // !!! FIXME: should this be SDL_GLOBAL_MOUSE_ID or SDL_DEFAULT_MOUSE_ID?
const double vertical = OH_ArkUI_AxisEvent_GetVerticalAxisValue(event);
if (vertical != 0.0) {
// !!! FIXME: OpenHarmony has a system setting for "natural" scrolling we can use for SDL_MouseWheelDirection, but I
// !!! FIXME: don't know how to access it right now.
// a single "click" of the wheel on my mouse is 45 degrees (360 / 8 click positions), so let's assume that's normal (and what Windows would do with WHEEL_DELTA, which is a different value with the same concept).
SDL_SendMouseWheel((Uint64) OH_ArkUI_UIInputEvent_GetEventTime(event), OPENHARMONY_Window, mouseid, 0.0f, -(vertical / 45.0f), SDL_MOUSEWHEEL_NORMAL);
}
// !!! FIXME: this is in pixels, not degrees, and I'm not sure how to convert that yet.
// !!! FIXME: It's possible they don't support horizontal mouse wheels, and this is only for two-finger scrolling.
#if 0
const double horizontal = OH_ArkUI_AxisEvent_GetHorizontalAxisValue(event);
if (horizontal != 0.0) {
}
#endif
}
void SDL_OpenHarmonyDispatchKeyEvent(void *component, void *window)
{
OH_NativeXComponent *xcomponent = (OH_NativeXComponent *) component;
OH_NativeXComponent_KeyEvent *event = NULL;
if (OH_NativeXComponent_GetKeyEvent(xcomponent, &event) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
return; // oh well.
}
OH_NativeXComponent_KeyCode keycode = KEY_UNKNOWN;
OH_NativeXComponent_GetKeyEventCode(event, &keycode);
const SDL_Scancode scancode = MapOHKeycodeToSDLScancode(keycode);
if (scancode == SDL_SCANCODE_UNKNOWN) {
return; // oh well.
}
OH_NativeXComponent_KeyAction action = OH_NATIVEXCOMPONENT_KEY_ACTION_UNKNOWN;
OH_NativeXComponent_GetKeyEventAction(event, &action);
bool down;
if (action == OH_NATIVEXCOMPONENT_KEY_ACTION_DOWN) {
down = true;
} else if (action == OH_NATIVEXCOMPONENT_KEY_ACTION_UP) {
down = false;
} else {
return; // oh well.
}
int64_t timestamp = 0;
OH_NativeXComponent_GetKeyEventTimestamp(event, &timestamp);
SDL_SendKeyboardKey((Uint64) timestamp, SDL_DEFAULT_KEYBOARD_ID, keycode, scancode, down);
}
void OPENHARMONY_InitEvents(void)
{
}
void OPENHARMONY_PumpEvents(SDL_VideoDevice *_this)
{
}
void OPENHARMONY_QuitEvents(void)
{
}
#endif // SDL_VIDEO_DRIVER_OPENHARMONY

View File

@@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_sysvideo.h"
// src/core/openharmony/SDL_openharmony.c calls these when it gets events from the system.
extern void SDL_OpenHarmonyDispatchTouchEvent(void *component, void *window);
extern void SDL_OpenHarmonyDispatchMouseEvent(void *component, void *window);
extern void SDL_OpenHarmonyDispatchKeyEvent(void *component, void *window);
extern void SDL_OpenHarmonyDispatchUIInputEvent(void *component, void *event, int32_t type);
extern void OPENHARMONY_InitEvents(void);
extern void OPENHARMONY_PumpEvents(SDL_VideoDevice *_this);
extern void OPENHARMONY_QuitEvents(void);

View File

@@ -0,0 +1,93 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_OPENHARMONY
#include "../../events/SDL_events_c.h"
#include "SDL_openharmonykeyboard.h"
#include "../../core/openharmony/SDL_openharmony.h"
bool OPENHARMONY_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
{
return true;
}
void OPENHARMONY_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
{
int input_type = 0;
int cap_type = 0;
if (SDL_HasProperty(props, SDL_PROP_TEXTINPUT_OPENHARMONY_INPUTTYPE_NUMBER)) {
input_type = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_OPENHARMONY_INPUTTYPE_NUMBER, 0);
} else {
switch (SDL_GetTextInputType(props)) {
#define INPUTTYPE(sdl,oh) case SDL_TEXTINPUT_TYPE_##sdl: input_type = oh; break
INPUTTYPE(TEXT, 0);
INPUTTYPE(TEXT_NAME, 0);
INPUTTYPE(TEXT_EMAIL, 5);
INPUTTYPE(TEXT_USERNAME, 10);
INPUTTYPE(TEXT_PASSWORD_HIDDEN, 7); // !!! FIXME: this is _also_ PASSWORD_VISIBLE, but I think this doesn't dictate rendering...?
INPUTTYPE(TEXT_PASSWORD_VISIBLE, 7);
INPUTTYPE(NUMBER_PASSWORD_HIDDEN, 8); // !!! FIXME: this is _also_ NUMBER_PASSWORD_VISIBLE, but I think this doesn't dictate rendering...?
INPUTTYPE(NUMBER_PASSWORD_VISIBLE, 8);
#undef INPUTTYPE
default: break;
}
switch (SDL_GetTextInputCapitalization(props)) {
#define CAPTYPE(sdl,oh) case SDL_CAPITALIZE_##sdl: cap_type = oh; break
CAPTYPE(NONE, 0);
CAPTYPE(LETTERS, 3);
CAPTYPE(WORDS, 2);
CAPTYPE(SENTENCES, 1);
#undef CAPTYPE
default: break;
}
#if 0 // !!! FIXME
if (SDL_GetTextInputAutocorrect(props)) {
input_type |= (TYPE_TEXT_FLAG_AUTO_CORRECT | TYPE_TEXT_FLAG_AUTO_COMPLETE);
}
#endif
if (SDL_GetTextInputMultiline(props)) {
input_type = 1; // sorry, this only works with basic TEXT type.
}
}
SDL_OpenHarmonyShowScreenKeyboard(input_type, cap_type, &window->text_input_rect);
}
void OPENHARMONY_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_OpenHarmonyHideScreenKeyboard();
}
void OPENHARMONY_RestoreScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
{
if (_this->screen_keyboard_shown) {
OPENHARMONY_ShowScreenKeyboard(_this, window, window->text_input_props);
}
}
#endif // SDL_VIDEO_DRIVER_OPENHARMONY

View File

@@ -0,0 +1,29 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_openharmonyvideo.h"
extern bool OPENHARMONY_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
extern void OPENHARMONY_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
extern void OPENHARMONY_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
extern void OPENHARMONY_RestoreScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);

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