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