diff --git a/BUILD.gn b/BUILD.gn index 2f4497a72af247166e45fade72c9d409e5d2745a..3edeb1bd9482c7bd7b4625064472ecb75d7abd93 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 0000000000000000000000000000000000000000..e63f818eab8946df8f177518b171759bf970f94c --- /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 c6b53687789ea598c1f49f0e1228690833196749..cbb7aa2d628337b4e9744a406b40859783487eef 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 82cac143aa0dc46a008bb5f815082bb183772956..caf8ab26071bfaf15ad5a9c51af86fdf24dad4af 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 5bde209aa7cfa3eb2c26c0392b6a66006705a7dc..0d0e2381a185d54f4430295f125c8e469c52ad3f 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 7f81325651bff745a8e7ade3fbe55d36b5acefb0..209bea9183665e0f152fc1a0a15e98caf10fd7ca 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 0db2ef2f7687a19729748a3069796ea164bc2352..e8236cf9c45a1a6d91ea6283a9bb3962fc361e94 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 706a17cee46f2092073fd5580e7ea595c5bb0c7a..f9358c91f673edef5509453779eb919f275fcff2 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 6daeaec9ae0138fa7253200b78160a6b761113a1..887bcd740d930f35f357344f74028aeef9e4c43f 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 efeb229e68da96a0a896204d72be5a03ef9cf6da..e6917cf51494bac057ebf4d20d0ac63ef77908d7 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 ef817b59b3bb7dedb8a0cfebc515bdc292ddf227..7fd32348252a84fd47677cca4500b001b0a92187 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 0000000000000000000000000000000000000000..2dc51a7995f430d0240937d6bb113fb79d6c659a --- /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 dcd728e70ddce9f64bac6f936d64b2448872eebd..8670e81c4c1ab18498057a04af7aab6fce5c7ecf 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 f7e5f54ac29059574d7eb0430d95178741175e9b..0a7d2d3048bd65a8a0bff07c071ed44db11021f4 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 9d9b5043c7e9612124b495d24c0436ef9e0249b2..93a237188102efaf2fe1d5a2ad236731e30b2b64 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 4c84cbe36903ab789018df1f019006aeed767489..29d8914ee0de6440f98ba54bab8383fbf3283ccf 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 5b91a2205e80ab54784dbc76bddf1ed81c662711..4dcb545dddfd89e553cb3a387bbee8bf1063144a 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 55e8ecc5cccb1fd521643c278fc7778b5fa5ee27..f66efb9d6f257196d0e6897a25f97b86b74f683b 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 8e47f41c68ba093060f7f52260346e51fc43c4bf..0e64c6ea998ea7d6f46d7aa6501c133ca099c2b6 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 2db7d880b05c767f720c57082577c039d7898c7b..d2b8dceac5c796156df9bb28a262c4accec558a4 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 7b6200fff5acd6db55beb73b59ad7a91a7d0e4a1..2f6d054a74bb00d9fe7de2afabd11c8f3da69c81 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 ae9b935c7abdd1581dbb60ee3f7713b12059ff91..7fdace6d8df804ddfbbc26072f60c91a2a78ed8c 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 f4ebb369a508d3233769a166bbc216636094b8de..dd568ec29b3086e1130adc9e39e02613c23d5c18 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 7ebba9193b08fde544671cabf9071e076507344f..22dc9410d6371b186039e217abfb3a3331eace9e 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 0000000000000000000000000000000000000000..963a6a93ebbebe394a940e5640aad5f718715d90 --- /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 0000000000000000000000000000000000000000..dd42d1a615a0e2682530692eef7a28b10f4c8258 --- /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 b6edd12f4b0b37376edaf94843d994402065538f..0029180e5ad9016ee623d6ee14f2d66f9e23c38b 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 297f9b4e53bcc07401baf4dbd08aa9b102ffba85..758db938fe15526fb1aad74c9333cfaba4729fdd 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 11e61e92752805061f02b248d037796026cc43e6..6b143c83cbc27c422c4f13c4a25cae50cd7fb8f9 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 a7ff6d5df59fccb0f9f133d6e7a7a8e3f11b11db..ab28b3117444d6bc2cb9a53b56285fdc793074c1 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 ea93a13197ce0e5a4f5d5120965b3fd85d9a7f83..6908115911c1f1548d13ac7581bd1aef861499c5 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 4201cf0baead47ade32a9ace034d4037e017b3a2..0e40e162980de025f11f3db101ea58dd7d5cc180 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 80d6610f73e3b678b94982c147b3d1108fb2d069..986e87c7c6d89d18158a271981faa1d505dd858e 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 456385e5823d8b9bd978015c22fd833d3d8ec0f0..5c1e9a48376a4586214388ebb964df62eaaa7de6 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 0000000000000000000000000000000000000000..08e900e26d134cbc9ccb22d81ed4f748ba3604b8 --- /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 fb700ed8a732ca896057f8804a6dec6fcd65b2cc..51f855ea1e3b6077fc6e6f5310f51bf4bd0373b5 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 c6e1cd802e68f97d4b50394b4c45c59e26d49d21..a199d74e8cffd4a432083e291f66cac50727ca7d 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 5c8e3232606d2953d9f93044eb6aa949bf3dfd1e..e264363c4c725b629d233e5c2c2f75c37c5bcc4f 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 ccd9a08939adea34ec84813e5a51aa5aef25088e..6efa77f57929ee410fffaed818d7c84acd094586 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 ec6a609ca6bb490e57b24f520624a6ed4077d6c7..c5727caf2c5304d5fd58525b4fbcf8338071ac78 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 acf01e34d4f2a79f4386e1e52432df15ab99993f..365478f7b5836bac8c126912ada99a5431109352 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 9d62090b3879b4b7c1f826cf7bf0efc4fb3a7a5c..da38fc5c4bb23bc9ff986414f5dbc0150a58294e 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 edafa0f938f38e2db12421cf578d9b4a6ee951c5..d1cf2884062605de7caeb7cc58f101e7722690af 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 0e5ccc1a91059f322849ff77aa521bbfaf4b76ef..c6230eb6eba700e0b0f974bc083b18842be03046 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 9ba31e29f8ce2b6a4f4417c024c645dce042d839..caf9a1fafc86c23672e8f697724c0dd33f18c6f6 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 0000000000000000000000000000000000000000..9cff441ef3e241e4cf7c37f6d8aff6ec13fc6c35 --- /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 0000000000000000000000000000000000000000..4ddc228393b504db03d4596a5dc29560aa25e7b3 --- /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 9ad93d7bfd7faf43fc2c6b64a7673a7ad2a9987d..ead7f0f665f0eb23b3d76c027d8695b2f48d27fa 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 d974cf6aa4ea5da24460489003215db8111e1d81..1a0771faee69e4478c345061ad6bdf11ff6a7e48 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;