Add run external processes

This commit is contained in:
Oleg Korshul
2025-09-03 22:55:46 +03:00
parent 079ffed6b5
commit c24ee5c879
4 changed files with 317 additions and 2 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ Thumbs.db
.idea/ .idea/
*Makefile* *Makefile*
*qmake.stash *qmake.stash
.qtc_clangd

View File

@ -121,6 +121,9 @@ SOURCES += \
$$PWD/src/keychain.cpp \ $$PWD/src/keychain.cpp \
$$PWD/src/filelocker.cpp $$PWD/src/filelocker.cpp
HEADERS += \
$$PWD/src/cefwrapper/external_process.h
SOURCES += \ SOURCES += \
$$CORE_ROOT_DIR/Common/OfficeFileFormatChecker2.cpp \ $$CORE_ROOT_DIR/Common/OfficeFileFormatChecker2.cpp \
$$CORE_ROOT_DIR/Common/3dParty/pole/pole.cpp \ $$CORE_ROOT_DIR/Common/3dParty/pole/pole.cpp \

View File

@ -29,6 +29,7 @@
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
* *
*/ */
#include "./external_process.h"
#include "./client_renderer.h" #include "./client_renderer.h"
@ -592,7 +593,7 @@ namespace asc_client_renderer
IMPLEMENT_REFCOUNTING(CLocalFileConvertV8Handler); IMPLEMENT_REFCOUNTING(CLocalFileConvertV8Handler);
}; };
class CAscEditorNativeV8Handler : public CefV8Handler, public CAIToolsHelper class CAscEditorNativeV8Handler : public CefV8Handler, public CAIToolsHelper, public NSProcesses::CProcessRunnerCallback
{ {
class CSavedPageInfo class CSavedPageInfo
{ {
@ -717,6 +718,10 @@ namespace asc_client_renderer
bool m_bIsMacrosesSupport; bool m_bIsMacrosesSupport;
bool m_bIsPluginsSupport; bool m_bIsPluginsSupport;
// External processes
CefRefPtr<CefFrame> m_frame;
NSProcesses::CProcessManager* m_external_processes;
CAscEditorNativeV8Handler(const std::wstring& sUrl) CAscEditorNativeV8Handler(const std::wstring& sUrl)
{ {
m_etType = AscEditorType::etUndefined; m_etType = AscEditorType::etUndefined;
@ -767,6 +772,8 @@ namespace asc_client_renderer
m_bEditorsCloudFeaturesCheck = false; m_bEditorsCloudFeaturesCheck = false;
m_arCloudFeaturesBlackList.push_back("personal.onlyoffice.com"); m_arCloudFeaturesBlackList.push_back("personal.onlyoffice.com");
m_external_processes = NULL;
} }
void CheckDefaults() void CheckDefaults()
@ -811,6 +818,9 @@ namespace asc_client_renderer
virtual ~CAscEditorNativeV8Handler() virtual ~CAscEditorNativeV8Handler()
{ {
RELEASEOBJECT(m_external_processes);
m_frame.reset();
if (m_pAES_Key) if (m_pAES_Key)
NSOpenSSL::openssl_free(m_pAES_Key); NSOpenSSL::openssl_free(m_pAES_Key);
NSBase::Release(m_pLocalApplicationFonts); NSBase::Release(m_pLocalApplicationFonts);
@ -939,6 +949,33 @@ return undefined; \n\
return true; return true;
} }
virtual void process_callback(const int& id, const NSProcesses::StreamType& type, const std::string& message) OVERRIDE
{
if (m_frame && m_external_processes)
{
std::string sMessageDst = message;
NSStringUtils::string_replaceA(sMessageDst, "\\", "\\\\");
NSStringUtils::string_replaceA(sMessageDst, "\"", "\\\"");
NSStringUtils::string_replaceA(sMessageDst, "\r", "");
NSStringUtils::string_replaceA(sMessageDst, "\n", "");
std::string sCode = "(function(){\n\
if (!window._external_process_callback) return;\n\
if (!window._external_process_callback[" + std::to_string(id) + "]) return;\n\
window._external_process_callback[" + std::to_string(id) + "]._onprocess(" + std::to_string((int)type) + ", \"" + sMessageDst + "\");\n\
})();";
CefPostTask(TID_RENDERER, base::BindOnce([](CefRefPtr<CefFrame> frame, const std::string& code) {
if (frame) {
frame->ExecuteJavaScript(
code,
frame->GetURL(), 0);
}
},
m_frame, sCode));
}
}
virtual bool Execute(const CefString& sMessageName, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) OVERRIDE virtual bool Execute(const CefString& sMessageName, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) OVERRIDE
{ {
std::string name = sMessageName.ToString(); std::string name = sMessageName.ToString();
@ -2425,6 +2462,35 @@ window.AscDesktopEditor.attachEvent=function(name,callback){if(undefined===windo
window.AscDesktopEditor.LocalFileTemplates=function(e){window.__lang_checker_templates__=e||\"\",window.__resize_checker_templates__||(window.__resize_checker_templates__=!0,window.addEventListener(\"resize\",function(){window.AscDesktopEditor._LocalFileTemplates(window.__lang_checker_templates__,(100*window.devicePixelRatio)>>0)})),window.AscDesktopEditor._LocalFileTemplates(window.__lang_checker_templates__,(100*window.devicePixelRatio)>>0)};"; window.AscDesktopEditor.LocalFileTemplates=function(e){window.__lang_checker_templates__=e||\"\",window.__resize_checker_templates__||(window.__resize_checker_templates__=!0,window.addEventListener(\"resize\",function(){window.AscDesktopEditor._LocalFileTemplates(window.__lang_checker_templates__,(100*window.devicePixelRatio)>>0)})),window.AscDesktopEditor._LocalFileTemplates(window.__lang_checker_templates__,(100*window.devicePixelRatio)>>0)};";
_frame->ExecuteJavaScript(sCodeInitJS, _frame->GetURL(), 0); _frame->ExecuteJavaScript(sCodeInitJS, _frame->GetURL(), 0);
std::string sUrl = _frame->GetURL().ToString();
if (0 == sUrl.find("file:///") || 0 == sUrl.find("onlyoffice://"))
{
std::string sCode = "function ExternalProcess(command)\n\
{\n\
this.command = command;this.id = -1;\n\
this.start = function() {\n\
this.id = AscDesktopEditor._createProcess(this.command);\n\
window._external_process_callback[this.id] = this;\n\
};\n\
this.end = function() {\n\
if (window._external_process_callback[this.id])\n\
delete window._external_process_callback[this.id];\n\
AscDesktopEditor._endProcess(this.id);\n\
};\n\
this._onprocess = function(type, message) {\n\
if (2 === type && window._external_process_callback[this.id])\n\
delete window._external_process_callback[this.id];\n\
if (this.onprocess)\n\
this.onprocess(type, message);\n\
};\n\
if (!window._external_process_callback) {\n\
window._external_process_callback = {};\n\
}\n\
}";
_frame->ExecuteJavaScript(sCode, _frame->GetURL(), 0);
}
} }
return true; return true;
@ -4505,6 +4571,24 @@ window.AscDesktopEditor.CallInFrame(\"" +
return true; return true;
} }
else if (name == "_createProcess")
{
if (!m_external_processes)
m_external_processes = new NSProcesses::CProcessManager(this);
std::string command = arguments[0]->GetStringValue().ToString();
int process_id = m_external_processes->Start(command);
retval = CefV8Value::CreateInt(process_id);
return true;
}
else if (name == "_endProcess")
{
int process_id = arguments[0]->GetIntValue();
if (m_external_processes)
m_external_processes->End(process_id);
return true;
}
// Function does not exist. // Function does not exist.
return false; return false;
@ -5097,6 +5181,17 @@ if (targetElem) { targetElem.dispatchEvent(event); }})();";
message_router_ = CefMessageRouterRendererSide::Create(config); message_router_ = CefMessageRouterRendererSide::Create(config);
} }
virtual void OnBrowserCreated(CefRefPtr<client::ClientAppRenderer> app,
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDictionaryValue> extra_info) OVERRIDE
{
}
virtual void OnBrowserDestroyed(CefRefPtr<client::ClientAppRenderer> app,
CefRefPtr<CefBrowser> browser) OVERRIDE
{
}
virtual void OnContextCreated(CefRefPtr<client::ClientAppRenderer> app, CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) OVERRIDE virtual void OnContextCreated(CefRefPtr<client::ClientAppRenderer> app, CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) OVERRIDE
{ {
message_router_->OnContextCreated(browser, frame, context); message_router_->OnContextCreated(browser, frame, context);
@ -5119,7 +5214,7 @@ if (targetElem) { targetElem.dispatchEvent(event); }})();";
CefRefPtr<CefV8Handler> handler = pWrapper; CefRefPtr<CefV8Handler> handler = pWrapper;
#define EXTEND_METHODS_COUNT 190 #define EXTEND_METHODS_COUNT 192
const char* methods[EXTEND_METHODS_COUNT] = { const char* methods[EXTEND_METHODS_COUNT] = {
"Copy", "Copy",
"Paste", "Paste",
@ -5384,6 +5479,9 @@ if (targetElem) { targetElem.dispatchEvent(event); }})();";
"_convertFileExternal", "_convertFileExternal",
"_onConvertFileExternal", "_onConvertFileExternal",
"_createProcess",
"_endProcess",
NULL}; NULL};
ExtendObject(obj, handler, methods); ExtendObject(obj, handler, methods);
@ -5453,6 +5551,8 @@ return this.split(str).join(newStr);\
#else #else
browser->SendProcessMessage(PID_BROWSER, message); browser->SendProcessMessage(PID_BROWSER, message);
#endif #endif
pWrapper->m_frame = curFrame;
} }
virtual void OnContextReleased(CefRefPtr<client::ClientAppRenderer> app, CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) OVERRIDE virtual void OnContextReleased(CefRefPtr<client::ClientAppRenderer> app, CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) OVERRIDE

