• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 31989261171

17 Aug 2026 02:51AM UTC coverage: 19.411%. First build
31989261171

Pull #2212

github

web-flow
Merge c52f4383f into bb5e533af
Pull Request #2212: test: make rendering testable from master

9 of 214 new or added lines in 12 files covered. (4.21%)

41120 of 211834 relevant lines covered (19.41%)

4317.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.1
/src/plugins/score-plugin-js/JS/ApplicationPlugin.cpp
1
#include "ApplicationPlugin.hpp"
2

3
#include <JS/DocumentPlugin.hpp>
4
#include <JS/Qml/DeviceContext.hpp>
5
#include <JS/Qml/EditContext.hpp>
6
#include <JS/Qml/Utils.hpp>
7
#include <JS/Qml/ViewContext.hpp>
8
#include <Library/LibrarySettings.hpp>
9
#include <LocalTree/LocalTreeDocumentPlugin.hpp>
10

11
#include <core/application/ApplicationInterface.hpp>
12
#include <core/document/Document.hpp>
13
#include <core/presenter/DocumentManager.hpp>
14

15
#include <ossia/detail/thread.hpp>
16

17
#include <ossia-qt/invoke.hpp>
18
#include <ossia-qt/qml_protocols.hpp>
19

20
#include <QCommandLineParser>
21
#include <QFile>
22
#include <QFileInfo>
23

24
#if __has_include(<QQuickWindow>)
25
#include <QGuiApplication>
26
#include <QQuickItem>
27
#include <QQuickWindow>
28
#endif
29

30
#if SCORE_HAS_GPU_JS
31
#include <Gfx/Settings/Model.hpp>
32
#endif
33

34
#include <ossia/network/context.hpp>
35

36
namespace JS
37
{
38
// Whether --script was given a program or the path of one. An existing file is
39
// always a path; anything with JS punctuation in it is a program.
40
static bool stringIsScript(const QString& input)
151✔
41
{
42
  if(input.isEmpty())
151✔
43
    return false;
151✔
44

NEW
45
  if(QFileInfo fileInfo{input}; fileInfo.exists() && fileInfo.isFile())
×
NEW
46
    return false;
×
47

NEW
48
  if(input.length() > 4096)
×
NEW
49
    return true;
×
50

NEW
51
  for(QChar ch : input)
×
52
  {
NEW
53
    const char16_t c = ch.unicode();
×
NEW
54
    if(c == '\n' || c == '\r' || c == ';' || c == '{' || c == '}' || c == '('
×
NEW
55
       || c == ')')
×
NEW
56
      return true;
×
57
  }
58

NEW
59
  return true;
×
60
}
151✔
61

62
ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationContext& ctx)
151✔
63
    : score::GUIApplicationPlugin{ctx}
