From d80da7772dd52902847fd463177ae9a0730b435d Mon Sep 17 00:00:00 2001 From: Alexey Khoraskin Date: Wed, 3 Sep 2025 16:49:29 +0300 Subject: [PATCH] Standalone Previewer. Signed-off-by: Alexey Khoraskin Change-Id: I01fa9d23bc73fee8f76f2dead59e21b2ce49a327 --- BUILD.gn | 33 +- PreviewerCLI.cpp | 162 ++++++++++ README.md | 12 + RichPreviewer.cpp | 21 +- bundle.json | 5 +- cli/CommandLine.cpp | 57 +++- cli/CommandLine.h | 29 ++ cli/CommandLineFactory.cpp | 34 +- cli/CommandLineFactory.h | 1 + cli/CommandLineInterface.cpp | 20 +- cli/CommandLineInterface.h | 6 +- config/config.json | 16 + jsapp/BUILD.gn | 5 +- jsapp/JsApp.cpp | 2 + jsapp/JsApp.h | 1 + jsapp/rich/JsAppImpl.cpp | 132 ++++++-- jsapp/rich/JsAppImpl.h | 7 +- jsapp/rich/external/EventHandler.cpp | 2 +- jsapp/rich/external/EventHandler.h | 2 +- jsapp/rich/external/EventQueue.cpp | 2 +- jsapp/rich/external/EventQueue.h | 2 +- jsapp/rich/external/EventRunner.cpp | 2 +- jsapp/rich/external/EventRunner.h | 2 +- mock/BUILD.gn | 10 +- mock/VideoRecorder.cpp | 290 ++++++++++++++++++ mock/VideoRecorder.h | 87 ++++++ mock/lite/VirtualScreenImpl.h | 2 +- mock/rich/VirtualScreenImpl.cpp | 37 ++- mock/rich/VirtualScreenImpl.h | 5 + test/fuzztest/commandparse_fuzzer/BUILD.gn | 9 + .../RichCommandParseFuzzer.cpp | 8 +- test/fuzztest/jsonparse_fuzzer/BUILD.gn | 15 + test/fuzztest/paramsparse_fuzzer/BUILD.gn | 3 + test/mock/graphic/MockGlfwRenderContext.cpp | 9 +- test/mock/mock/MockEventAdapter.cpp | 47 +++ test/mock/mock/MockVirtualScreenImpl.cpp | 2 + test/mock/util/MockLocalSocket.cpp | 7 +- util/BUILD.gn | 4 + util/CommandParser.cpp | 284 ++++++++++++++--- util/CommandParser.h | 18 +- util/FileSystem.cpp | 175 ++++++++++- util/FileSystem.h | 8 +- util/JsonReader.cpp | 5 + util/JsonReader.h | 1 + util/LocalSocket.h | 5 + util/Unhap.cpp | 124 ++++++++ util/Unhap.h | 23 ++ util/unix/LocalSocket.cpp | 122 +++++++- util/windows/LocalSocket.cpp | 80 ++++- 49 files changed, 1792 insertions(+), 143 deletions(-) create mode 100644 PreviewerCLI.cpp create mode 100755 config/config.json create mode 100644 mock/VideoRecorder.cpp create mode 100644 mock/VideoRecorder.h create mode 100644 test/mock/mock/MockEventAdapter.cpp create mode 100755 util/Unhap.cpp create mode 100755 util/Unhap.h diff --git a/BUILD.gn b/BUILD.gn index 2f4497a..3edeb1b 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -95,6 +95,37 @@ previewer_executable("rich_previewer") { ] } +ohos_executable("previewer_cli") { + part_name = "previewer" + output_name = "PreviewerCLI" + sources = [ "PreviewerCLI.cpp" ] + include_dirs = [ "./util/" ] + include_dirs += [ "//arkcompiler/runtime_core/libpandabase" ] + include_dirs += os_include_dirs + libs = [] + deps = [ + "util:util_rich", + "//third_party/bounds_checking_function:libsec_static", + "//third_party/libwebsockets:websockets_static", + ] + defines = [] + if (platform == "mingw_x86_64") { + defines += [ "NOGDI" ] + ldflags = [ + "-static", + "-lws2_32", + "-lshlwapi", + "-ldbghelp", + ] + } + cflags = [ + "-std=c++17", + "-Wno-deprecated-declarations", + "-Wno-reorder", + "-Wno-sign-compare", + ] +} + previewer_executable("lite_previewer") { part_name = "litePreviewer" output_name = "Simulator" @@ -164,7 +195,7 @@ ohos_copy("copy_ide_library") { deps += [ "jsapp/rich/external:ide_extension" ] out_path = get_label_info("jsapp/rich/external:ide_extension", "root_out_dir") sources += [ out_path + "/ide/previewer/libide_extension" + suffix ] - + sources += [ rebase_path("config/config.json", root_out_dir) ] outputs = [ target_out_dir + "/previewer/common/bin/{{source_file_part}}" ] module_source_dir = target_out_dir + "/previewer/common/bin/" module_install_name = "" diff --git a/PreviewerCLI.cpp b/PreviewerCLI.cpp new file mode 100644 index 0000000..e63f818 --- /dev/null +++ b/PreviewerCLI.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "JsonReader.h" +#include "LocalSocket.h" + +#include "utils/pandargs.h" + +using namespace std; + +static bool ToDouble(const char *str, double *result) +{ + char *end; + *result = strtod(str, &end); + return end != str && *end == '\0'; +} + +static Json2::Value parseOneArg(const char* arg) +{ + double value; + if (ToDouble(arg, &value)) { + return JsonReader::CreateNumber(value); + } + return JsonReader::CreateString(arg); +} + +static Json2::Value parseArguments(const vector &args) +{ + if (args.empty()) { + return JsonReader::CreateNull(); + } + const string &firstArg = args[0]; + if (firstArg[0] == '-') { + size_t dashCount = firstArg.find_first_not_of('-'); + Json2::Value jsonObject = JsonReader::CreateObject(); + string key = args[0].substr(dashCount); + vector values; + bool ret = false; + for (size_t i = 1; i < args.size(); ++i) { + size_t currentDashCount = args[i].find_first_not_of('-'); + if (currentDashCount == dashCount) { + auto value = parseArguments(values); + ret = value.IsValid() ? false : true; + jsonObject.Add(key.c_str(), value); + values.clear(); + key = args[i].substr(dashCount); + } else if (currentDashCount < dashCount) { + values.push_back(args[i]); + } else { + ret = true; + } + if (ret) { + return Json2::Value(); + } + } + auto value = parseArguments(values); + if (!value.IsValid()) { + return Json2::Value(); + } + jsonObject.Add(key.c_str(), value); + return JsonReader::DepthCopy(jsonObject); + } else { + if (args.size() == 1) { + return parseOneArg(args[0].c_str()); + } else { + Json2::Value jsonArray = JsonReader::CreateArray(); + for (const auto &arg : args) { + jsonArray.Add(parseOneArg(arg.c_str())); + } + return JsonReader::DepthCopy(jsonArray); + } + } +} + +static void checkResponse(LocalSocket &localSocket, int &timeout) +{ + constexpr int defaultSleep = 10; + string response; + auto start = chrono::high_resolution_clock::now(); + while (chrono::duration_cast(chrono::high_resolution_clock::now() - start).count() < + timeout) { + localSocket >> response; + if (response.length() != 0) { + break; + } + this_thread::sleep_for(chrono::milliseconds(defaultSleep)); + } + + if (response.length()) { + Json2::Value responseJson = JsonReader::ParseJsonData2(response); + auto error = JsonReader::GetErrorPtr(); + if (error.length()) { + fprintf(stderr, "Json error while parsing response: %s\n", error.c_str()); + printf("Response: %s\n", response.c_str()); + } else { + printf("Response:\n%s\n", responseJson.ToStyledString().c_str()); + } + } else { + printf("No response provided.\n"); + } +} + +int main(int argc, char *argv[]) +{ + constexpr int defaultTimeout = 1000; + panda::PandArgParser argparser; + panda::PandArg helpArg("help", false, "Print this message and exit"); + panda::PandArg nameArg("name", "", "Local socket name for command line interface."); + panda::PandArg timeoutArg("timeout", defaultTimeout, "Waiting response timeout in milliseconds."); + argparser.Add(&helpArg); + argparser.Add(&nameArg); + argparser.EnableRemainder(); + if (!argparser.Parse(argc, argv) || helpArg.GetValue()) { + printf("Usage:\n%s", argparser.GetHelpString().c_str()); + return 0; + } + Json2::Value result = parseArguments(argparser.GetRemainder()); + if (!result.IsValid()) { + fprintf(stderr, "Invalid json args\n"); + return -1; + } + printf("Request:\n%s\n", result.ToStyledString().c_str()); + if (nameArg.GetValue().length() == 0) { + fprintf(stderr, "Use --name to set socket name.\n"); + return -1; + } + LocalSocket localSocket; + if (!localSocket.ConnectToServer(localSocket.GetCommandPipeName(nameArg.GetValue()), + LocalSocket::OpenMode::READ_WRITE)) { + fprintf(stderr, "Unable to connect to server socket \"%s\".\n", + localSocket.GetCommandPipeName(nameArg.GetValue()).c_str()); + return -1; + } + if (result.IsString()) { + localSocket << result.AsString(); + } else { + localSocket << result.ToString(); + } + int timeout = timeoutArg.GetValue(); + checkResponse(localSocket, timeout); + return 0; +} diff --git a/README.md b/README.md index c6b5368..cbb7aa2 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,18 @@ The Previewer is a component that empowers the DevEco Studio Previewer to levera ![](figures/Previewer-Component-Architecture.PNG "Previewer Component Architecture") To start with, the DevEco Studio Previewer launches the Previewer component through the command line and passes to it startup parameters such as the ArkTS build product path and preview specifications. When starting up, the Previewer component launches the ArkUI rendering engine, which then renders pages based on the startup parameters and delivers preview images for the DevEco Studio Previewer. When page information changes, the DevEco Studio Previewer sends page refresh commands through the named pipe to the Previewer component. Based on the received commands, the Previewer component calls the ArkUI processing APIs to refresh and render the pages and deliver the images. + +The Previewer can be started without the DevEco Studio and a project. Just with any hap file. + +For example on Linux PC: + +``` +./Previewer -d -gui -hap ~/MyApplication.hap +``` +You will see GUI Window with application content, and you can interacts with application by a mouse and a keyboard. + +The default parameters are into config.json file. You can change it if needs. + ## Directory Structure The source code of the Previewer component is stored in **/ide_previewer**. The following shows the directory structure. diff --git a/RichPreviewer.cpp b/RichPreviewer.cpp index 82cac14..caf8ab2 100644 --- a/RichPreviewer.cpp +++ b/RichPreviewer.cpp @@ -28,6 +28,7 @@ #include "SharedData.h" #include "TraceTool.h" #include "VirtualScreenImpl.h" +#include "FileSystem.h" static const int NOTIFY_INTERVAL_TIME = 1000; // Unit millisecond @@ -109,18 +110,24 @@ int main(int argc, char* argv[]) if (ret >= 0) { return ret; } + if (parser.IsStandaloneMode()) { + chdir(FileSystem::GetExecutablePath().c_str()); + } InitSharedData(); if (parser.IsSet("s")) { - CommandLineInterface::GetInstance().Init(parser.Value("s")); + CommandLineInterface::GetInstance().Init(parser.Value("s"), parser.IsStandaloneMode()); } - - TraceTool::GetInstance().HandleTrace("Enter the main function"); - - std::thread commandThead(ProcessCommand); - commandThead.detach(); + if (!parser.IsStandaloneMode()) { + TraceTool::GetInstance().HandleTrace("Enter the main function"); + } + std::thread commandThread(ProcessCommand); VirtualScreenImpl::GetInstance().InitResolution(); - ApplyConfig(); + if (!parser.IsStandaloneMode()) { + ApplyConfig(); + } JsAppImpl::GetInstance().InitJsApp(); + Interrupter::Interrupt(); + commandThread.join(); std::this_thread::sleep_for(std::chrono::milliseconds(500)); // sleep 500 ms return 0; } diff --git a/bundle.json b/bundle.json index 5bde209..0d0e238 100644 --- a/bundle.json +++ b/bundle.json @@ -33,7 +33,9 @@ "bounds_checking_function", "libjpeg-turbo", "libwebsockets", - "cJSON" + "cJSON", + "glfw", + "ffmpeg" ] }, "build": { @@ -47,6 +49,7 @@ "//ide/tools/previewer/util:util_lite", "//ide/tools/previewer/util:util_rich", "//ide/tools/previewer:rich_previewer", + "//ide/tools/previewer:previewer_cli", "//ide/tools/previewer:lite_previewer", "//ide/tools/previewer/jsapp/rich/external:ide_extension" ], diff --git a/cli/CommandLine.cpp b/cli/CommandLine.cpp index 7f81325..209bea9 100644 --- a/cli/CommandLine.cpp +++ b/cli/CommandLine.cpp @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include "CommandLineInterface.h" #include "CommandParser.h" @@ -35,6 +38,7 @@ #include "SharedData.h" #include "VirtualMessageImpl.h" #include "VirtualScreenImpl.h" +#include "VideoRecorder.h" CommandLine::CommandLine(CommandType commandType, const Json2::Value& arg, const LocalSocket& socket) : args(arg), cliSocket(socket), type(commandType), commandName("") @@ -512,6 +516,7 @@ bool OrientationCommand::IsSetArgValid() const void OrientationCommand::RunSet() { + VideoRecorder::GetInstance().Stop(); std::string commandOrientation = args["Orientation"].AsString(); std::string currentOrientation = JsAppImpl::GetInstance().GetOrientation(); if (commandOrientation != currentOrientation) { @@ -1599,4 +1604,54 @@ void AvoidAreaChangedCommand::RunGet() { SetResultToManager("args", args, "AvoidAreaChanged"); ILOG("Get AvoidAreaChangedCommand run finished."); -} \ No newline at end of file +} + +static std::string GetTimeString() +{ + auto now = std::chrono::system_clock::now(); + auto cNow = std::chrono::system_clock::to_time_t(now); + tm* tmNow = localtime(&cNow); + + std::stringstream timeString; + timeString << std::put_time(tmNow, "%Y-%m-%d_%H-%M-%S"); + return timeString.str(); +} + +ScreenShotCommand::ScreenShotCommand(CommandType commandType, const Json2::Value& arg, const LocalSocket& socket) + : CommandLine(commandType, arg, socket) +{ +} + +void ScreenShotCommand::RunAction() +{ + VirtualScreenImpl::GetInstance().MakeScreenShot("screen_" + GetTimeString()); + SetCommandResult("result", JsonReader::CreateBool(true)); +} + +StartVideoRecordCommand::StartVideoRecordCommand(CommandType commandType, const Json2::Value& arg, + const LocalSocket& socket) : CommandLine(commandType, arg, socket) +{ +} + +void StartVideoRecordCommand::RunAction() +{ + int32_t width = VirtualScreenImpl::GetInstance().GetCurrentWidth(); + int32_t height = VirtualScreenImpl::GetInstance().GetCurrentHeight(); +#ifdef __APPLE__ + JsAppImpl::GetInstance().GetFramebufferSize(width, height); +#endif + VideoRecorder::GetInstance().Start("video_" + GetTimeString(), width, height, videoRecordingFps); + SetCommandResult("result", JsonReader::CreateBool(true)); +} + +StopVideoRecordCommand::StopVideoRecordCommand(CommandType commandType, const Json2::Value& arg, + const LocalSocket& socket) : CommandLine(commandType, arg, socket) +{ +} + +void StopVideoRecordCommand::RunAction() +{ + VideoRecorder::GetInstance().Stop(); + SetCommandResult("result", JsonReader::CreateBool(true)); +} + diff --git a/cli/CommandLine.h b/cli/CommandLine.h index 0db2ef2..e8236cf 100644 --- a/cli/CommandLine.h +++ b/cli/CommandLine.h @@ -63,6 +63,7 @@ protected: const int minActionVal = 0; const int maxLoadDocWidth = 3000; const int minLoadDocWidth = 20; + const int videoRecordingFps = 30; virtual bool IsSetArgValid() const { @@ -522,4 +523,32 @@ public: protected: void RunGet() override; }; + +class ScreenShotCommand : public CommandLine { +public: + ScreenShotCommand(CommandType commandType, const Json2::Value& arg, const LocalSocket& socket); + ~ScreenShotCommand() override {} + +protected: + void RunAction() override; +}; + +class StartVideoRecordCommand : public CommandLine { +public: + StartVideoRecordCommand(CommandType commandType, const Json2::Value& arg, const LocalSocket& socket); + ~StartVideoRecordCommand() override {} + +protected: + void RunAction() override; +}; + +class StopVideoRecordCommand : public CommandLine { +public: + StopVideoRecordCommand(CommandType commandType, const Json2::Value& arg, const LocalSocket& socket); + ~StopVideoRecordCommand() override {} + +protected: + void RunAction() override; +}; + #endif // COMMANDLINE_H diff --git a/cli/CommandLineFactory.cpp b/cli/CommandLineFactory.cpp index 706a17c..f9358c9 100644 --- a/cli/CommandLineFactory.cpp +++ b/cli/CommandLineFactory.cpp @@ -24,6 +24,23 @@ CommandLineFactory::CommandTypeMap CommandLineFactory::typeMap = CommandLineFactory::CommandTypeMap(); CommandLineFactory::CommandLineFactory() {} +void CommandLineFactory::InitCommandMapLite() +{ + typeMap["Power"] = &CommandLineFactory::CreateObject; + typeMap["Volume"] = &CommandLineFactory::CreateObject; + typeMap["Barometer"] = &CommandLineFactory::CreateObject; + typeMap["Location"] = &CommandLineFactory::CreateObject; + typeMap["KeepScreenOnState"] = &CommandLineFactory::CreateObject; + typeMap["WearingState"] = &CommandLineFactory::CreateObject; + typeMap["BrightnessMode"] = &CommandLineFactory::CreateObject; + typeMap["ChargeMode"] = &CommandLineFactory::CreateObject; + typeMap["Brightness"] = &CommandLineFactory::CreateObject; + typeMap["HeartRate"] = &CommandLineFactory::CreateObject; + typeMap["StepCount"] = &CommandLineFactory::CreateObject; + typeMap["DistributedCommunications"] = &CommandLineFactory::CreateObject; + typeMap["CrownRotate"] = &CommandLineFactory::CreateObject; +} + void CommandLineFactory::InitCommandMap() { CommandParser& cmdParser = CommandParser::GetInstance(); @@ -48,20 +65,11 @@ void CommandLineFactory::InitCommandMap() typeMap["FoldStatus"] = &CommandLineFactory::CreateObject; typeMap["AvoidArea"] = &CommandLineFactory::CreateObject; typeMap["AvoidAreaChanged"] = &CommandLineFactory::CreateObject; + typeMap["ScreenShot"] = &CommandLineFactory::CreateObject; + typeMap["StartVideoRecord"] = &CommandLineFactory::CreateObject; + typeMap["StopVideoRecord"] = &CommandLineFactory::CreateObject; } else { - typeMap["Power"] = &CommandLineFactory::CreateObject; - typeMap["Volume"] = &CommandLineFactory::CreateObject; - typeMap["Barometer"] = &CommandLineFactory::CreateObject; - typeMap["Location"] = &CommandLineFactory::CreateObject; - typeMap["KeepScreenOnState"] = &CommandLineFactory::CreateObject; - typeMap["WearingState"] = &CommandLineFactory::CreateObject; - typeMap["BrightnessMode"] = &CommandLineFactory::CreateObject; - typeMap["ChargeMode"] = &CommandLineFactory::CreateObject; - typeMap["Brightness"] = &CommandLineFactory::CreateObject; - typeMap["HeartRate"] = &CommandLineFactory::CreateObject; - typeMap["StepCount"] = &CommandLineFactory::CreateObject; - typeMap["DistributedCommunications"] = &CommandLineFactory::CreateObject; - typeMap["CrownRotate"] = &CommandLineFactory::CreateObject; + InitCommandMapLite(); } typeMap["MousePress"] = &CommandLineFactory::CreateObject; typeMap["MouseRelease"] = &CommandLineFactory::CreateObject; diff --git a/cli/CommandLineFactory.h b/cli/CommandLineFactory.h index 6daeaec..887bcd7 100644 --- a/cli/CommandLineFactory.h +++ b/cli/CommandLineFactory.h @@ -38,6 +38,7 @@ private: std::string, std::unique_ptr (*)(CommandLine::CommandType, const Json2::Value&, const LocalSocket& socket)>; static CommandTypeMap typeMap; + static void InitCommandMapLite(); }; #endif // COMMANDLINEFACTORY_H diff --git a/cli/CommandLineInterface.cpp b/cli/CommandLineInterface.cpp index efeb229..e6917cf 100644 --- a/cli/CommandLineInterface.cpp +++ b/cli/CommandLineInterface.cpp @@ -33,7 +33,7 @@ CommandLineInterface::CommandLineInterface() : socket(nullptr) {} CommandLineInterface::~CommandLineInterface() {} -void CommandLineInterface::InitPipe(const std::string name) +void CommandLineInterface::InitPipe(const std::string name, bool isServer) { if (socket != nullptr) { socket.reset(); @@ -45,8 +45,14 @@ void CommandLineInterface::InitPipe(const std::string name) FLOG("CommandLineInterface::Connect socket memory allocation failed!"); } - if (!socket->ConnectToServer(socket->GetCommandPipeName(name), LocalSocket::READ_WRITE)) { - FLOG("CommandLineInterface command pipe connect failed"); + if (isServer) { + if (!socket->RunServer(socket->GetCommandPipeName(name))) { + FLOG("CommandLineInterface command pipe connect failed"); + } + } else { + if (!socket->ConnectToServer(socket->GetCommandPipeName(name), LocalSocket::OpenMode::READ_WRITE)) { + FLOG("CommandLineInterface command pipe connect failed"); + } } isPipeConnected = true; } @@ -236,10 +242,10 @@ void CommandLineInterface::ApplyConfigCommands(const std::string& key, } } -void CommandLineInterface::Init(std::string pipeBaseName) +void CommandLineInterface::Init(std::string pipeBaseName, bool isServer) { CommandLineFactory::InitCommandMap(); - InitPipe(pipeBaseName); + InitPipe(pipeBaseName, isServer); } void CommandLineInterface::ReadAndApplyConfig(std::string path) const @@ -256,6 +262,10 @@ void CommandLineInterface::CreatCommandToSendData(const std::string commandName, const Json2::Value& jsonData, const std::string type) const { + if (CommandParser::GetInstance().IsStandaloneMode()) { + ILOG("CommandLineInterface::CreatCommandToSendData : no need to create command in standalone mode"); + return; + } CommandLine::CommandType commandType = GetCommandType(type); std::unique_ptr commandLine = CommandLineFactory::CreateCommandLine(commandName, commandType, jsonData, *socket); diff --git a/cli/CommandLineInterface.h b/cli/CommandLineInterface.h index ef817b5..7fd3234 100644 --- a/cli/CommandLineInterface.h +++ b/cli/CommandLineInterface.h @@ -26,7 +26,7 @@ class CommandLineInterface { public: CommandLineInterface(const CommandLineInterface&) = delete; CommandLineInterface& operator=(const CommandLineInterface&) = delete; - void InitPipe(const std::string name); + void InitPipe(const std::string name, bool isServer); static CommandLineInterface& GetInstance(); static void SendJsonData(const Json2::Value&); void SendJSHeapMemory(size_t total, size_t alloc, size_t peak) const; @@ -36,7 +36,7 @@ public: void ApplyConfig(const Json2::Value& val) const; void ApplyConfigMembers(const Json2::Value& commands, const Json2::Value::Members& members) const; void ApplyConfigCommands(const std::string& key, const std::unique_ptr& command) const; - void Init(std::string pipeBaseName); + void Init(std::string pipeBaseName, bool isServer = false); void ReadAndApplyConfig(std::string path) const; void CreatCommandToSendData(const std::string, const Json2::Value&, const std::string) const; @@ -46,7 +46,7 @@ private: explicit CommandLineInterface(); virtual ~CommandLineInterface(); bool ProcessCommandValidate(bool parsingSuccessful, const Json2::Value& jsonData, const std::string& errors) const; - CommandLine::CommandType GetCommandType(std::string name) const; + CommandLine::CommandType GetCommandType(std::string) const; std::unique_ptr socket; const static uint32_t MAX_COMMAND_LENGTH = 128; static bool isFirstWsSend; diff --git a/config/config.json b/config/config.json new file mode 100755 index 0000000..2dc51a7 --- /dev/null +++ b/config/config.json @@ -0,0 +1,16 @@ +{ + "s": "previewer", + "refresh": "region", + "cpm": "false", + "device": "phone", + "shape": "rect", + "sd": 160, + "or": [432, 936], + "cr": [432, 936], + "n": "entry", + "av": "ACE_2_0", + "pm": "Stage", + "l": "en_US", + "cm": "light", + "o": "portrait" +} diff --git a/jsapp/BUILD.gn b/jsapp/BUILD.gn index dcd728e..8670e81 100644 --- a/jsapp/BUILD.gn +++ b/jsapp/BUILD.gn @@ -59,12 +59,11 @@ ohos_source_set("jsapp_rich") { external_deps = [ "ace_engine:libace_compatible", "graphic_2d:libglfw_render_context", + "ace_engine:libevent_adapter", "graphic_2d:librender_service_client", "window_manager:previewer_window", ] - if (platform != "linux_x64") { - external_deps += [ "ability_runtime:ability_simulator" ] - } + external_deps += [ "ability_runtime:ability_simulator" ] part_name = "previewer" subsystem_name = "ide" } diff --git a/jsapp/JsApp.cpp b/jsapp/JsApp.cpp index f7e5f54..0a7d2d3 100644 --- a/jsapp/JsApp.cpp +++ b/jsapp/JsApp.cpp @@ -173,6 +173,8 @@ void JsApp::OrientationChanged(std::string commandOrientation) void JsApp::ResolutionChanged(ResolutionParam&, int32_t, std::string) {} +void JsApp::GetFramebufferSize(int32_t &width, int32_t &height) {} + void JsApp::ReloadRuntimePage(const std::string) {} bool JsApp::MemoryRefresh(const std::string) const diff --git a/jsapp/JsApp.h b/jsapp/JsApp.h index 9d9b504..93a2371 100644 --- a/jsapp/JsApp.h +++ b/jsapp/JsApp.h @@ -121,6 +121,7 @@ public: virtual std::string GetDefaultJSONTree(); virtual void OrientationChanged(std::string commandOrientation); virtual void ResolutionChanged(ResolutionParam&, int32_t, std::string); + virtual void GetFramebufferSize(int32_t &width, int32_t &height); virtual void SetArgsColorMode(const std::string& value); virtual void SetArgsAceVersion(const std::string& value); virtual std::string GetOrientation() const; diff --git a/jsapp/rich/JsAppImpl.cpp b/jsapp/rich/JsAppImpl.cpp index 4c84cbe..29d8914 100644 --- a/jsapp/rich/JsAppImpl.cpp +++ b/jsapp/rich/JsAppImpl.cpp @@ -32,10 +32,11 @@ #include "window_model.h" #include "window_display.h" #include "ace_preview_helper.h" +#include "samples/event_adapter.h" #include "ClipboardHelper.h" #include "CommandLineInterface.h" #include "ui_content_impl.h" -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) #include "options.h" #include "simulator.h" #endif @@ -65,7 +66,7 @@ OHOS::sptr listener = nullptr; JsAppImpl::JsAppImpl() noexcept : ability(nullptr), isStop(false) { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) windowModel = std::make_shared(); #endif } @@ -81,15 +82,19 @@ JsAppImpl& JsAppImpl::GetInstance() void JsAppImpl::Start() { VirtualScreenImpl::GetInstance().InitVirtualScreen(); - VirtualScreenImpl::GetInstance().InitAll(pipeName, pipePort); + bool standaloneMode = CommandParser::GetInstance().IsStandaloneMode(); + if (!standaloneMode) { + VirtualScreenImpl::GetInstance().InitAll(pipeName, pipePort); + } isFinished = false; ILOG("Start run js app"); - OHOS::AppExecFwk::EventHandler::SetMainThreadId(std::this_thread::get_id()); + Ide::EventHandler::SetMainThreadId(std::this_thread::get_id()); RunJsApp(); ILOG("Js app run finished"); - while (!isStop) { + while (!isStop && + !(isGUI ? glfwRenderContext->WindowShouldClose() : false)) { // Execute all tasks in the main thread - OHOS::AppExecFwk::EventHandler::Run(); + Ide::EventHandler::Run(); glfwRenderContext->PollEvents(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -139,7 +144,7 @@ void JsAppImpl::OrientationChanged(std::string commandOrientation) ILOG("OrientationChanged: %s %d %d %f", orientation.c_str(), aceRunArgs.deviceWidth, aceRunArgs.deviceHeight, aceRunArgs.deviceConfig.density); if (ability != nullptr) { - OHOS::AppExecFwk::EventHandler::PostTask([this]() { + Ide::EventHandler::PostTask([this]() { glfwRenderContext->SetWindowSize(width, height); }); ability->SurfaceChanged(aceRunArgs.deviceConfig.orientation, aceRunArgs.deviceConfig.density, @@ -215,11 +220,29 @@ void JsAppImpl::RunJsApp() OHOS::Previewer::PreviewerDisplay::GetInstance().SetFoldable(screenInfo.foldable); OHOS::Previewer::PreviewerDisplay::GetInstance().SetFoldStatus(ConvertFoldStatus(screenInfo.foldStatus)); InitGlfwEnv(); - Platform::AcePreviewHelper::GetInstance()->SetCallbackOfPostTask(AppExecFwk::EventHandler::PostTask); + Platform::AcePreviewHelper::GetInstance()->SetCallbackOfPostTask(Ide::EventHandler::PostTask); Platform::AcePreviewHelper::GetInstance()-> - SetCallbackOfIsCurrentRunnerThread(AppExecFwk::EventHandler::IsCurrentRunnerThread); - Platform::AcePreviewHelper::GetInstance()->SetCallbackOfSetClipboardData(ClipboardHelper::SetClipboardData); - Platform::AcePreviewHelper::GetInstance()->SetCallbackOfGetClipboardData(ClipboardHelper::GetClipboardData); + SetCallbackOfIsCurrentRunnerThread(Ide::EventHandler::IsCurrentRunnerThread); + + if (CommandParser::GetInstance().IsStandaloneMode()) { + Platform::AcePreviewHelper::GetInstance()->SetCallbackOfSetClipboardData( + [this](const std::string &data) { + if (!data.empty() && glfwRenderContext) { + glfwRenderContext->SetClipboardData(data); + } + }); + Platform::AcePreviewHelper::GetInstance()->SetCallbackOfGetClipboardData( + [this]() { + if (glfwRenderContext) { + return glfwRenderContext->GetClipboardData(); + } + return std::string(""); + }); + } else { + Platform::AcePreviewHelper::GetInstance()->SetCallbackOfSetClipboardData(ClipboardHelper::SetClipboardData); + Platform::AcePreviewHelper::GetInstance()->SetCallbackOfGetClipboardData(ClipboardHelper::GetClipboardData); + } + listener = new(std::nothrow) PreviewerListener(); if (!listener) { ELOG("Memory allocation failed: listener."); @@ -251,7 +274,9 @@ void JsAppImpl::RunNormalAbility() if (ability != nullptr) { ability.reset(); } - TraceTool::GetInstance().HandleTrace("Launch Js App"); + if (!CommandParser::GetInstance().IsStandaloneMode()) { + TraceTool::GetInstance().HandleTrace("Launch Js App"); + } ability = Platform::AceAbility::CreateInstance(aceRunArgs); if (ability == nullptr) { ELOG("JsApp::Run ability create failed."); @@ -269,7 +294,7 @@ void JsAppImpl::RunNormalAbility() ability->InitEnv(); } -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) void JsAppImpl::RunDebugAbility() { // init window params @@ -387,7 +412,7 @@ void JsAppImpl::SetSimulatorCommonParams(OHOS::AbilityRuntime::Options& options) options.previewPath = fPath.substr(0, pos) + ".idea" + FileSystem::GetSeparator() + "previewer"; ILOG("previewPath info:%s", options.previewPath.c_str()); } - options.postTask = AppExecFwk::EventHandler::PostTask; + options.postTask = Ide::EventHandler::PostTask; } std::shared_ptr JsAppImpl::UpdateConfiguration(OHOS::Ace::Platform::AceRunArgs& args) @@ -624,7 +649,7 @@ void JsAppImpl::ResolutionChanged(ResolutionParam& param, int32_t screenDensity, SetResolutionParams(param.orignalWidth, param.orignalHeight, param.compressionWidth, param.compressionHeight, screenDensity); if (isDebug && debugServerPort >= 0) { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) SetWindowParams(); OHOS::Ace::ViewportConfig config; config.SetSize(windowModel->originWidth, windowModel->originHeight); @@ -638,7 +663,7 @@ void JsAppImpl::ResolutionChanged(ResolutionParam& param, int32_t screenDensity, return; } InitAvoidAreas(window); - OHOS::AppExecFwk::EventHandler::PostTask([this]() { + Ide::EventHandler::PostTask([this]() { glfwRenderContext->SetWindowSize(aceRunArgs.deviceWidth, aceRunArgs.deviceHeight); }); simulator->UpdateConfiguration(*(UpdateConfiguration(aceRunArgs).get())); @@ -647,7 +672,7 @@ void JsAppImpl::ResolutionChanged(ResolutionParam& param, int32_t screenDensity, } else { if (ability != nullptr) { InitAvoidAreas(ability->GetWindow()); - OHOS::AppExecFwk::EventHandler::PostTask([this]() { + Ide::EventHandler::PostTask([this]() { glfwRenderContext->SetWindowSize(aceRunArgs.deviceWidth, aceRunArgs.deviceHeight); }); ability->SurfaceChanged(aceRunArgs.deviceConfig.orientation, aceRunArgs.deviceConfig.density, @@ -656,6 +681,11 @@ void JsAppImpl::ResolutionChanged(ResolutionParam& param, int32_t screenDensity, } } +void JsAppImpl::GetFramebufferSize(int32_t &destWidth, int32_t &destHeight) +{ + GlfwRenderContext::GetGlobal()->GetFrameBufferSize(destWidth, destHeight); +} + WindowSizeChangeReason JsAppImpl::ConvertResizeReason(std::string reason) { if (reason == "undefined") { @@ -835,11 +865,27 @@ void JsAppImpl::LoadDocument(const std::string filePath, ((params.colorMode == ColorMode::DARK) ? "dark" : "light"), ((params.orientation == DeviceOrientation::LANDSCAPE) ? "landscape" : "portrait"), GetDeviceTypeName(params.deviceType).c_str()); - OHOS::AppExecFwk::EventHandler::PostTask([this]() { + Ide::EventHandler::PostTask([this]() { glfwRenderContext->SetWindowSize(aceRunArgs.deviceWidth, aceRunArgs.deviceHeight); }); if (ability != nullptr) { + OHOS::Ace::Platform::SystemParams params; + SetSystemParams(params, previewContext); + ILOG("LoadDocument params is density: %f region: %s language: %s deviceWidth: %d\ + deviceHeight: %d isRound:%d colorMode:%s orientation: %s deviceType: %s", + params.density, + params.region.c_str(), + params.language.c_str(), + params.deviceWidth, + params.deviceHeight, + (params.isRound ? "true" : "false"), + ((params.colorMode == ColorMode::DARK) ? "dark" : "light"), + ((params.orientation == DeviceOrientation::LANDSCAPE) ? "landscape" : "portrait"), + GetDeviceTypeName(params.deviceType).c_str()); + Ide::EventHandler::PostTask([this]() { + glfwRenderContext->SetWindowSize(aceRunArgs.deviceWidth, aceRunArgs.deviceHeight); + }); ability->LoadDocument(filePath, componentName, params); } else { auto uiContent = GetWindow()->GetUIContent(); @@ -854,7 +900,7 @@ void JsAppImpl::DispatchBackPressedEvent() const void JsAppImpl::DispatchKeyEvent(const std::shared_ptr& keyEvent) const { if (isDebug && debugServerPort >= 0) { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) OHOS::Rosen::Window* window = OHOS::Previewer::PreviewerWindow::GetInstance().GetWindowObject(); if (!window) { ELOG("JsApp::Run get window failed."); @@ -869,7 +915,7 @@ void JsAppImpl::DispatchKeyEvent(const std::shared_ptr& key void JsAppImpl::DispatchPointerEvent(const std::shared_ptr& pointerEvent) const { if (isDebug && debugServerPort >= 0) { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) OHOS::Rosen::Window* window = OHOS::Previewer::PreviewerWindow::GetInstance().GetWindowObject(); if (!window) { ELOG("JsApp::Run get window failed."); @@ -916,7 +962,22 @@ void JsAppImpl::InitGlfwEnv() ELOG("Could not create window: InitGlfwEnv failed."); return; } - glfwRenderContext->CreateGlfwWindow(aceRunArgs.deviceWidth, aceRunArgs.deviceHeight, false); + + ILOG("CreateGlfwWindow WINDOW SIZE %d x %d", aceRunArgs.deviceWidth, aceRunArgs.deviceHeight); + glfwRenderContext->CreateGlfwWindow(aceRunArgs.deviceWidth, aceRunArgs.deviceHeight, isGUI); + if (isGUI) { + auto&& keyEventCallback = [](const std::shared_ptr& keyEvent) { + ILOG("JsAppImpl GLFWwindow KeyEventCallback"); + JsAppImpl::GetInstance().DispatchKeyEvent(keyEvent); + }; + auto&& mouseEventCallback = [](const std::shared_ptr& pointerEvent) { + ILOG("JsAppImpl GLFWwindow MouseEventCallback"); + JsAppImpl::GetInstance().DispatchPointerEvent(pointerEvent); + }; + OHOS::Ace::Sample::EventAdapter::GetInstance().Initialize(glfwRenderContext); + OHOS::Ace::Sample::EventAdapter::GetInstance().RegisterKeyEventCallback(keyEventCallback); + OHOS::Ace::Sample::EventAdapter::GetInstance().RegisterPointerEventCallback(mouseEventCallback); + } ILOG("InitGlfwEnv finished"); } @@ -924,7 +985,7 @@ void JsAppImpl::SetMockJsonInfo() { std::string filePath = commandInfo.appResourcePath + FileSystem::GetSeparator() + "mock-config.json"; if (isDebug && debugServerPort >= 0) { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) simulator->SetMockList(Ide::StageContext::GetInstance().ParseMockJsonFile(filePath)); #endif } else { @@ -1029,7 +1090,7 @@ void JsAppImpl::UpdateAvoidArea2Ide(const std::string& key, const OHOS::Rosen::R OHOS::Rosen::Window* JsAppImpl::GetWindow() const { if (isDebug && debugServerPort >= 0) { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) return OHOS::Previewer::PreviewerWindow::GetInstance().GetWindowObject(); #else return nullptr; @@ -1047,11 +1108,9 @@ void JsAppImpl::InitAvoidAreas(OHOS::Rosen::Window* window) window->GetSystemBarPropertyByType(OHOS::Rosen::WindowType::WINDOW_TYPE_NAVIGATION_INDICATOR)); } -void JsAppImpl::InitJsApp() +void JsAppImpl::InitParams() { CommandParser& parser = CommandParser::GetInstance(); - InitCommandInfo(); - SetJsAppPath(parser.Value("j")); if (parser.IsSet("s")) { SetPipeName(parser.Value("s")); } @@ -1073,6 +1132,25 @@ void JsAppImpl::InitJsApp() if (parser.IsSet("cc")) { SetConfigChanges(parser.Value("cc")); } + if (parser.IsSet("gui")) { + isGUI = true; + } +} + +void JsAppImpl::InitJsApp() +{ + CommandParser& parser = CommandParser::GetInstance(); + InitCommandInfo(); + if (parser.IsSet("hap")) { + SetJsAppPath(FileSystem::NormalizePath(parser.GetHapDir() + "/ets")); + } else { + SetJsAppPath(parser.Value("j")); + } + InitParams(); + if (parser.IsStandaloneMode() && parser.IsResolutionValid()) { + aceRunArgs.deviceWidth = parser.GetOrignalResolutionWidth(); + aceRunArgs.deviceHeight = parser.GetOrignalResolutionHeight(); + } if (commandInfo.compressionResolutionWidth <= commandInfo.compressionResolutionHeight) { SetDeviceOrentation("portrait"); } else { @@ -1116,7 +1194,7 @@ void JsAppImpl::StopAbility() } } if (isDebug && debugServerPort >= 0) { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) if (simulator) { simulator->TerminateAbility(debugAbilityId); } diff --git a/jsapp/rich/JsAppImpl.h b/jsapp/rich/JsAppImpl.h index 5b91a22..4dcb545 100644 --- a/jsapp/rich/JsAppImpl.h +++ b/jsapp/rich/JsAppImpl.h @@ -39,7 +39,7 @@ namespace Rosen { struct SystemBarProperty; struct Rect; } -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) namespace AbilityRuntime { class Simulator; struct Options; @@ -66,6 +66,7 @@ public: std::string GetDefaultJSONTree() override; void OrientationChanged(std::string commandOrientation) override; void ResolutionChanged(ResolutionParam& param, int32_t screenDensity, std::string reason) override; + void GetFramebufferSize(int32_t &destWidth, int32_t &destHeight) override; OHOS::Ace::WindowSizeChangeReason ConvertResizeReason(std::string reason); void SetResolutionParams(int32_t changedOriginWidth, int32_t changedOriginHeight, int32_t changedWidth, int32_t changedHeight, int32_t screenDensity); @@ -116,6 +117,7 @@ protected: double twoInOneScreenDensity = 240; // Car Screen Density private: + void InitParams(); void SetPkgContextInfo(); void SetMockJsonInfo(); void SetAssetPath(OHOS::Ace::Platform::AceRunArgs& args, const std::string) const; @@ -158,13 +160,14 @@ private: int32_t height = 0; int32_t orignalWidth = 0; int32_t orignalHeight = 0; + bool isGUI = false; AvoidAreas avoidInitialAreas; OHOS::Ace::Platform::AceRunArgs aceRunArgs; std::shared_ptr glfwRenderContext; #ifdef COMPONENT_TEST_ENABLED std::string componentTestModeConfig; #endif // COMPONENT_TEST_ENABLED -#if defined(__APPLE__) || defined(_WIN32) +#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__) std::shared_ptr simulator; int64_t debugAbilityId = -1; void SetSimulatorParams(OHOS::AbilityRuntime::Options& options); diff --git a/jsapp/rich/external/EventHandler.cpp b/jsapp/rich/external/EventHandler.cpp index 55e8ecc..f66efb9 100644 --- a/jsapp/rich/external/EventHandler.cpp +++ b/jsapp/rich/external/EventHandler.cpp @@ -15,7 +15,7 @@ #include "EventHandler.h" -namespace OHOS::AppExecFwk { +namespace OHOS::Ide { void EventHandler::SetMainThreadId(std::thread::id id) { EventRunner::Current().SetMainThreadId(id); diff --git a/jsapp/rich/external/EventHandler.h b/jsapp/rich/external/EventHandler.h index 8e47f41..0e64c6e 100644 --- a/jsapp/rich/external/EventHandler.h +++ b/jsapp/rich/external/EventHandler.h @@ -18,7 +18,7 @@ #include "EventRunner.h" -namespace OHOS::AppExecFwk { +namespace OHOS::Ide { class EventHandler final { public: EventHandler() = default; diff --git a/jsapp/rich/external/EventQueue.cpp b/jsapp/rich/external/EventQueue.cpp index 2db7d88..d2b8dce 100644 --- a/jsapp/rich/external/EventQueue.cpp +++ b/jsapp/rich/external/EventQueue.cpp @@ -15,7 +15,7 @@ #include "EventQueue.h" -namespace OHOS::AppExecFwk { +namespace OHOS::Ide { EventTask::EventTask(size_t order, Callback task, std::chrono::steady_clock::time_point targetTime) : order(order), task(task), targetTime(targetTime) { diff --git a/jsapp/rich/external/EventQueue.h b/jsapp/rich/external/EventQueue.h index 7b6200f..2f6d054 100644 --- a/jsapp/rich/external/EventQueue.h +++ b/jsapp/rich/external/EventQueue.h @@ -21,7 +21,7 @@ #include #include -namespace OHOS::AppExecFwk { +namespace OHOS::Ide { using Callback = std::function; class EventTask { diff --git a/jsapp/rich/external/EventRunner.cpp b/jsapp/rich/external/EventRunner.cpp index ae9b935..7fdace6 100644 --- a/jsapp/rich/external/EventRunner.cpp +++ b/jsapp/rich/external/EventRunner.cpp @@ -15,7 +15,7 @@ #include "EventRunner.h" -namespace OHOS::AppExecFwk { +namespace OHOS::Ide { EventRunner& EventRunner::Current() { static EventRunner mainRunner; diff --git a/jsapp/rich/external/EventRunner.h b/jsapp/rich/external/EventRunner.h index f4ebb36..dd568ec 100644 --- a/jsapp/rich/external/EventRunner.h +++ b/jsapp/rich/external/EventRunner.h @@ -21,7 +21,7 @@ #include #include "EventQueue.h" -namespace OHOS::AppExecFwk { +namespace OHOS::Ide { class EventRunner final { public: EventRunner() = default; diff --git a/mock/BUILD.gn b/mock/BUILD.gn index 7ebba91..22dc941 100644 --- a/mock/BUILD.gn +++ b/mock/BUILD.gn @@ -34,6 +34,7 @@ ohos_source_set("mock_rich") { deps = [ "../jsapp:jsapp_rich", "../util:util_rich", + "//third_party/ffmpeg:libohosffmpeg", "//third_party/libjpeg-turbo:turbojpeg_static", "//third_party/libwebsockets:websockets_static", ] @@ -43,6 +44,7 @@ ohos_source_set("mock_rich") { "MouseInput.cpp", "MouseWheel.cpp", "SystemCapability.cpp", + "VideoRecorder.cpp", "VirtualMessage.cpp", "VirtualScreen.cpp", "rich/KeyInputImpl.cpp", @@ -53,7 +55,10 @@ ohos_source_set("mock_rich") { "rich/VirtualScreenImpl.cpp", ] - include_dirs += [ "./rich/" ] + include_dirs += [ + "./rich/", + "//third_party/ffmpeg/", + ] part_name = "previewer" subsystem_name = "ide" @@ -77,6 +82,7 @@ ohos_source_set("mock_lite") { "//commonlibrary/utils_lite/js/builtin/simulator:ace_kit_file_simulator", "//commonlibrary/utils_lite/js/builtin/simulator:ace_kit_kvstore_simulator", "//foundation/arkui/ace_engine_lite/frameworks/targets/simulator:ace_lite", + "//third_party/ffmpeg:libohosffmpeg", "//third_party/libjpeg-turbo:turbojpeg_static", "//third_party/libwebsockets:websockets_static", ] @@ -87,6 +93,7 @@ ohos_source_set("mock_lite") { "MouseInput.cpp", "MouseWheel.cpp", "SystemCapability.cpp", + "VideoRecorder.cpp", "VirtualMessage.cpp", "VirtualScreen.cpp", "lite/AblityKit.cpp", @@ -112,6 +119,7 @@ ohos_source_set("mock_lite") { "//foundation/arkui/ui_lite/interfaces/kits/", "//foundation/graphic/graphic_utils_lite/interfaces/kits/", "//foundation/graphic/graphic_utils_lite/interfaces/innerkits/", + "//third_party/ffmpeg/", ] if (build_lite_full) { diff --git a/mock/VideoRecorder.cpp b/mock/VideoRecorder.cpp new file mode 100644 index 0000000..963a6a9 --- /dev/null +++ b/mock/VideoRecorder.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "VideoRecorder.h" +#include "PreviewerEngineLog.h" + +#include +#include + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif +#endif /* __cplusplus */ +#include +#include +#include +#include +#include +#ifdef __cplusplus +#if __cplusplus +} +#endif +#endif /* __cplusplus */ + +// List of popular codecs to try +static const std::vector codecIds = { + AV_CODEC_ID_H264, // H.264 + AV_CODEC_ID_HEVC, // H.265 + AV_CODEC_ID_MPEG4, // MPEG-4 + AV_CODEC_ID_VP8, // VP8 + AV_CODEC_ID_VP9, // VP9 + AV_CODEC_ID_AV1, // AV1 + AV_CODEC_ID_THEORA, // Theora + AV_CODEC_ID_MJPEG, // MJPEG + AV_CODEC_ID_PRORES // ProRes +}; + +VideoRecorder::VideoRecorder() +{ +} + +VideoRecorder::~VideoRecorder() +{ + Stop(); +} + +VideoRecorder& VideoRecorder::GetInstance() +{ + static VideoRecorder instance; + return instance; +} + +void VideoRecorder::Start(const std::string& filename, int w, int h, int f) +{ + if (recording.load()) { + ELOG("Recording is already in progress."); + return; + } + + width = w; + height = h; + fps = f; + outputFilename = filename; + nextFramePts = 0; + + recording = true; + workerThread = std::thread(&VideoRecorder::EncodingThread, this); +} + +void VideoRecorder::Stop() +{ + if (recording.load()) { + recording = false; + + if (workerThread.joinable()) { + workerThread.join(); + } + + Cleanup(); + } +} + +void VideoRecorder::PushFrame(const uint8_t* rgbData, size_t rgbDataSize) +{ + std::lock_guard lock(frameMutex); + lastFrame.resize(rgbDataSize); + std::copy(rgbData, rgbData + rgbDataSize, lastFrame.data()); +} + +void VideoRecorder::EncodingThread() +{ + constexpr int defaultTimeout = 5; + auto lastFrameTime = std::chrono::steady_clock::now(); + auto frameInterval = std::chrono::milliseconds(1000 / fps); + + if (!InitEncoder()) { + Stop(); // Stop recording in case of initialization failure + return; + } + + while (recording.load()) { + auto currentTime = std::chrono::steady_clock::now(); + + { + std::lock_guard lock(frameMutex); + if (std::chrono::duration_cast(currentTime - lastFrameTime) >= frameInterval) { + lastFrameTime = currentTime; + + if (!EncodeFrame(lastFrame.data())) { + Stop(); // Stop recording in case of encoding failure + return; + } + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(defaultTimeout)); + } + + FinalizeEncoder(); +} + +static std::string GetFileExtension(AVCodecID codecId) +{ + switch (codecId) { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_HEVC: + case AV_CODEC_ID_MPEG4: + default: + return ".mp4"; // Default to MP4 if codec is not specifically handled + case AV_CODEC_ID_VP8: + case AV_CODEC_ID_VP9: + return ".webm"; + case AV_CODEC_ID_AV1: + return ".mkv"; + case AV_CODEC_ID_THEORA: + return ".ogv"; + case AV_CODEC_ID_MJPEG: + return ".avi"; + case AV_CODEC_ID_PRORES: + return ".mov"; + } +} + +void VideoRecorder::InitCodecContext(const AVCodec* codec) +{ + constexpr int defaultBitRate = 400000; + constexpr int defaultGopSize = 10; + codecContext = avcodec_alloc_context3(codec); + codecContext->codec_id = codec->id; + codecContext->bit_rate = defaultBitRate; + codecContext->width = width; + codecContext->height = height; + codecContext->time_base = {1, fps}; + codecContext->framerate = {fps, 1}; + codecContext->gop_size = defaultGopSize; + codecContext->max_b_frames = 1; + codecContext->pix_fmt = AV_PIX_FMT_YUV420P; +} + +bool VideoRecorder::InitEncoder() +{ + const AVCodec* codec = nullptr; + for (const auto& codecId : codecIds) { + codec = avcodec_find_encoder(codecId); + if (codec) { + outputFilename += GetFileExtension(codecId); + break; // Stop after finding the first working codec + } + } + if (!codec) { + return false; + } + if (avformat_alloc_output_context2(&formatContext, nullptr, nullptr, outputFilename.c_str()) < 0) { + return false; + } + AVStream* stream = avformat_new_stream(formatContext, codec); + if (!stream) { + return false; + } + InitCodecContext(codec); + formatContext->streams[0]->time_base = codecContext->time_base; + if (formatContext->oformat->flags & AVFMT_GLOBALHEADER) { + codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + if (avcodec_open2(codecContext, codec, nullptr) < 0) { + return false; + } + avcodec_parameters_from_context(stream->codecpar, codecContext); + if (!(formatContext->oformat->flags & AVFMT_NOFILE)) { + if (avio_open(&formatContext->pb, outputFilename.c_str(), AVIO_FLAG_WRITE) < 0) { + ELOG("Failed to open file."); + return false; + } + } + if (avformat_write_header(formatContext, nullptr) < 0) { + ELOG("Failed to write header."); + return false; + } + swsContext = sws_getContext(width, height, AV_PIX_FMT_RGB24, width, height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, + nullptr, nullptr, nullptr); + if (!swsContext) { + ELOG("Failed to initialize the scaling context."); + return false; + } + return true; +} + +bool VideoRecorder::EncodeFrame(const uint8_t* rgbData) +{ + AVFrame* rgbFrame = av_frame_alloc(); + rgbFrame->format = AV_PIX_FMT_RGB24; + rgbFrame->width = width; + rgbFrame->height = height; + av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbData, AV_PIX_FMT_RGB24, width, height, 1); + + AVFrame* yuvFrame = av_frame_alloc(); + yuvFrame->format = AV_PIX_FMT_YUV420P; + yuvFrame->width = width; + yuvFrame->height = height; + av_image_alloc(yuvFrame->data, yuvFrame->linesize, width, height, AV_PIX_FMT_YUV420P, 1); + + sws_scale(swsContext, rgbFrame->data, rgbFrame->linesize, 0, height, yuvFrame->data, yuvFrame->linesize); + + yuvFrame->pts = nextFramePts++; + + av_frame_free(&rgbFrame); + + AVPacket packet; + av_init_packet(&packet); + packet.data = nullptr; + packet.size = 0; + + int ret = avcodec_send_frame(codecContext, yuvFrame); + if (ret < 0) { + ELOG("Error sending frame to encoder."); + av_frame_free(&yuvFrame); + return false; + } + + ret = avcodec_receive_packet(codecContext, &packet); + if (ret == 0) { + packet.stream_index = 0; + av_packet_rescale_ts(&packet, codecContext->time_base, formatContext->streams[0]->time_base); + + if (av_interleaved_write_frame(formatContext, &packet) < 0) { + ELOG("Error writing frame."); + av_packet_unref(&packet); + av_frame_free(&yuvFrame); + return false; + } + av_packet_unref(&packet); + } else if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { + ELOG("Error receiving packet from encoder."); + av_frame_free(&yuvFrame); + return false; + } + + av_frame_free(&yuvFrame); + return true; +} + +void VideoRecorder::FinalizeEncoder() +{ + av_write_trailer(formatContext); + + if (!(formatContext->oformat->flags & AVFMT_NOFILE)) { + avio_closep(&formatContext->pb); + } + + sws_freeContext(swsContext); + avcodec_free_context(&codecContext); +} + +void VideoRecorder::Cleanup() +{ + avformat_free_context(formatContext); // Free the format context +} diff --git a/mock/VideoRecorder.h b/mock/VideoRecorder.h new file mode 100644 index 0000000..dd42d1a --- /dev/null +++ b/mock/VideoRecorder.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef VIDEO_RECORDER_H +#define VIDEO_RECORDER_H + +#include +#include +#include +#include +#include +#include + +// Forward declarations for FFmpeg structures +struct AVCodec; +struct AVCodecContext; +struct AVFormatContext; +struct AVFrame; +struct AVPacket; +struct SwsContext; + +class VideoRecorder { +public: + // Get the singleton instance + static VideoRecorder& GetInstance(); + + // Delete copy constructor and assignment operator + VideoRecorder(const VideoRecorder&) = delete; + VideoRecorder& operator=(const VideoRecorder&) = delete; + + // Function to start video recording + void Start(const std::string& filename, int w, int h, int f); + + // Function to stop video recording + void Stop(); + + // Function to pass a frame in RGB format + void PushFrame(const uint8_t* rgbData, size_t rgbDataSize); + +private: + VideoRecorder(); + ~VideoRecorder(); + + std::thread workerThread; + std::atomic recording{false}; + std::mutex frameMutex; + std::vector lastFrame; + + std::string outputFilename; + int width{0}; + int height{0}; + int fps{0}; + AVCodecContext* codecContext{nullptr}; + SwsContext* swsContext{nullptr}; + AVFormatContext* formatContext{nullptr}; + int64_t nextFramePts = 0; + + void InitCodecContext(const AVCodec* codec); + // Encoding thread + void EncodingThread(); + + // Encoder initialization + bool InitEncoder(); + + // Encoding a single frame + bool EncodeFrame(const uint8_t* rgbData); + + // Finalize the encoder + void FinalizeEncoder(); + + // Resource cleanup + void Cleanup(); +}; + +#endif // VIDEO_RECORDER_H diff --git a/mock/lite/VirtualScreenImpl.h b/mock/lite/VirtualScreenImpl.h index b6edd12..0029180 100644 --- a/mock/lite/VirtualScreenImpl.h +++ b/mock/lite/VirtualScreenImpl.h @@ -38,7 +38,7 @@ public: uint16_t GetScreenHeight() override; void InitBuffer(); void InitAll(std::string pipeName, std::string pipePort); - + void MakeScreenShot(const std::string &fileName) {} private: VirtualScreenImpl(); ~VirtualScreenImpl(); diff --git a/mock/rich/VirtualScreenImpl.cpp b/mock/rich/VirtualScreenImpl.cpp index 297f9b4..758db93 100644 --- a/mock/rich/VirtualScreenImpl.cpp +++ b/mock/rich/VirtualScreenImpl.cpp @@ -24,7 +24,9 @@ #include "CommandParser.h" #include "PreviewerEngineLog.h" #include "TraceTool.h" +#include "VideoRecorder.h" #include +#include VirtualScreenImpl& VirtualScreenImpl::GetInstance() { @@ -318,15 +320,26 @@ void VirtualScreenImpl::Send(const void* data, int32_t retWidth, int32_t retHeig dataTemp[nowBasePos + bluePos] = *((char*)data + inputBasePos + bluePos); } } + + VideoRecorder::GetInstance().PushFrame(dataTemp, retWidth * retHeight * jpgPix); + VirtualScreen::RgbToJpg(dataTemp, retWidth, retHeight); + delete [] dataTemp; + UpdateScreenshotBuffer(); +} + +void VirtualScreenImpl::UpdateScreenshotBuffer() +{ if (jpgBufferSize > bufferSize - headSize) { FLOG("VirtualScreenImpl::Send length must < %d", bufferSize - headSize); return; } std::copy(jpgScreenBuffer, jpgScreenBuffer + jpgBufferSize, screenBuffer + headSize); - writed = WebSocketServer::GetInstance().WriteData(screenBuffer, headSize + jpgBufferSize); + if (isWebSocketConfiged) { + writed = WebSocketServer::GetInstance().WriteData(screenBuffer, headSize + jpgBufferSize); + } BackupAndDeleteBuffer(jpgBufferSize); } @@ -354,6 +367,8 @@ void VirtualScreenImpl::BackupAndDeleteBuffer(const unsigned long imageBufferSiz std::copy(screenBuffer, screenBuffer + headSize + imageBufferSize, WebSocketServer::GetInstance().firstImageBuffer + LWS_PRE); + screenShotBuffer = WebSocketServer::GetInstance().firstImageBuffer + LWS_PRE + headSize; + screenShotBufferSize = imageBufferSize; FreeJpgMemory(); } @@ -365,7 +380,7 @@ bool VirtualScreenImpl::JudgeBeforeSend(const void* data) invalidFrameCountPerMinute++; return false; } - if (!isWebSocketConfiged) { + if (!isWebSocketConfiged && !CommandParser::GetInstance().IsStandaloneMode()) { ELOG("image socket is not ready"); return false; } @@ -378,8 +393,9 @@ bool VirtualScreenImpl::SendPixmap(const void* data, size_t length, int32_t retW return false; } if (isFirstRender) { - ILOG("Get first render buffer"); - TraceTool::GetInstance().HandleTrace("Get first render buffer"); + if (!CommandParser::GetInstance().IsStandaloneMode()) { + TraceTool::GetInstance().HandleTrace("Get first render buffer"); + } isFirstRender = false; } isFrameUpdated = true; @@ -472,4 +488,15 @@ void VirtualScreenImpl::InitFoldParams() if (parser.IsSet("fr")) { SetFoldResolution(info.foldResolutionWidth, info.foldResolutionHeight); } -} \ No newline at end of file +} + +void VirtualScreenImpl::MakeScreenShot(const std::string &fileName) +{ + std::ofstream myfile; + myfile.open((fileName + ".jpg").c_str(), std::ios::out | std::ios::binary); + if (screenShotBuffer != nullptr) { + std::lock_guard guard(WebSocketServer::GetInstance().mutex); + myfile.write((char *)screenShotBuffer, screenShotBufferSize); + } + myfile.close(); +} diff --git a/mock/rich/VirtualScreenImpl.h b/mock/rich/VirtualScreenImpl.h index 11e61e9..6b143c8 100644 --- a/mock/rich/VirtualScreenImpl.h +++ b/mock/rich/VirtualScreenImpl.h @@ -52,6 +52,7 @@ public: void InitAll(std::string pipeName, std::string pipePort); ScreenInfo GetScreenInfo(); void InitFoldParams(); + void MakeScreenShot(const std::string &fileName); private: VirtualScreenImpl(); ~VirtualScreenImpl(); @@ -69,6 +70,7 @@ private: std::copy(startPos, startPos + sizeof(dataToSend), screenBuffer + currentPos); currentPos += sizeof(dataToSend); } + void UpdateScreenshotBuffer(); bool isFirstSend; bool isFirstRender; @@ -95,6 +97,9 @@ private: uint64_t flushEmptyTimeStamp = 0; std::chrono::system_clock::time_point flushEmptyTime = std::chrono::system_clock::time_point::min(); std::chrono::system_clock::time_point onRenderTime = std::chrono::system_clock::time_point::min(); + + uint8_t *screenShotBuffer = nullptr; + size_t screenShotBufferSize = 0; }; #endif // VIRTUALSREENIMPL_H diff --git a/test/fuzztest/commandparse_fuzzer/BUILD.gn b/test/fuzztest/commandparse_fuzzer/BUILD.gn index a7ff6d5..ab28b31 100644 --- a/test/fuzztest/commandparse_fuzzer/BUILD.gn +++ b/test/fuzztest/commandparse_fuzzer/BUILD.gn @@ -38,6 +38,7 @@ ide_fuzztest("RichCommandParseFuzzTest") { "//third_party/libwebsockets/include", "//third_party/cJSON", "//third_party/bounds_checking_function/include", + "//third_party/zlib", ] include_dirs += graphic_2d_include_path include_dirs += window_manager_include_path @@ -84,6 +85,7 @@ ide_fuzztest("RichCommandParseFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -96,6 +98,7 @@ ide_fuzztest("RichCommandParseFuzzTest") { deps = [ "//third_party/bounds_checking_function:libsec_static", "//third_party/cJSON:cjson_static", + "//third_party/zlib:libz", ] libs = [] cflags = [ "-Wno-error=overflow" ] @@ -117,6 +120,7 @@ ide_fuzztest("LiteCommandParseFuzzTest") { "//third_party/libwebsockets/include", "//third_party/cJSON", "//third_party/bounds_checking_function/include", + "//third_party/zlib", ] include_dirs += graphic_2d_include_path include_dirs += window_manager_include_path @@ -163,6 +167,7 @@ ide_fuzztest("LiteCommandParseFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -175,6 +180,7 @@ ide_fuzztest("LiteCommandParseFuzzTest") { deps = [ "//third_party/bounds_checking_function:libsec_static", "//third_party/cJSON:cjson_static", + "//third_party/zlib:libz", ] libs = [] cflags = [ "-Wno-error=overflow" ] @@ -196,6 +202,7 @@ ide_fuzztest("CommonCommandParseFuzzTest") { "//third_party/libwebsockets/include", "//third_party/cJSON", "//third_party/bounds_checking_function/include", + "//third_party/zlib", ] include_dirs += graphic_2d_include_path include_dirs += window_manager_include_path @@ -242,6 +249,7 @@ ide_fuzztest("CommonCommandParseFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -254,6 +262,7 @@ ide_fuzztest("CommonCommandParseFuzzTest") { deps = [ "//third_party/bounds_checking_function:libsec_static", "//third_party/cJSON:cjson_static", + "//third_party/zlib:libz", ] libs = [] cflags = [ "-Wno-error=overflow" ] diff --git a/test/fuzztest/commandparse_fuzzer/RichCommandParseFuzzer.cpp b/test/fuzztest/commandparse_fuzzer/RichCommandParseFuzzer.cpp index ea93a13..6908115 100644 --- a/test/fuzztest/commandparse_fuzzer/RichCommandParseFuzzer.cpp +++ b/test/fuzztest/commandparse_fuzzer/RichCommandParseFuzzer.cpp @@ -52,7 +52,11 @@ std::map richDataMap = { {"AvoidArea", R"({"topRect":{"posX":0,"posY":0,"width":2340,"height":117},"bottomRect":{"posX": 0,"posY":0,"width":0,"height":0},"leftRect":{"posX":0,"posY":0,"width":0,"height":0}, "rightRect":{"posX":0,"posY":0,"width":2340,"height":84}})"}, - {"AvoidAreaChanged", ""} + {"AvoidAreaChanged", ""}, + {"AvoidAreaChanged", ""}, + {"ScreenShot", ""}, + {"StartVideoRecord", ""}, + {"StopVideoRecord", ""} }; TEST(RichCommandParseFuzzTest, test_command) @@ -73,4 +77,4 @@ TEST(RichCommandParseFuzzTest, test_command) } std::cout << "--> RichCommandParseFuzzTest for rich end <--" << std::endl; } -} \ No newline at end of file +} diff --git a/test/fuzztest/jsonparse_fuzzer/BUILD.gn b/test/fuzztest/jsonparse_fuzzer/BUILD.gn index 4201cf0..0e40e16 100644 --- a/test/fuzztest/jsonparse_fuzzer/BUILD.gn +++ b/test/fuzztest/jsonparse_fuzzer/BUILD.gn @@ -42,6 +42,7 @@ ide_fuzztest("DeviceConfigParseFuzzTest") { "//third_party/libwebsockets/include", "//third_party/cJSON", "//third_party/bounds_checking_function/include", + "//third_party/zlib", ] include_dirs += graphic_2d_include_path include_dirs += window_manager_include_path @@ -88,6 +89,7 @@ ide_fuzztest("DeviceConfigParseFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -99,6 +101,7 @@ ide_fuzztest("DeviceConfigParseFuzzTest") { deps = [ "//third_party/bounds_checking_function:libsec_static", "//third_party/cJSON:cjson_static", + "//third_party/zlib:libz", ] libs = [] cflags = [ "-Wno-error=overflow" ] @@ -132,6 +135,7 @@ ide_fuzztest("ReadFileContentsFuzzTest") { "$ide_previewer_path/test/mock/arkui/MockAceAbility.cpp", "$ide_previewer_path/test/mock/arkui/MockAcePreviewHelper.cpp", "$ide_previewer_path/test/mock/graphic/MockGlfwRenderContext.cpp", + "$ide_previewer_path/test/mock/mock/MockEventAdapter.cpp", "$ide_previewer_path/test/mock/mock/MockKeyInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseWheelImpl.cpp", @@ -155,6 +159,7 @@ ide_fuzztest("ReadFileContentsFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -223,6 +228,7 @@ ide_fuzztest("GetModulePathMapFuzzTest") { "$ide_previewer_path/test/mock/arkui/MockAceAbility.cpp", "$ide_previewer_path/test/mock/arkui/MockAcePreviewHelper.cpp", "$ide_previewer_path/test/mock/graphic/MockGlfwRenderContext.cpp", + "$ide_previewer_path/test/mock/mock/MockEventAdapter.cpp", "$ide_previewer_path/test/mock/mock/MockKeyInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseWheelImpl.cpp", @@ -246,6 +252,7 @@ ide_fuzztest("GetModulePathMapFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -313,6 +320,7 @@ ide_fuzztest("GetHspAceModuleBuildFuzzTest") { "$ide_previewer_path/test/mock/arkui/MockAceAbility.cpp", "$ide_previewer_path/test/mock/arkui/MockAcePreviewHelper.cpp", "$ide_previewer_path/test/mock/graphic/MockGlfwRenderContext.cpp", + "$ide_previewer_path/test/mock/mock/MockEventAdapter.cpp", "$ide_previewer_path/test/mock/mock/MockKeyInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseWheelImpl.cpp", @@ -336,6 +344,7 @@ ide_fuzztest("GetHspAceModuleBuildFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -403,6 +412,7 @@ ide_fuzztest("GetModuleBufferFromHspFuzzTest") { "$ide_previewer_path/test/mock/arkui/MockAceAbility.cpp", "$ide_previewer_path/test/mock/arkui/MockAcePreviewHelper.cpp", "$ide_previewer_path/test/mock/graphic/MockGlfwRenderContext.cpp", + "$ide_previewer_path/test/mock/mock/MockEventAdapter.cpp", "$ide_previewer_path/test/mock/mock/MockKeyInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseWheelImpl.cpp", @@ -426,6 +436,7 @@ ide_fuzztest("GetModuleBufferFromHspFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -494,6 +505,7 @@ ide_fuzztest("ParseMockJsonFileFuzzTest") { "$ide_previewer_path/test/mock/arkui/MockAceAbility.cpp", "$ide_previewer_path/test/mock/arkui/MockAcePreviewHelper.cpp", "$ide_previewer_path/test/mock/graphic/MockGlfwRenderContext.cpp", + "$ide_previewer_path/test/mock/mock/MockEventAdapter.cpp", "$ide_previewer_path/test/mock/mock/MockKeyInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseWheelImpl.cpp", @@ -517,6 +529,7 @@ ide_fuzztest("ParseMockJsonFileFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] @@ -584,6 +597,7 @@ ide_fuzztest("SetPkgContextInfoFuzzTest") { "$ide_previewer_path/test/mock/arkui/MockAceAbility.cpp", "$ide_previewer_path/test/mock/arkui/MockAcePreviewHelper.cpp", "$ide_previewer_path/test/mock/graphic/MockGlfwRenderContext.cpp", + "$ide_previewer_path/test/mock/mock/MockEventAdapter.cpp", "$ide_previewer_path/test/mock/mock/MockKeyInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseInputImpl.cpp", "$ide_previewer_path/test/mock/mock/MockMouseWheelImpl.cpp", @@ -607,6 +621,7 @@ ide_fuzztest("SetPkgContextInfoFuzzTest") { "$ide_previewer_path/util/SharedDataManager.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", ] diff --git a/test/fuzztest/paramsparse_fuzzer/BUILD.gn b/test/fuzztest/paramsparse_fuzzer/BUILD.gn index 80d6610..986e87c 100644 --- a/test/fuzztest/paramsparse_fuzzer/BUILD.gn +++ b/test/fuzztest/paramsparse_fuzzer/BUILD.gn @@ -35,6 +35,7 @@ ide_fuzztest("ParamsParseFuzzTest") { "$ide_previewer_path/util/PreviewerEngineLog.cpp", "$ide_previewer_path/util/TimeTool.cpp", "$ide_previewer_path/util/TraceTool.cpp", + "$ide_previewer_path/util/Unhap.cpp", "$ide_previewer_path/util/unix/LocalDate.cpp", "$ide_previewer_path/util/unix/NativeFileSystem.cpp", "../main.cpp", @@ -48,10 +49,12 @@ ide_fuzztest("ParamsParseFuzzTest") { "$ide_previewer_path/util/unix", "//third_party/cJSON", "//third_party/bounds_checking_function/include", + "//third_party/zlib", ] deps = [ "//third_party/bounds_checking_function:libsec_static", "//third_party/cJSON:cjson_static", + "//third_party/zlib:libz", ] libs = [] cflags = [] diff --git a/test/mock/graphic/MockGlfwRenderContext.cpp b/test/mock/graphic/MockGlfwRenderContext.cpp index 456385e..5c1e9a4 100644 --- a/test/mock/graphic/MockGlfwRenderContext.cpp +++ b/test/mock/graphic/MockGlfwRenderContext.cpp @@ -53,4 +53,11 @@ void GlfwRenderContext::PollEvents() } void GlfwRenderContext::SetWindowSize(int32_t width, int32_t height) {} -} \ No newline at end of file + +void GlfwRenderContext::GetFrameBufferSize(int32_t &width, int32_t &height) {} + +int GlfwRenderContext::WindowShouldClose() +{ + return 1; +} +} diff --git a/test/mock/mock/MockEventAdapter.cpp b/test/mock/mock/MockEventAdapter.cpp new file mode 100644 index 0000000..08e900e --- /dev/null +++ b/test/mock/mock/MockEventAdapter.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "adapter/preview/entrance/samples/event_adapter.h" + +#include +#include + +namespace OHOS::Ace::Sample { + +EventAdapter::EventAdapter() {} + +EventAdapter::~EventAdapter() = default; + +void EventAdapter::Initialize(std::shared_ptr& glfwRenderContext) {} + +void EventAdapter::RegisterKeyEventCallback(MMIKeyEventCallback&& callback) {} + +void EventAdapter::RegisterPointerEventCallback(MMIPointerEventCallback&& callback) {} + +void EventAdapter::RegisterInspectorCallback(InspectorCallback&& callback) {} + +bool EventAdapter::RecognizeKeyEvent(int key, int action, int mods) +{ + return true; +} + +void EventAdapter::RecognizePointerEvent(const TouchType type) {} + +bool EventAdapter::RunSpecialOperations(int key, int action, int mods) +{ + return true; +} + +} // namespace OHOS::Ace::Sample diff --git a/test/mock/mock/MockVirtualScreenImpl.cpp b/test/mock/mock/MockVirtualScreenImpl.cpp index fb700ed..51f855e 100644 --- a/test/mock/mock/MockVirtualScreenImpl.cpp +++ b/test/mock/mock/MockVirtualScreenImpl.cpp @@ -57,3 +57,5 @@ ScreenInfo VirtualScreenImpl::GetScreenInfo() } void VirtualScreenImpl::InitFoldParams() {} + +void VirtualScreenImpl::MakeScreenShot(const std::string &fileName) {} diff --git a/test/mock/util/MockLocalSocket.cpp b/test/mock/util/MockLocalSocket.cpp index c6e1cd8..a199d74 100644 --- a/test/mock/util/MockLocalSocket.cpp +++ b/test/mock/util/MockLocalSocket.cpp @@ -60,4 +60,9 @@ int64_t LocalSocket::ReadData(char* data, size_t length) const size_t LocalSocket::WriteData(const void* data, size_t length) const { return length; -} \ No newline at end of file +} + +bool LocalSocket::RunServer(std::string name) +{ + return true; +} diff --git a/util/BUILD.gn b/util/BUILD.gn index 5c8e323..e264363 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -30,6 +30,7 @@ ohos_source_set("util_lite") { "SharedDataManager.cpp", "TimeTool.cpp", "TraceTool.cpp", + "Unhap.cpp", "WebSocketServer.cpp", ] cflags = [ "-std=c++17" ] @@ -68,6 +69,7 @@ ohos_source_set("util_lite") { "//third_party/cJSON:cjson_static", "//third_party/libwebsockets:websockets_static", ] + external_deps = [ "zlib:shared_libz" ] part_name = "previewer" subsystem_name = "ide" } @@ -125,6 +127,7 @@ ohos_shared_library("ide_util") { "PreviewerEngineLog.cpp", "TimeTool.cpp", "TraceTool.cpp", + "Unhap.cpp", ] cflags = [ "-std=c++17" ] cflags_cc = cflags @@ -184,6 +187,7 @@ ohos_shared_library("ide_util") { "//third_party/bounds_checking_function:libsec_shared", "//third_party/cJSON:cjson", ] + external_deps = [ "zlib:shared_libz" ] } part_name = "previewer" subsystem_name = "ide" diff --git a/util/CommandParser.cpp b/util/CommandParser.cpp index ccd9a08..6efa77f 100644 --- a/util/CommandParser.cpp +++ b/util/CommandParser.cpp @@ -21,6 +21,7 @@ #include "FileSystem.h" #include "PreviewerEngineLog.h" #include "TraceTool.h" +#include "Unhap.h" CommandParser* CommandParser::example = nullptr; CommandParser::CommandParser() @@ -92,11 +93,22 @@ CommandParser::CommandParser() Register("-fr", 2, "Fold resolution "); // 2 arguments Register("-ljPath", 1, "Set loader.json path for Previewer"); Register("-sid", 1, "Set sid for websocket"); + Register("-gui", 0, "Standalone Previewer GUI mode."); + Register("-cli", 0, "Standalone Previewer CLI mode."); + Register("-hap", 1, "Path to HAP."); + Register("-config", 1, "Config file name."); #ifdef COMPONENT_TEST_ENABLED Register("-componentTest", 1, "Set component test config"); #endif // COMPONENT_TEST_ENABLED } +CommandParser::~CommandParser() +{ + if (isHapDirTemp) { + FileSystem::RemoveDir(hapDir); + } +} + CommandParser& CommandParser::GetInstance() { static CommandParser instance; @@ -137,7 +149,7 @@ bool CommandParser::IsCommandValid() partRet = partRet && IsAbilityNameValid() && IsLanguageValid() && IsTracePipeNameValid(); partRet = partRet && IsLocalSocketNameValid() && IsConfigChangesValid() && IsScreenDensityValid(); partRet = partRet && IsSidValid(); - if (partRet) { + if (partRet || IsStandaloneMode()) { return true; } ELOG(errorInfo.c_str()); @@ -148,28 +160,53 @@ bool CommandParser::IsCommandValid() bool CommandParser::IsSet(std::string key) { - if (argsMap.find(std::string("-") + key) == argsMap.end()) { - return false; + if (argsMap.find(std::string("-") + key) != argsMap.end()) { + return true; } - return true; + if (config[key].IsValid()) { + return true; + } + return false; } std::string CommandParser::Value(std::string key) { - auto args = argsMap[std::string("-") + key]; - if (args.size() > 0) { - return args[0]; + auto argsIt = argsMap.find(std::string("-") + key); + if (argsIt != argsMap.end()) { + if (argsIt->second.size() > 0) { + return argsIt->second[0]; + } + return std::string(); + } + Json2::Value configValue = config[key]; + if (configValue.IsString()) { + return configValue.AsString(); + } else if (configValue.IsNumber()) { + return std::to_string(configValue.AsDouble()); } return std::string(); } std::vector CommandParser::Values(std::string key) { - if (argsMap.find(key) == argsMap.end()) { - return std::vector(); + auto argsIt = argsMap.find(key); + if (argsIt != argsMap.end()) { + return argsIt->second; } - std::vector args = argsMap[key]; - return args; + Json2::Value configValue = config[key.substr(1)]; + if (configValue.IsArray()) { + std::vector args; + for (uint32_t i = 0; i < configValue.GetArraySize(); ++i) { + Json2::Value item = configValue.GetArrayItem(i); + if (item.IsString()) { + args.push_back(item.AsString()); + } else if (item.IsNumber()) { + args.push_back(std::to_string(item.AsDouble())); + } + } + return args; + } + return std::vector(); } void CommandParser::Register(std::string key, uint32_t argc, std::string help) @@ -323,12 +360,17 @@ bool CommandParser::IsDebugPortValid() bool CommandParser::IsAppPathValid() { - if (!IsSet("j")) { + if (!IsSet("j") && !IsSet("hap")) { errorInfo = std::string("No app path specified."); ELOG("Launch -j parameters abnormal!"); return false; } - std::string path = Value("j"); + std::string path = ""; + if (IsSet("hap")) { + path = FileSystem::NormalizePath(GetHapDir() + "/ets"); + } else { + path = Value("j"); + } if (!FileSystem::IsDirectoryExists(path)) { errorInfo = std::string("Js app path not exist."); ELOG("Launch -j parameters abnormal!"); @@ -338,6 +380,53 @@ bool CommandParser::IsAppPathValid() return true; } +bool CommandParser::GetModuleInfoJson() +{ + if (!IsSet("hap")) { + return false; + } + if (moduleInfoJson.IsValid()) { + return true; + } + + return GetJson(FileSystem::NormalizePath(GetHapDir() + "/module.json"), moduleInfoJson); +} + +bool CommandParser::GetResourcesProfieJson() +{ + if (!IsSet("hap")) { + return false; + } + if (resourcesProfieJson.IsValid()) { + return true; + } + if (!IsPagesValid()) { + return false; + } + std::string path = GetHapDir() + "/resources/base/profile/" + pages + ".json"; + return GetJson(FileSystem::NormalizePath(path), resourcesProfieJson); +} + +bool CommandParser::GetJson(const std::string &fileName, Json2::Value &jsonDataOut) +{ + // Read the JSON file into a string + std::string jsonStr = JsonReader::ReadFile(fileName); + if (jsonStr.empty()) { + ELOG("Error: Failed to read JSON file \"%s\" or file is empty.", fileName.c_str()); + return false; + } + + // Parse the string into a JSON object + Json2::Value jsonData = JsonReader::ParseJsonData2(jsonStr); + if (!jsonData.IsValid()) { + ELOG("Error: Failed to parse JSON data."); + return false; + } + + std::swap(jsonDataOut, jsonData); + return true; +} + bool CommandParser::IsAppNameValid() { if (IsSet("n")) { @@ -351,6 +440,11 @@ bool CommandParser::IsAppNameValid() return false; } appName = Value("n"); + } else if (GetModuleInfoJson() && + moduleInfoJson.IsMember("module") && + moduleInfoJson["module"].IsMember("name") && + moduleInfoJson["module"]["name"].IsString()) { + appName = moduleInfoJson["module"]["name"].AsString(); } ILOG("CommandParser app name: %s", appName.c_str()); return true; @@ -443,8 +537,22 @@ bool CommandParser::IsDeviceValid() bool CommandParser::IsUrlValid() { - urlPath = Value("url"); - if (urlPath.empty()) { + auto url = Value("url"); + if (!url.empty()) { + urlPath = url; + } else if (GetResourcesProfieJson()) { + Json2::Value src = resourcesProfieJson["src"]; + if (src.GetArraySize() == 0) { + ELOG("Error: Key 'src' or first src not found in 'main_pages.json'."); + return false; + } + Json2::Value firstSrc = src.GetArrayItem(0); + urlPath = firstSrc.AsString(); + if (urlPath.empty()) { + ELOG("Error: First src record is empty."); + return false; + } + } else { errorInfo = "Launch -url parameters is empty."; return false; } @@ -470,11 +578,16 @@ bool CommandParser::IsConfigPathValid() bool CommandParser::IsAppResourcePathValid() { - if (!IsSet("arp")) { + if (!IsSet("arp") && !IsSet("hap")) { return true; } - std::string path = Value("arp"); + std::string path = ""; + if (IsSet("hap")) { + path = GetHapDir(); + } else { + path = Value("arp"); + } if (!FileSystem::IsDirectoryExists(path)) { errorInfo = std::string("The configuration appResource path does not exist."); ELOG("Launch -arp parameters abnormal!"); @@ -505,10 +618,18 @@ bool CommandParser::IsProjectModelValid() bool CommandParser::IsPagesValid() { - if (!IsSet("pages")) { + const std::string prefix = "$profile:"; + if (IsSet("pages")) { + pages = Value("pages"); + } else if (GetModuleInfoJson() && + moduleInfoJson.IsMember("module") && + moduleInfoJson["module"].IsMember("pages") && + moduleInfoJson["module"]["pages"].IsString() && + moduleInfoJson["module"]["pages"].AsString().substr(0, prefix.length()) == prefix) { + pages = moduleInfoJson["module"]["pages"].AsString().substr(prefix.length()); + } else { return true; } - pages = Value("pages"); if (CheckParamInvalidity(pages, false)) { errorInfo = "Launch -pages parameters is not match regex."; return false; @@ -844,17 +965,39 @@ bool CommandParser::IsAbilityPathValid() if (deviceType == "liteWearable" || deviceType == "smartVision") { return true; } - if (!IsSet("abp")) { + if (IsSet("abp")) { + std::string path = Value("abp"); + if (path.empty()) { + errorInfo = std::string("The ability path is empty."); + ELOG("Launch -abp parameters abnormal!"); + return false; + } + abilityPath = path; + } else if (GetModuleInfoJson()) { + // Check if "abilities[0].srcEntry" and "abilities[0].name" keys are present + if (!moduleInfoJson["module"].IsMember("abilities") || + moduleInfoJson["module"]["abilities"].GetArraySize() == 0) { + ELOG("Error: Key 'abilities' or first ability not found in 'module.json'."); + return false; + } + Json2::Value firstAbility = moduleInfoJson["module"]["abilities"].GetArrayItem(0); + if (!firstAbility.IsMember("srcEntry") || !firstAbility.IsMember("name")) { + ELOG("Error: Key 'srcEntry' or 'name' not found in first ability."); + return false; + } + auto path = firstAbility["srcEntry"].AsString(); + if (path.find("./") == 0) { + path = path.substr(headSymCount); + } + auto baseDir = FileSystem::BaseDir(path); + auto baseName = FileSystem::BaseName(path); + auto name = baseName.substr(0, baseName.rfind('.')); + abilityPath = FileSystem::NormalizePath(baseDir + '/' + name + ".abc"); + } else { errorInfo = "Launch -d parameters without -abp parameters."; return false; } - std::string path = Value("abp"); - if (path.empty()) { - errorInfo = std::string("The ability path is empty."); - ELOG("Launch -abp parameters abnormal!"); - return false; - } - abilityPath = path; + ILOG("CommandParser ability path: %s", abilityPath.c_str()); return true; } @@ -866,17 +1009,31 @@ bool CommandParser::IsAbilityNameValid() if (deviceType == "liteWearable" || deviceType == "smartVision") { return true; } - if (!IsSet("abn")) { + if (IsSet("abn")) { + std::string name = Value("abn"); + if (name.empty()) { + errorInfo = std::string("The ability name is empty."); + ELOG("Launch -abn parameters abnormal!"); + return false; + } + abilityName = name; + } else if (GetModuleInfoJson()) { + // Check if "abilities[0].srcEntry" and "abilities[0].name" keys are present + if (!moduleInfoJson["module"].IsMember("abilities") || + moduleInfoJson["module"]["abilities"].GetArraySize() == 0) { + ELOG("Error: Key 'abilities' or first ability not found in 'module.json'."); + return false; + } + Json2::Value firstAbility = moduleInfoJson["module"]["abilities"].GetArrayItem(0); + if (!firstAbility.IsMember("srcEntry") || !firstAbility.IsMember("name")) { + ELOG("Error: Key 'srcEntry' or 'name' not found in first ability."); + return false; + } + abilityName = firstAbility["name"].AsString(); + } else { ELOG("Launch -d parameters without -abn parameters."); return true; // 兼容老版本IDE(沒有abn参数) } - std::string name = Value("abn"); - if (name.empty()) { - errorInfo = std::string("The ability name is empty."); - ELOG("Launch -abn parameters abnormal!"); - return false; - } - abilityName = name; return true; } @@ -1000,6 +1157,43 @@ bool CommandParser::IsLoaderJsonPathValid() return true; } +bool CommandParser::IsStandaloneMode() +{ + if (IsSet("gui") || IsSet("cli")) { + return true; + } + return false; +} + +bool CommandParser::LoadConfig() +{ + if (config.IsValid()) { + return true; + } + + auto fileName = FileSystem::NormalizePath(FileSystem::GetExecutablePath() + "/config.json"); + if (IsSet("config")) { + fileName = Value("config"); + } + + // Read the JSON file into a string + std::string jsonStr = JsonReader::ReadFile(fileName); + if (jsonStr.empty()) { + ELOG("Error: Failed to read JSON file or file is empty."); + return false; + } + + // Parse the string into a JSON object + Json2::Value jsonData = JsonReader::ParseJsonData2(jsonStr); + if (!jsonData.IsValid()) { + ELOG("Error: Failed to parse JSON data."); + return false; + } + + std::swap(config, jsonData); + return true; +} + int CommandParser::ParseArgs(int argc, char* argv[]) { int startParamInvalidCode = 11; @@ -1014,6 +1208,9 @@ int CommandParser::ParseArgs(int argc, char* argv[]) if (!ProcessCommand(strs)) { return 0; } + if (IsStandaloneMode()) { + LoadConfig(); + } if (!IsCommandValid()) { FLOG("Start args is invalid."); return startParamInvalidCode; @@ -1048,6 +1245,23 @@ void CommandParser::GetFoldInfo(FoldInfo& info) const info.foldResolutionHeight = GetFoldResolutionHeight(); } +std::string CommandParser::GetHapDir() +{ + if (hapDir.empty() && IsSet("hap")) { + std::string hap = Value("hap"); + if (FileSystem::IsDirectoryExists(hap)) { + hapDir = FileSystem::GetFullPath(hap); + } else if (FileSystem::IsFileExists(hap)) { + hapDir = FileSystem::CreateTempDirectory(); + if (!hapDir.empty()) { + Unhap(hap, hapDir); + isHapDirTemp = true; + } + } + } + return hapDir; +} + std::string CommandParser::GetSid() const { return sid; diff --git a/util/CommandParser.h b/util/CommandParser.h index ec6a609..c5727ca 100644 --- a/util/CommandParser.h +++ b/util/CommandParser.h @@ -20,6 +20,8 @@ #include #include +#include "JsonReader.h" + class CommandInfo { public: std::string deviceType; @@ -58,6 +60,7 @@ public: std::string Value(std::string key); std::vector Values(std::string key); void Register(std::string key, uint32_t argc, std::string help); + bool IsResolutionValid(); bool IsResolutionValid(int32_t resolution) const; int32_t GetOrignalResolutionWidth() const; int32_t GetOrignalResolutionHeight() const; @@ -95,6 +98,8 @@ public: int ParseArgs(int argc, char* argv[]); void GetCommandInfo(CommandInfo& info) const; void GetFoldInfo(FoldInfo& info) const; + bool IsStandaloneMode(); + std::string GetHapDir(); std::string GetSid() const; #ifdef COMPONENT_TEST_ENABLED std::string GetComponentTestConfig() const; @@ -102,7 +107,7 @@ public: private: CommandParser(); - ~CommandParser() {} + ~CommandParser(); std::string errorInfo; std::map> argsMap; std::map regsArgsCountMap; @@ -128,6 +133,7 @@ private: const int MAX_JSHEAPSIZE = 512 * 1024; const int MIN_JSHEAPSIZE = 48 * 1024; const size_t MAX_NAME_LENGTH = 256; + const size_t headSymCount = 2; bool isSendJSHeap; int32_t orignalResolutionWidth; int32_t orignalResolutionHeight; @@ -165,11 +171,15 @@ private: int32_t foldResolutionHeight; std::string loaderJsonPath; std::string sid; + std::string hapDir; + bool isHapDirTemp = false; + Json2::Value moduleInfoJson; + Json2::Value resourcesProfieJson; + Json2::Value config; bool IsDebugPortValid(); bool IsAppPathValid(); bool IsAppNameValid(); - bool IsResolutionValid(); bool IsConfigPathValid(); bool IsAppResourcePathValid(); bool IsResolutionRangeValid(std::string value); @@ -206,6 +216,10 @@ private: bool IsSidValid(); std::string HelpText(); void ProcessingCommand(const std::vector& strs); + bool GetModuleInfoJson(); + bool GetResourcesProfieJson(); + static bool GetJson(const std::string &fileName, Json2::Value &jsonData); + bool LoadConfig(); }; #endif // COMMANDPARSER_H diff --git a/util/FileSystem.cpp b/util/FileSystem.cpp index acf01e3..365478f 100644 --- a/util/FileSystem.cpp +++ b/util/FileSystem.cpp @@ -17,6 +17,20 @@ #include #include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#elif __linux__ +#include +#elif __APPLE__ +#include +#endif + #include "PreviewerEngineLog.h" #include "NativeFileSystem.h" @@ -25,11 +39,7 @@ std::vector FileSystem::pathList = {"file_system", "app", "ace", "d std::string FileSystem::bundleName = ""; std::string FileSystem::fileSystemPath = ""; -#ifdef _WIN32 -std::string FileSystem::separator = "\\"; -#else std::string FileSystem::separator = "/"; -#endif bool FileSystem::IsFileExists(std::string path) { @@ -75,8 +85,14 @@ void FileSystem::MakeVirtualFileSystemPath() fileSystemPath = dirToMake; } -int FileSystem::MakeDir(std::string path) +int FileSystem::MakeDir(const std::string &path) { + std::string basePath = BaseDir(path); + if (!basePath.empty() && !IsDirectoryExists(basePath)) { + if (int err = MakeDir(basePath)) { + return err; + } + } int result = 0; #ifdef _WIN32 result = mkdir(path.data()); @@ -86,6 +102,102 @@ int FileSystem::MakeDir(std::string path) return result; } +std::string FileSystem::BaseDir(const std::string &path) +{ + return path.substr(0, path.rfind(separator)); +} + +std::string FileSystem::BaseName(const std::string &path) +{ + return path.substr(path.rfind(separator) + 1); +} + +bool FileSystem::RemoveDir(const std::string &dirPath) +{ + DIR *dir = opendir(dirPath.c_str()); + if (dir == nullptr) { + ELOG("Failed to open directory"); + return false; + } + + struct dirent *entry; + while ((entry = readdir(dir)) != nullptr) { + std::string fileOrDirName = entry->d_name; + if (fileOrDirName == "." || fileOrDirName == "..") { + continue; + } + + std::string fullPath = dirPath + separator + fileOrDirName; + + struct stat pathStat; + if (stat(fullPath.c_str(), &pathStat) == -1) { + ELOG(("Failed to get file status: " + fullPath).c_str()); + closedir(dir); + return false; + } + + if (S_ISDIR(pathStat.st_mode)) { + if (!RemoveDir(fullPath)) { + closedir(dir); + return false; + } + } else { + if (remove(fullPath.c_str()) != 0) { + ELOG(("Failed to delete file: " + fullPath).c_str()); + closedir(dir); + return false; + } + } + } + + closedir(dir); + + if (rmdir(dirPath.c_str()) != 0) { + ELOG(("Failed to remove directory: " + dirPath).c_str()); + return false; + } + + return true; +} + +static std::string GetTempDir() +{ +#ifdef _WIN32 + char tempPath[MAX_PATH]; + GetTempPath(MAX_PATH, tempPath); + return std::string(tempPath); +#else + char* tempPath = getenv("TMPDIR"); + return tempPath == nullptr ? std::string("/tmp") : std::string(tempPath); +#endif +} + +std::string FileSystem::CreateTempDirectory() +{ +#ifdef _WIN32 + char tempDir[MAX_PATH]; + if (GetTempFileName(GetTempDir().c_str(), "previewer", 0, tempDir) == 0) { + ELOG("Failed to create temporary directory"); + return ""; + } + DeleteFile(tempDir); + if (!CreateDirectory(tempDir, nullptr)) { + ELOG("Failed to create temporary directory"); + return ""; + } + return std::string(tempDir); +#else + std::string tempDirTemplate = GetTempDir() + "/previewer_XXXXXX"; + char tempDir[tempDirTemplate.size() + 1]; + tempDirTemplate.copy(tempDir, tempDirTemplate.size()); + if (mkdtemp(tempDir) == nullptr) { + ELOG("Failed to create temporary directory"); + return ""; + } + return std::string(tempDir); +#endif +} + void FileSystem::SetBundleName(std::string name) { bundleName = name; @@ -120,4 +232,55 @@ std::string FileSystem::NormalizePath(const std::string& path) } } return normalizedPath; -} \ No newline at end of file +} + +std::string FileSystem::GetFullPath(const std::string& path) +{ + char fullPath[PATH_MAX]; + +#ifdef _WIN32 + if (GetFullPathName(path.c_str(), PATH_MAX, fullPath, NULL)) { + return std::string(fullPath); + } +#elif __linux__ || __APPLE__ + if (realpath(path.c_str(), fullPath)) { + return std::string(fullPath); + } +#endif + + return path; +} + +std::string FileSystem::GetExecutablePath() +{ + std::string path; + +#ifdef _WIN32 + char buffer[MAX_PATH]; + GetModuleFileName(NULL, buffer, MAX_PATH); + std::string::size_type pos = std::string(buffer).find_last_of("\\/"); + path = std::string(buffer).substr(0, pos); + +#elif __linux__ + char buffer[PATH_MAX]; + ssize_t count = readlink("/proc/self/exe", buffer, PATH_MAX); + if (count != -1) { + path = std::string(buffer, (count > 0) ? count : 0); + std::string::size_type pos = path.find_last_of('/'); + path = path.substr(0, pos); + } + +#elif __APPLE__ + char buffer[PATH_MAX]; + uint32_t size = sizeof(buffer); + if (_NSGetExecutablePath(buffer, &size) == 0) { + path = std::string(buffer); + std::string::size_type pos = path.find_last_of('/'); + path = path.substr(0, pos); + } +#endif + + ILOG("Executable path: %s", path.c_str()); + + return path; +} diff --git a/util/FileSystem.h b/util/FileSystem.h index 9d62090..da38fc5 100644 --- a/util/FileSystem.h +++ b/util/FileSystem.h @@ -26,11 +26,17 @@ public: static std::string GetApplicationPath(); static const std::string& GetVirtualFileSystemPath(); static void MakeVirtualFileSystemPath(); - static int MakeDir(std::string path); + static int MakeDir(const std::string &path); + static std::string BaseDir(const std::string &path); + static std::string BaseName(const std::string &path); + static bool RemoveDir(const std::string &dirPath); + static std::string CreateTempDirectory(); static void SetBundleName(std::string name); static std::string GetSeparator(); static std::string FindSubfolderByName(const std::string& parentFolderPath, const std::string& subfolderName); static std::string NormalizePath(const std::string& path); + static std::string GetFullPath(const std::string &path); + static std::string GetExecutablePath(); private: static std::vector pathList; static std::string separator; diff --git a/util/JsonReader.cpp b/util/JsonReader.cpp index edafa0f..d1cf288 100644 --- a/util/JsonReader.cpp +++ b/util/JsonReader.cpp @@ -665,6 +665,11 @@ Json2::Value JsonReader::CreateBool(const bool value) return Json2::Value(cJSON_CreateBool(value)); } +Json2::Value JsonReader::CreateNumber(double value) +{ + return Json2::Value(cJSON_CreateNumber(value)); +} + Json2::Value JsonReader::CreateString(const std::string& value) { return Json2::Value(cJSON_CreateString(value.c_str())); diff --git a/util/JsonReader.h b/util/JsonReader.h index 0e5ccc1..c6230eb 100644 --- a/util/JsonReader.h +++ b/util/JsonReader.h @@ -127,6 +127,7 @@ public: static Json2::Value CreateObject(); static Json2::Value CreateArray(); static Json2::Value CreateBool(const bool value); + static Json2::Value CreateNumber(double value); static Json2::Value CreateString(const std::string& value); static Json2::Value DepthCopy(const Json2::Value& value); static Json2::Value CreateNull(); diff --git a/util/LocalSocket.h b/util/LocalSocket.h index 9ba31e2..caf9a1f 100644 --- a/util/LocalSocket.h +++ b/util/LocalSocket.h @@ -36,6 +36,8 @@ public: LocalSocket& operator=(const LocalSocket&) = delete; LocalSocket(const LocalSocket&) = delete; bool ConnectToServer(std::string name, OpenMode openMode, TransMode transMode = TRANS_BYTE); + bool RunServer(std::string name); + bool ConnectClient(bool wait = true); std::string GetCommandPipeName(std::string baseName) const; std::string GetImagePipeName(std::string baseName) const; std::string GetTracePipeName(std::string baseName) const; @@ -59,6 +61,9 @@ public: const LocalSocket& operator>>(std::string& data) const; private: + bool isServer = false; + bool isConnected = false; + std::string serverName; #ifdef _WIN32 HANDLE pipeHandle; DWORD GetWinOpenMode(OpenMode mode) const; diff --git a/util/Unhap.cpp b/util/Unhap.cpp new file mode 100755 index 0000000..9cff441 --- /dev/null +++ b/util/Unhap.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Unhap.h" + +#include +#include +#include +#include + +#include "PreviewerEngineLog.h" +#include "FileSystem.h" +#include "contrib/minizip/unzip.h" +#include "zlib.h" + +using namespace std; + +bool SetupPandaClassPath(const string &destDir) +{ + constexpr uint8_t extFileSize = 3; + string pandaClassPath = FileSystem::NormalizePath(destDir + "/libs/x86-64/"); + if (!FileSystem::IsDirectoryExists(pandaClassPath) || !FileSystem::IsDirectoryExists("./module/")) { + return true; + } +#ifdef _WIN32 + if (getenv("PANDA_CLASS_PATH") == nullptr) { + string env = string("PANDA_CLASS_PATH=") + pandaClassPath; + putenv(env.data()); + } +#elif __linux__ || __APPLE__ + setenv("PANDA_CLASS_PATH", pandaClassPath.c_str(), 0); +#endif + DIR *hPandaClassDir = opendir(pandaClassPath.c_str()); + if (!hPandaClassDir) { + return true; + } + struct dirent *dir; + while ((dir = readdir(hPandaClassDir)) != nullptr) { + // Rename .abc.so files to .abc because Panda VM could not start .abc.so + string fileNameSrc = dir->d_name; + if (fileNameSrc.find(".abc.so") != string::npos) { + string fileNameDst = fileNameSrc; + fileNameDst.replace(fileNameDst.end() - extFileSize, fileNameDst.end(), ""); + rename((pandaClassPath + fileNameSrc).c_str(), (pandaClassPath + fileNameDst).c_str()); + continue; + } + // Copy native libs to "./module" folder of Previewer + if (fileNameSrc.find(".so") != string::npos) { + ifstream src(pandaClassPath + fileNameSrc, ios::binary); + string fileNameDst = "./module/" + FileSystem::BaseName(dir->d_name); + ofstream dst(fileNameDst, ios::binary); + dst << src.rdbuf(); + src.close(); + dst.close(); + } + } + closedir(hPandaClassDir); + return true; +} + +bool Unhap(const string &hapPath, const string &destDir) +{ + unzFile zipfile = unzOpen(hapPath.c_str()); + if (zipfile == nullptr) { + ELOG("Failed to open ZIP archive"); + return false; + } + // Iterate through all files in the archive + if (unzGoToFirstFile(zipfile) != UNZ_OK) { + unzClose(zipfile); + ELOG("Error while going to the first file"); + return false; + } + do { + char filename[FILENAME_MAX]; + unz_file_info fileInfo; + if (unzGetCurrentFileInfo(zipfile, &fileInfo, filename, sizeof(filename), nullptr, 0, nullptr, 0) != UNZ_OK) { + unzClose(zipfile); + ELOG("Error while getting file information"); + return false; + } + string fullPath = FileSystem::NormalizePath(destDir + "/" + filename); + // Check if it is a directory + if (fullPath.back() == FileSystem::GetSeparator()[0]) { + FileSystem::MakeDir(fullPath); + } else { + FileSystem::MakeDir(FileSystem::BaseDir(fullPath)); + // It's a file, extract it + if (unzOpenCurrentFile(zipfile) != UNZ_OK) { + unzClose(zipfile); + ELOG("Failed to open file inside the archive"); + return false; + } + FILE *f = fopen(fullPath.c_str(), "wb"); + if (!f) { + unzCloseCurrentFile(zipfile); + unzClose(zipfile); + ELOG("Failed to create file for writing"); + return false; + } + char buffer[2 * FILENAME_MAX]; + int bytesRead; + while ((bytesRead = unzReadCurrentFile(zipfile, buffer, sizeof(buffer))) > 0) { + fwrite(buffer, 1, bytesRead, f); + } + fclose(f); + unzCloseCurrentFile(zipfile); + } + } while (unzGoToNextFile(zipfile) == UNZ_OK); + unzClose(zipfile); + return SetupPandaClassPath(destDir); +} diff --git a/util/Unhap.h b/util/Unhap.h new file mode 100755 index 0000000..4ddc228 --- /dev/null +++ b/util/Unhap.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __PREVIEWER__UTIL__UNHAP_H__ +#define __PREVIEWER__UTIL__UNHAP_H__ + +#include + +bool Unhap(const std::string &hapPath, const std::string &destDir); + +#endif // __PREVIEWER__UTIL__UNHAP_H__ diff --git a/util/unix/LocalSocket.cpp b/util/unix/LocalSocket.cpp index 9ad93d7..ead7f0f 100644 --- a/util/unix/LocalSocket.cpp +++ b/util/unix/LocalSocket.cpp @@ -20,6 +20,8 @@ #include #include +#include + #include "PreviewerEngineLog.h" LocalSocket::LocalSocket() : socketHandle(-1) {} @@ -47,7 +49,71 @@ bool LocalSocket::ConnectToServer(std::string name, OpenMode openMode, TransMode ELOG("connect socket failed"); return false; } + isConnected = true; + return true; +} +bool LocalSocket::RunServer(std::string name) +{ + constexpr int defaultTimeout = 5; + DisconnectFromServer(); + serverName = name; + socketHandle = socket(AF_UNIX, SOCK_STREAM, 0); + if (socketHandle < 0) { + ELOG("Failed to create socket"); + return false; + } + struct sockaddr_un un; + un.sun_family = AF_UNIX; + std::size_t length = name.copy(un.sun_path, name.size()); + un.sun_path[length] = '\0'; + unlink(un.sun_path); + struct sockaddr* sockun = reinterpret_cast(&un); + if (bind(socketHandle, sockun, sizeof(un)) < 0) { + ELOG("Bind failed"); + return false; + } + if (listen(socketHandle, defaultTimeout) < 0) { + ELOG("Listen failed"); + return false; + } + isServer = true; + isConnected = false; + return true; +} + +bool LocalSocket::ConnectClient(bool wait) +{ + if (isConnected) { + return true; + } + if (!isServer) { + return false; + } + if (!wait) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(socketHandle, &readfds); + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + int selectResult = select(socketHandle + 1, &readfds, nullptr, nullptr, &timeout); + if (selectResult <= 0) { + return false; + } + } + struct sockaddr_un clientAddr; + socklen_t clientLen = sizeof(clientAddr); + int clientSocket = accept(socketHandle, (struct sockaddr*)&clientAddr, &clientLen); + if (clientSocket < 0) { + ELOG("Accept failed"); + return false; + } + close(socketHandle); + socketHandle = clientSocket; + isConnected = true; return true; } @@ -68,32 +134,58 @@ std::string LocalSocket::GetImagePipeName(std::string baseName) const void LocalSocket::DisconnectFromServer() { - shutdown(socketHandle, SHUT_RDWR); + if (socketHandle != -1) { + shutdown(socketHandle, SHUT_RDWR); + close(socketHandle); + } + isConnected = false; } int64_t LocalSocket::ReadData(char* data, size_t length) const { + if (!isConnected) { + if (!isServer || !const_cast(this)->ConnectClient(false)) + return 0; + } if (length > UINT32_MAX) { - ELOG("LocalSocket::ReadData length must < %d", UINT32_MAX); return -1; } - int32_t bytes_read; - ioctl(socketHandle, FIONREAD, &bytes_read); - - if (bytes_read <= 0) { - return 0; + if (ioctl(socketHandle, FIONREAD, &bytes_read) < 0) { + if (isServer) { + const_cast(this)->RunServer(serverName); + } + return -1; } - - int32_t readSize = recv(socketHandle, data, length, 0); - if (readSize == 0) { - ELOG("LocalSocket::ReadData Server is shut down"); + if (bytes_read == 0) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(socketHandle, &readfds); + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + int selectResult = select(socketHandle + 1, &readfds, nullptr, nullptr, &timeout); + if (selectResult < 0) { + if (isServer) { + const_cast(this)->RunServer(serverName); + } + return -1; + } else if (selectResult == 0) { + return 0; + } } - + int32_t readSize = recv(socketHandle, data, length, 0); if (readSize < 0) { - ELOG("LocalSocket::ReadData ReadFile failed"); + if (isServer) { + const_cast(this)->RunServer(serverName); + } + return -1; + } else if (readSize == 0) { + if (isServer) { + const_cast(this)->RunServer(serverName); + } + return 0; } - return readSize; } @@ -108,11 +200,9 @@ size_t LocalSocket::WriteData(const void* data, size_t length) const if (writeSize == 0) { ELOG("LocalSocket::WriteData Server is shut down"); } - if (writeSize < 0) { ELOG("LocalSocket::WriteData ReadFile failed"); } - return writeSize; } diff --git a/util/windows/LocalSocket.cpp b/util/windows/LocalSocket.cpp index d974cf6..1a0771f 100644 --- a/util/windows/LocalSocket.cpp +++ b/util/windows/LocalSocket.cpp @@ -19,7 +19,10 @@ LocalSocket::LocalSocket() : pipeHandle(nullptr) {} -LocalSocket::~LocalSocket() {} +LocalSocket::~LocalSocket() +{ + DisconnectFromServer(); +} bool LocalSocket::ConnectToServer(std::string name, LocalSocket::OpenMode openMode, TransMode transMode) { @@ -38,6 +41,58 @@ bool LocalSocket::ConnectToServer(std::string name, LocalSocket::OpenMode openMo return false; } + isConnected = true; + return true; +} + +bool LocalSocket::RunServer(std::string name) +{ + DisconnectFromServer(); + serverName = name; + std::wstring tempName = std::wstring(name.begin(), name.end()); + constexpr int bufSize = 4096; + pipeHandle = CreateNamedPipeW( + tempName.c_str(), + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + bufSize, + bufSize, + 0, + nullptr); + if (pipeHandle == INVALID_HANDLE_VALUE) { + ELOG("LocalSocket::RunServer CreateNamedPipeW failed: %d", GetLastError()); + return false; + } + isServer = true; + isConnected = false; + return true; +} + +bool LocalSocket::ConnectClient(bool wait) +{ + if (isConnected) { + return true; + } + if (!isServer) { + return false; + } + + if (!wait) { + DWORD bytesAvailable = 0; + BOOL result = PeekNamedPipe(pipeHandle, nullptr, 0, nullptr, &bytesAvailable, nullptr); + if (!result || bytesAvailable == 0) { + return false; + } + } + + BOOL connected = ConnectNamedPipe(pipeHandle, nullptr) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + if (!connected) { + ELOG("LocalSocket::ConnectClient ConnectNamedPipe failed: %d", GetLastError()); + return false; + } + + isConnected = true; return true; } @@ -58,11 +113,23 @@ std::string LocalSocket::GetImagePipeName(std::string baseName) const void LocalSocket::DisconnectFromServer() { - CloseHandle(pipeHandle); + if (pipeHandle != INVALID_HANDLE_VALUE) { + FlushFileBuffers(pipeHandle); + DisconnectNamedPipe(pipeHandle); + CloseHandle(pipeHandle); + pipeHandle = INVALID_HANDLE_VALUE; + } + isConnected = false; } int64_t LocalSocket::ReadData(char* data, size_t length) const { + if (!isConnected) { + if (!isServer || !const_cast(this)->ConnectClient(false)) { + return 0; + } + } + if (length > UINT32_MAX) { ELOG("LocalSocket::ReadData length must < %d", UINT32_MAX); return -1; @@ -70,7 +137,11 @@ int64_t LocalSocket::ReadData(char* data, size_t length) const DWORD readSize = 0; if (!PeekNamedPipe(pipeHandle, nullptr, 0, nullptr, &readSize, nullptr)) { - return 0; + ELOG("LocalSocket::ReadData PeekNamedPipe failed: %d", GetLastError()); + if (isServer) { + const_cast(this)->RunServer(serverName); + } + return -1; } if (readSize == 0) { @@ -80,6 +151,9 @@ int64_t LocalSocket::ReadData(char* data, size_t length) const if (!ReadFile(pipeHandle, data, static_cast(length), &readSize, NULL)) { DWORD error = GetLastError(); ELOG("LocalSocket::ReadData ReadFile failed: %d", error); + if (isServer) { + const_cast(this)->RunServer(serverName); + } return 0 - static_cast(error); } return readSize; -- Gitee