1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2011-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29
30 #include <utility>
31
32 #include <QApplication>
33 #include <QClipboard>
34 #include <QFile>
35 #include <QTextCodec>
36 #include <QThread>
37 #include <QTimer>
38 #include <QTranslator>
39
40 #include "interpreter-qobject.h"
41 #include "main-window.h"
42 #include "octave-qobject.h"
43 #include "qt-application.h"
44 #include "qt-interpreter-events.h"
45 #include "resource-manager.h"
46 #include "shortcut-manager.h"
47
48 // Bug #55940 (Disable App Nap on Mac)
49 #if defined (Q_OS_MAC)
50 # include <objc/runtime.h>
51 # include <objc/message.h>
52 #endif
53
54 #include "oct-env.h"
55 #include "version.h"
56
57 #include "ovl.h"
58
59
60 // Bug #55940 (Disable App Nap on Mac)
61 #if defined (Q_OS_MAC)
disable_app_nap(void)62 static void disable_app_nap (void)
63 {
64 Class process_info_class;
65 SEL process_info_selector;
66 SEL begin_activity_with_options_selector;
67 id process_info;
68 id reason_string;
69 id osx_latencycritical_activity;
70
71 // Option codes found at https://stackoverflow.com/questions/22784886/what-can-make-nanosleep-drift-with-exactly-10-sec-on-mac-os-x-10-9/32729281#32729281
72 unsigned long long NSActivityUserInitiatedAllowingIdleSystemSleep = 0x00FFFFFFULL;
73 unsigned long long NSActivityLatencyCritical = 0xFF00000000ULL;
74
75 // Avoid errors on older versions of OS X
76 process_info_class = static_cast<Class> (objc_getClass ("NSProcessInfo"));
77 if (process_info_class == nil)
78 return;
79
80 process_info_selector = sel_getUid ("processInfo");
81 if (class_getClassMethod (process_info_class, process_info_selector)
82 == nullptr)
83 return;
84
85 begin_activity_with_options_selector = sel_getUid ("beginActivityWithOptions:reason:");
86 if (class_getInstanceMethod (process_info_class,
87 begin_activity_with_options_selector)
88 == nullptr)
89 return;
90
91 process_info = reinterpret_cast<id (*) (id, SEL)> (objc_msgSend)
92 (reinterpret_cast<id> (process_info_class),
93 process_info_selector);
94 if (process_info == nil)
95 return;
96
97 reason_string = reinterpret_cast<id (*) (id, SEL)> (objc_msgSend)
98 (reinterpret_cast<id> (objc_getClass ("NSString")),
99 sel_getUid ("alloc"));
100 reason_string = reinterpret_cast<id (*) (id, SEL, const char *)> (objc_msgSend)
101 (reason_string, sel_getUid ("initWithUTF8String:"),
102 "App Nap causes pause() malfunction");
103
104 // Start an Activity that suppresses App Nap. This Activity will run for
105 // the entire duration of the Octave process. This is intentional,
106 // not a leak.
107 osx_latencycritical_activity =
108 reinterpret_cast<id (*) (id, SEL, unsigned long long, id)> (objc_msgSend)
109 (process_info,
110 begin_activity_with_options_selector,
111 NSActivityUserInitiatedAllowingIdleSystemSleep
112 | NSActivityLatencyCritical,
113 reason_string);
114 }
115 #endif
116
117 namespace octave
118 {
119 // Disable all Qt messages by default.
120
121 static void
122 #if defined (QTMESSAGEHANDLER_ACCEPTS_QMESSAGELOGCONTEXT)
message_handler(QtMsgType,const QMessageLogContext &,const QString &)123 message_handler (QtMsgType, const QMessageLogContext &, const QString &)
124 #else
125 message_handler (QtMsgType, const char *)
126 #endif
127 { }
128
129 //! Reimplement QApplication::notify. Octave's own exceptions are
130 //! caught and rethrown in the interpreter thread.
131
notify(QObject * receiver,QEvent * ev)132 bool octave_qapplication::notify (QObject *receiver, QEvent *ev)
133 {
134 try
135 {
136 return QApplication::notify (receiver, ev);
137 }
138 catch (execution_exception& ee)
139 {
140 emit interpreter_event
141 ([ee] (void)
142 {
143 // INTERPRETER THREAD
144
145 throw ee;
146 });
147 }
148
149 return false;
150 }
151
152 // We will create a QApplication object, even if START_GUI is false,
153 // so that we can use Qt widgets for plot windows when running in
154 // command-line mode. Note that we are creating an
155 // octave_qapplication object but handling it as a QApplication object
156 // because the octave_qapplication should behave identically to a
157 // QApplication object except that it overrides the notify method so
158 // we can handle forward Octave interpreter exceptions from the GUI
159 // thread to the interpreter thread.
160
base_qobject(qt_application & app_context)161 base_qobject::base_qobject (qt_application& app_context)
162 : QObject (), m_app_context (app_context),
163 m_argc (m_app_context.sys_argc ()),
164 m_argv (m_app_context.sys_argv ()),
165 m_qapplication (new octave_qapplication (m_argc, m_argv)),
166 m_resource_manager (), m_shortcut_manager (*this),
167 m_qt_tr (new QTranslator ()), m_gui_tr (new QTranslator ()),
168 m_qsci_tr (new QTranslator ()), m_translators_installed (false),
169 m_qt_interpreter_events (new qt_interpreter_events (*this)),
170 m_interpreter_qobj (new interpreter_qobject (*this)),
171 m_main_thread (new QThread ())
172 {
173 std::string show_gui_msgs =
174 sys::env::getenv ("OCTAVE_SHOW_GUI_MESSAGES");
175
176 // Installing our handler suppresses the messages.
177
178 if (show_gui_msgs.empty ())
179 {
180 #if defined (HAVE_QINSTALLMESSAGEHANDLER)
181 qInstallMessageHandler (message_handler);
182 #else
183 qInstallMsgHandler (message_handler);
184 #endif
185 }
186
187 // Set the codec for all strings (before wizard or any GUI object)
188 #if ! defined (Q_OS_WIN32)
189 QTextCodec::setCodecForLocale (QTextCodec::codecForName ("UTF-8"));
190 #endif
191
192 #if defined (HAVE_QT4)
193 QTextCodec::setCodecForCStrings (QTextCodec::codecForName ("UTF-8"));
194 #endif
195
196 // Initialize global Qt application metadata.
197
198 QCoreApplication::setApplicationName ("GNU Octave");
199 QCoreApplication::setApplicationVersion (OCTAVE_VERSION);
200
201 // Register octave_value_list for connecting thread crossing signals.
202
203 qRegisterMetaType<octave_value_list> ("octave_value_list");
204
205
206 // Bug #55940 (Disable App Nap on Mac)
207 #if defined (Q_OS_MAC)
208 // Mac App Nap feature causes pause() and sleep() to misbehave.
209 // Disable it for the entire program run.
210 disable_app_nap ();
211 #endif
212
213 // Force left-to-right alignment (see bug #46204)
214 m_qapplication->setLayoutDirection (Qt::LeftToRight);
215
216 connect (m_interpreter_qobj, SIGNAL (execution_finished (int)),
217 this, SLOT (handle_interpreter_execution_finished (int)));
218
219 connect (this, SIGNAL (request_interpreter_shutdown (int)),
220 m_interpreter_qobj, SLOT (shutdown (int)));
221
222 connect (m_interpreter_qobj, SIGNAL (shutdown_finished (int)),
223 this, SLOT (handle_interpreter_shutdown_finished (int)));
224
225 connect (m_main_thread, SIGNAL (finished (void)),
226 m_main_thread, SLOT (deleteLater (void)));
227
228 // Handle any interpreter_event signal from the octave_qapplication
229 // object here.
230
231 connect (m_qapplication, SIGNAL (interpreter_event (const fcn_callback&)),
232 this, SLOT (interpreter_event (const fcn_callback&)));
233
234 connect (m_qapplication, SIGNAL (interpreter_event (const meth_callback&)),
235 this, SLOT (interpreter_event (const meth_callback&)));
236
237 connect (qt_link (),
238 SIGNAL (copy_image_to_clipboard_signal (const QString&, bool)),
239 this, SLOT (copy_image_to_clipboard (const QString&, bool)));
240 }
241
~base_qobject(void)242 base_qobject::~base_qobject (void)
243 {
244 // Note that we don't delete m_main_thread here. That is handled by
245 // deleteLater slot that is called when the m_main_thread issues a
246 // finished signal.
247
248 delete m_interpreter_qobj;
249 delete m_qsci_tr;
250 delete m_gui_tr;
251 delete m_qt_tr;
252 delete m_qapplication;
253
254 string_vector::delete_c_str_vec (m_argv);
255 }
256
config_translators(void)257 void base_qobject::config_translators (void)
258 {
259 if (m_translators_installed)
260 return;
261
262 m_resource_manager.config_translators (m_qt_tr, m_qsci_tr, m_gui_tr);
263
264 m_qapplication->installTranslator (m_qt_tr);
265 m_qapplication->installTranslator (m_gui_tr);
266 m_qapplication->installTranslator (m_qsci_tr);
267
268 m_translators_installed = true;
269 }
270
start_main_thread(void)271 void base_qobject::start_main_thread (void)
272 {
273 // Defer initializing and executing the interpreter until after the main
274 // window and QApplication are running to prevent race conditions
275 QTimer::singleShot (0, m_interpreter_qobj, SLOT (execute (void)));
276
277 m_interpreter_qobj->moveToThread (m_main_thread);
278
279 m_main_thread->start ();
280 }
281
exec(void)282 int base_qobject::exec (void)
283 {
284 return m_qapplication->exec ();
285 }
286
confirm_shutdown(void)287 bool base_qobject::confirm_shutdown (void)
288 {
289 return true;
290 }
291
handle_interpreter_execution_finished(int exit_status)292 void base_qobject::handle_interpreter_execution_finished (int exit_status)
293 {
294 emit request_interpreter_shutdown (exit_status);
295 }
296
handle_interpreter_shutdown_finished(int exit_status)297 void base_qobject::handle_interpreter_shutdown_finished (int exit_status)
298 {
299 #if defined (Q_OS_MAC)
300 // fprintf to stderr is needed by macOS, for poorly-understood reasons.
301 fprintf (stderr, "\n");
302 #endif
303
304 m_main_thread->quit ();
305 m_main_thread->wait ();
306
307 qApp->exit (exit_status);
308 }
309
interpreter_event(const fcn_callback & fcn)310 void base_qobject::interpreter_event (const fcn_callback& fcn)
311 {
312 // The following is a direct function call across threads. It works
313 // because the it is accessing a thread-safe queue of events that
314 // are later executed by the Octave interpreter in the other thread.
315
316 // See also the comments in interpreter-qobject.h about
317 // interpreter_qobject slots.
318
319 m_interpreter_qobj->interpreter_event (fcn);
320 }
321
interpreter_event(const meth_callback & meth)322 void base_qobject::interpreter_event (const meth_callback& meth)
323 {
324 // The following is a direct function call across threads. It works
325 // because the it is accessing a thread-safe queue of events that
326 // are later executed by the Octave interpreter in the other thread.
327
328 // See also the comments in interpreter-qobject.h about
329 // interpreter_qobject slots.
330
331 m_interpreter_qobj->interpreter_event (meth);
332 }
333
copy_image_to_clipboard(const QString & file,bool remove_file)334 void base_qobject::copy_image_to_clipboard (const QString& file,
335 bool remove_file)
336 {
337 QClipboard *clipboard = QApplication::clipboard ();
338
339 QImage img (file);
340
341 if (img.isNull ())
342 {
343 // Report error?
344 return;
345 }
346
347 clipboard->setImage (img);
348
349 if (remove_file)
350 QFile::remove (file);
351 }
352
cli_qobject(qt_application & app_context)353 cli_qobject::cli_qobject (qt_application& app_context)
354 : base_qobject (app_context)
355 {
356 // Get settings file.
357 m_resource_manager.reload_settings ();
358
359 // After settings.
360 config_translators ();
361
362 m_qapplication->setQuitOnLastWindowClosed (false);
363
364 start_main_thread ();
365 }
366
gui_qobject(qt_application & app_context)367 gui_qobject::gui_qobject (qt_application& app_context)
368 : base_qobject (app_context), m_main_window (new main_window (*this))
369 {
370 connect (m_interpreter_qobj, SIGNAL (ready (void)),
371 m_main_window, SLOT (handle_octave_ready (void)));
372
373 connect (qt_link (),
374 SIGNAL (focus_window_signal (const QString&)),
375 m_main_window, SLOT (focus_window (const QString&)));
376
377 m_app_context.gui_running (true);
378
379 start_main_thread ();
380 }
381
~gui_qobject(void)382 gui_qobject::~gui_qobject (void)
383 {
384 delete m_main_window;
385 }
386
confirm_shutdown(void)387 bool gui_qobject::confirm_shutdown (void)
388 {
389 // Currently, we forward to main_window::confirm_shutdown instead of
390 // just displaying a dialog box here because the main_window also
391 // knows about and is responsible for notifying the editor.
392
393 return m_main_window ? m_main_window->confirm_shutdown () : true;
394 }
395 }
396