151✔
64
{
151✔
65
  // For the console
66
  m_consoleEngine.globalObject().setProperty("Score", m_consoleEngine.newQObject(new EditJsContext));
151✔
67
  m_consoleEngine.globalObject().setProperty("Util", m_consoleEngine.newQObject(new JsUtils));
151✔
68
  m_consoleEngine.globalObject().setProperty(
302✔
69
      "System", m_consoleEngine.newQObject(new JsSystem));
151✔
70
  m_consoleEngine.globalObject().setProperty(
302✔
71
      "Library", m_consoleEngine.newQObject(new JsLibrary));
151✔
72
  m_consoleEngine.globalObject().setProperty("Device", m_consoleEngine.newQObject(new DeviceContext{m_consoleEngine}));
151✔
73
  m_consoleEngine.globalObject().setProperty("View", m_consoleEngine.newQObject(new JsViewContext));
151✔
74
  connect(&m_consoleEngine, &QQmlEngine::exit, this, [&](int retCode) {
151✔
75
    for(auto& doc : score::GUIAppContext().docManager.documents())
×
76
      doc->commandStack().markCurrentIndexAsSaved();
×
77
    // quit() is exit(0), which discarded the code Qt.exit() was given: a script
78
    // could stop the app but never report that it had failed.
NEW
79
    qApp->exit(retCode);
×
80
    QTimer::singleShot(
×
81
        500, [] { score::GUIApplicationInterface::instance().forceExit(); });
×
82
  });
×
83
  m_asioContext = std::make_shared<ossia::net::network_context>();
151✔
84
  m_processMessages = true;
151✔
85
  m_consoleEngine.globalObject().setProperty(
302✔
86
      "Protocols",
151✔
87
      m_consoleEngine.newQObject(new ossia::qt::qml_protocols{m_asioContext, this}));
151✔
88
  m_asioThread = std::thread{[this] {
302✔
89
    ossia::set_thread_name("ossia app asio");
151✔
90
    while(m_processMessages)
302✔
91
    {
92
      m_asioContext->run();
151✔
93
    }
94
  }};
151✔
95

96
  // For scripts of processes that run in the ui thread:
97
  m_scriptProcessUIEngine.globalObject().setProperty(
302✔
98
      "Util", m_scriptProcessUIEngine.newQObject(new JsUtils));
151✔
99
  m_scriptProcessUIEngine.globalObject().setProperty(
302✔
100
      "System", m_scriptProcessUIEngine.newQObject(new JsSystem));
151✔
101
  m_scriptProcessUIEngine.globalObject().setProperty(
302✔
102
      "Library", m_scriptProcessUIEngine.newQObject(new JsLibrary));
151✔
103
  m_scriptProcessUIEngine.globalObject().setProperty(
302✔
104
      "View", m_scriptProcessUIEngine.newQObject(new JsViewContext));
151✔
105

106
  // Command-line option parsing
107
  QCommandLineParser parser;
151✔
108

109
  QCommandLineOption script_opt(
151✔
110
      "script", QCoreApplication::translate("js", "script"), "Script", "");
151✔
111
  parser.addOption(script_opt);
151✔
112

113
  parser.parse(ctx.applicationSettings.arguments);
151✔
114
  const auto script = parser.value(script_opt);
151✔
115
  if(stringIsScript(script))
151✔
116
  {
NEW
117
    this->m_start_script = script;
×
NEW
118
  }
×
119
  else if(!script.isEmpty())
151✔
120
  {
NEW
121
    QFile f{script};
×
NEW
122
    if(f.open(QIODevice::ReadOnly))
×
123
    {
NEW
124
      this->m_start_script = f.readAll();
×
NEW
125
      this->m_start_script_name = script;
×
NEW
126
      this->m_start_script_path = QFileInfo{f}.canonicalPath();
×
NEW
127
    }
×
128
    else
129
    {
NEW
130
      qCritical() << "--script: cannot open" << script << ":" << f.errorString();
×
NEW
131
      this->m_start_script_failed = true;
×
132
    }
NEW
133
  }
×
134
}
×
135

136
void ApplicationPlugin::on_newDocument(score::Document& doc)
59✔
137
{
138
  score::addDocumentPlugin<DocumentPlugin>(doc);
59✔
139
}
59✔
140

141
ApplicationPlugin::~ApplicationPlugin()
300✔
142
{
150✔
143
  m_processMessages = false;
150✔
144
  m_asioContext->context.stop();
150✔
145
  m_asioThread.join();
150✔
146
}
300✔
147

