mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-09-17 02:32:04 +00:00
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:
10
.gitignore
vendored
10
.gitignore
vendored
@@ -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
|
||||
|
||||
169
CMakeLists.txt
169
CMakeLists.txt
@@ -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
209
build-scripts/harmonyos-tool.sh
Executable 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
|
||||
|
||||
188
cmake/PreseedOpenHarmonyCache.cmake
Normal file
188
cmake/PreseedOpenHarmonyCache.cmake
Normal 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()
|
||||
@@ -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
|
||||
|
||||
@@ -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
872
docs/README-harmonyos.md
Normal 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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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)
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
12
openharmony-project/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/node_modules
|
||||
/oh_modules
|
||||
/local.properties
|
||||
/.idea
|
||||
**/build
|
||||
/.hvigor
|
||||
.cxx
|
||||
/.clangd
|
||||
/.clang-format
|
||||
/.clang-tidy
|
||||
**/.test
|
||||
/.appanalyzer
|
||||
11
openharmony-project/AppScope/app.json5
Normal file
11
openharmony-project/AppScope/app.json5
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "app_name",
|
||||
"value": "app"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
openharmony-project/AppScope/resources/base/media/background.png
Normal file
BIN
openharmony-project/AppScope/resources/base/media/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
openharmony-project/AppScope/resources/base/media/foreground.png
Normal file
BIN
openharmony-project/AppScope/resources/base/media/foreground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"layered-image":
|
||||
{
|
||||
"background" : "$media:background",
|
||||
"foreground" : "$media:foreground"
|
||||
}
|
||||
}
|
||||
42
openharmony-project/build-profile.json5
Normal file
42
openharmony-project/build-profile.json5
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
32
openharmony-project/code-linter.json5
Normal file
32
openharmony-project/code-linter.json5
Normal 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
6
openharmony-project/entry/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/node_modules
|
||||
/oh_modules
|
||||
/.preview
|
||||
/build
|
||||
/.cxx
|
||||
/.test
|
||||
45
openharmony-project/entry/build-profile.json5
Normal file
45
openharmony-project/entry/build-profile.json5
Normal 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"
|
||||
},
|
||||
]
|
||||
}
|
||||
6
openharmony-project/entry/hvigorfile.ts
Normal file
6
openharmony-project/entry/hvigorfile.ts
Normal 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. */
|
||||
}
|
||||
23
openharmony-project/entry/obfuscation-rules.txt
Normal file
23
openharmony-project/entry/obfuscation-rules.txt
Normal 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
|
||||
19
openharmony-project/entry/oh-package-lock.json5
Normal file
19
openharmony-project/entry/oh-package-lock.json5
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
openharmony-project/entry/oh-package.json5
Normal file
11
openharmony-project/entry/oh-package.json5
Normal 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",
|
||||
}
|
||||
}
|
||||
18
openharmony-project/entry/src/main/cpp/CMakeLists.txt
Normal file
18
openharmony-project/entry/src/main/cpp/CMakeLists.txt
Normal 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)
|
||||
1
openharmony-project/entry/src/main/cpp/types/libSDL3/Index.d.ts
vendored
Normal file
1
openharmony-project/entry/src/main/cpp/types/libSDL3/Index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const provideArkTSObjects: (ability: UIAbility, atManager: abilityAccessCtrl.AtManager, locale: intl.Locale) => void;
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "libSDL3.so",
|
||||
"types": "./Index.d.ts",
|
||||
"version": "1.0.0",
|
||||
"description": "SDL3 interfaces callable from ArkTS."
|
||||
}
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
15
openharmony-project/entry/src/main/ets/pages/Index.ets
Normal file
15
openharmony-project/entry/src/main/ets/pages/Index.ets
Normal 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%')
|
||||
}
|
||||
}
|
||||
67
openharmony-project/entry/src/main/module.json5
Normal file
67
openharmony-project/entry/src/main/module.json5
Normal 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"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"float": [
|
||||
{
|
||||
"name": "page_text_font_size",
|
||||
"value": "50fp"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"layered-image":
|
||||
{
|
||||
"background" : "$media:background",
|
||||
"foreground" : "$media:foreground"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"allowToBackupRestore": true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"src": [
|
||||
"pages/Index"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
openharmony-project/hvigor/hvigor-config.json5
Normal file
23
openharmony-project/hvigor/hvigor-config.json5
Normal 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*/
|
||||
}
|
||||
}
|
||||
6
openharmony-project/hvigorfile.ts
Normal file
6
openharmony-project/hvigorfile.ts
Normal 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. */
|
||||
}
|
||||
28
openharmony-project/oh-package-lock.json5
Normal file
28
openharmony-project/oh-package-lock.json5
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
openharmony-project/oh-package.json5
Normal file
10
openharmony-project/oh-package.json5
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
648
src/audio/ohaudio/SDL_ohaudio.c
Normal file
648
src/audio/ohaudio/SDL_ohaudio.c
Normal 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
|
||||
38
src/audio/ohaudio/SDL_ohaudio.h
Normal file
38
src/audio/ohaudio/SDL_ohaudio.h
Normal 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_
|
||||
62
src/audio/ohaudio/SDL_ohaudiofuncs.h
Normal file
62
src/audio/ohaudio/SDL_ohaudiofuncs.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
775
src/camera/openharmony/SDL_camera_openharmony.c
Normal file
775
src/camera/openharmony/SDL_camera_openharmony.c
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
1207
src/core/openharmony/SDL_openharmony.c
Normal file
1207
src/core/openharmony/SDL_openharmony.c
Normal file
File diff suppressed because it is too large
Load Diff
66
src/core/openharmony/SDL_openharmony.h
Normal file
66
src/core/openharmony/SDL_openharmony.h
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -1311,3 +1311,6 @@ _SDL_SetRenderViewportFloat
|
||||
_SDL_GetRenderViewportFloat
|
||||
_SDL_SetRenderClipRectFloat
|
||||
_SDL_GetRenderClipRectFloat
|
||||
_SDL_GetOpenHarmonySDKVersion
|
||||
_SDL_GetOpenHarmonyInternalStoragePath
|
||||
_SDL_RequestOpenHarmonyPermission
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: *;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
78
src/filesystem/openharmony/SDL_sysfilesystem.c
Normal file
78
src/filesystem/openharmony/SDL_sysfilesystem.c
Normal 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
|
||||
@@ -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 == '/') {
|
||||
|
||||
@@ -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
|
||||
|
||||
36
src/locale/openharmony/SDL_syslocale.c
Normal file
36
src/locale/openharmony/SDL_syslocale.c
Normal 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;
|
||||
}
|
||||
|
||||
@@ -75,4 +75,3 @@ bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* vi: set ts=4 sw=4 expandtab: */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
60
src/main/openharmony/SDL_sysmain_callbacks.c
Normal file
60
src/main/openharmony/SDL_sysmain_callbacks.c
Normal 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
|
||||
|
||||
102
src/main/openharmony/SDL_sysmain_runapp.c
Normal file
102
src/main/openharmony/SDL_sysmain_runapp.c
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
34
src/misc/openharmony/SDL_sysurl.c
Normal file
34
src/misc/openharmony/SDL_sysurl.c
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 *);
|
||||
|
||||
46
src/power/openharmony/SDL_syspower.c
Normal file
46
src/power/openharmony/SDL_syspower.c
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
220
src/video/openharmony/SDL_openharmonyclipboard.c
Normal file
220
src/video/openharmony/SDL_openharmonyclipboard.c
Normal 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
|
||||
34
src/video/openharmony/SDL_openharmonyclipboard.h
Normal file
34
src/video/openharmony/SDL_openharmonyclipboard.h
Normal 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_
|
||||
574
src/video/openharmony/SDL_openharmonyevents.c
Normal file
574
src/video/openharmony/SDL_openharmonyevents.c
Normal 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, ×tamp);
|
||||
|
||||
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
|
||||
33
src/video/openharmony/SDL_openharmonyevents.h
Normal file
33
src/video/openharmony/SDL_openharmonyevents.h
Normal 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);
|
||||
93
src/video/openharmony/SDL_openharmonykeyboard.c
Normal file
93
src/video/openharmony/SDL_openharmonykeyboard.c
Normal 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
|
||||
|
||||
29
src/video/openharmony/SDL_openharmonykeyboard.h
Normal file
29
src/video/openharmony/SDL_openharmonykeyboard.h
Normal 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
Reference in New Issue
Block a user