View File

@ -0,0 +1,211 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <windows.h>
#endif
#include <boost/process.hpp>
#include <thread>
#include <atomic>
#include <string>
#include <iostream>
#include "../../../../../core/DesktopEditor/common/File.h"
#include "../../../../../core/DesktopEditor/graphics/TemporaryCS.h"
namespace NSProcesses
{
enum class StreamType
{
StdOut,
StdErr,
Stop
};
class CProcessRunnerCallback
{
public:
CProcessRunnerCallback(){}
virtual ~CProcessRunnerCallback(){}
virtual void process_callback(const int& id, const StreamType& type, const std::string& message) = 0;
};
class CProcessRunner
{
public:
CProcessRunner(const int& id, const std::string& command, CProcessRunnerCallback* cb)
: m_command(command), m_callback(cb), m_running(false), m_id(id)
{
}
~CProcessRunner()
{
stop();
}
void start()
{
if (m_running)
return;
m_running = true;
m_worker = std::thread([this]() {
run();
});
}
void stop()
{
if (!m_running)
return;
m_running = false;
if (m_proc.valid() && m_proc.running())
m_proc.terminate();
if (m_worker.joinable())
m_worker.join();
}
int get_id()
{
return m_id;
}
private:
void run()
{
boost::process::ipstream out;
boost::process::ipstream err;
try
{
#ifdef _WIN32
std::wstring commandW = UTF8_TO_U(m_command);
m_proc = boost::process::child(commandW, boost::process::std_out > out, boost::process::std_err > err);
#else
m_proc = boost::process::child(m_command, boost::process::std_out > out, boost::process::std_err > err);
#endif
}
catch (const std::exception& ex)
{
m_callback->process_callback(m_id, StreamType::StdErr, std::string("Failed to start process: ") + ex.what() + "\n");
return;
}
std::thread t_out([this, &out]() {
std::string line;
while (m_running && std::getline(out, line))
{
m_callback->process_callback(m_id, StreamType::StdOut, line);
}
});
std::thread t_err([this, &err]() {
std::string line;
while (m_running && std::getline(err, line))
{
m_callback->process_callback(m_id, StreamType::StdErr, line);
}
});
if (m_proc.running())
m_proc.wait();
m_callback->process_callback(m_id, StreamType::Stop, "");
t_out.join();
t_err.join();
}
private:
std::string m_command;
CProcessRunnerCallback* m_callback;
std::thread m_worker;
std::atomic<bool> m_running;
boost::process::child m_proc;
int m_id;
};
class CProcessManager : public CProcessRunnerCallback
{
private:
std::vector<CProcessRunner*> m_processes;
int m_counter;
bool m_enable_callback;
CProcessRunnerCallback* m_callback;
NSCriticalSection::CRITICAL_SECTION m_cs;
public:
CProcessManager(CProcessRunnerCallback* cb)
{
m_cs.InitializeCriticalSection();
m_counter = 1;
m_enable_callback = true;
m_callback = cb;
}
~CProcessManager()
{
StopAll();
m_cs.DeleteCriticalSection();
}
int Start(const std::string& command)
{
m_cs.Enter();
int cur_id = m_counter++;
CProcessRunner* runner = new CProcessRunner(cur_id, command, this);
m_processes.push_back(runner);
m_cs.Leave();
runner->start();
return cur_id;
}
void End(const int& id)
{
CTemporaryCS oCS(&m_cs);
for (std::vector<CProcessRunner*>::iterator iter = m_processes.begin(); iter != m_processes.end(); iter++)
{
CProcessRunner* runner = *iter;
if (runner->get_id() == id)
{
delete runner;
m_processes.erase(iter);
return;
}
}
}
void StopAll()
{
CTemporaryCS oCS(&m_cs);
m_enable_callback = false;
for (std::vector<CProcessRunner*>::iterator iter = m_processes.begin(); iter != m_processes.end(); iter++)
{
CProcessRunner* runner = *iter;
delete runner;
}
m_processes.clear();
}
void DisableCallbacks()
{
CTemporaryCS oCS(&m_cs);
m_enable_callback = false;
}
virtual void process_callback(const int& id, const StreamType& type, const std::string& message)
{
CTemporaryCS oCS(&m_cs);
if (!m_enable_callback)
return;
m_callback->process_callback(id, type, message);
}
};
}