148
void ApplicationPlugin::on_createdDocument(score::Document& doc)
63✔
149
{
150
  // Local Tree
151
  LocalTree::DocumentPlugin* lt = doc.context().findPlugin<LocalTree::DocumentPlugin>();
63✔
152
  if(lt)
63✔
153
  {
154
    auto& root = lt->device().get_root_node();
63✔
155

156
    auto node = root.create_child("script");
63✔
157
    auto address = node->create_parameter(ossia::val_type::STRING);
63✔
158
    address->set_value(std::string{});
63✔
159
    address->set_access(ossia::access_mode::SET);
63✔
160
    address->add_callback([&](const ossia::value& v) {
63✔
161
      ossia::qt::run_async(
×
162
          this, [this, str = QString::fromStdString(ossia::convert<std::string>(v))] {
×
163
        auto res = m_consoleEngine.evaluate(str);
×
164
        if(res.isError())
×
165
        {
166
          qDebug() << res.toString();
×
167
        }
×
168
      });
×
169
    });
×
170
  }
63✔
171

172
  // Custom data
173
  if(auto customData = doc.context().findPlugin<DocumentPlugin>(); !customData)
63✔
174
    score::addDocumentPlugin<DocumentPlugin>(doc);
×
175

176
  if(m_start_script_failed)
63✔
177
  {
NEW
178
    qGuiApp->exit(2);
×
NEW
179
    return;
×
180
  }
181

182
  if(!m_start_script.isEmpty())
63✔
183
  {
NEW
184
    QTimer::singleShot(100, this, [this] {
×
NEW
185
      if(!m_start_script_path.isEmpty())
×
NEW
186
        m_consoleEngine.addImportPath(m_start_script_path);
×
187

188
      // A --script that throws used to fail silently with exit code 0: an
189
      // unresolvable readFile returns an empty string, eval("") is a no-op, and
190
      // the process exits reporting success. A harness cannot tell that from a
191
      // pass, so the whole run is unfalsifiable.
NEW
192
      const auto res = m_consoleEngine.evaluate(m_start_script, m_start_script_name);
×
NEW
193
      if(res.isError())
×
194
      {
NEW
195
        qCritical().noquote()
×
NEW
196
            << "--script:"
×
NEW
197
            << (m_start_script_name.isEmpty() ? QStringLiteral("<inline>")
×
NEW
198
                                              : m_start_script_name)
×
NEW
199
            << "line" << res.property("lineNumber").toInt() << ":" << res.toString();
×
NEW
200
        qGuiApp->exit(3);
×
NEW
201
      }
×
NEW
202
    });
×
203
  }
×
204
}
63✔
205
void ApplicationPlugin::afterStartup()
×
206
{
207
  // Dummy engine setup for JS processes
208
  // eng.importModule(
209
  //     "/home/jcelerier/Documents/ossia/score/packages/default/Scripts/include/"
210
  //     "tonal.mjs");
211
  for(auto& p : this->context.settings<Library::Settings::Model>().getIncludePaths())
×
212
  {
213
    m_scriptProcessUIEngine.addImportPath(p);
×
214
  }
215

216
#if __has_include(<QQuickWindow>)
217
  if(QFileInfo f{context.applicationSettings.ui}; f.isFile())
×
218
  {
219
    m_comp = new QQmlComponent{&m_consoleEngine, f.absoluteFilePath(), this};
×
220

221
    if(auto obj = m_comp->create())
×
222
    {
223
      if(auto item = qobject_cast<QQuickItem*>(obj))
×
224
      {
225
        m_window = new QQuickWindow{};
×
226
  // QWidget gets these from QWidgetPrivate::adjustFlags; a bare QQuickWindow
227
  // does not, and on platforms where Qt draws the chrome itself (wasm) that
228
  // leaves the window with no title bar, close or minimise button.
229
  m_window->setFlags(
×
230
      m_window->flags() | Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint
×
231
      | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint
×
232
      | Qt::WindowMaximizeButtonHint);
×
233
#if defined(__EMSCRIPTEN__)
234
  // Qt for wasm reports ShowIsFullScreen unconditionally, so QWindow::show()
235
  // turns into showFullScreen() for every top level: the requested size is
236
  // discarded and the full-screen state suppresses the frame. Only Qt::Dialog
237
  // and Qt::Popup opt out (QWasmIntegration::defaultWindowState).
238
  m_window->setFlags(m_window->flags() | Qt::Dialog);
239
#endif
240
        m_window->setWidth(640);
×
241
        m_window->setHeight(480);
×
242
        item->setParentItem(m_window->contentItem());
×
243
#if defined(__EMSCRIPTEN__)
244
        m_window->showNormal();
245
#else
246
        m_window->show();
×
247
#endif
248
        return;
×
249
      }
250
    }
×
251
    else
252
    {
253
      qDebug() << m_comp->errorString();
×
254
      qGuiApp->exit(1);
×
255
    }
256
    delete m_comp;
×
257
  }
×
258
#endif
259
}
×
260
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc