From b9b4b7321bb7e194fd06db52d1a43e77dff0b893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 21 Aug 2019 13:36:10 +0200 Subject: [PATCH 001/470] Set Bitrate in LaunchSpec --- lib/include/chiaki/launchspec.h | 1 + lib/include/chiaki/session.h | 1 + lib/src/launchspec.c | 16 ++++++++-------- lib/src/session.c | 7 ++++++- lib/src/streamconnection.c | 1 + 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/include/chiaki/launchspec.h b/lib/include/chiaki/launchspec.h index ce52527..7772dc3 100644 --- a/lib/include/chiaki/launchspec.h +++ b/lib/include/chiaki/launchspec.h @@ -35,6 +35,7 @@ typedef struct chiaki_launch_spec_t unsigned int width; unsigned int height; unsigned int max_fps; + unsigned int bw_kbps_sent; } ChiakiLaunchSpec; CHIAKI_EXPORT int chiaki_launchspec_format(char *buf, size_t buf_size, ChiakiLaunchSpec *launch_spec); diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h index 79bb97f..8d8e712 100644 --- a/lib/include/chiaki/session.h +++ b/lib/include/chiaki/session.h @@ -48,6 +48,7 @@ typedef struct chiaki_connect_video_profile_t unsigned int width; unsigned int height; unsigned int max_fps; + unsigned int bitrate; } ChiakiConnectVideoProfile; typedef enum { diff --git a/lib/src/launchspec.c b/lib/src/launchspec.c index 76bfdb1..438ae9d 100644 --- a/lib/src/launchspec.c +++ b/lib/src/launchspec.c @@ -28,18 +28,18 @@ static const char launchspec_fmt[] = "{" "\"resolution\":" "{" - "\"width\":%u," - "\"height\":%u" + "\"width\":%u," // 0 + "\"height\":%u" // 1 "}," - "\"maxFps\":%u," + "\"maxFps\":%u," // 2 "\"score\":10" "}" "]," "\"network\":{" - "\"bwKbpsSent\":2000," + "\"bwKbpsSent\":%u," // 3 "\"bwLoss\":0.001000," - "\"mtu\":%u," // 0 - "\"rtt\":%u," // 1 + "\"mtu\":%u," // 4 + "\"rtt\":%u," // 5 "\"ports\":[53,2053]" "}," "\"slotId\":1," @@ -73,7 +73,7 @@ static const char launchspec_fmt[] = "\"region\":\"US\"," "\"languagesUsed\":[\"en\",\"jp\"]" "}," - "\"handshakeKey\":\"%s\"" // 2 + "\"handshakeKey\":\"%s\"" // 6 "}"; CHIAKI_EXPORT int chiaki_launchspec_format(char *buf, size_t buf_size, ChiakiLaunchSpec *launch_spec) @@ -85,7 +85,7 @@ CHIAKI_EXPORT int chiaki_launchspec_format(char *buf, size_t buf_size, ChiakiLau int written = snprintf(buf, buf_size, launchspec_fmt, launch_spec->width, launch_spec->height, launch_spec->max_fps, - launch_spec->mtu, launch_spec->rtt, handshake_key_b64); + launch_spec->bw_kbps_sent, launch_spec->mtu, launch_spec->rtt, handshake_key_b64); if(written < 0 || written >= buf_size) return -1; return written; diff --git a/lib/src/session.c b/lib/src/session.c index f7f093d..c423b5d 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -54,22 +54,27 @@ CHIAKI_EXPORT void chiaki_connect_video_profile_preset(ChiakiConnectVideoProfile case CHIAKI_VIDEO_RESOLUTION_PRESET_360p: profile->width = 640; profile->height = 360; + profile->bitrate = 2000; break; case CHIAKI_VIDEO_RESOLUTION_PRESET_540p: profile->width = 960; profile->height = 540; + profile->bitrate = 6000; break; case CHIAKI_VIDEO_RESOLUTION_PRESET_720p: profile->width = 1280; profile->height = 720; + profile->bitrate = 6000; // TODO: 10000 by default break; case CHIAKI_VIDEO_RESOLUTION_PRESET_1080p: profile->width = 1920; profile->height = 1080; + profile->bitrate = 10000; // TODO break; default: profile->width = 0; profile->height = 0; + profile->bitrate = 0; break; } @@ -595,7 +600,7 @@ static bool session_thread_request_session(ChiakiSession *session) ChiakiHttpResponse http_response; CHIAKI_LOGV(session->log, "Session Response Header:"); - chiaki_log_hexdump(session->log, CHIAKI_LOG_VERBOSE, buf, header_size); + chiaki_log_hexdump(session->log, CHIAKI_LOG_VERBOSE, (const uint8_t *)buf, header_size); err = chiaki_http_response_parse(&http_response, buf, header_size); if(err != CHIAKI_ERR_SUCCESS) { diff --git a/lib/src/streamconnection.c b/lib/src/streamconnection.c index 2bde304..58ada6e 100644 --- a/lib/src/streamconnection.c +++ b/lib/src/streamconnection.c @@ -659,6 +659,7 @@ static ChiakiErrorCode stream_connection_send_big(ChiakiStreamConnection *stream launch_spec.width = session->connect_info.video_profile.width; launch_spec.height = session->connect_info.video_profile.height; launch_spec.max_fps = session->connect_info.video_profile.max_fps; + launch_spec.bw_kbps_sent = session->connect_info.video_profile.bitrate; union { From 1f62756b85ddd29ec00021db7b406ca974152124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 21 Aug 2019 18:06:12 +0200 Subject: [PATCH 002/470] Print Unknown Ctrl Messages (Verbose) --- lib/src/ctrl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/ctrl.c b/lib/src/ctrl.c index f1d264f..7621ab5 100644 --- a/lib/src/ctrl.c +++ b/lib/src/ctrl.c @@ -219,6 +219,7 @@ static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t * break; default: CHIAKI_LOGW(ctrl->session->log, "Received Ctrl Message with unknown type %#x", msg_type); + chiaki_log_hexdump(ctrl->session->log, CHIAKI_LOG_VERBOSE, payload, payload_size); break; } } From 3f6cc3647e37b7e5dda0ddf85dc8282475e4d30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 21 Aug 2019 18:44:12 +0200 Subject: [PATCH 003/470] Add Bitrate Setting --- gui/include/settings.h | 8 ++++++++ gui/include/settingsdialog.h | 5 +++++ gui/src/settings.cpp | 19 +++++++++++++++++++ gui/src/settingsdialog.cpp | 20 ++++++++++++++++++++ gui/src/streamsession.cpp | 2 +- lib/src/session.c | 2 +- 6 files changed, 54 insertions(+), 2 deletions(-) diff --git a/gui/include/settings.h b/gui/include/settings.h index a533f6a..608f065 100644 --- a/gui/include/settings.h +++ b/gui/include/settings.h @@ -54,9 +54,17 @@ class Settings : public QObject ChiakiVideoResolutionPreset GetResolution() const; void SetResolution(ChiakiVideoResolutionPreset resolution); + /** + * @return 0 if set to "automatic" + */ ChiakiVideoFPSPreset GetFPS() const; void SetFPS(ChiakiVideoFPSPreset fps); + unsigned int GetBitrate() const; + void SetBitrate(unsigned int bitrate); + + ChiakiConnectVideoProfile GetVideoProfile(); + QList GetRegisteredHosts() const { return registered_hosts.values(); } void AddRegisteredHost(const RegisteredHost &host); void RemoveRegisteredHost(const HostMAC &mac); diff --git a/gui/include/settingsdialog.h b/gui/include/settingsdialog.h index f879931..e794cf8 100644 --- a/gui/include/settingsdialog.h +++ b/gui/include/settingsdialog.h @@ -24,6 +24,7 @@ class Settings; class QListWidget; class QComboBox; class QCheckBox; +class QLineEdit; class SettingsDialog : public QDialog { @@ -36,15 +37,19 @@ class SettingsDialog : public QDialog QComboBox *resolution_combo_box; QComboBox *fps_combo_box; + QLineEdit *bitrate_edit; QListWidget *registered_hosts_list_widget; QPushButton *delete_registered_host_button; + void UpdateBitratePlaceholder(); + private slots: void LogVerboseChanged(); void ResolutionSelected(); void FPSSelected(); + void BitrateEdited(); void UpdateRegisteredHosts(); void UpdateRegisteredHostsButtons(); diff --git a/gui/src/settings.cpp b/gui/src/settings.cpp index b68a527..f37d48b 100644 --- a/gui/src/settings.cpp +++ b/gui/src/settings.cpp @@ -73,6 +73,25 @@ void Settings::SetFPS(ChiakiVideoFPSPreset fps) settings.setValue("settings/fps", fps_values[fps]); } +unsigned int Settings::GetBitrate() const +{ + return settings.value("settings/bitrate", 0).toUInt(); +} + +void Settings::SetBitrate(unsigned int bitrate) +{ + settings.setValue("settings/bitrate", bitrate); +} + +ChiakiConnectVideoProfile Settings::GetVideoProfile() +{ + ChiakiConnectVideoProfile profile; + chiaki_connect_video_profile_preset(&profile, GetResolution(), GetFPS()); + unsigned int bitrate = GetBitrate(); + if(bitrate) + profile.bitrate = bitrate; + return profile; +} void Settings::LoadRegisteredHosts() { diff --git a/gui/src/settingsdialog.cpp b/gui/src/settingsdialog.cpp index 0c4a0e4..112036e 100644 --- a/gui/src/settingsdialog.cpp +++ b/gui/src/settingsdialog.cpp @@ -122,6 +122,14 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa connect(fps_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(FPSSelected())); stream_settings_layout->addRow(tr("FPS:"), fps_combo_box); + bitrate_edit = new QLineEdit(this); + bitrate_edit->setValidator(new QIntValidator(2000, 50000, bitrate_edit)); + unsigned int bitrate = settings->GetBitrate(); + bitrate_edit->setText(bitrate ? QString::number(bitrate) : ""); + stream_settings_layout->addRow(tr("Bitrate:"), bitrate_edit); + connect(bitrate_edit, &QLineEdit::textEdited, this, &SettingsDialog::BitrateEdited); + UpdateBitratePlaceholder(); + // Registered Consoles @@ -150,6 +158,7 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa auto button_box = new QDialogButtonBox(QDialogButtonBox::Close, this); layout->addWidget(button_box); connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + button_box->button(QDialogButtonBox::Close)->setDefault(true); UpdateRegisteredHosts(); UpdateRegisteredHostsButtons(); @@ -161,6 +170,7 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa void SettingsDialog::ResolutionSelected() { settings->SetResolution((ChiakiVideoResolutionPreset)resolution_combo_box->currentData().toInt()); + UpdateBitratePlaceholder(); } void SettingsDialog::LogVerboseChanged() @@ -173,6 +183,16 @@ void SettingsDialog::FPSSelected() settings->SetFPS((ChiakiVideoFPSPreset)fps_combo_box->currentData().toInt()); } +void SettingsDialog::BitrateEdited() +{ + settings->SetBitrate(bitrate_edit->text().toUInt()); +} + +void SettingsDialog::UpdateBitratePlaceholder() +{ + bitrate_edit->setPlaceholderText(tr("Automatic (%1)").arg(settings->GetVideoProfile().bitrate)); +} + void SettingsDialog::UpdateRegisteredHosts() { registered_hosts_list_widget->clear(); diff --git a/gui/src/streamsession.cpp b/gui/src/streamsession.cpp index 9e3f74a..244f4f1 100644 --- a/gui/src/streamsession.cpp +++ b/gui/src/streamsession.cpp @@ -41,7 +41,7 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, QString h { log_level_mask = settings->GetLogLevelMask(); log_file = CreateLogFilename(); - chiaki_connect_video_profile_preset(&video_profile, settings->GetResolution(), settings->GetFPS()); + video_profile = settings->GetVideoProfile(); this->host = host; this->regist_key = regist_key; this->morning = morning; diff --git a/lib/src/session.c b/lib/src/session.c index c423b5d..a2f3311 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -64,7 +64,7 @@ CHIAKI_EXPORT void chiaki_connect_video_profile_preset(ChiakiConnectVideoProfile case CHIAKI_VIDEO_RESOLUTION_PRESET_720p: profile->width = 1280; profile->height = 720; - profile->bitrate = 6000; // TODO: 10000 by default + profile->bitrate = 10000; break; case CHIAKI_VIDEO_RESOLUTION_PRESET_1080p: profile->width = 1920; From 08f5a3d4bf537b25dc385a35b6351c80afef86de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 21 Aug 2019 18:57:29 +0200 Subject: [PATCH 004/470] Create Tarball on Travis (Fix #3) --- .travis.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index eca6016..53dc4a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,8 @@ language: cpp matrix: include: - - os: linux + - name: Linux + os: linux dist: bionic addons: apt: @@ -34,7 +35,8 @@ matrix: - ./linuxdeploy-x86_64.AppImage --appdir=appdir -e appdir/usr/bin/chiaki -d appdir/usr/share/applications/chiaki.desktop --plugin qt --output appimage - mv Chiaki-*-x86_64.AppImage Chiaki-x86_64.AppImage - - os: osx + - name: macOS + os: osx osx_image: xcode11 addons: homebrew: @@ -53,6 +55,17 @@ matrix: - cp -a build/gui/chiaki.app Chiaki.app - /usr/local/opt/qt/bin/macdeployqt Chiaki.app -dmg + - name: "Source Package" + os: linux + dist: bionic + install: ~ + script: + - find . -name ".git*" | xargs rm -rfv + - mkdir chiaki && shopt -s extglob && mv !(chiaki) chiaki + - tar -czvf "$DEPLOY_FILE" chiaki + env: + - DEPLOY_FILE="chiaki-src.tar.gz" + install: - scripts/build-ffmpeg.sh From 8e5ad25c3cb104e4ba0eaa0cc52ed9a67db36da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 21 Aug 2019 19:04:28 +0200 Subject: [PATCH 005/470] Disable Windows Thread Name by default (Fix #7) --- lib/src/thread.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/thread.c b/lib/src/thread.c index 1dea3b2..27efd2f 100644 --- a/lib/src/thread.c +++ b/lib/src/thread.c @@ -66,9 +66,11 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_thread_join(ChiakiThread *thread, void **re return CHIAKI_ERR_SUCCESS; } +//#define CHIAKI_WINDOWS_THREAD_NAME + CHIAKI_EXPORT ChiakiErrorCode chiaki_thread_set_name(ChiakiThread *thread, const char *name) { -#ifdef _WIN32 +#if defined(_WIN32) && defined(CHIAKI_WINDOWS_THREAD_NAME) int len = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0); wchar_t *wstr = calloc(sizeof(wchar_t), len+1); if(!wstr) From a2b9de0eccbc1e1499c8fe29358dc8da9dd86a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 21 Aug 2019 19:04:53 +0200 Subject: [PATCH 006/470] Version 1.0.2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ae441e..a3d9185 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" O set(CHIAKI_VERSION_MAJOR 1) set(CHIAKI_VERSION_MINOR 0) -set(CHIAKI_VERSION_PATCH 1) +set(CHIAKI_VERSION_PATCH 2) set(CHIAKI_VERSION ${CHIAKI_VERSION_MAJOR}.${CHIAKI_VERSION_MINOR}.${CHIAKI_VERSION_PATCH}) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") From eda5dc0c77bf6208a9af816b2140a117246c2614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Wed, 21 Aug 2019 19:14:49 +0200 Subject: [PATCH 007/470] Remove Bitrate info from README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 11c5435..9ef87e4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ registration and wakeup of the console, is supported. The following features however are yet to be implemented: * Congestion Control * H264 Error Concealment (FEC and active error recovery however are implemented) -* Make the console send in higher Bitrate (if possible) * Touchpad support (Triggering the Touchpad Button is currently possible by pressing `T` on the keyboard) * Configurable Keybindings From a5ad2ad4f52b3b371811122d36113f118dc3597b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Thu, 22 Aug 2019 14:29:28 +0200 Subject: [PATCH 008/470] Better Build Names in Travis (#16) --- .travis.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53dc4a7..c62d091 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,8 @@ matrix: - wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage && chmod +x linuxdeploy-plugin-qt-x86_64.AppImage - source /opt/qt512/bin/qt512-env.sh - ./linuxdeploy-x86_64.AppImage --appdir=appdir -e appdir/usr/bin/chiaki -d appdir/usr/share/applications/chiaki.desktop --plugin qt --output appimage - - mv Chiaki-*-x86_64.AppImage Chiaki-x86_64.AppImage + - export DEPLOY_FILE="Chiaki-Linux-${CHIAKI_VERSION}-x86_64.AppImage" + - mv Chiaki-*-x86_64.AppImage "$DEPLOY_FILE" - name: macOS os: osx @@ -49,11 +50,12 @@ matrix: env: - CMAKE_PREFIX_PATH="$TRAVIS_BUILD_DIR/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt" - CMAKE_EXTRA_ARGS="" - - DEPLOY_FILE="Chiaki.dmg" after_success: - cd .. - cp -a build/gui/chiaki.app Chiaki.app - /usr/local/opt/qt/bin/macdeployqt Chiaki.app -dmg + - export DEPLOY_FILE="Chiaki-macOS-${CHIAKI_VERSION}-x86_64.dmg" + - mv Chiaki.dmg "$DEPLOY_FILE" - name: "Source Package" os: linux @@ -62,13 +64,16 @@ matrix: script: - find . -name ".git*" | xargs rm -rfv - mkdir chiaki && shopt -s extglob && mv !(chiaki) chiaki + - export DEPLOY_FILE="chiaki-$CHIAKI_VERSION-src.tar.gz" - tar -czvf "$DEPLOY_FILE" chiaki - env: - - DEPLOY_FILE="chiaki-src.tar.gz" install: - scripts/build-ffmpeg.sh +before_script: + - export CHIAKI_VERSION="$TRAVIS_TAG" + - if [ -z "$CHIAKI_VERSION" ]; then export CHIAKI_VERSION="$TRAVIS_COMMIT"; fi + script: - mkdir build && cd build - cmake From 94431ab70409b1f5eeec636a9a6d1a6844526dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Thu, 22 Aug 2019 20:08:29 +0200 Subject: [PATCH 009/470] Disable CI for Branches --- .appveyor.yml | 4 ++++ .travis.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index c367a4c..9b9542e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,5 +1,9 @@ image: 'Visual Studio 2017' +branches: + only: + - master + configuration: - Release diff --git a/.travis.yml b/.travis.yml index c62d091..f3b6f7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ language: cpp +branches: + only: + - master + matrix: include: - name: Linux From 8f539cc3fa40595dc82b68b64efbafff365883ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Thu, 22 Aug 2019 20:37:14 +0200 Subject: [PATCH 010/470] Add Fullscreen Mode on F11 (Fix #9) --- gui/include/streamwindow.h | 3 +++ gui/src/streamwindow.cpp | 51 ++++++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/gui/include/streamwindow.h b/gui/include/streamwindow.h index 45d1e1b..ffe3524 100644 --- a/gui/include/streamwindow.h +++ b/gui/include/streamwindow.h @@ -38,6 +38,8 @@ class StreamWindow: public QMainWindow AVOpenGLWidget *av_widget; + void Init(const StreamSessionConnectInfo &connect_info); + protected: void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; @@ -45,6 +47,7 @@ class StreamWindow: public QMainWindow private slots: void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); + void ToggleFullscreen(); }; #endif // CHIAKI_GUI_STREAMWINDOW_H diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index 1dd0d20..b5959ea 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -22,6 +22,7 @@ #include #include #include +#include StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent) : QMainWindow(parent) @@ -30,19 +31,7 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget setWindowTitle(qApp->applicationName()); try { - session = new StreamSession(connect_info, this); - - connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit); - - av_widget = new AVOpenGLWidget(session->GetVideoDecoder(), this); - setCentralWidget(av_widget); - - grabKeyboard(); - - session->Start(); - - resize(connect_info.video_profile.width, connect_info.video_profile.height); - show(); + Init(connect_info); } catch(const Exception &e) { @@ -59,6 +48,28 @@ StreamWindow::~StreamWindow() delete av_widget; } +void StreamWindow::Init(const StreamSessionConnectInfo &connect_info) +{ + session = new StreamSession(connect_info, this); + + connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit); + + av_widget = new AVOpenGLWidget(session->GetVideoDecoder(), this); + setCentralWidget(av_widget); + + grabKeyboard(); + + session->Start(); + + auto fullscreen_action = new QAction(tr("Fullscreen"), this); + fullscreen_action->setShortcut(Qt::Key_F11); + addAction(fullscreen_action); + connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen); + + resize(connect_info.video_profile.width, connect_info.video_profile.height); + show(); +} + void StreamWindow::keyPressEvent(QKeyEvent *event) { if(session) @@ -87,3 +98,17 @@ void StreamWindow::SessionQuit(ChiakiQuitReason reason, const QString &reason_st QMessageBox::critical(this, tr("Session has quit"), m); close(); } + +void StreamWindow::ToggleFullscreen() +{ + if(isFullScreen()) + { + setCursor(Qt::ArrowCursor); + showNormal(); + } + else + { + setCursor(Qt::BlankCursor); + showFullScreen(); + } +} \ No newline at end of file From 0971afdabc76dc1dab09bd1fcd2cc98261b85e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Thu, 22 Aug 2019 20:55:42 +0200 Subject: [PATCH 011/470] Enable Unit Tests on AppVeyor (#4) --- scripts/appveyor.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/appveyor.sh b/scripts/appveyor.sh index aafa89d..c6ec644 100755 --- a/scripts/appveyor.sh +++ b/scripts/appveyor.sh @@ -43,10 +43,12 @@ export PATH="$PWD/protoc/bin:$PATH" || exit 1 PYTHON="C:/Python37/python.exe" "$PYTHON" -m pip install protobuf || exit 1 -mkdir build && cd build || exit 1 - QT_PATH="C:/Qt/5.12.4/msvc2017_64" +COPY_DLLS="$PWD/openssl-1.1/x64/bin/libcrypto-1_1-x64.dll $PWD/openssl-1.1/x64/bin/libssl-1_1-x64.dll $SDL_ROOT/lib/x64/SDL2.dll" + +mkdir build && cd build || exit 1 + cmake \ -G Ninja \ -DCMAKE_C_COMPILER=cl \ @@ -61,6 +63,9 @@ cmake \ .. || exit 1 ninja || exit 1 + +test/chiaki-unit.exe || exit 1 + cd .. || exit 1 @@ -72,8 +77,4 @@ mkdir Chiaki && cp build/gui/chiaki.exe Chiaki || exit 1 # cp build/gui/chiaki.pdb Chiaki "$QT_PATH/bin/windeployqt.exe" Chiaki/chiaki.exe || exit 1 -cp \ - openssl-1.1/x64/bin/libcrypto-1_1-x64.dll \ - openssl-1.1/x64/bin/libssl-1_1-x64.dll \ - "$SDL_ROOT/lib/x64/SDL2.dll" \ - Chiaki +cp -v $COPY_DLLS Chiaki From 5346d7b16ff798bde47eeb8ee37f7f6513b7a9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Thu, 22 Aug 2019 20:57:00 +0200 Subject: [PATCH 012/470] Add Rumble Info to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9ef87e4..ee532cf 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ The following features however are yet to be implemented: * Congestion Control * H264 Error Concealment (FEC and active error recovery however are implemented) * Touchpad support (Triggering the Touchpad Button is currently possible by pressing `T` on the keyboard) +* Rumble * Configurable Keybindings ## Downloading a Release From d2f56097d56a2fa306609add9b356750897e1c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 23 Aug 2019 12:26:23 +0200 Subject: [PATCH 013/470] Fix a Leak in ChiakiRegist --- lib/src/regist.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/regist.c b/lib/src/regist.c index c4e8bd5..fd4c6e0 100644 --- a/lib/src/regist.c +++ b/lib/src/regist.c @@ -85,6 +85,7 @@ CHIAKI_EXPORT void chiaki_regist_fini(ChiakiRegist *regist) { chiaki_thread_join(®ist->thread, NULL); chiaki_stop_pipe_fini(®ist->stop_pipe); + free(regist->info.psn_id); free(regist->info.host); } From 50161fc552089b1ddcfdef5d330205670d76fd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 23 Aug 2019 12:59:14 +0200 Subject: [PATCH 014/470] Add Sourcehut Build Manifest for FreeBSD --- .builds/freebsd.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .builds/freebsd.yml diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml new file mode 100644 index 0000000..530466c --- /dev/null +++ b/.builds/freebsd.yml @@ -0,0 +1,25 @@ + +image: freebsd/latest + +packages: + - cmake + - protobuf + - py36-protobuf + - opus + - qt5-core + - qt5-qmake + - qt5-buildtools + - qt5-gui + - qt5-widgets + - qt5-svg + - qt5-opengl + - qt5-multimedia + - ffmpeg + - sdl2 + +tasks: + - build: | + cd chiaki + mkdir build && cd build + cmake .. + make -j4 From 2e752eaef2e550c9d3fc7a22717a0c2855f527d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 23 Aug 2019 13:37:39 +0200 Subject: [PATCH 015/470] Fix Box and Pyramid Buttons being swapped (Fix #5) --- lib/src/feedback.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/feedback.c b/lib/src/feedback.c index 0643058..a4832c3 100644 --- a/lib/src/feedback.c +++ b/lib/src/feedback.c @@ -64,10 +64,10 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_feedback_history_event_set_button(ChiakiFee event->buf[1] = 0x89; break; case CHIAKI_CONTROLLER_BUTTON_BOX: - event->buf[1] = 0x8b; + event->buf[1] = 0x8a; break; case CHIAKI_CONTROLLER_BUTTON_PYRAMID: - event->buf[1] = 0x8a; + event->buf[1] = 0x8b; break; case CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT: event->buf[1] = 0x82; From 59a8f0168c5058a0987bd155173cb5168d79e1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 24 Aug 2019 16:22:04 +0200 Subject: [PATCH 016/470] Use CONFIG_MODE in FindSDL2.cmake --- cmake/FindSDL2.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake index 2bc61b5..dbac860 100644 --- a/cmake/FindSDL2.cmake +++ b/cmake/FindSDL2.cmake @@ -19,4 +19,4 @@ if(SDL2_FOUND AND (NOT TARGET SDL2::SDL2)) endif() include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SDL2 DEFAULT_MSG) +find_package_handle_standard_args(SDL2 CONFIG_MODE) From b716bf42ac0caf80be6cd1924b3ba5046c798875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 24 Aug 2019 16:22:05 +0200 Subject: [PATCH 017/470] FreeBSD Fixes --- lib/src/ctrl.c | 1 + lib/src/senkusha.c | 1 + lib/src/takion.c | 7 ++++++- lib/src/utils.h | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/ctrl.c b/lib/src/ctrl.c index 7621ab5..332af2a 100644 --- a/lib/src/ctrl.c +++ b/lib/src/ctrl.c @@ -35,6 +35,7 @@ #include #include #include +#include #endif #define SESSION_OSTYPE "Win10.0.0" diff --git a/lib/src/senkusha.c b/lib/src/senkusha.c index 2cba4ac..abfa937 100644 --- a/lib/src/senkusha.c +++ b/lib/src/senkusha.c @@ -36,6 +36,7 @@ #include #include #include +#include #endif diff --git a/lib/src/takion.c b/lib/src/takion.c index dcbf89b..117167e 100644 --- a/lib/src/takion.c +++ b/lib/src/takion.c @@ -30,7 +30,9 @@ #include #else #include +#include #include +#include #endif @@ -253,9 +255,12 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_connect(ChiakiTakion *takion, Chiaki #if defined(_WIN32) const DWORD dontfragment_val = 1; r = setsockopt(takion->sock, IPPROTO_IP, IP_DONTFRAGMENT, (const void *)&dontfragment_val, sizeof(dontfragment_val)); +#elif defined(__FreeBSD__) + const int dontfrag_val = 1; + r = setsockopt(takion->sock, IPPROTO_IP, IP_DONTFRAG, (const void *)&dontfrag_val, sizeof(dontfrag_val)); #else const int mtu_discover_val = IP_PMTUDISC_DO; - r = setsockopt(takion->sock, IPPROTO_IP, IP_MTU_DISCOVER, (const void *) &mtu_discover_val, sizeof(mtu_discover_val)); + r = setsockopt(takion->sock, IPPROTO_IP, IP_MTU_DISCOVER, (const void *)&mtu_discover_val, sizeof(mtu_discover_val)); #endif if(r < 0) { diff --git a/lib/src/utils.h b/lib/src/utils.h index 27b3de8..a4bbfc1 100644 --- a/lib/src/utils.h +++ b/lib/src/utils.h @@ -24,6 +24,7 @@ #else #include #include +#include #endif #include From 1f307116c622ca1b963b32b96a9656f36ab7df08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 24 Aug 2019 16:20:08 +0200 Subject: [PATCH 018/470] Fix SourceHut FreeBSD --- .builds/freebsd.yml | 53 ++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 530466c..2224d1b 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -1,25 +1,28 @@ - -image: freebsd/latest - -packages: - - cmake - - protobuf - - py36-protobuf - - opus - - qt5-core - - qt5-qmake - - qt5-buildtools - - qt5-gui - - qt5-widgets - - qt5-svg - - qt5-opengl - - qt5-multimedia - - ffmpeg - - sdl2 - -tasks: - - build: | - cd chiaki - mkdir build && cd build - cmake .. - make -j4 + +image: freebsd/latest + +sources: + - https://github.com/thestr4ng3r/chiaki + +packages: + - cmake + - protobuf + - py36-protobuf + - opus + - qt5-core + - qt5-qmake + - qt5-buildtools + - qt5-gui + - qt5-widgets + - qt5-svg + - qt5-opengl + - qt5-multimedia + - ffmpeg + - sdl2 + +tasks: + - build: | + cd chiaki + mkdir build && cd build + cmake .. + make -j4 From 947b01250bbbb4c4208afba9c476ab5f5ebb9930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 23 Aug 2019 14:20:51 +0200 Subject: [PATCH 019/470] Add More RP-Application-Reason --- gui/src/registdialog.cpp | 2 +- lib/include/chiaki/session.h | 7 +++++++ lib/src/regist.c | 11 +++++++++++ lib/src/session.c | 29 ++++++++++++++++++++++------- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/gui/src/registdialog.cpp b/gui/src/registdialog.cpp index d1fddff..0e7dbb1 100644 --- a/gui/src/registdialog.cpp +++ b/gui/src/registdialog.cpp @@ -58,7 +58,7 @@ RegistDialog::RegistDialog(Settings *settings, const QString &host, QWidget *par broadcast_check_box->setChecked(host.isEmpty()); psn_id_edit = new QLineEdit(this); - form_layout->addRow(tr("PSN ID (username):"), psn_id_edit); + form_layout->addRow(tr("PSN ID (username, case-sensitive):"), psn_id_edit); pin_edit = new QLineEdit(this); pin_edit->setValidator(new QRegularExpressionValidator(pin_re, pin_edit)); diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h index 8d8e712..165242a 100644 --- a/lib/include/chiaki/session.h +++ b/lib/include/chiaki/session.h @@ -38,6 +38,13 @@ extern "C" { #endif +#define CHIAKI_RP_APPLICATION_REASON_REGIST_FAILED 0x80108b09 +#define CHIAKI_RP_APPLICATION_REASON_INVALID_PSN_ID 0x80108b02 +#define CHIAKI_RP_APPLICATION_REASON_IN_USE 0x80108b10 +#define CHIAKI_RP_APPLICATION_REASON_CRASH 0x80108b15 + +const char *chiaki_rp_application_reason_string(uint32_t reason); + #define CHIAKI_RP_DID_SIZE 32 #define CHIAKI_SESSION_ID_SIZE_MAX 80 diff --git a/lib/src/regist.c b/lib/src/regist.c index fd4c6e0..3fa6114 100644 --- a/lib/src/regist.c +++ b/lib/src/regist.c @@ -453,6 +453,17 @@ static ChiakiErrorCode regist_recv_response(ChiakiRegist *regist, ChiakiRegister if(http_response.code != 200) { CHIAKI_LOGE(regist->log, "Regist received HTTP code %d", http_response.code); + + for(ChiakiHttpHeader *header=http_response.headers; header; header=header->next) + { + if(strcmp(header->key, "RP-Application-Reason") == 0) + { + uint32_t reason = strtoul(header->value, NULL, 0x10); + CHIAKI_LOGE(regist->log, "Reported Application Reason: %#x (%s)", (unsigned int)reason, chiaki_rp_application_reason_string(reason)); + break; + } + } + chiaki_http_response_fini(&http_response); return CHIAKI_ERR_UNKNOWN; } diff --git a/lib/src/session.c b/lib/src/session.c index a2f3311..c0052df 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -42,11 +42,27 @@ #define SESSION_PORT 9295 -#define RP_APPLICATION_REASON_IN_USE 0x80108b10 -#define RP_APPLICATION_REASON_CRASH 0x80108b15 - #define SESSION_EXPECT_TIMEOUT_MS 5000 + +const char *chiaki_rp_application_reason_string(uint32_t reason) +{ + switch(reason) + { + case CHIAKI_RP_APPLICATION_REASON_REGIST_FAILED: + return "Regist failed, probably invalid PIN"; + case CHIAKI_RP_APPLICATION_REASON_INVALID_PSN_ID: + return "Invalid PSN ID"; + case CHIAKI_RP_APPLICATION_REASON_IN_USE: + return "Remote is already in use"; + case CHIAKI_RP_APPLICATION_REASON_CRASH: + return "Remote Play on Console crashed"; + default: + return "unknown"; + } +} + + CHIAKI_EXPORT void chiaki_connect_video_profile_preset(ChiakiConnectVideoProfile *profile, ChiakiVideoResolutionPreset resolution, ChiakiVideoFPSPreset fps) { switch(resolution) @@ -626,14 +642,13 @@ static bool session_thread_request_session(ChiakiSession *session) } else { + CHIAKI_LOGE(session->log, "Reported Application Reason: %#x (%s)", (unsigned int)response.error_code, chiaki_rp_application_reason_string(response.error_code)); switch(response.error_code) { - case RP_APPLICATION_REASON_IN_USE: - CHIAKI_LOGE(session->log, "Remote is already in use"); + case CHIAKI_RP_APPLICATION_REASON_IN_USE: session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_RP_IN_USE; break; - case RP_APPLICATION_REASON_CRASH: - CHIAKI_LOGE(session->log, "Remote seems to have crashed"); + case CHIAKI_RP_APPLICATION_REASON_CRASH: session->quit_reason = CHIAKI_QUIT_REASON_SESSION_REQUEST_RP_CRASH; break; default: From a93b8718efeb02137e5e9725d43d130ec3f0843e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 25 Aug 2019 16:38:13 +0200 Subject: [PATCH 020/470] Add Login PIN Support to lib --- lib/include/chiaki/ctrl.h | 11 ++- lib/include/chiaki/session.h | 8 ++- lib/include/chiaki/stoppipe.h | 1 + lib/src/ctrl.c | 122 +++++++++++++++++++++++++++------- lib/src/session.c | 68 ++++++++++++++++++- lib/src/stoppipe.c | 13 ++++ 6 files changed, 195 insertions(+), 28 deletions(-) diff --git a/lib/include/chiaki/ctrl.h b/lib/include/chiaki/ctrl.h index 38a04bf..4e0258b 100644 --- a/lib/include/chiaki/ctrl.h +++ b/lib/include/chiaki/ctrl.h @@ -37,16 +37,25 @@ typedef struct chiaki_ctrl_t { struct chiaki_session_t *session; ChiakiThread thread; - ChiakiStopPipe stop_pipe; + + bool should_stop; + bool login_pin_entered; + uint8_t *login_pin; // not owned + size_t login_pin_size; + ChiakiStopPipe notif_pipe; + ChiakiMutex notif_mutex; + chiaki_socket_t sock; uint8_t recv_buf[512]; size_t recv_buf_size; + uint64_t crypt_counter_local; uint64_t crypt_counter_remote; } ChiakiCtrl; CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, struct chiaki_session_t *session); CHIAKI_EXPORT void chiaki_ctrl_stop(ChiakiCtrl *ctrl); CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_join(ChiakiCtrl *ctrl); +CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, uint8_t *pin, size_t pin_size); #ifdef __cplusplus } diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h index 165242a..8942cc1 100644 --- a/lib/include/chiaki/session.h +++ b/lib/include/chiaki/session.h @@ -111,6 +111,7 @@ typedef struct chiaki_audio_stream_info_event_t typedef enum { + CHIAKI_EVENT_LOGIN_PIN_REQUEST, CHIAKI_EVENT_QUIT } ChiakiEventType; @@ -164,7 +165,7 @@ typedef struct chiaki_session_t ChiakiAudioSettingsCallback audio_settings_cb; ChiakiAudioFrameCallback audio_frame_cb; void *audio_cb_user; - ChiakiVideoSampleCallback video_sample_cb; + ChiakiVideoSampleCallback video_sample_cb; void *video_sample_cb_user; ChiakiThread session_thread; @@ -175,6 +176,10 @@ typedef struct chiaki_session_t bool should_stop; bool ctrl_failed; bool ctrl_session_id_received; + bool ctrl_login_pin_requested; + bool login_pin_entered; + uint8_t *login_pin; + size_t login_pin_size; ChiakiCtrl ctrl; @@ -193,6 +198,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_start(ChiakiSession *session); CHIAKI_EXPORT ChiakiErrorCode chiaki_session_stop(ChiakiSession *session); CHIAKI_EXPORT ChiakiErrorCode chiaki_session_join(ChiakiSession *session); CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_controller_state(ChiakiSession *session, ChiakiControllerState *state); +CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *session, uint8_t *pin, size_t pin_size); static inline void chiaki_session_set_event_cb(ChiakiSession *session, ChiakiEventCallback cb, void *user) { diff --git a/lib/include/chiaki/stoppipe.h b/lib/include/chiaki/stoppipe.h index 700118c..b5d93b3 100644 --- a/lib/include/chiaki/stoppipe.h +++ b/lib/include/chiaki/stoppipe.h @@ -45,6 +45,7 @@ CHIAKI_EXPORT void chiaki_stop_pipe_fini(ChiakiStopPipe *stop_pipe); CHIAKI_EXPORT void chiaki_stop_pipe_stop(ChiakiStopPipe *stop_pipe); CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_select_single(ChiakiStopPipe *stop_pipe, chiaki_socket_t fd, uint64_t timeout_ms); static inline ChiakiErrorCode chiaki_stop_pipe_sleep(ChiakiStopPipe *stop_pipe, uint64_t timeout_ms) { return chiaki_stop_pipe_select_single(stop_pipe, CHIAKI_INVALID_SOCKET, timeout_ms); } +CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_reset(ChiakiStopPipe *stop_pipe); #ifdef __cplusplus } diff --git a/lib/src/ctrl.c b/lib/src/ctrl.c index 332af2a..750838c 100644 --- a/lib/src/ctrl.c +++ b/lib/src/ctrl.c @@ -47,40 +47,77 @@ typedef enum ctrl_message_type_t { CTRL_MESSAGE_TYPE_SESSION_ID = 0x33, CTRL_MESSAGE_TYPE_HEARTBEAT_REQ = 0xfe, - CTRL_MESSAGE_TYPE_HEARTBEAT_REP = 0x1fe + CTRL_MESSAGE_TYPE_HEARTBEAT_REP = 0x1fe, + CTRL_MESSAGE_TYPE_LOGIN_PIN_REQ = 0x4, + CTRL_MESSAGE_TYPE_LOGIN_PIN_REP = 0x8004 } CtrlMessageType; static void *ctrl_thread_func(void *user); +static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, CtrlMessageType type, const uint8_t *payload, size_t payload_size); +static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); +static void ctrl_message_received_heartbeat_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); +static void ctrl_message_received_login_pin_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, ChiakiSession *session) { ctrl->session = session; - ChiakiErrorCode err = chiaki_stop_pipe_init(&ctrl->stop_pipe); + + ctrl->should_stop = false; + ctrl->login_pin_entered = false; + ctrl->login_pin = NULL; + ctrl->login_pin_size = 0; + + ChiakiErrorCode err = chiaki_stop_pipe_init(&ctrl->notif_pipe); if(err != CHIAKI_ERR_SUCCESS) return err; + + err = chiaki_mutex_init(&ctrl->notif_mutex, false); + if(err != CHIAKI_ERR_SUCCESS) + goto error_notif_pipe; + err = chiaki_thread_create(&ctrl->thread, ctrl_thread_func, ctrl); if(err != CHIAKI_ERR_SUCCESS) - { - chiaki_stop_pipe_fini(&ctrl->stop_pipe); - return err; - } + goto error_notif_mutex; + chiaki_thread_set_name(&ctrl->thread, "Chiaki Ctrl"); return err; + +error_notif_mutex: + chiaki_mutex_fini(&ctrl->notif_mutex); +error_notif_pipe: + chiaki_stop_pipe_fini(&ctrl->notif_pipe); + return err; } CHIAKI_EXPORT void chiaki_ctrl_stop(ChiakiCtrl *ctrl) { - chiaki_stop_pipe_stop(&ctrl->stop_pipe); + ChiakiErrorCode err = chiaki_mutex_lock(&ctrl->notif_mutex); + assert(err == CHIAKI_ERR_SUCCESS); + ctrl->should_stop = true; + chiaki_stop_pipe_stop(&ctrl->notif_pipe); + chiaki_mutex_unlock(&ctrl->notif_mutex); } CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_join(ChiakiCtrl *ctrl) { ChiakiErrorCode err = chiaki_thread_join(&ctrl->thread, NULL); - chiaki_stop_pipe_fini(&ctrl->stop_pipe); + chiaki_stop_pipe_fini(&ctrl->notif_pipe); + chiaki_mutex_fini(&ctrl->notif_mutex); return err; } +CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, uint8_t *pin, size_t pin_size) +{ + ChiakiErrorCode err = chiaki_mutex_lock(&ctrl->notif_mutex); + assert(err == CHIAKI_ERR_SUCCESS); + ctrl->login_pin_entered = true; + ctrl->login_pin = pin; + ctrl->login_pin_size = pin_size; + chiaki_stop_pipe_stop(&ctrl->notif_pipe); + chiaki_mutex_unlock(&ctrl->notif_mutex); +} + static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl); static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t *payload, size_t payload_size); @@ -108,6 +145,9 @@ static void *ctrl_thread_func(void *user) CHIAKI_LOGI(ctrl->session->log, "Ctrl connected"); + err = chiaki_mutex_lock(&ctrl->notif_mutex); + assert(err == CHIAKI_ERR_SUCCESS); + while(true) { bool overflow = false; @@ -141,11 +181,32 @@ static void *ctrl_thread_func(void *user) break; } - err = chiaki_stop_pipe_select_single(&ctrl->stop_pipe, ctrl->sock, UINT64_MAX); - if(err != CHIAKI_ERR_SUCCESS) + chiaki_mutex_unlock(&ctrl->notif_mutex); + err = chiaki_stop_pipe_select_single(&ctrl->notif_pipe, ctrl->sock, UINT64_MAX); + chiaki_mutex_lock(&ctrl->notif_mutex); + if(err == CHIAKI_ERR_CANCELED) { - if(err == CHIAKI_ERR_CANCELED) + if(ctrl->should_stop) + { CHIAKI_LOGI(ctrl->session->log, "Ctrl requested to stop"); + break; + } + + if(ctrl->login_pin_entered) + { + ctrl_message_send(ctrl, CTRL_MESSAGE_TYPE_LOGIN_PIN_REP, ctrl->login_pin, ctrl->login_pin_size); + ctrl->login_pin_entered = false; + chiaki_stop_pipe_reset(&ctrl->notif_pipe); + } + else + { + CHIAKI_LOGE(ctrl->session->log, "Ctrl notif pipe set without state"); + break; + } + } + else if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(ctrl->session->log, "Ctrl select error: %s", chiaki_error_string(err)); break; } @@ -160,6 +221,8 @@ static void *ctrl_thread_func(void *user) ctrl->recv_buf_size += received; } + chiaki_mutex_unlock(&ctrl->notif_mutex); + CHIAKI_SOCKET_CLOSE(ctrl->sock); return NULL; @@ -194,10 +257,6 @@ static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, CtrlMessageType type, return CHIAKI_ERR_SUCCESS; } - -static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); -static void ctrl_message_received_heartbeat_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); - static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t *payload, size_t payload_size) { if(payload_size > 0) @@ -218,6 +277,9 @@ static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t * case CTRL_MESSAGE_TYPE_HEARTBEAT_REQ: ctrl_message_received_heartbeat_req(ctrl, payload, payload_size); break; + case CTRL_MESSAGE_TYPE_LOGIN_PIN_REQ: + ctrl_message_received_login_pin_req(ctrl, payload, payload_size); + break; default: CHIAKI_LOGW(ctrl->session->log, "Received Ctrl Message with unknown type %#x", msg_type); chiaki_log_hexdump(ctrl->session->log, CHIAKI_LOG_VERBOSE, payload, payload_size); @@ -265,7 +327,7 @@ static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, continue; if(c >= '0' && c <= '9') continue; - CHIAKI_LOGE(ctrl->session->log, "Received Session Id contains invalid characters"); + CHIAKI_LOGE(ctrl->session->log, "Ctrl received Session Id contains invalid characters"); return; } @@ -276,19 +338,32 @@ static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, chiaki_mutex_unlock(&ctrl->session->state_mutex); chiaki_cond_signal(&ctrl->session->state_cond); - CHIAKI_LOGI(ctrl->session->log, "Received valid Session Id: %s", ctrl->session->session_id); + CHIAKI_LOGI(ctrl->session->log, "Ctrl received valid Session Id: %s", ctrl->session->session_id); } static void ctrl_message_received_heartbeat_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size) { if(payload_size != 0) - CHIAKI_LOGW(ctrl->session->log, "Received Heartbeat request with non-empty payload"); + CHIAKI_LOGW(ctrl->session->log, "Ctrl received Heartbeat request with non-empty payload"); - CHIAKI_LOGI(ctrl->session->log, "Received Ctrl Heartbeat, sending reply"); + CHIAKI_LOGI(ctrl->session->log, "Ctrl received Heartbeat, sending reply"); ctrl_message_send(ctrl, CTRL_MESSAGE_TYPE_HEARTBEAT_REP, NULL, 0); } +static void ctrl_message_received_login_pin_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size) +{ + if(payload_size != 0) + CHIAKI_LOGW(ctrl->session->log, "Ctrl received Login PIN request with non-empty payload"); + + CHIAKI_LOGI(ctrl->session->log, "Ctrl received Login PIN request"); + + chiaki_mutex_lock(&ctrl->session->state_mutex); + ctrl->session->ctrl_login_pin_requested = true; + chiaki_mutex_unlock(&ctrl->session->state_mutex); + chiaki_cond_signal(&ctrl->session->state_cond); +} + typedef struct ctrl_response_t { @@ -322,6 +397,7 @@ static void parse_ctrl_response(CtrlResponse *response, ChiakiHttpResponse *http static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) { + ctrl->crypt_counter_local = 0; ctrl->crypt_counter_remote = 0; ChiakiSession *session = ctrl->session; @@ -361,7 +437,7 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) uint8_t auth_enc[CHIAKI_RPCRYPT_KEY_SIZE]; - ChiakiErrorCode err = chiaki_rpcrypt_encrypt(&session->rpcrypt, 0, (uint8_t *)session->connect_info.regist_key, auth_enc, CHIAKI_RPCRYPT_KEY_SIZE); + ChiakiErrorCode err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (uint8_t *)session->connect_info.regist_key, auth_enc, CHIAKI_RPCRYPT_KEY_SIZE); if(err != CHIAKI_ERR_SUCCESS) goto error; char auth_b64[CHIAKI_RPCRYPT_KEY_SIZE*2]; @@ -370,7 +446,7 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) goto error; uint8_t did_enc[CHIAKI_RP_DID_SIZE]; - err = chiaki_rpcrypt_encrypt(&session->rpcrypt, 1, (uint8_t *)session->connect_info.did, did_enc, CHIAKI_RP_DID_SIZE); + err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (uint8_t *)session->connect_info.did, did_enc, CHIAKI_RP_DID_SIZE); if(err != CHIAKI_ERR_SUCCESS) goto error; char did_b64[CHIAKI_RP_DID_SIZE*2]; @@ -382,7 +458,7 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) size_t ostype_len = strlen(SESSION_OSTYPE) + 1; if(ostype_len > sizeof(ostype_enc)) goto error; - err = chiaki_rpcrypt_encrypt(&session->rpcrypt, 2, (uint8_t *)SESSION_OSTYPE, ostype_enc, ostype_len); + err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (uint8_t *)SESSION_OSTYPE, ostype_enc, ostype_len); if(err != CHIAKI_ERR_SUCCESS) goto error; char ostype_b64[256]; @@ -422,7 +498,7 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) size_t header_size; size_t received_size; - err = chiaki_recv_http_header(sock, buf, sizeof(buf), &header_size, &received_size, &ctrl->stop_pipe, CTRL_EXPECT_TIMEOUT); + err = chiaki_recv_http_header(sock, buf, sizeof(buf), &header_size, &received_size, &ctrl->notif_pipe, CTRL_EXPECT_TIMEOUT); if(err != CHIAKI_ERR_SUCCESS) { if(err != CHIAKI_ERR_CANCELED) diff --git a/lib/src/session.c b/lib/src/session.c index c0052df..112221e 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -161,6 +161,10 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_init(ChiakiSession *session, Chiaki session->should_stop = false; session->ctrl_session_id_received = false; + session->ctrl_login_pin_requested = false; + session->login_pin_entered = false; + session->login_pin = NULL; + session->login_pin_size = 0; err = chiaki_stream_connection_init(&session->stream_connection, session); if(err != CHIAKI_ERR_SUCCESS) @@ -204,6 +208,7 @@ CHIAKI_EXPORT void chiaki_session_fini(ChiakiSession *session) { if(!session) return; + free(session->login_pin); free(session->quit_reason_str); chiaki_stream_connection_fini(&session->stream_connection); chiaki_stop_pipe_fini(&session->stop_pipe); @@ -253,6 +258,22 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_controller_state(ChiakiSession return CHIAKI_ERR_SUCCESS; } +CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *session, uint8_t *pin, size_t pin_size) +{ + uint8_t *buf = malloc(pin_size); + memcpy(buf, pin, pin_size); + if(!buf) + return CHIAKI_ERR_MEMORY; + ChiakiErrorCode err = chiaki_mutex_lock(&session->state_mutex); + assert(err == CHIAKI_ERR_SUCCESS); + if(session->login_pin_entered) + free(session->login_pin); + session->login_pin_entered = true; + session->login_pin = buf; + session->login_pin_size = pin_size; + chiaki_mutex_unlock(&session->state_mutex); +} + static void session_send_event(ChiakiSession *session, ChiakiEvent *event) { if(!session->event_cb) @@ -267,8 +288,24 @@ static bool session_check_state_pred(void *user) { ChiakiSession *session = user; return session->should_stop - || session->ctrl_failed - || session->ctrl_session_id_received; + || session->ctrl_failed; +} + +static bool session_check_state_pred_ctrl_start(void *user) +{ + ChiakiSession *session = user; + return session->should_stop + || session->ctrl_failed + || session->ctrl_session_id_received + || session->ctrl_login_pin_requested; +} + +static bool session_check_state_pred_pin(void *user) +{ + ChiakiSession *session = user; + return session->should_stop + || session->ctrl_failed + || session->login_pin_entered; } #define ENABLE_SENKUSHA @@ -312,11 +349,36 @@ static void *session_thread_func(void *arg) if(err != CHIAKI_ERR_SUCCESS) QUIT(quit); - chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, SESSION_EXPECT_TIMEOUT_MS, session_check_state_pred, session); + chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, SESSION_EXPECT_TIMEOUT_MS, session_check_state_pred_ctrl_start, session); CHECK_STOP(quit_ctrl); + if(session->ctrl_failed) + goto ctrl_failed; + + if(session->ctrl_login_pin_requested) + { + session->ctrl_login_pin_requested = false; + CHIAKI_LOGI(session->log, "Ctrl requested Login PIN"); + ChiakiEvent event = { 0 }; + event.type = CHIAKI_EVENT_LOGIN_PIN_REQUEST; + session_send_event(session, &event); + + chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, UINT64_MAX, session_check_state_pred_pin, session); + CHECK_STOP(quit_ctrl); + if(session->ctrl_failed) + goto ctrl_failed; + + assert(session->login_pin_entered && session->login_pin); + chiaki_ctrl_set_login_pin(&session->ctrl, session->login_pin, session->login_pin_size); + session->login_pin_entered = false; + free(session->login_pin); + session->login_pin = NULL; + session->login_pin_size = 0; + } + if(!session->ctrl_session_id_received) { +ctrl_failed: CHIAKI_LOGE(session->log, "Ctrl has failed, shutting down"); if(session->quit_reason == CHIAKI_QUIT_REASON_NONE) session->quit_reason = CHIAKI_QUIT_REASON_CTRL_UNKNOWN; diff --git a/lib/src/stoppipe.c b/lib/src/stoppipe.c index 60d402c..fd69d91 100644 --- a/lib/src/stoppipe.c +++ b/lib/src/stoppipe.c @@ -136,3 +136,16 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_select_single(ChiakiStopPipe *sto return CHIAKI_ERR_TIMEOUT; #endif } + +CHIAKI_EXPORT ChiakiErrorCode chiaki_stop_pipe_reset(ChiakiStopPipe *stop_pipe) +{ +#ifdef _WIN32 + BOOL r = WSAResetEvent(stop_pipe->event); + return r ? CHIAKI_ERR_SUCCESS : CHIAKI_ERR_UNKNOWN; +#else + uint8_t v; + int r; + while((r = read(stop_pipe->fds[0], &v, sizeof(v))) > 0); + return r < 0 ? CHIAKI_ERR_UNKNOWN : CHIAKI_ERR_SUCCESS; +#endif +} From 31fb11fd43a36a7ef3cdf36b0493cd34a1c31464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 25 Aug 2019 16:38:13 +0200 Subject: [PATCH 021/470] Add LoginPINDialog --- gui/CMakeLists.txt | 4 ++- gui/include/loginpindialog.h | 44 ++++++++++++++++++++++++++++ gui/include/streamsession.h | 3 ++ gui/include/streamwindow.h | 1 + gui/src/loginpindialog.cpp | 57 ++++++++++++++++++++++++++++++++++++ gui/src/streamsession.cpp | 9 ++++++ gui/src/streamwindow.cpp | 40 ++++++++++++++++++++----- lib/include/chiaki/ctrl.h | 4 +-- lib/include/chiaki/rpcrypt.h | 4 +-- lib/include/chiaki/session.h | 2 +- lib/src/ctrl.c | 41 +++++++++++++++++++++----- lib/src/rpcrypt.c | 6 ++-- lib/src/session.c | 19 ++++++++++-- 13 files changed, 208 insertions(+), 26 deletions(-) create mode 100644 gui/include/loginpindialog.h create mode 100644 gui/src/loginpindialog.cpp diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 45bc9df..65e8d1c 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -63,7 +63,9 @@ add_executable(chiaki WIN32 src/manualhostdialog.cpp res/resources.qrc include/controllermanager.h - src/controllermanager.cpp) + src/controllermanager.cpp + include/loginpindialog.h + src/loginpindialog.cpp) target_include_directories(chiaki PRIVATE include) target_link_libraries(chiaki chiaki-lib) diff --git a/gui/include/loginpindialog.h b/gui/include/loginpindialog.h new file mode 100644 index 0000000..2784694 --- /dev/null +++ b/gui/include/loginpindialog.h @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +#ifndef CHIAKI_LOGINPINDIALOG_H +#define CHIAKI_LOGINPINDIALOG_H + +#include + +class QLineEdit; +class QDialogButtonBox; + +class LoginPINDialog : public QDialog +{ + Q_OBJECT + + private: + QString pin; + + QLineEdit *pin_edit; + QDialogButtonBox *button_box; + + void UpdateButtons(); + + public: + explicit LoginPINDialog(QWidget *parent = nullptr); + + QString GetPIN() { return pin; } +}; + +#endif // CHIAKI_LOGINPINDIALOG_H diff --git a/gui/include/streamsession.h b/gui/include/streamsession.h index b555577..c695e0b 100644 --- a/gui/include/streamsession.h +++ b/gui/include/streamsession.h @@ -92,6 +92,8 @@ class StreamSession : public QObject void Start(); void Stop(); + void SetLoginPIN(const QString &pin); + #if CHIAKI_GUI_ENABLE_QT_GAMEPAD QGamepad *GetGamepad() { return gamepad; } #endif @@ -103,6 +105,7 @@ class StreamSession : public QObject signals: void CurrentImageUpdated(); void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); + void LoginPINRequested(); private slots: void UpdateGamepads(); diff --git a/gui/include/streamwindow.h b/gui/include/streamwindow.h index ffe3524..ab698f6 100644 --- a/gui/include/streamwindow.h +++ b/gui/include/streamwindow.h @@ -47,6 +47,7 @@ class StreamWindow: public QMainWindow private slots: void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); + void LoginPINRequested(); void ToggleFullscreen(); }; diff --git a/gui/src/loginpindialog.cpp b/gui/src/loginpindialog.cpp new file mode 100644 index 0000000..a9a35ea --- /dev/null +++ b/gui/src/loginpindialog.cpp @@ -0,0 +1,57 @@ +/* + * 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 . + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define PIN_LENGTH 4 + +static const QRegularExpression pin_re(QString("[0-9]").repeated(PIN_LENGTH)); + +LoginPINDialog::LoginPINDialog(QWidget *parent) : QDialog(parent) +{ + auto layout = new QVBoxLayout(this); + setLayout(layout); + + pin_edit = new QLineEdit(this); + pin_edit->setPlaceholderText(tr("Login PIN")); + pin_edit->setValidator(new QRegularExpressionValidator(pin_re, pin_edit)); + layout->addWidget(pin_edit); + connect(pin_edit, &QLineEdit::textChanged, this, [this](const QString &text) { + this->pin = text; + UpdateButtons(); + }); + + button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + layout->addWidget(button_box); + + connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + UpdateButtons(); +} + +void LoginPINDialog::UpdateButtons() +{ + button_box->button(QDialogButtonBox::Ok)->setEnabled(pin_edit->text().length() == PIN_LENGTH); +} \ No newline at end of file diff --git a/gui/src/streamsession.cpp b/gui/src/streamsession.cpp index 244f4f1..7dbd244 100644 --- a/gui/src/streamsession.cpp +++ b/gui/src/streamsession.cpp @@ -124,6 +124,12 @@ void StreamSession::Stop() chiaki_session_stop(&session); } +void StreamSession::SetLoginPIN(const QString &pin) +{ + QByteArray data = pin.toUtf8(); + chiaki_session_set_login_pin(&session, (const uint8_t *)data.constData(), data.size()); +} + void StreamSession::HandleKeyboardEvent(QKeyEvent *event) { uint64_t button_mask; @@ -325,6 +331,9 @@ void StreamSession::Event(ChiakiEvent *event) case CHIAKI_EVENT_QUIT: emit SessionQuit(event->quit.reason, event->quit.reason_str ? QString::fromUtf8(event->quit.reason_str) : QString()); break; + case CHIAKI_EVENT_LOGIN_PIN_REQUEST: + emit LoginPINRequested(); + break; } } diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index b5959ea..4bb4ccb 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -29,14 +30,16 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(qApp->applicationName()); + + session = nullptr; + av_widget = nullptr; + try { Init(connect_info); } catch(const Exception &e) { - session = nullptr; - av_widget = nullptr; QMessageBox::critical(this, tr("Stream failed"), tr("Failed to initialize Stream Session: %1").arg(e.what())); close(); } @@ -53,6 +56,7 @@ void StreamWindow::Init(const StreamSessionConnectInfo &connect_info) session = new StreamSession(connect_info, this); connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit); + connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested); av_widget = new AVOpenGLWidget(session->GetVideoDecoder(), this); setCentralWidget(av_widget); @@ -90,15 +94,35 @@ void StreamWindow::closeEvent(QCloseEvent *) void StreamWindow::SessionQuit(ChiakiQuitReason reason, const QString &reason_str) { - if(reason == CHIAKI_QUIT_REASON_STOPPED) - return; - QString m = tr("Chiaki Session has quit") + ":\n" + chiaki_quit_reason_string(reason); - if(!reason_str.isEmpty()) - m += "\n" + tr("Reason") + ": \"" + reason_str + "\""; - QMessageBox::critical(this, tr("Session has quit"), m); + if(reason != CHIAKI_QUIT_REASON_STOPPED) + { + QString m = tr("Chiaki Session has quit") + ":\n" + chiaki_quit_reason_string(reason); + if(!reason_str.isEmpty()) + m += "\n" + tr("Reason") + ": \"" + reason_str + "\""; + QMessageBox::critical(this, tr("Session has quit"), m); + } close(); } +void StreamWindow::LoginPINRequested() +{ + auto dialog = new LoginPINDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(dialog, &QDialog::finished, this, [this, dialog](int result) { + grabKeyboard(); + + if(!session) + return; + + if(result == QDialog::Accepted) + session->SetLoginPIN(dialog->GetPIN()); + else + session->Stop(); + }); + releaseKeyboard(); + dialog->show(); +} + void StreamWindow::ToggleFullscreen() { if(isFullScreen()) diff --git a/lib/include/chiaki/ctrl.h b/lib/include/chiaki/ctrl.h index 4e0258b..e2ab838 100644 --- a/lib/include/chiaki/ctrl.h +++ b/lib/include/chiaki/ctrl.h @@ -40,7 +40,7 @@ typedef struct chiaki_ctrl_t bool should_stop; bool login_pin_entered; - uint8_t *login_pin; // not owned + uint8_t *login_pin; size_t login_pin_size; ChiakiStopPipe notif_pipe; ChiakiMutex notif_mutex; @@ -55,7 +55,7 @@ typedef struct chiaki_ctrl_t CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, struct chiaki_session_t *session); CHIAKI_EXPORT void chiaki_ctrl_stop(ChiakiCtrl *ctrl); CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_join(ChiakiCtrl *ctrl); -CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, uint8_t *pin, size_t pin_size); +CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, const uint8_t *pin, size_t pin_size); #ifdef __cplusplus } diff --git a/lib/include/chiaki/rpcrypt.h b/lib/include/chiaki/rpcrypt.h index d5b9dcc..12d0705 100644 --- a/lib/include/chiaki/rpcrypt.h +++ b/lib/include/chiaki/rpcrypt.h @@ -41,8 +41,8 @@ CHIAKI_EXPORT void chiaki_rpcrypt_aeropause(uint8_t *aeropause, const uint8_t *a CHIAKI_EXPORT void chiaki_rpcrypt_init_auth(ChiakiRPCrypt *rpcrypt, const uint8_t *nonce, const uint8_t *morning); CHIAKI_EXPORT void chiaki_rpcrypt_init_regist(ChiakiRPCrypt *rpcrypt, const uint8_t *ambassador, uint32_t pin); CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_generate_iv(ChiakiRPCrypt *rpcrypt, uint8_t *iv, uint64_t counter); -CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_encrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, uint8_t *in, uint8_t *out, size_t sz); -CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_decrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, uint8_t *in, uint8_t *out, size_t sz); +CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_encrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz); +CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_decrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz); #ifdef __cplusplus } diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h index 8942cc1..f4811da 100644 --- a/lib/include/chiaki/session.h +++ b/lib/include/chiaki/session.h @@ -198,7 +198,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_start(ChiakiSession *session); CHIAKI_EXPORT ChiakiErrorCode chiaki_session_stop(ChiakiSession *session); CHIAKI_EXPORT ChiakiErrorCode chiaki_session_join(ChiakiSession *session); CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_controller_state(ChiakiSession *session, ChiakiControllerState *state); -CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *session, uint8_t *pin, size_t pin_size); +CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *session, const uint8_t *pin, size_t pin_size); static inline void chiaki_session_set_event_cb(ChiakiSession *session, ChiakiEventCallback cb, void *user) { diff --git a/lib/src/ctrl.c b/lib/src/ctrl.c index 750838c..fc6651e 100644 --- a/lib/src/ctrl.c +++ b/lib/src/ctrl.c @@ -104,15 +104,22 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_join(ChiakiCtrl *ctrl) ChiakiErrorCode err = chiaki_thread_join(&ctrl->thread, NULL); chiaki_stop_pipe_fini(&ctrl->notif_pipe); chiaki_mutex_fini(&ctrl->notif_mutex); + free(ctrl->login_pin); return err; } -CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, uint8_t *pin, size_t pin_size) +CHIAKI_EXPORT void chiaki_ctrl_set_login_pin(ChiakiCtrl *ctrl, const uint8_t *pin, size_t pin_size) { + uint8_t *buf = malloc(pin_size); + if(!buf) + return; + memcpy(buf, pin, pin_size); ChiakiErrorCode err = chiaki_mutex_lock(&ctrl->notif_mutex); assert(err == CHIAKI_ERR_SUCCESS); + if(ctrl->login_pin_entered) + free(ctrl->login_pin); ctrl->login_pin_entered = true; - ctrl->login_pin = pin; + ctrl->login_pin = buf; ctrl->login_pin_size = pin_size; chiaki_stop_pipe_stop(&ctrl->notif_pipe); chiaki_mutex_unlock(&ctrl->notif_mutex); @@ -194,8 +201,12 @@ static void *ctrl_thread_func(void *user) if(ctrl->login_pin_entered) { + CHIAKI_LOGI(ctrl->session->log, "Ctrl received entered Login PIN, sending to console"); ctrl_message_send(ctrl, CTRL_MESSAGE_TYPE_LOGIN_PIN_REP, ctrl->login_pin, ctrl->login_pin_size); ctrl->login_pin_entered = false; + free(ctrl->login_pin); + ctrl->login_pin = NULL; + ctrl->login_pin_size = 0; chiaki_stop_pipe_reset(&ctrl->notif_pipe); } else @@ -232,6 +243,21 @@ static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, CtrlMessageType type, { assert(payload_size == 0 || payload); + uint8_t *enc = NULL; + if(payload && payload_size) + { + enc = malloc(payload_size); + if(!enc) + return CHIAKI_ERR_MEMORY; + ChiakiErrorCode err = chiaki_rpcrypt_encrypt(&ctrl->session->rpcrypt, ctrl->crypt_counter_local++, payload, enc, payload_size); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(ctrl->session->log, "Ctrl failed to encrypt payload"); + free(enc); + return err; + } + } + uint8_t header[8]; *((uint32_t *)header) = htonl((uint32_t)payload_size); *((uint16_t *)(header + 4)) = htons(type); @@ -244,9 +270,10 @@ static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, CtrlMessageType type, return CHIAKI_ERR_NETWORK; } - if(payload) + if(enc) { - sent = send(ctrl->sock, payload, payload_size, 0); + sent = send(ctrl->sock, enc, payload_size, 0); + free(enc); if(sent < 0) { CHIAKI_LOGE(ctrl->session->log, "Failed to send Ctrl Message Payload"); @@ -437,7 +464,7 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) uint8_t auth_enc[CHIAKI_RPCRYPT_KEY_SIZE]; - ChiakiErrorCode err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (uint8_t *)session->connect_info.regist_key, auth_enc, CHIAKI_RPCRYPT_KEY_SIZE); + ChiakiErrorCode err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (const uint8_t *)session->connect_info.regist_key, auth_enc, CHIAKI_RPCRYPT_KEY_SIZE); if(err != CHIAKI_ERR_SUCCESS) goto error; char auth_b64[CHIAKI_RPCRYPT_KEY_SIZE*2]; @@ -446,7 +473,7 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) goto error; uint8_t did_enc[CHIAKI_RP_DID_SIZE]; - err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (uint8_t *)session->connect_info.did, did_enc, CHIAKI_RP_DID_SIZE); + err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, session->connect_info.did, did_enc, CHIAKI_RP_DID_SIZE); if(err != CHIAKI_ERR_SUCCESS) goto error; char did_b64[CHIAKI_RP_DID_SIZE*2]; @@ -458,7 +485,7 @@ static ChiakiErrorCode ctrl_connect(ChiakiCtrl *ctrl) size_t ostype_len = strlen(SESSION_OSTYPE) + 1; if(ostype_len > sizeof(ostype_enc)) goto error; - err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (uint8_t *)SESSION_OSTYPE, ostype_enc, ostype_len); + err = chiaki_rpcrypt_encrypt(&session->rpcrypt, ctrl->crypt_counter_local++, (const uint8_t *)SESSION_OSTYPE, ostype_enc, ostype_len); if(err != CHIAKI_ERR_SUCCESS) goto error; char ostype_b64[256]; diff --git a/lib/src/rpcrypt.c b/lib/src/rpcrypt.c index 4181b09..708947f 100644 --- a/lib/src/rpcrypt.c +++ b/lib/src/rpcrypt.c @@ -104,7 +104,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_generate_iv(ChiakiRPCrypt *rpcrypt, return CHIAKI_ERR_SUCCESS; } -static ChiakiErrorCode chiaki_rpcrypt_crypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, uint8_t *in, uint8_t *out, size_t sz, bool encrypt) +static ChiakiErrorCode chiaki_rpcrypt_crypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz, bool encrypt) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if(!ctx) @@ -151,12 +151,12 @@ static ChiakiErrorCode chiaki_rpcrypt_crypt(ChiakiRPCrypt *rpcrypt, uint64_t cou return CHIAKI_ERR_SUCCESS; } -CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_encrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, uint8_t *in, uint8_t *out, size_t sz) +CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_encrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz) { return chiaki_rpcrypt_crypt(rpcrypt, counter, in, out, sz, true); } -CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_decrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, uint8_t *in, uint8_t *out, size_t sz) +CHIAKI_EXPORT ChiakiErrorCode chiaki_rpcrypt_decrypt(ChiakiRPCrypt *rpcrypt, uint64_t counter, const uint8_t *in, uint8_t *out, size_t sz) { return chiaki_rpcrypt_crypt(rpcrypt, counter, in, out, sz, false); } diff --git a/lib/src/session.c b/lib/src/session.c index 112221e..23528c4 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -258,12 +258,12 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_controller_state(ChiakiSession return CHIAKI_ERR_SUCCESS; } -CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *session, uint8_t *pin, size_t pin_size) +CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *session, const uint8_t *pin, size_t pin_size) { uint8_t *buf = malloc(pin_size); - memcpy(buf, pin, pin_size); if(!buf) return CHIAKI_ERR_MEMORY; + memcpy(buf, pin, pin_size); ChiakiErrorCode err = chiaki_mutex_lock(&session->state_mutex); assert(err == CHIAKI_ERR_SUCCESS); if(session->login_pin_entered) @@ -272,6 +272,8 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_session_set_login_pin(ChiakiSession *sessio session->login_pin = buf; session->login_pin_size = pin_size; chiaki_mutex_unlock(&session->state_mutex); + chiaki_cond_signal(&session->state_cond); + return CHIAKI_ERR_SUCCESS; } static void session_send_event(ChiakiSession *session, ChiakiEvent *event) @@ -308,6 +310,14 @@ static bool session_check_state_pred_pin(void *user) || session->login_pin_entered; } +static bool session_check_state_pred_session_id(void *user) +{ + ChiakiSession *session = user; + return session->should_stop + || session->ctrl_failed + || session->ctrl_session_id_received; +} + #define ENABLE_SENKUSHA static void *session_thread_func(void *arg) @@ -369,11 +379,16 @@ static void *session_thread_func(void *arg) goto ctrl_failed; assert(session->login_pin_entered && session->login_pin); + CHIAKI_LOGI(session->log, "Session received entered Login PIN, forwarding to Ctrl"); chiaki_ctrl_set_login_pin(&session->ctrl, session->login_pin, session->login_pin_size); session->login_pin_entered = false; free(session->login_pin); session->login_pin = NULL; session->login_pin_size = 0; + + // wait for session id again + chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, SESSION_EXPECT_TIMEOUT_MS, session_check_state_pred_session_id, session); + CHECK_STOP(quit_ctrl); } if(!session->ctrl_session_id_received) From d668423775c5e9c9dd9c1c1487b0ca763a836533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 25 Aug 2019 16:38:13 +0200 Subject: [PATCH 022/470] Support multiple PIN Entries --- gui/include/loginpindialog.h | 2 +- gui/include/streamsession.h | 2 +- gui/include/streamwindow.h | 2 +- gui/src/loginpindialog.cpp | 8 +++++- gui/src/streamsession.cpp | 3 +- gui/src/streamwindow.cpp | 4 +-- lib/include/chiaki/ctrl.h | 2 ++ lib/include/chiaki/session.h | 4 +++ lib/src/ctrl.c | 55 ++++++++++++++++++++++++++++++++++-- lib/src/session.c | 12 ++++++-- 10 files changed, 81 insertions(+), 13 deletions(-) diff --git a/gui/include/loginpindialog.h b/gui/include/loginpindialog.h index 2784694..fc6d0e5 100644 --- a/gui/include/loginpindialog.h +++ b/gui/include/loginpindialog.h @@ -36,7 +36,7 @@ class LoginPINDialog : public QDialog void UpdateButtons(); public: - explicit LoginPINDialog(QWidget *parent = nullptr); + explicit LoginPINDialog(bool incorrect, QWidget *parent = nullptr); QString GetPIN() { return pin; } }; diff --git a/gui/include/streamsession.h b/gui/include/streamsession.h index c695e0b..3c17ca8 100644 --- a/gui/include/streamsession.h +++ b/gui/include/streamsession.h @@ -105,7 +105,7 @@ class StreamSession : public QObject signals: void CurrentImageUpdated(); void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); - void LoginPINRequested(); + void LoginPINRequested(bool incorrect); private slots: void UpdateGamepads(); diff --git a/gui/include/streamwindow.h b/gui/include/streamwindow.h index ab698f6..5e67e6e 100644 --- a/gui/include/streamwindow.h +++ b/gui/include/streamwindow.h @@ -47,7 +47,7 @@ class StreamWindow: public QMainWindow private slots: void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); - void LoginPINRequested(); + void LoginPINRequested(bool incorrect); void ToggleFullscreen(); }; diff --git a/gui/src/loginpindialog.cpp b/gui/src/loginpindialog.cpp index a9a35ea..51e0836 100644 --- a/gui/src/loginpindialog.cpp +++ b/gui/src/loginpindialog.cpp @@ -23,16 +23,22 @@ #include #include #include +#include #define PIN_LENGTH 4 static const QRegularExpression pin_re(QString("[0-9]").repeated(PIN_LENGTH)); -LoginPINDialog::LoginPINDialog(QWidget *parent) : QDialog(parent) +LoginPINDialog::LoginPINDialog(bool incorrect, QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Console Login PIN")); + auto layout = new QVBoxLayout(this); setLayout(layout); + if(incorrect) + layout->addWidget(new QLabel(tr("Entered PIN was incorrect!"), this)); + pin_edit = new QLineEdit(this); pin_edit->setPlaceholderText(tr("Login PIN")); pin_edit->setValidator(new QRegularExpressionValidator(pin_re, pin_edit)); diff --git a/gui/src/streamsession.cpp b/gui/src/streamsession.cpp index 7dbd244..0c3f0da 100644 --- a/gui/src/streamsession.cpp +++ b/gui/src/streamsession.cpp @@ -30,6 +30,7 @@ #include #include +#include StreamSessionConnectInfo::StreamSessionConnectInfo() { @@ -332,7 +333,7 @@ void StreamSession::Event(ChiakiEvent *event) emit SessionQuit(event->quit.reason, event->quit.reason_str ? QString::fromUtf8(event->quit.reason_str) : QString()); break; case CHIAKI_EVENT_LOGIN_PIN_REQUEST: - emit LoginPINRequested(); + emit LoginPINRequested(event->login_pin_request.pin_incorrect); break; } } diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index 4bb4ccb..acb7071 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -104,9 +104,9 @@ void StreamWindow::SessionQuit(ChiakiQuitReason reason, const QString &reason_st close(); } -void StreamWindow::LoginPINRequested() +void StreamWindow::LoginPINRequested(bool incorrect) { - auto dialog = new LoginPINDialog(this); + auto dialog = new LoginPINDialog(incorrect, this); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &QDialog::finished, this, [this, dialog](int result) { grabKeyboard(); diff --git a/lib/include/chiaki/ctrl.h b/lib/include/chiaki/ctrl.h index e2ab838..ce7af8c 100644 --- a/lib/include/chiaki/ctrl.h +++ b/lib/include/chiaki/ctrl.h @@ -45,6 +45,8 @@ typedef struct chiaki_ctrl_t ChiakiStopPipe notif_pipe; ChiakiMutex notif_mutex; + bool login_pin_requested; + chiaki_socket_t sock; uint8_t recv_buf[512]; size_t recv_buf_size; diff --git a/lib/include/chiaki/session.h b/lib/include/chiaki/session.h index f4811da..566988a 100644 --- a/lib/include/chiaki/session.h +++ b/lib/include/chiaki/session.h @@ -121,6 +121,10 @@ typedef struct chiaki_event_t union { ChiakiQuitEvent quit; + struct + { + bool pin_incorrect; // false on first request, true if the pin entered before was incorrect + } login_pin_request; }; } ChiakiEvent; diff --git a/lib/src/ctrl.c b/lib/src/ctrl.c index fc6651e..21c64c7 100644 --- a/lib/src/ctrl.c +++ b/lib/src/ctrl.c @@ -49,15 +49,22 @@ typedef enum ctrl_message_type_t { CTRL_MESSAGE_TYPE_HEARTBEAT_REQ = 0xfe, CTRL_MESSAGE_TYPE_HEARTBEAT_REP = 0x1fe, CTRL_MESSAGE_TYPE_LOGIN_PIN_REQ = 0x4, - CTRL_MESSAGE_TYPE_LOGIN_PIN_REP = 0x8004 + CTRL_MESSAGE_TYPE_LOGIN_PIN_REP = 0x8004, + CTRL_MESSAGE_TYPE_LOGIN = 0x5 } CtrlMessageType; +typedef enum ctrl_login_state_t { + CTRL_LOGIN_STATE_SUCCESS = 0x0, + CTRL_LOGIN_STATE_PIN_INCORRECT = 0x1 +} CtrlLoginState; + static void *ctrl_thread_func(void *user); static ChiakiErrorCode ctrl_message_send(ChiakiCtrl *ctrl, CtrlMessageType type, const uint8_t *payload, size_t payload_size); static void ctrl_message_received_session_id(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); static void ctrl_message_received_heartbeat_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); static void ctrl_message_received_login_pin_req(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); +static void ctrl_message_received_login(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size); CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, ChiakiSession *session) { @@ -65,6 +72,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_ctrl_start(ChiakiCtrl *ctrl, ChiakiSession ctrl->should_stop = false; ctrl->login_pin_entered = false; + ctrl->login_pin_requested = false; ctrl->login_pin = NULL; ctrl->login_pin_size = 0; @@ -307,9 +315,12 @@ static void ctrl_message_received(ChiakiCtrl *ctrl, uint16_t msg_type, uint8_t * case CTRL_MESSAGE_TYPE_LOGIN_PIN_REQ: ctrl_message_received_login_pin_req(ctrl, payload, payload_size); break; + case CTRL_MESSAGE_TYPE_LOGIN: + ctrl_message_received_login(ctrl, payload, payload_size); + break; default: CHIAKI_LOGW(ctrl->session->log, "Received Ctrl Message with unknown type %#x", msg_type); - chiaki_log_hexdump(ctrl->session->log, CHIAKI_LOG_VERBOSE, payload, payload_size); + chiaki_log_hexdump(ctrl->session->log, CHIAKI_LOG_WARNING, payload, payload_size); break; } } @@ -385,12 +396,50 @@ static void ctrl_message_received_login_pin_req(ChiakiCtrl *ctrl, uint8_t *paylo CHIAKI_LOGI(ctrl->session->log, "Ctrl received Login PIN request"); - chiaki_mutex_lock(&ctrl->session->state_mutex); + ctrl->login_pin_requested = true; + + ChiakiErrorCode err = chiaki_mutex_lock(&ctrl->session->state_mutex); + assert(err == CHIAKI_ERR_SUCCESS); ctrl->session->ctrl_login_pin_requested = true; chiaki_mutex_unlock(&ctrl->session->state_mutex); chiaki_cond_signal(&ctrl->session->state_cond); } +static void ctrl_message_received_login(ChiakiCtrl *ctrl, uint8_t *payload, size_t payload_size) +{ + if(payload_size != 1) + { + CHIAKI_LOGW(ctrl->session->log, "Ctrl received Login message with payload of size %llx", (unsigned long long)payload_size); + if(payload_size < 1) + return; + } + + CtrlLoginState state = payload[0]; + switch(state) + { + case CTRL_LOGIN_STATE_SUCCESS: + CHIAKI_LOGI(ctrl->session->log, "Ctrl received Login message: success"); + ctrl->login_pin_requested = false; + break; + case CTRL_LOGIN_STATE_PIN_INCORRECT: + CHIAKI_LOGI(ctrl->session->log, "Ctrl received Login message: PIN incorrect"); + if(ctrl->login_pin_requested) + { + CHIAKI_LOGI(ctrl->session->log, "Ctrl requesting PIN from Session again"); + ChiakiErrorCode err = chiaki_mutex_lock(&ctrl->session->state_mutex); + assert(err == CHIAKI_ERR_SUCCESS); + ctrl->session->ctrl_login_pin_requested = true; + chiaki_mutex_unlock(&ctrl->session->state_mutex); + chiaki_cond_signal(&ctrl->session->state_cond); + } + else + CHIAKI_LOGW(ctrl->session->log, "Ctrl Login PIN incorrect message, but PIN was not requested"); + break; + default: + CHIAKI_LOGI(ctrl->session->log, "Ctrl received Login message with state: %x", state); + break; + } +} typedef struct ctrl_response_t { diff --git a/lib/src/session.c b/lib/src/session.c index 23528c4..c3c6f98 100644 --- a/lib/src/session.c +++ b/lib/src/session.c @@ -365,13 +365,19 @@ static void *session_thread_func(void *arg) if(session->ctrl_failed) goto ctrl_failed; - if(session->ctrl_login_pin_requested) + bool pin_incorrect = false; + while(session->ctrl_login_pin_requested) { session->ctrl_login_pin_requested = false; - CHIAKI_LOGI(session->log, "Ctrl requested Login PIN"); + if(pin_incorrect) + CHIAKI_LOGI(session->log, "Login PIN was incorrect, requested again by Ctrl"); + else + CHIAKI_LOGI(session->log, "Ctrl requested Login PIN"); ChiakiEvent event = { 0 }; event.type = CHIAKI_EVENT_LOGIN_PIN_REQUEST; + event.login_pin_request.pin_incorrect = pin_incorrect; session_send_event(session, &event); + pin_incorrect = true; chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, UINT64_MAX, session_check_state_pred_pin, session); CHECK_STOP(quit_ctrl); @@ -387,7 +393,7 @@ static void *session_thread_func(void *arg) session->login_pin_size = 0; // wait for session id again - chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, SESSION_EXPECT_TIMEOUT_MS, session_check_state_pred_session_id, session); + chiaki_cond_timedwait_pred(&session->state_cond, &session->state_mutex, SESSION_EXPECT_TIMEOUT_MS, session_check_state_pred_ctrl_start, session); CHECK_STOP(quit_ctrl); } From 497b69dfd335ab2afec37ef5bff9134eb8dd4dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 25 Aug 2019 16:38:13 +0200 Subject: [PATCH 023/470] Add Sourcehut Build Status Badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee532cf..4411e96 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **Disclaimer:** This project is not endorsed or certified by Sony Interactive Entertainment LLC. -[![Travis Build Status](https://travis-ci.com/thestr4ng3r/chiaki.svg?branch=master)](https://travis-ci.com/thestr4ng3r/chiaki) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/dnj9cmx5mmaaawer?svg=true)](https://ci.appveyor.com/project/thestr4ng3r/chiaki) +[![Travis Build Status](https://travis-ci.com/thestr4ng3r/chiaki.svg?branch=master)](https://travis-ci.com/thestr4ng3r/chiaki) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/dnj9cmx5mmaaawer?svg=true)](https://ci.appveyor.com/project/thestr4ng3r/chiaki) [![builds.sr.ht Status](https://builds.sr.ht/~thestr4ng3r/chiaki.svg)](https://builds.sr.ht/~thestr4ng3r/chiaki?) Chiaki is a Free and Open Source Software Client for PlayStation 4 Remote Play for Linux, macOS, Windows and potentially even more platforms. From 6321f669cbfeab0ce2c6da172f619fa9a391dc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 25 Aug 2019 16:38:14 +0200 Subject: [PATCH 024/470] Fix CLI for FreeBSD --- CMakeLists.txt | 1 + cli/CMakeLists.txt | 5 +++++ cli/src/discover.c | 3 ++- cmake/FindArgp.cmake | 20 ++++++++++++++++++++ gui/CMakeLists.txt | 2 +- 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 cmake/FindArgp.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d9185..b3d25e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ option(CHIAKI_ENABLE_TESTS "Enable tests for Chiaki" ON) option(CHIAKI_ENABLE_CLI "Enable CLI for Chiaki" OFF) option(CHIAKI_GUI_ENABLE_QT_GAMEPAD "Use QtGamepad for Input" OFF) option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" ON) +option(CHIAKI_CLI_ARGP_STANDALONE "Search for standalone argp lib for CLI" OFF) set(CHIAKI_VERSION_MAJOR 1) set(CHIAKI_VERSION_MINOR 0) diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index b44d0f5..c718185 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -7,5 +7,10 @@ add_library(chiaki-cli-lib STATIC ${SOURCE}) target_include_directories(chiaki-cli-lib PUBLIC "include") target_link_libraries(chiaki-cli-lib chiaki-lib) +if(CHIAKI_CLI_ARGP_STANDALONE) + find_package(Argp REQUIRED) + target_link_libraries(chiaki-cli-lib Argp::Argp) +endif() + add_executable(chiaki-cli src/main.c) target_link_libraries(chiaki-cli chiaki-cli-lib) diff --git a/cli/src/discover.c b/cli/src/discover.c index 2f2e854..04f3f74 100644 --- a/cli/src/discover.c +++ b/cli/src/discover.c @@ -24,6 +24,7 @@ #include #include #include +#include static char doc[] = "Send a PS4 discovery request."; @@ -168,4 +169,4 @@ CHIAKI_EXPORT int chiaki_cli_cmd_discover(ChiakiLog *log, int argc, char *argv[] sleep(1); return 0; -} \ No newline at end of file +} diff --git a/cmake/FindArgp.cmake b/cmake/FindArgp.cmake new file mode 100644 index 0000000..d88fc2f --- /dev/null +++ b/cmake/FindArgp.cmake @@ -0,0 +1,20 @@ +# FindArgp +# Will define Target Argp::Argp + +find_path(Argp_INCLUDE_DIR + NAMES argp.h) + +find_library(Argp_LIBRARY + NAMES argp) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Argp + REQUIRED_VARS Argp_LIBRARY Argp_INCLUDE_DIR) + +if(Argp_FOUND AND NOT TARGET Argp::Argp) + add_library(Argp::Argp UNKNOWN IMPORTED) + set_target_properties(Argp::Argp PROPERTIES + IMPORTED_LOCATION "${Argp_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${Argp_INCLUDE_DIR}") +endif() + diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 65e8d1c..b303324 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -70,7 +70,7 @@ target_include_directories(chiaki PRIVATE include) target_link_libraries(chiaki chiaki-lib) if(CHIAKI_ENABLE_CLI) - add_definitions(CHIAKI_ENABLE_CLI) + add_definitions(-DCHIAKI_ENABLE_CLI) target_link_libraries(chiaki chiaki-cli-lib) endif() From 9a006423bf2ef538300703cbe5e24e3d1893bae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 25 Aug 2019 16:38:14 +0200 Subject: [PATCH 025/470] Fix Broadcast on FreeBSD --- lib/src/discovery.c | 9 ++++++++- lib/src/regist.c | 2 +- lib/src/utils.h | 48 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/src/discovery.c b/lib/src/discovery.c index ad93814..9199554 100644 --- a/lib/src/discovery.c +++ b/lib/src/discovery.c @@ -163,6 +163,13 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_init(ChiakiDiscovery *discovery, if(r < 0) CHIAKI_LOGE(discovery->log, "Discovery failed to setsockopt SO_BROADCAST"); +//#ifdef __FreeBSD__ +// const int onesbcast = 1; +// r = setsockopt(discovery->socket, IPPROTO_IP, IP_ONESBCAST, &onesbcast, sizeof(onesbcast)); +// if(r < 0) +// CHIAKI_LOGE(discovery->log, "Discovery failed to setsockopt IP_ONESBCAST"); +//#endif + return CHIAKI_ERR_SUCCESS; } @@ -183,7 +190,7 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_discovery_send(ChiakiDiscovery *discovery, if((size_t)len >= sizeof(buf)) return CHIAKI_ERR_BUF_TOO_SMALL; - int rc = sendto(discovery->socket, buf, (size_t)len + 1, 0, addr, addr_size); + int rc = sendto_broadcast(discovery->log, discovery->socket, buf, (size_t)len + 1, 0, addr, addr_size); if(rc < 0) { CHIAKI_LOGE(discovery->log, "Discovery failed to send: %s", strerror(errno)); diff --git a/lib/src/regist.c b/lib/src/regist.c index 3fa6114..8c43df3 100644 --- a/lib/src/regist.c +++ b/lib/src/regist.c @@ -276,7 +276,7 @@ static ChiakiErrorCode regist_search(ChiakiRegist *regist, struct addrinfo *addr ChiakiErrorCode err = CHIAKI_ERR_SUCCESS; CHIAKI_LOGI(regist->log, "Regist sending search packet"); - int r = sendto(sock, "SRC2", 4, 0, &send_addr, send_addr_len); + int r = sendto_broadcast(regist->log, sock, "SRC2", 4, 0, &send_addr, send_addr_len); if(r < 0) { CHIAKI_LOGE(regist->log, "Regist failed to send search: %s", strerror(errno)); diff --git a/lib/src/utils.h b/lib/src/utils.h index a4bbfc1..a88208f 100644 --- a/lib/src/utils.h +++ b/lib/src/utils.h @@ -19,6 +19,7 @@ #define CHIAKI_UTILS_H #include +#include #ifdef _WIN32 #include #else @@ -27,6 +28,13 @@ #include #endif +#ifdef __FreeBSD__ +#include +#include +#include +#include +#endif + #include static inline ChiakiErrorCode set_port(struct sockaddr *sa, uint16_t port) @@ -60,6 +68,46 @@ static inline const char *sockaddr_str(struct sockaddr *addr, char *addr_buf, si return NULL; } +static inline int sendto_broadcast(ChiakiLog *log, chiaki_socket_t s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) +{ +#ifdef __FreeBSD__ + // see https://wiki.freebsd.org/NetworkRFCCompliance + if(to->sa_family == AF_INET && ((const struct sockaddr_in *)to)->sin_addr.s_addr == htonl(INADDR_BROADCAST)) + { + struct ifaddrs *ifap; + if(getifaddrs(&ifap) < 0) + { + CHIAKI_LOGE(log, "Failed to getifaddrs for Broadcast: %s", strerror(errno)); + return -1; + } + int r = -1; + for(struct ifaddrs *a=ifap; a; a=a->ifa_next) + { + if(!a->ifa_broadaddr) + continue; + if(!(a->ifa_flags & IFF_BROADCAST)) + continue; + if(a->ifa_broadaddr->sa_family != to->sa_family) + continue; + ((struct sockaddr_in *)a->ifa_broadaddr)->sin_port = ((const struct sockaddr_in *)to)->sin_port; + char addr_buf[64]; + const char *addr_str = sockaddr_str(a->ifa_broadaddr, addr_buf, sizeof(addr_buf)); + CHIAKI_LOGV(log, "Broadcast to %s on %s", addr_str ? addr_str : "(null)", a->ifa_name); + int sr = sendto(s, msg, len, flags, a->ifa_broadaddr, sizeof(*a->ifa_broadaddr)); + if(sr < 0) + { + CHIAKI_LOGE(log, "Broadcast on iface %s failed: %s", a->ifa_name, strerror(errno)); + continue; + } + r = sr; + } + freeifaddrs(ifap); + return r; + } +#endif + return sendto(s, msg, len, flags, to, tolen); +} + static inline void xor_bytes(uint8_t *dst, uint8_t *src, size_t sz) { while(sz > 0) From bd79895015d715c46086811950809d6a9f94bf6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 25 Aug 2019 16:38:14 +0200 Subject: [PATCH 026/470] Fix Regist Search Non-Broadcast Send (Fix #11) --- lib/src/regist.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/regist.c b/lib/src/regist.c index 8c43df3..dab7bfa 100644 --- a/lib/src/regist.c +++ b/lib/src/regist.c @@ -276,7 +276,11 @@ static ChiakiErrorCode regist_search(ChiakiRegist *regist, struct addrinfo *addr ChiakiErrorCode err = CHIAKI_ERR_SUCCESS; CHIAKI_LOGI(regist->log, "Regist sending search packet"); - int r = sendto_broadcast(regist->log, sock, "SRC2", 4, 0, &send_addr, send_addr_len); + int r; + if(regist->info.broadcast) + r = sendto_broadcast(regist->log, sock, "SRC2", 4, 0, &send_addr, send_addr_len); + else + r = send(sock, "SRC2", 4, 0); if(r < 0) { CHIAKI_LOGE(regist->log, "Regist failed to send search: %s", strerror(errno)); From 9e1faf1347b76675c9015b00296e4205d358f4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 26 Aug 2019 13:25:13 +0200 Subject: [PATCH 027/470] Fix sending Cumulative DATA ACK --- lib/src/takion.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/src/takion.c b/lib/src/takion.c index 117167e..d760675 100644 --- a/lib/src/takion.c +++ b/lib/src/takion.c @@ -901,12 +901,15 @@ static void takion_handle_packet_message(ChiakiTakion *takion, uint8_t *buf, siz static void takion_flush_data_queue(ChiakiTakion *takion) { + uint64_t seq_num = 0; + bool ack = false; while(true) { TakionDataPacketEntry *entry; - bool pulled = chiaki_reorder_queue_pull(&takion->data_queue, NULL, (void **)&entry); + bool pulled = chiaki_reorder_queue_pull(&takion->data_queue, &seq_num, (void **)&entry); if(!pulled) break; + ack = true; if(entry->payload_size < 9) { @@ -939,6 +942,9 @@ static void takion_flush_data_queue(ChiakiTakion *takion) free(entry->packet_buf); free(entry); } + + if(ack) + chiaki_takion_send_message_data_ack(takion, (uint32_t)seq_num); } static void takion_handle_packet_message_data(ChiakiTakion *takion, uint8_t *packet_buf, size_t packet_buf_size, uint8_t type_b, uint8_t *payload, size_t payload_size) @@ -964,7 +970,6 @@ static void takion_handle_packet_message_data(ChiakiTakion *takion, uint8_t *pac entry->channel = ntohs(*((uint16_t *)(payload + 4))); ChiakiSeqNum32 seq_num = ntohl(*((uint32_t *)(payload + 0))); - chiaki_takion_send_message_data_ack(takion, seq_num); chiaki_reorder_queue_push(&takion->data_queue, seq_num, entry); takion_flush_data_queue(takion); } From 7baa0ffec1fc14758ff4838618109ef2f04ef3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 31 Aug 2019 18:18:04 +0200 Subject: [PATCH 028/470] Fix Crash if Frame Uploader Thread not started --- gui/src/avopenglwidget.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gui/src/avopenglwidget.cpp b/gui/src/avopenglwidget.cpp index 434d77d..38622b9 100644 --- a/gui/src/avopenglwidget.cpp +++ b/gui/src/avopenglwidget.cpp @@ -99,9 +99,12 @@ AVOpenGLWidget::AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent) AVOpenGLWidget::~AVOpenGLWidget() { - frame_uploader_thread->quit(); - frame_uploader_thread->wait(); - delete frame_uploader_thread; + if(frame_uploader_thread) + { + frame_uploader_thread->quit(); + frame_uploader_thread->wait(); + delete frame_uploader_thread; + } delete frame_uploader; delete frame_uploader_context; } From aa4b648fef5459826e18f41ddc53dc7ee6255560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 31 Aug 2019 18:18:04 +0200 Subject: [PATCH 029/470] Free Cond and Mutex of GKCrypt --- lib/src/gkcrypt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/gkcrypt.c b/lib/src/gkcrypt.c index a8baf47..f8be2c1 100644 --- a/lib/src/gkcrypt.c +++ b/lib/src/gkcrypt.c @@ -113,6 +113,8 @@ CHIAKI_EXPORT void chiaki_gkcrypt_fini(ChiakiGKCrypt *gkcrypt) chiaki_mutex_unlock(&gkcrypt->key_buf_mutex); chiaki_cond_signal(&gkcrypt->key_buf_cond); chiaki_thread_join(&gkcrypt->key_buf_thread, NULL); + chiaki_cond_fini(&gkcrypt->key_buf_cond); + chiaki_mutex_fini(&gkcrypt->key_buf_mutex); chiaki_aligned_free(gkcrypt->key_buf); } } From 4cc64c1344aa02dec0c9ee479cae535356ad4291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 31 Aug 2019 18:18:04 +0200 Subject: [PATCH 030/470] Free Mutexes in Takion --- lib/src/takion.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/takion.c b/lib/src/takion.c index d760675..bdba21a 100644 --- a/lib/src/takion.c +++ b/lib/src/takion.c @@ -307,6 +307,8 @@ CHIAKI_EXPORT void chiaki_takion_close(ChiakiTakion *takion) chiaki_stop_pipe_stop(&takion->stop_pipe); chiaki_thread_join(&takion->thread, NULL); chiaki_stop_pipe_fini(&takion->stop_pipe); + chiaki_mutex_fini(&takion->seq_num_local_mutex); + chiaki_mutex_fini(&takion->gkcrypt_local_mutex); } CHIAKI_EXPORT ChiakiErrorCode chiaki_takion_crypt_advance_key_pos(ChiakiTakion *takion, size_t data_size, size_t *key_pos) From 9194f5cf83744f23257602bd6cc5bf479a32680d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 31 Aug 2019 18:18:04 +0200 Subject: [PATCH 031/470] Add Visual Studio stuff to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7069241..07c513c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ appdir /ffmpeg* /protoc* /openssl* +.vs +CMakeSettings.json From ba3ed016f3ddc27699f377da783e3ee07ccdd690 Mon Sep 17 00:00:00 2001 From: DSchndr Date: Fri, 6 Sep 2019 18:26:20 +0200 Subject: [PATCH 032/470] Add REQUIRED to Threads Build fails on Ubuntu 19.04 --- lib/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 5d986ba..80ac650 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -87,7 +87,7 @@ endif() target_include_directories(chiaki-lib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -find_package(Threads) +find_package(Threads REQUIRED) target_link_libraries(chiaki-lib Threads::Threads) find_package(OpenSSL REQUIRED) From b1196b30f64fb2c73a5e7d6ef296d986b1e315cf Mon Sep 17 00:00:00 2001 From: DSchndr Date: Fri, 6 Sep 2019 18:31:41 +0200 Subject: [PATCH 033/470] Add " | Stream" to streamwindow Makes it easier to find the window in obs. --- gui/src/streamwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index acb7071..a993ebb 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -29,8 +29,8 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget : QMainWindow(parent) { setAttribute(Qt::WA_DeleteOnClose); - setWindowTitle(qApp->applicationName()); - + setWindowTitle(qApp->applicationName() + " | Stream"); + session = nullptr; av_widget = nullptr; @@ -135,4 +135,4 @@ void StreamWindow::ToggleFullscreen() setCursor(Qt::BlankCursor); showFullScreen(); } -} \ No newline at end of file +} From ded2d1a6b7e35e7eb1ea9ad5a2c4ce8750c1c4d3 Mon Sep 17 00:00:00 2001 From: G-Ray Date: Mon, 9 Sep 2019 00:00:17 +0200 Subject: [PATCH 034/470] Readme: Add git submodule command --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4411e96..e1325cf 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Dependencies are CMake, Qt 5 with QtMultimedia, QtOpenGL and QtSvg, FFMPEG (liba protoc and the protobuf Python library (only used during compilation for Nanopb). Then, Chiaki builds just like any other CMake project: ``` +git submodule update --init mkdir build && cd build cmake .. make @@ -69,4 +70,4 @@ 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 this program. If not, see . \ No newline at end of file +along with this program. If not, see . From 1b26e899b82700d799e87d8549c11023514b5ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Tue, 10 Sep 2019 17:20:37 +0200 Subject: [PATCH 035/470] Add PDB AppVeyor Artifact (#28) --- .appveyor.yml | 2 ++ scripts/appveyor.sh | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 9b9542e..ef9ec18 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,6 +17,8 @@ build_script: artifacts: - path: Chiaki name: Chiaki + - path: Chiaki-PDB + name: Chiaki-PDB deploy: description: 'Chiaki Binaries' diff --git a/scripts/appveyor.sh b/scripts/appveyor.sh index c6ec644..bec5c31 100755 --- a/scripts/appveyor.sh +++ b/scripts/appveyor.sh @@ -53,7 +53,7 @@ cmake \ -G Ninja \ -DCMAKE_C_COMPILER=cl \ -DCMAKE_C_FLAGS="-we4013" \ - -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_PREFIX_PATH="$APPVEYOR_BUILD_FOLDER/ffmpeg-prefix;$APPVEYOR_BUILD_FOLDER/opus-prefix;$APPVEYOR_BUILD_FOLDER/openssl-1.1/x64;$QT_PATH;$SDL_ROOT" \ -DPYTHON_EXECUTABLE="$PYTHON" \ -DCHIAKI_ENABLE_TESTS=ON \ @@ -72,9 +72,7 @@ cd .. || exit 1 # Deploy mkdir Chiaki && cp build/gui/chiaki.exe Chiaki || exit 1 - -# set CMAKE_BUILD_TYPE=RelWithDebInfo for pdbs -# cp build/gui/chiaki.pdb Chiaki +mkdir Chiaki-PDB && cp build/gui/chiaki.pdb Chiaki-PDB || exit 1 "$QT_PATH/bin/windeployqt.exe" Chiaki/chiaki.exe || exit 1 cp -v $COPY_DLLS Chiaki From 57418e5e3340b11313079383ea8033ea5759f02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 13 Sep 2019 15:47:26 +0200 Subject: [PATCH 036/470] Fix frame_fg Initialization --- gui/src/avopenglwidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/src/avopenglwidget.cpp b/gui/src/avopenglwidget.cpp index 38622b9..245c746 100644 --- a/gui/src/avopenglwidget.cpp +++ b/gui/src/avopenglwidget.cpp @@ -95,6 +95,7 @@ AVOpenGLWidget::AVOpenGLWidget(VideoDecoder *decoder, QWidget *parent) frame_uploader_context = nullptr; frame_uploader = nullptr; frame_uploader_thread = nullptr; + frame_fg = 0; } AVOpenGLWidget::~AVOpenGLWidget() From c9401aa4b23f2b6bbb8eb6181affc82d45df2ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 13 Sep 2019 15:49:15 +0200 Subject: [PATCH 037/470] Switch to Xenial for Releases (#29) (Fix #25) --- .appveyor.yml | 1 + .gitignore | 1 + .travis.yml | 34 +++++++++++++++++++++++++++++++--- cmake/FindSDL2.cmake | 1 + scripts/build-sdl2.sh | 41 +++++++++++++++++++++++++++++++++++++++++ scripts/fetch-protoc.sh | 10 ++++++++++ 6 files changed, 85 insertions(+), 3 deletions(-) create mode 100755 scripts/build-sdl2.sh create mode 100755 scripts/fetch-protoc.sh diff --git a/.appveyor.yml b/.appveyor.yml index ef9ec18..5a38c98 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -23,6 +23,7 @@ artifacts: deploy: description: 'Chiaki Binaries' provider: GitHub + draft: true auth_token: secure: Amvzm3PMM5nv+iFsqaU7TZ9fgyYt/YIrOLV0QMiCyOoUlLRIaiYxWiJ7maTpxhZ9 artifact: "Chiaki" diff --git a/.gitignore b/.gitignore index 07c513c..55d8b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ appdir /SDL2-* /opus* /ffmpeg* +/sdl2-* /protoc* /openssl* .vs diff --git a/.travis.yml b/.travis.yml index f3b6f7f..6a03886 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ branches: matrix: include: - - name: Linux + - name: Linux (Bionic) os: linux dist: bionic addons: @@ -15,7 +15,6 @@ matrix: sources: - sourceline: "ppa:beineri/opt-qt-5.12.0-bionic" packages: - - libprotoc-dev - protobuf-compiler - python3-protobuf - libopus-dev @@ -29,7 +28,33 @@ matrix: env: - CMAKE_PREFIX_PATH="$TRAVIS_BUILD_DIR/ffmpeg-prefix;/opt/qt512" - CMAKE_EXTRA_ARGS="-DCMAKE_INSTALL_PREFIX=/usr" + - DEPLOY=0 + + - name: Linux (Xenial, Deploy) + os: linux + dist: xenial + addons: + apt: + sources: + - sourceline: "ppa:beineri/opt-qt-5.12.3-xenial" + packages: + - python3-pip + - libopus-dev + - qt512base + - qt512multimedia + - qt512svg + - libgl1-mesa-dev + - nasm + env: + - CMAKE_PREFIX_PATH="$TRAVIS_BUILD_DIR/ffmpeg-prefix;$TRAVIS_BUILD_DIR/sdl2-prefix;/opt/qt512" + - CMAKE_EXTRA_ARGS="-DCMAKE_INSTALL_PREFIX=/usr" - DEPLOY_FILE="Chiaki-x86_64.AppImage" + - SDL2_FROM_SRC=1 + - DEPLOY=1 + before_install: + - sudo pip3 install protobuf + - scripts/fetch-protoc.sh + - export PATH="$TRAVIS_BUILD_DIR/protoc/bin:$PATH" after_success: - make install DESTDIR=../appdir - cd .. @@ -54,6 +79,7 @@ matrix: env: - CMAKE_PREFIX_PATH="$TRAVIS_BUILD_DIR/ffmpeg-prefix;/usr/local/opt/openssl@1.1;/usr/local/opt/qt" - CMAKE_EXTRA_ARGS="" + - DEPLOY=1 after_success: - cd .. - cp -a build/gui/chiaki.app Chiaki.app @@ -73,6 +99,7 @@ matrix: install: - scripts/build-ffmpeg.sh + - if [ ! -z "$SDL2_FROM_SRC" ]; then scripts/build-sdl2.sh || exit 1; fi before_script: - export CHIAKI_VERSION="$TRAVIS_TAG" @@ -82,7 +109,6 @@ script: - mkdir build && cd build - cmake -DCMAKE_BUILD_TYPE=Release - -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH -DCHIAKI_ENABLE_TESTS=ON -DCHIAKI_ENABLE_CLI=OFF @@ -96,8 +122,10 @@ script: deploy: skip_cleanup: true provider: releases + draft: true api_key: secure: R7RjLOuGFda05EJeNX2lNG135xKU2w9IQn7p1H1P2zw4zlQMgSBpNRaW8hE408x5KJUjptJTF6QaYYmPWbHlf9VEPFVIcVzSp8YSd2Cdr+GKhmFgWF+fJPBj5y9NNqohwxvK3Nrugh0v6yVQiEYEGF7WArU6dvymSNNTw/EqXtfrOvwUgSf1bDAzQAsXn3E6Ptzf9DrQU8+mOgMSqT/3Wy5207KLmWTtwBWDgkskKwS9OEXk3tDd6U4uT7NFHHmcw+ZjQXRD+yHSHUWYs1oKR4IfgPFxQfEK0Txhkxdf3yj1aNweuk7GGC3cfRaarUfRQpoYqYYCxhTfGZ2b4rVgX3XpssMY7ZmSZHRi/SX08ETXF/c7PZGzr0RPFXZLgAGjgN6O2Dbb9agc3tOUGDUuqKEWX9sALm82WS0FRAFrFLENgMFsj5hu+DKIIkAU2yEsadYKjjhC+q+mTAEkxKKknvM50Xpx3tE1TlP/31Z53v4/NydHIHXPJ72V3mnuoTacwhG2SkGtjMbLCnEZDCtu9C4556oa7Z29cqafv90ZD7lTQMV+ijKvjxgOC9u1GeemmZLofRGDFyYSqKxOpYxxxXGOhs+7FMAdKP00h++MTLwRwIebKQs0fW0XiNKmwushWOUU8sXI1jxTbwe9dPQsspxHRv/mVo6l2vUcBjC19K0= file: $DEPLOY_FILE on: tags: true + condition: $DEPLOY = 1 diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake index dbac860..a0b9ebd 100644 --- a/cmake/FindSDL2.cmake +++ b/cmake/FindSDL2.cmake @@ -8,6 +8,7 @@ if(SDL2_FOUND AND (NOT TARGET SDL2::SDL2)) endif() find_library(SDL2_LIBRARY SDL2 PATHS "${SDL2_LIBDIR}" NO_DEFAULT_PATH) if(SDL2_LIBRARY) + string(STRIP "${SDL2_LIBRARIES}" SDL2_LIBRARIES) set_target_properties(SDL2::SDL2 PROPERTIES IMPORTED_LOCATION "${SDL2_LIBRARY}" IMPORTED_LINK_INTERFACE_LIBRARIES "${SDL2_LIBRARIES}" diff --git a/scripts/build-sdl2.sh b/scripts/build-sdl2.sh new file mode 100755 index 0000000..7382bca --- /dev/null +++ b/scripts/build-sdl2.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +cd $(dirname "${BASH_SOURCE[0]}")/.. +ROOT="`pwd`" + +URL=https://www.libsdl.org/release/SDL2-2.0.10.tar.gz +FILE=SDL2-2.0.10.tar.gz +DIR=SDL2-2.0.10 + +if [ ! -d "$DIR" ]; then + wget "$URL" || exit 1 + tar -xf "$FILE" || exit 1 +fi + +cd "$DIR" || exit 1 + +mkdir -p build && cd build || exit 1 +cmake \ + -DCMAKE_INSTALL_PREFIX="$ROOT/sdl2-prefix" \ + -DSDL_ATOMIC=OFF \ + -DSDL_AUDIO=OFF \ + -DSDL_CPUINFO=OFF \ + -DSDL_EVENTS=ON \ + -DSDL_FILE=OFF \ + -DSDL_FILESYSTEM=OFF \ + -DSDL_HAPTIC=ON \ + -DSDL_JOYSTICK=ON \ + -DSDL_LOADSO=OFF \ + -DSDL_RENDER=OFF \ + -DSDL_SHARED=ON \ + -DSDL_STATIC=OFF \ + -DSDL_TEST=OFF \ + -DSDL_THREADS=ON \ + -DSDL_TIMERS=OFF \ + -DSDL_VIDEO=OFF \ + .. || exit 1 +# SDL_THREADS is not needed, but it doesn't compile without + +make -j4 || exit 1 +make install || exit 1 + diff --git a/scripts/fetch-protoc.sh b/scripts/fetch-protoc.sh new file mode 100755 index 0000000..2dec889 --- /dev/null +++ b/scripts/fetch-protoc.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd $(dirname "${BASH_SOURCE[0]}")/.. +ROOT="`pwd`" + +URL=https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protoc-3.9.1-linux-x86_64.zip + +wget "$URL" -O protoc.zip || exit 1 +unzip protoc.zip -d protoc || exit 1 + From 144023843baae8b56268610169e115092e752ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 13 Sep 2019 16:06:49 +0200 Subject: [PATCH 038/470] Fix Travis Tag Rule --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6a03886..83082eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ language: cpp branches: only: - master + - /^v\d.*$/ + - /^deploy-test(-.*)?$/ matrix: include: From c5f0766e6c72223f39899c4b8295f8f41b4b9009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 13 Sep 2019 16:26:50 +0200 Subject: [PATCH 039/470] Fix Building AppImage with SDL2 (#30) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 83082eb..9882d48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,6 +63,7 @@ matrix: - wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod +x linuxdeploy-x86_64.AppImage - wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage && chmod +x linuxdeploy-plugin-qt-x86_64.AppImage - source /opt/qt512/bin/qt512-env.sh + - export LD_LIBRARY_PATH="$TRAVIS_BUILD_DIR/sdl2-prefix/lib:$LD_LIBRARY_PATH" - ./linuxdeploy-x86_64.AppImage --appdir=appdir -e appdir/usr/bin/chiaki -d appdir/usr/share/applications/chiaki.desktop --plugin qt --output appimage - export DEPLOY_FILE="Chiaki-Linux-${CHIAKI_VERSION}-x86_64.AppImage" - mv Chiaki-*-x86_64.AppImage "$DEPLOY_FILE" From 7505e4a5a01284461446f3e9b48a02dd032eaeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 13 Sep 2019 16:56:54 +0200 Subject: [PATCH 040/470] Version 1.0.3 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b3d25e5..814817a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ option(CHIAKI_CLI_ARGP_STANDALONE "Search for standalone argp lib for CLI" OFF) set(CHIAKI_VERSION_MAJOR 1) set(CHIAKI_VERSION_MINOR 0) -set(CHIAKI_VERSION_PATCH 2) +set(CHIAKI_VERSION_PATCH 3) set(CHIAKI_VERSION ${CHIAKI_VERSION_MAJOR}.${CHIAKI_VERSION_MINOR}.${CHIAKI_VERSION_PATCH}) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") From 2b6a41957aab95e252ea06309d6a2f2867694d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 13 Sep 2019 17:07:57 +0200 Subject: [PATCH 041/470] Fix Source Deploy --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9882d48..f862629 100644 --- a/.travis.yml +++ b/.travis.yml @@ -93,6 +93,8 @@ matrix: - name: "Source Package" os: linux dist: bionic + env: + - DEPLOY=1 install: ~ script: - find . -name ".git*" | xargs rm -rfv From c40b5a95d18ce0e1b718c34faa019f45d6f7f97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 22 Sep 2019 21:21:41 +0200 Subject: [PATCH 042/470] Make YUV/RGB conversion more accurate --- gui/src/avopenglwidget.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gui/src/avopenglwidget.cpp b/gui/src/avopenglwidget.cpp index 245c746..49f83c3 100644 --- a/gui/src/avopenglwidget.cpp +++ b/gui/src/avopenglwidget.cpp @@ -54,13 +54,13 @@ out vec4 out_color; void main() { vec3 yuv = vec3( - texture(tex_y, uv_var).r, - texture(tex_u, uv_var).r - 0.5, - texture(tex_v, uv_var).r - 0.5); + (texture(tex_y, uv_var).r - (16.0 / 255.0)) / ((235.0 - 16.0) / 255.0), + (texture(tex_u, uv_var).r - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5, + (texture(tex_v, uv_var).r - (16.0 / 255.0)) / ((240.0 - 16.0) / 255.0) - 0.5); vec3 rgb = mat3( 1.0, 1.0, 1.0, - 0.0, -0.39393, 2.02839, - 1.14025, -0.58081, 0.0) * yuv; + 0.0, -0.21482, 2.12798, + 1.28033, -0.38059, 0.0) * yuv; out_color = vec4(rgb, 1.0); } )glsl"; From dbe4839165516eecf5c329ab47f40dec9c675e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 14 Sep 2019 11:39:49 +0200 Subject: [PATCH 043/470] Add stub Android Project --- android/.gitignore | 14 ++ android/app/.gitignore | 1 + android/app/build.gradle | 31 ++++ android/app/proguard-rules.pro | 21 +++ android/app/src/main/AndroidManifest.xml | 21 +++ .../java/com/metallic/chiaki/MainActivity.kt | 13 ++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++++ .../res/drawable/ic_launcher_background.xml | 170 +++++++++++++++++ .../app/src/main/res/layout/activity_main.xml | 18 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes android/app/src/main/res/values/colors.xml | 6 + android/app/src/main/res/values/strings.xml | 3 + android/app/src/main/res/values/styles.xml | 11 ++ android/build.gradle | 28 +++ android/gradle.properties | 21 +++ android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + android/gradlew | 172 ++++++++++++++++++ android/gradlew.bat | 84 +++++++++ android/settings.gradle | 2 + 23 files changed, 666 insertions(+) create mode 100644 android/.gitignore create mode 100644 android/app/.gitignore create mode 100644 android/app/build.gradle create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/java/com/metallic/chiaki/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 android/app/src/main/res/layout/activity_main.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100755 android/gradlew create mode 100644 android/gradlew.bat create mode 100644 android/settings.gradle diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..3f4a170 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.metallic.chiaki" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7a79c5c --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt new file mode 100644 index 0000000..658b8c4 --- /dev/null +++ b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt @@ -0,0 +1,13 @@ +package com.metallic.chiaki + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle + +class MainActivity : AppCompatActivity() +{ + override fun onCreate(savedInstanceState: Bundle?) + { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } +} diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4fc2444 --- /dev/null +++ b/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..64ba76f75e9ce021aa3d95c213491f73bcacb597 GIT binary patch literal 2060 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 lV%Z2*hu); + + #008577 + #00574B + #D81B60 + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..20eac75 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Chiaki + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..4420c59 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.3.41' + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..23339e0 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2362665 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Sep 14 10:53:58 CEST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..1f50fd9 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name='Chiaki' From 7fb4c67f332f957d5504802c9b068aa110ab18de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 14 Sep 2019 15:14:55 +0200 Subject: [PATCH 044/470] Add Chiaki Lib to Android --- CMakeLists.txt | 16 +++++- android/app/CMakeLists.txt | 5 ++ android/app/build.gradle | 17 ++++++ android/app/src/main/cpp/chiaki-jni.c | 9 ++++ .../java/com/metallic/chiaki/MainActivity.kt | 4 ++ .../java/com/metallic/chiaki/lib/Chiaki.kt | 14 +++++ .../app/src/main/res/layout/activity_main.xml | 1 + cmake/OpenSSLExternalProject.cmake | 52 +++++++++++++++++++ lib/CMakeLists.txt | 19 +++++-- lib/src/audioreceiver.c | 10 ++++ lib/src/common.c | 2 +- 11 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 android/app/CMakeLists.txt create mode 100644 android/app/src/main/cpp/chiaki-jni.c create mode 100644 android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt create mode 100644 cmake/OpenSSLExternalProject.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 814817a..1de7b40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,10 @@ project(chiaki) option(CHIAKI_ENABLE_TESTS "Enable tests for Chiaki" ON) option(CHIAKI_ENABLE_CLI "Enable CLI for Chiaki" OFF) +option(CHIAKI_ENABLE_GUI "Enable Qt GUI" ON) +option(CHIAKI_ENABLE_ANDROID "Enable Android (Use only as part of the Gradle Project)" OFF) +option(CHIAKI_LIB_ENABLE_OPUS "Use Opus as part of Chiaki Lib" ON) +option(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT "Use OpenSSL as CMake external project" OFF) option(CHIAKI_GUI_ENABLE_QT_GAMEPAD "Use QtGamepad for Input" OFF) option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" ON) option(CHIAKI_CLI_ARGP_STANDALONE "Search for standalone argp lib for CLI" OFF) @@ -20,15 +24,25 @@ add_subdirectory(third-party) add_definitions(-DCHIAKI_VERSION_MAJOR=${CHIAKI_VERSION_MAJOR} -DCHIAKI_VERSION_MINOR=${CHIAKI_VERSION_MINOR} -DCHIAKI_VERSION_PATCH=${CHIAKI_VERSION_PATCH} -DCHIAKI_VERSION=\"${CHIAKI_VERSION}\") +if(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT) + include(OpenSSLExternalProject) +endif() + add_subdirectory(lib) if(CHIAKI_ENABLE_CLI) add_subdirectory(cli) endif() -add_subdirectory(gui) +if(CHIAKI_ENABLE_GUI) + add_subdirectory(gui) +endif() if(CHIAKI_ENABLE_TESTS) enable_testing() add_subdirectory(test) endif() + +if(CHIAKI_ENABLE_ANDROID) + add_subdirectory(android/app) +endif() \ No newline at end of file diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt new file mode 100644 index 0000000..9d755a8 --- /dev/null +++ b/android/app/CMakeLists.txt @@ -0,0 +1,5 @@ + +cmake_minimum_required(VERSION 3.2) + +add_library(chiaki-jni SHARED src/main/cpp/chiaki-jni.c) +target_link_libraries(chiaki-jni chiaki-lib) \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 3f4a170..e58415d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -13,6 +13,23 @@ android { targetSdkVersion 29 versionCode 1 versionName "1.0" + externalNativeBuild { + cmake { + arguments "-DCHIAKI_ENABLE_TESTS=OFF", + "-DCHIAKI_ENABLE_CLI=OFF", + "-DCHIAKI_ENABLE_GUI=OFF", + "-DCHIAKI_ENABLE_ANDROID=ON", + "-DCHIAKI_LIB_ENABLE_OPUS=OFF", + "-DCHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT=ON", + "-DCMAKE_VERBOSE_MAKEFILE=ON" + } + } + } + externalNativeBuild { + cmake { + version "3.10.2+" + path "../../CMakeLists.txt" + } } buildTypes { release { diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c new file mode 100644 index 0000000..ff10e06 --- /dev/null +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -0,0 +1,9 @@ + +#include + +#include + +JNIEXPORT jstring JNICALL Java_com_metallic_chiaki_lib_Chiaki_stringFromJNI(JNIEnv* env, jobject thiz) +{ + return (*env)->NewStringUTF(env, chiaki_error_string(CHIAKI_ERR_SUCCESS)); +} diff --git a/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt index 658b8c4..da93a70 100644 --- a/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt @@ -2,6 +2,8 @@ package com.metallic.chiaki import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import com.metallic.chiaki.lib.Chiaki +import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { @@ -9,5 +11,7 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + testTextView.text = Chiaki.stringFromJNI() } } 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 new file mode 100644 index 0000000..bb17e18 --- /dev/null +++ b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt @@ -0,0 +1,14 @@ +package com.metallic.chiaki.lib + +class Chiaki +{ + companion object + { + init + { + System.loadLibrary("chiaki-jni") + } + + @JvmStatic external fun stringFromJNI(): String + } +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 4fc2444..145e02f 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -7,6 +7,7 @@ tools:context=".MainActivity"> /Configure" "--prefix=" no-shared ${OPENSSL_CONFIG_EXTRA_ARGS} "${OPENSSL_OS_COMPILER}" + BUILD_COMMAND ${CMAKE_COMMAND} -E env ${OPENSSL_BUILD_ENV} "${MAKE_EXE}" build_libs + INSTALL_COMMAND ${CMAKE_COMMAND} -E env ${OPENSSL_BUILD_ENV} "${MAKE_EXE}" install_dev) + +add_library(OpenSSL_Crypto INTERFACE) +add_dependencies(OpenSSL_Crypto OpenSSL-ExternalProject) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.13.0") + target_link_directories(OpenSSL_Crypto INTERFACE "${OPENSSL_INSTALL_DIR}/lib") +else() + link_directories("${OPENSSL_INSTALL_DIR}/lib") +endif() +target_link_libraries(OpenSSL_Crypto INTERFACE crypto) +target_include_directories(OpenSSL_Crypto INTERFACE "${OPENSSL_INSTALL_DIR}/include") diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 80ac650..ac6eab0 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -74,8 +74,11 @@ include_directories("${NANOPB_SOURCE_DIR}") set_source_files_properties(${CHIAKI_LIB_PROTO_SOURCE_FILES} ${CHIAKI_LIB_PROTO_HEADER_FILES} PROPERTIES GENERATED TRUE) include_directories("${CHIAKI_LIB_PROTO_INCLUDE_DIR}") -find_package(Opus REQUIRED) -include_directories(${Opus_INCLUDE_DIRS}) +if(CHIAKI_LIB_ENABLE_OPUS) + find_package(Opus REQUIRED) + include_directories(${Opus_INCLUDE_DIRS}) + add_definitions(-DCHIAKI_LIB_ENABLE_OPUS) +endif() add_library(chiaki-lib ${HEADER_FILES} ${SOURCE_FILES} ${CHIAKI_LIB_PROTO_SOURCE_FILES} ${CHIAKI_LIB_PROTO_HEADER_FILES}) add_dependencies(chiaki-lib chiaki-pb) @@ -90,10 +93,16 @@ target_include_directories(chiaki-lib PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/includ find_package(Threads REQUIRED) target_link_libraries(chiaki-lib Threads::Threads) -find_package(OpenSSL REQUIRED) -target_link_libraries(chiaki-lib OpenSSL::Crypto) +if(CHIAKI_LIB_OPENSSL_EXTERNAL_PROJECT) + target_link_libraries(chiaki-lib OpenSSL_Crypto) +else() + find_package(OpenSSL REQUIRED) + target_link_libraries(chiaki-lib OpenSSL::Crypto) +endif() target_link_libraries(chiaki-lib protobuf-nanopb-static) target_link_libraries(chiaki-lib jerasure) -target_link_libraries(chiaki-lib ${Opus_LIBRARIES}) +if(CHIAKI_LIB_ENABLE_OPUS) + target_link_libraries(chiaki-lib ${Opus_LIBRARIES}) +endif() \ No newline at end of file diff --git a/lib/src/audioreceiver.c b/lib/src/audioreceiver.c index a34898e..2a2535d 100644 --- a/lib/src/audioreceiver.c +++ b/lib/src/audioreceiver.c @@ -18,7 +18,9 @@ #include #include +#ifdef CHIAKI_LIB_ENABLE_OPUS #include +#endif #include @@ -47,7 +49,9 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_audio_receiver_init(ChiakiAudioReceiver *au CHIAKI_EXPORT void chiaki_audio_receiver_fini(ChiakiAudioReceiver *audio_receiver) { +#ifdef CHIAKI_LIB_ENABLE_OPUS opus_decoder_destroy(audio_receiver->opus_decoder); +#endif chiaki_mutex_fini(&audio_receiver->mutex); free(audio_receiver->pcm_buf); } @@ -65,6 +69,7 @@ CHIAKI_EXPORT void chiaki_audio_receiver_stream_info(ChiakiAudioReceiver *audio_ CHIAKI_LOGI(audio_receiver->log, " unknown = %d", audio_header->unknown); memcpy(&audio_receiver->audio_header, audio_header, sizeof(audio_receiver->audio_header)); +#ifdef CHIAKI_LIB_ENABLE_OPUS opus_decoder_destroy(audio_receiver->opus_decoder); int error; @@ -97,6 +102,9 @@ CHIAKI_EXPORT void chiaki_audio_receiver_stream_info(ChiakiAudioReceiver *audio_ if(audio_receiver->session->audio_settings_cb) audio_receiver->session->audio_settings_cb(audio_header->channels, audio_header->rate, audio_receiver->session->audio_cb_user); +#else + CHIAKI_LOGE(audio_receiver->log, "Opus disabled, not initializing decoder"); +#endif beach: chiaki_mutex_unlock(&audio_receiver->mutex); @@ -160,6 +168,7 @@ CHIAKI_EXPORT void chiaki_audio_receiver_av_packet(ChiakiAudioReceiver *audio_re static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, ChiakiSeqNum16 frame_index, uint8_t *buf, size_t buf_size) { +#ifdef CHIAKI_LIB_ENABLE_OPUS chiaki_mutex_lock(&audio_receiver->mutex); if(!audio_receiver->opus_decoder) @@ -181,4 +190,5 @@ static void chiaki_audio_receiver_frame(ChiakiAudioReceiver *audio_receiver, Chi beach: chiaki_mutex_unlock(&audio_receiver->mutex); +#endif } \ No newline at end of file diff --git a/lib/src/common.c b/lib/src/common.c index a188a98..5808039 100644 --- a/lib/src/common.c +++ b/lib/src/common.c @@ -72,7 +72,7 @@ CHIAKI_EXPORT void *chiaki_aligned_alloc(size_t alignment, size_t size) { #if defined(_WIN32) return _aligned_malloc(size, alignment); -#elif __APPLE__ +#elif __APPLE__ || __ANDROID__ void *r; if(posix_memalign(&r, alignment, size) == 0) return r; From 103df69d2939dcd982266c4c6650aff132c313fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 14 Sep 2019 16:21:59 +0200 Subject: [PATCH 045/470] Init Chiaki Lib on Android --- android/app/CMakeLists.txt | 5 ++- android/app/src/main/cpp/chiaki-jni.c | 44 ++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 9d755a8..375ea7d 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -2,4 +2,7 @@ cmake_minimum_required(VERSION 3.2) add_library(chiaki-jni SHARED src/main/cpp/chiaki-jni.c) -target_link_libraries(chiaki-jni chiaki-lib) \ No newline at end of file +target_link_libraries(chiaki-jni chiaki-lib) + +find_library(ANDROID_LIB_LOG log) +target_link_libraries(chiaki-jni "${ANDROID_LIB_LOG}") \ No newline at end of file diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c index ff10e06..2b0dc24 100644 --- a/android/app/src/main/cpp/chiaki-jni.c +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -1,9 +1,51 @@ #include +#include #include +#include -JNIEXPORT jstring JNICALL Java_com_metallic_chiaki_lib_Chiaki_stringFromJNI(JNIEnv* env, jobject thiz) +#define LOG_TAG "Chiaki" + +static void log_cb_android(ChiakiLogLevel level, const char *msg, void *user) +{ + int prio; + switch(level) + { + case CHIAKI_LOG_DEBUG: + prio = ANDROID_LOG_DEBUG; + break; + case CHIAKI_LOG_VERBOSE: + prio = ANDROID_LOG_VERBOSE; + break; + case CHIAKI_LOG_INFO: + prio = ANDROID_LOG_INFO; + break; + case CHIAKI_LOG_WARNING: + prio = ANDROID_LOG_ERROR; + break; + case CHIAKI_LOG_ERROR: + prio = ANDROID_LOG_ERROR; + break; + default: + prio = ANDROID_LOG_INFO; + break; + } + __android_log_print(prio, LOG_TAG, "%s", msg); +} + +ChiakiLog log_ctx; + +JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) +{ + chiaki_log_init(&log_ctx, CHIAKI_LOG_ALL & ~CHIAKI_LOG_VERBOSE, log_cb_android, NULL); + CHIAKI_LOGI(&log_ctx, "Loading Chiaki Library"); + ChiakiErrorCode err = chiaki_lib_init(); + CHIAKI_LOGI(&log_ctx, "Chiaki Library Init Result: %s\n", chiaki_error_string(err)); + return JNI_VERSION_1_2; +} + +JNIEXPORT jstring JNICALL Java_com_metallic_chiaki_lib_Chiaki_stringFromJNI(JNIEnv *env, jobject thiz) { return (*env)->NewStringUTF(env, chiaki_error_string(CHIAKI_ERR_SUCCESS)); } From cd50874705534a580878cfbd051990e7bdea3b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sat, 14 Sep 2019 18:30:03 +0200 Subject: [PATCH 046/470] Init Session in Android --- android/app/src/main/cpp/chiaki-jni.c | 77 ++++++++++++++++++- .../java/com/metallic/chiaki/MainActivity.kt | 13 +++- .../java/com/metallic/chiaki/lib/Chiaki.kt | 31 +++++++- 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c index 2b0dc24..6c95d7d 100644 --- a/android/app/src/main/cpp/chiaki-jni.c +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -4,6 +4,9 @@ #include #include +#include + +#include #define LOG_TAG "Chiaki" @@ -31,7 +34,7 @@ static void log_cb_android(ChiakiLogLevel level, const char *msg, void *user) prio = ANDROID_LOG_INFO; break; } - __android_log_print(prio, LOG_TAG, "%s", msg); + __android_log_write(prio, LOG_TAG, msg); } ChiakiLog log_ctx; @@ -45,7 +48,75 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) return JNI_VERSION_1_2; } -JNIEXPORT jstring JNICALL Java_com_metallic_chiaki_lib_Chiaki_stringFromJNI(JNIEnv *env, jobject thiz) +#define E (*env) + +JNIEXPORT jstring JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_errorCodeToString(JNIEnv *env, jobject obj, jint value) { - return (*env)->NewStringUTF(env, chiaki_error_string(CHIAKI_ERR_SUCCESS)); + return E->NewStringUTF(env, chiaki_error_string((ChiakiErrorCode)value)); } + +JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_createSession(JNIEnv *env, jobject obj, jobject result, jobject connect_info_obj) +{ + ChiakiSession *session = NULL; + ChiakiErrorCode err = CHIAKI_ERR_SUCCESS; + char *host_str = NULL; + + jclass result_class = E->GetObjectClass(env, result); + + jclass connect_info_class = E->GetObjectClass(env, connect_info_obj); + jstring host_string = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "host", "Ljava/lang/String;")); + jbyteArray regist_key_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "registKey", "[B")); + jbyteArray morning_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "morning", "[B")); + jobject connect_video_profile_obj = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "videoProfile", "Lcom/metallic/chiaki/lib/ConnectVideoProfile;")); + jclass connect_video_profile_class = E->GetObjectClass(env, connect_video_profile_obj); + + ChiakiConnectInfo connect_info; + const char *str_borrow = E->GetStringUTFChars(env, host_string, NULL); + connect_info.host = host_str = strdup(str_borrow); + E->ReleaseStringUTFChars(env, host_string, str_borrow); + if(!connect_info.host) + { + err = CHIAKI_ERR_MEMORY; + goto beach; + } + + if(E->GetArrayLength(env, regist_key_array) != sizeof(connect_info.regist_key)) + { + CHIAKI_LOGE(&log_ctx, "Regist Key passed from Java has invalid length"); + err = CHIAKI_ERR_INVALID_DATA; + goto beach; + } + jbyte *bytes = E->GetByteArrayElements(env, regist_key_array, NULL); + memcpy(connect_info.regist_key, bytes, sizeof(connect_info.regist_key)); + E->ReleaseByteArrayElements(env, regist_key_array, bytes, JNI_ABORT); + + if(E->GetArrayLength(env, morning_array) != sizeof(connect_info.morning)) + { + CHIAKI_LOGE(&log_ctx, "Morning passed from Java has invalid length"); + err = CHIAKI_ERR_INVALID_DATA; + goto beach; + } + bytes = E->GetByteArrayElements(env, morning_array, NULL); + memcpy(connect_info.morning, bytes, sizeof(connect_info.morning)); + E->ReleaseByteArrayElements(env, morning_array, bytes, JNI_ABORT); + + + connect_info.video_profile.width = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "width", "I")); + connect_info.video_profile.height = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "height", "I")); + connect_info.video_profile.max_fps = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "maxFPS", "I")); + connect_info.video_profile.bitrate = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "bitrate", "I")); + + session = CHIAKI_NEW(ChiakiSession); + if(!session) + { + err = CHIAKI_ERR_MEMORY; + goto beach; + } + + err = chiaki_session_init(session, &connect_info, &log_ctx); + +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); +} \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt index da93a70..e03e48a 100644 --- a/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt @@ -2,7 +2,10 @@ package com.metallic.chiaki import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import com.metallic.chiaki.lib.Chiaki +import com.metallic.chiaki.lib.ChiakiNative +import com.metallic.chiaki.lib.ConnectInfo +import com.metallic.chiaki.lib.ConnectVideoProfile +import com.metallic.chiaki.lib.ErrorCode import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() @@ -12,6 +15,12 @@ class MainActivity : AppCompatActivity() super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - testTextView.text = Chiaki.stringFromJNI() + val result = ChiakiNative.CreateSessionResult(0, 0) + ChiakiNative.createSession(result, ConnectInfo("host", + ByteArray(0x10) { 0 }, + ByteArray(0x10) { 0 }, + ConnectVideoProfile(0, 0, 0, 0))) + + testTextView.text = "${ErrorCode(0).toString()}\n${result}\n${ErrorCode(result.errorCode)}" } } 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 bb17e18..d01e663 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 @@ -1,14 +1,39 @@ package com.metallic.chiaki.lib -class Chiaki +data class ConnectVideoProfile( + val width: Int, + val height: Int, + val maxFPS: Int, + val bitrate: Int +) + +data class ConnectInfo( + val host: String, + val registKey: ByteArray, + val morning: ByteArray, + val videoProfile: ConnectVideoProfile +) + +class ChiakiNative { + data class CreateSessionResult(var errorCode: Int, var sessionPtr: Long) companion object { init { System.loadLibrary("chiaki-jni") } - - @JvmStatic external fun stringFromJNI(): String + @JvmStatic external fun errorCodeToString(value: Int): String + @JvmStatic external fun createSession(result: CreateSessionResult, connectInfo: ConnectInfo) } +} + +class ErrorCode(val value: Int) +{ + override fun toString() = ChiakiNative.errorCodeToString(value) +} + +class Session +{ + private val nativePtr: Long = 0 } \ No newline at end of file From 093cd6e4430f9e74e74475940094283466448b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 15 Sep 2019 11:53:04 +0200 Subject: [PATCH 047/470] Start Session on Android --- android/app/build.gradle | 5 ++ android/app/src/main/AndroidManifest.xml | 7 ++- android/app/src/main/cpp/chiaki-jni.c | 8 ++- .../java/com/metallic/chiaki/MainActivity.kt | 53 +++++++++++++++---- .../com/metallic/chiaki/StreamActivity.kt | 34 ++++++++++++ .../java/com/metallic/chiaki/lib/Chiaki.kt | 37 ++++++++++--- .../app/src/main/res/layout/activity_main.xml | 43 +++++++++++++-- .../src/main/res/layout/activity_stream.xml | 19 +++++++ cmake/OpenSSLExternalProject.cmake | 2 +- lib/src/session.c | 5 ++ 10 files changed, 190 insertions(+), 23 deletions(-) create mode 100644 android/app/src/main/java/com/metallic/chiaki/StreamActivity.kt create mode 100644 android/app/src/main/res/layout/activity_stream.xml diff --git a/android/app/build.gradle b/android/app/build.gradle index e58415d..c7a72e1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -39,6 +39,11 @@ android { } } +androidExtensions { + // for @Parcelize + experimental = true +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7a79c5c..01d8172 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + + - + + \ No newline at end of file diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c index 6c95d7d..64bf535 100644 --- a/android/app/src/main/cpp/chiaki-jni.c +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -55,7 +55,7 @@ JNIEXPORT jstring JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_errorCodeToS return E->NewStringUTF(env, chiaki_error_string((ChiakiErrorCode)value)); } -JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_createSession(JNIEnv *env, jobject obj, jobject result, jobject connect_info_obj) +JNIEXPORT void JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionCreate(JNIEnv *env, jobject obj, jobject result, jobject connect_info_obj) { ChiakiSession *session = NULL; ChiakiErrorCode err = CHIAKI_ERR_SUCCESS; @@ -119,4 +119,10 @@ 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); +} + +JNIEXPORT jint JNICALL Java_com_metallic_chiaki_lib_ChiakiNative_sessionStart(JNIEnv *env, jobject obj, jlong ptr) +{ + ChiakiSession *session = (ChiakiSession *)ptr; + return chiaki_session_start(session); } \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt index e03e48a..8423c27 100644 --- a/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/MainActivity.kt @@ -1,11 +1,13 @@ package com.metallic.chiaki +import android.content.Context +import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import com.metallic.chiaki.lib.ChiakiNative -import com.metallic.chiaki.lib.ConnectInfo -import com.metallic.chiaki.lib.ConnectVideoProfile -import com.metallic.chiaki.lib.ErrorCode +import android.util.Base64 +import androidx.core.content.edit +import androidx.core.widget.addTextChangedListener +import com.metallic.chiaki.lib.* import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() @@ -15,12 +17,43 @@ class MainActivity : AppCompatActivity() super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - val result = ChiakiNative.CreateSessionResult(0, 0) - ChiakiNative.createSession(result, ConnectInfo("host", - ByteArray(0x10) { 0 }, - ByteArray(0x10) { 0 }, - ConnectVideoProfile(0, 0, 0, 0))) + val prefs = getPreferences(Context.MODE_PRIVATE) - testTextView.text = "${ErrorCode(0).toString()}\n${result}\n${ErrorCode(result.errorCode)}" + hostEditText.setText(prefs.getString("debug_host", "")) + registKeyEditText.setText(prefs.getString("debug_regist_key", "")) + morningEditText.setText(prefs.getString("debug_morning", "")) + + hostEditText.addTextChangedListener { + prefs.edit { + putString("debug_host", it?.toString()) + } + } + + registKeyEditText.addTextChangedListener { + prefs.edit { + putString("debug_regist_key", it?.toString()) + } + } + + morningEditText.addTextChangedListener { + prefs.edit { + putString("debug_morning", it?.toString()) + } + } + + startButton.setOnClickListener { + val registKeyBase = registKeyEditText.text.toString().toByteArray(Charsets.US_ASCII) + val registKey = registKeyBase + ByteArray(0x10 - registKeyBase.size) { 0 } + val morning = Base64.decode(morningEditText.text.toString(), Base64.DEFAULT) + val connectInfo = ConnectInfo(hostEditText.text.toString(), + registKey, + morning, + ConnectVideoProfile(1280, 720, 60, 10000)) + + Intent(this, StreamActivity::class.java).let { + it.putExtra(StreamActivity.EXTRA_CONNECT_INFO, connectInfo) + startActivity(it) + } + } } } diff --git a/android/app/src/main/java/com/metallic/chiaki/StreamActivity.kt b/android/app/src/main/java/com/metallic/chiaki/StreamActivity.kt new file mode 100644 index 0000000..3ef5116 --- /dev/null +++ b/android/app/src/main/java/com/metallic/chiaki/StreamActivity.kt @@ -0,0 +1,34 @@ +package com.metallic.chiaki + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.metallic.chiaki.lib.ConnectInfo +import com.metallic.chiaki.lib.Session +import kotlinx.android.synthetic.main.activity_stream.* + +class StreamActivity : AppCompatActivity() +{ + companion object + { + const val EXTRA_CONNECT_INFO = "connect_info" + } + + private lateinit var session: Session + + override fun onCreate(savedInstanceState: Bundle?) + { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_stream) + + val connectInfo = intent.getParcelableExtra(EXTRA_CONNECT_INFO) + if(connectInfo == null) + { + finish() + return + } + + session = Session(connectInfo) + session.start() + testTextView.text = "" + } +} 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 d01e663..246df5e 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 @@ -1,22 +1,28 @@ package com.metallic.chiaki.lib +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import java.lang.Exception + +@Parcelize data class ConnectVideoProfile( val width: Int, val height: Int, val maxFPS: Int, val bitrate: Int -) +): Parcelable +@Parcelize data class ConnectInfo( val host: String, val registKey: ByteArray, val morning: ByteArray, val videoProfile: ConnectVideoProfile -) +): Parcelable class ChiakiNative { - data class CreateSessionResult(var errorCode: Int, var sessionPtr: Long) + data class SessionCreateResult(var errorCode: Int, var sessionPtr: Long) companion object { init @@ -24,16 +30,35 @@ class ChiakiNative System.loadLibrary("chiaki-jni") } @JvmStatic external fun errorCodeToString(value: Int): String - @JvmStatic external fun createSession(result: CreateSessionResult, connectInfo: ConnectInfo) + @JvmStatic external fun sessionCreate(result: SessionCreateResult, connectInfo: ConnectInfo) + @JvmStatic external fun sessionStart(ptr: Long): Int } } class ErrorCode(val value: Int) { override fun toString() = ChiakiNative.errorCodeToString(value) + var isSuccess = value == 0 } -class Session +class SessionCreateError(val errorCode: ErrorCode): Exception("Failed to create Session: $errorCode") + +class Session(connectInfo: ConnectInfo) { - private val nativePtr: Long = 0 + private val nativePtr: Long + + init + { + val result = ChiakiNative.SessionCreateResult(0, 0) + ChiakiNative.sessionCreate(result, connectInfo) + val errorCode = ErrorCode(result.errorCode) + if(!errorCode.isSuccess) + throw SessionCreateError(errorCode) + nativePtr = result.sessionPtr + } + + fun start() + { + ChiakiNative.sessionStart(nativePtr) + } } \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 145e02f..7e350d7 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -6,14 +6,49 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - + + + + + + +