diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c index 48fc1e5..eb4fb8f 100644 --- a/android/app/src/main/cpp/chiaki-jni.c +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -22,8 +22,12 @@ #include #include #include +#include #include +#include +#include +#include #include "video-decoder.h" #include "audio-decoder.h" @@ -286,7 +290,7 @@ JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionCreate(J beach: free(host_str); E->SetIntField(env, result, E->GetFieldID(env, result_class, "errorCode", "I"), (jint)err); - E->SetLongField(env, result, E->GetFieldID(env, result_class, "sessionPtr", "J"), (jlong)session); + E->SetLongField(env, result, E->GetFieldID(env, result_class, "ptr", "J"), (jlong)session); } JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionFree(JNIEnv *env, jobject obj, jlong ptr) @@ -352,3 +356,116 @@ JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionSetLogin chiaki_session_set_login_pin(&session->session, (const uint8_t *)pin, strlen(pin)); E->ReleaseStringUTFChars(env, pin_java, pin); } + +typedef struct android_discovery_service_t +{ + ChiakiDiscoveryService service; + jobject java_service; +} AndroidDiscoveryService; + +static void android_discovery_service_cb(ChiakiDiscoveryHost *hosts, size_t hosts_count, void *user) +{ + AndroidDiscoveryService *service = user; + // TODO + CHIAKI_LOGD(&global_log, "Discovered %d hosts", (int)hosts_count); +} + +static ChiakiErrorCode sockaddr_from_java(JNIEnv *env, jobject /*InetSocketAddress*/ sockaddr_obj, struct sockaddr **addr, size_t *addr_size) +{ + jclass sockaddr_class = E->GetObjectClass(env, sockaddr_obj); + uint16_t port = (uint16_t)E->CallIntMethod(env, sockaddr_obj, E->GetMethodID(env, sockaddr_class, "getPort", "()I")); + jobject addr_obj = E->CallObjectMethod(env, sockaddr_obj, E->GetMethodID(env, sockaddr_class, "getAddress", "()Ljava/net/InetAddress;")); + jclass addr_class = E->GetObjectClass(env, addr_obj); + jbyteArray addr_byte_array = E->CallObjectMethod(env, addr_obj, E->GetMethodID(env, addr_class, "getAddress", "()[B")); + jsize addr_byte_array_len = E->GetArrayLength(env, addr_byte_array); + + if(addr_byte_array_len == 4) + { + struct sockaddr_in *inaddr = CHIAKI_NEW(struct sockaddr_in); + if(!inaddr) + return CHIAKI_ERR_MEMORY; + memset(inaddr, 0, sizeof(*inaddr)); + inaddr->sin_family = AF_INET; + jbyte *bytes = E->GetByteArrayElements(env, addr_byte_array, NULL); + memcpy(&inaddr->sin_addr.s_addr, bytes, sizeof(inaddr->sin_addr.s_addr)); + E->ReleaseByteArrayElements(env, addr_byte_array, bytes, JNI_ABORT); + inaddr->sin_port = htons(port); + + *addr = (struct sockaddr *)inaddr; + *addr_size = sizeof(*inaddr); + } + else if(addr_byte_array_len == 0x10) + { + struct sockaddr_in6 *inaddr6 = CHIAKI_NEW(struct sockaddr_in6); + if(!inaddr6) + return CHIAKI_ERR_MEMORY; + memset(inaddr6, 0, sizeof(*inaddr6)); + inaddr6->sin6_family = AF_INET6; + jbyte *bytes = E->GetByteArrayElements(env, addr_byte_array, NULL); + memcpy(&inaddr6->sin6_addr.in6_u, bytes, sizeof(inaddr6->sin6_addr.in6_u)); + E->ReleaseByteArrayElements(env, addr_byte_array, bytes, JNI_ABORT); + inaddr6->sin6_port = htons(port); + + *addr = (struct sockaddr *)inaddr6; + *addr_size = sizeof(*inaddr6); + } + else + return CHIAKI_ERR_INVALID_DATA; + + return CHIAKI_ERR_SUCCESS; +} + +JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_discoveryServiceCreate(JNIEnv *env, jobject obj, jobject result, jobject options_obj, jobject java_service) +{ + jclass result_class = E->GetObjectClass(env, result); + ChiakiErrorCode err = CHIAKI_ERR_SUCCESS; + ChiakiDiscoveryServiceOptions options = { 0 }; + + AndroidDiscoveryService *service = CHIAKI_NEW(AndroidDiscoveryService); + if(!service) + { + err = CHIAKI_ERR_MEMORY; + goto beach; + } + + jclass options_class = E->GetObjectClass(env, options_obj); + + options.hosts_max = (size_t)E->GetLongField(env, options_obj, E->GetFieldID(env, options_class, "hostsMax", "J")); + options.host_drop_pings = (uint64_t)E->GetLongField(env, options_obj, E->GetFieldID(env, options_class, "hostDropPings", "J")); + options.ping_ms = (uint64_t)E->GetLongField(env, options_obj, E->GetFieldID(env, options_class, "pingMs", "J")); + options.cb = android_discovery_service_cb; + options.cb_user = service; + + err = sockaddr_from_java(env, E->GetObjectField(env, options_obj, E->GetFieldID(env, options_class, "sendAddr", "Ljava/net/InetSocketAddress;")), &options.send_addr, &options.send_addr_size); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(&global_log, "Failed to get sockaddr from InetSocketAddress"); + goto beach; + } + + service->java_service = E->NewGlobalRef(env, java_service); + // TODO: service->whatever = get id for callback method + + err = chiaki_discovery_service_init(&service->service, &options, &global_log); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(&global_log, "Failed to create discovery service (JNI)"); + E->DeleteGlobalRef(env, service->java_service); + goto beach; + } + +beach: + free(options.send_addr); + E->SetIntField(env, result, E->GetFieldID(env, result_class, "errorCode", "I"), (jint)err); + E->SetLongField(env, result, E->GetFieldID(env, result_class, "ptr", "J"), (jlong)service); +} + +JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_discoveryServiceFree(JNIEnv *env, jobject obj, jlong ptr) +{ + AndroidDiscoveryService *service = (AndroidDiscoveryService *)ptr; + if(!service) + return; + chiaki_discovery_service_fini(&service->service); + E->DeleteGlobalRef(env, service->java_service); + free(service); +} diff --git a/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt b/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt index 1e9397a..3069bc4 100644 --- a/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt +++ b/android/app/src/main/java/com/metallic/chiaki/StreamSession.kt @@ -25,13 +25,12 @@ import android.view.TextureView import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.metallic.chiaki.lib.* -import java.util.stream.Stream sealed class StreamState object StreamStateIdle: StreamState() object StreamStateConnecting: StreamState() object StreamStateConnected: StreamState() -data class StreamStateCreateError(val error: SessionCreateError): StreamState() +data class StreamStateCreateError(val error: CreateError): StreamState() data class StreamStateQuit(val reason: QuitReason, val reasonString: String?): StreamState() data class StreamStateLoginPinRequest(val pinIncorrect: Boolean): StreamState() @@ -77,7 +76,7 @@ class StreamSession(val connectInfo: ConnectInfo) session.setSurface(Surface(surfaceTexture)) this.session = session } - catch(e: SessionCreateError) + catch(e: CreateError) { _state.value = StreamStateCreateError(e) } diff --git a/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt b/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt new file mode 100644 index 0000000..082b910 --- /dev/null +++ b/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt @@ -0,0 +1,52 @@ +/* + * This file is part of Chiaki. + * + * Chiaki is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chiaki is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chiaki. If not, see . + */ + +package com.metallic.chiaki.discovery + +import com.metallic.chiaki.lib.DiscoveryService +import com.metallic.chiaki.lib.DiscoveryServiceOptions +import java.net.InetSocketAddress + +class DiscoveryManager +{ + companion object + { + const val HOSTS_MAX: ULong = 16U + const val DROP_PINGS: ULong = 3U + const val PING_MS: ULong = 500U + const val PORT = 987 + } + + private var discoveryService: DiscoveryService? = null + + fun start() + { + if(discoveryService != null) + return + // TODO: catch CreateError + discoveryService = DiscoveryService(DiscoveryServiceOptions( + HOSTS_MAX, DROP_PINGS, PING_MS, InetSocketAddress("255.255.255.255", PORT) + )) + } + + fun stop() + { + val service = discoveryService ?: return + service.dispose() + discoveryService = null + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt index 5a197b9..8cd76e2 100644 --- a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt +++ b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt @@ -4,6 +4,7 @@ import android.os.Parcelable import android.view.Surface import kotlinx.android.parcel.Parcelize import java.lang.Exception +import java.net.InetSocketAddress import kotlin.math.abs @Parcelize @@ -24,7 +25,7 @@ data class ConnectInfo( private class ChiakiNative { - data class SessionCreateResult(var errorCode: Int, var sessionPtr: Long) + data class CreateResult(var errorCode: Int, var ptr: Long) companion object { init @@ -34,7 +35,7 @@ private class ChiakiNative @JvmStatic external fun errorCodeToString(value: Int): String @JvmStatic external fun quitReasonToString(value: Int): String @JvmStatic external fun quitReasonIsStopped(value: Int): Boolean - @JvmStatic external fun sessionCreate(result: SessionCreateResult, connectInfo: ConnectInfo, javaSession: Session) + @JvmStatic external fun sessionCreate(result: CreateResult, connectInfo: ConnectInfo, javaSession: Session) @JvmStatic external fun sessionFree(ptr: Long) @JvmStatic external fun sessionStart(ptr: Long): Int @JvmStatic external fun sessionStop(ptr: Long): Int @@ -42,6 +43,8 @@ private class ChiakiNative @JvmStatic external fun sessionSetSurface(ptr: Long, surface: Surface) @JvmStatic external fun sessionSetControllerState(ptr: Long, controllerState: ControllerState) @JvmStatic external fun sessionSetLoginPin(ptr: Long, pin: String) + @JvmStatic external fun discoveryServiceCreate(result: CreateResult, options: DiscoveryServiceOptions, javaService: DiscoveryService) + @JvmStatic external fun discoveryServiceFree(ptr: Long) } } @@ -108,7 +111,7 @@ object ConnectedEvent: Event() data class LoginPinRequestEvent(val pinIncorrect: Boolean): Event() data class QuitEvent(val reason: QuitReason, val reasonString: String?): Event() -class SessionCreateError(val errorCode: ErrorCode): Exception("Failed to create Session: $errorCode") +class CreateError(val errorCode: ErrorCode): Exception("Failed to create a native object: $errorCode") class Session(connectInfo: ConnectInfo) { @@ -122,12 +125,12 @@ class Session(connectInfo: ConnectInfo) init { - val result = ChiakiNative.SessionCreateResult(0, 0) + val result = ChiakiNative.CreateResult(0, 0) ChiakiNative.sessionCreate(result, connectInfo, this) val errorCode = ErrorCode(result.errorCode) if(!errorCode.isSuccess) - throw SessionCreateError(errorCode) - nativePtr = result.sessionPtr + throw CreateError(errorCode) + nativePtr = result.ptr } fun start() = ErrorCode(ChiakiNative.sessionStart(nativePtr)) @@ -196,4 +199,35 @@ data class DiscoveryHost( READY, STANDBY } +} + + +data class DiscoveryServiceOptions( + val hostsMax: ULong, + val hostDropPings: ULong, + val pingMs: ULong, + val sendAddr: InetSocketAddress +) + +class DiscoveryService(options: DiscoveryServiceOptions) +{ + private var nativePtr: Long + + init + { + val result = ChiakiNative.CreateResult(0, 0) + ChiakiNative.discoveryServiceCreate(result, options, this) + val errorCode = ErrorCode(result.errorCode) + if(!errorCode.isSuccess) + throw CreateError(errorCode) + nativePtr = result.ptr + } + + fun dispose() + { + if(nativePtr == 0L) + return + ChiakiNative.discoveryServiceFree(nativePtr) + nativePtr = 0L + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt index ead0ec0..f5f6aec 100644 --- a/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt +++ b/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.ViewModel import com.metallic.chiaki.common.AppDatabase import com.metallic.chiaki.common.ManualDisplayHost import com.metallic.chiaki.common.ext.toLiveData +import com.metallic.chiaki.discovery.DiscoveryManager class MainViewModel(val database: AppDatabase): ViewModel() { @@ -33,4 +34,12 @@ class MainViewModel(val database: AppDatabase): ViewModel() } .toLiveData() } + + val discoveryManager = DiscoveryManager().also { it.start() } + + override fun onCleared() + { + super.onCleared() + discoveryManager.stop() + } } \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_discover_off.xml b/android/app/src/main/res/drawable/ic_discover_off.xml new file mode 100644 index 0000000..32baf35 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_discover_off.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_discover_on.xml b/android/app/src/main/res/drawable/ic_discover_on.xml new file mode 100644 index 0000000..7515126 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_discover_on.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_settings_48dp.xml b/android/app/src/main/res/drawable/ic_settings.xml similarity index 86% rename from android/app/src/main/res/drawable/ic_settings_48dp.xml rename to android/app/src/main/res/drawable/ic_settings.xml index 826bf4c..59b8712 100644 --- a/android/app/src/main/res/drawable/ic_settings_48dp.xml +++ b/android/app/src/main/res/drawable/ic_settings.xml @@ -1,4 +1,4 @@ - + diff --git a/android/app/src/main/res/menu/main.xml b/android/app/src/main/res/menu/main.xml index 25755cc..724958c 100644 --- a/android/app/src/main/res/menu/main.xml +++ b/android/app/src/main/res/menu/main.xml @@ -1,9 +1,17 @@ + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 268bfd0..3360cf1 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -9,4 +9,5 @@ Quit Connect Settings + Discover Consoles Automatically