1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/app.h"
12 
13 #include "app/check_update.h"
14 #include "app/cli/app_options.h"
15 #include "app/cli/cli_processor.h"
16 #include "app/cli/default_cli_delegate.h"
17 #include "app/cli/preview_cli_delegate.h"
18 #include "app/color_utils.h"
19 #include "app/commands/commands.h"
20 #include "app/console.h"
21 #include "app/crash/data_recovery.h"
22 #include "app/extensions.h"
23 #include "app/file/file.h"
24 #include "app/file/file_formats_manager.h"
25 #include "app/file_system.h"
26 #include "app/gui_xml.h"
27 #include "app/i18n/strings.h"
28 #include "app/ini_file.h"
29 #include "app/log.h"
30 #include "app/modules.h"
31 #include "app/modules/gfx.h"
32 #include "app/modules/gui.h"
33 #include "app/modules/palettes.h"
34 #include "app/pref/preferences.h"
35 #include "app/recent_files.h"
36 #include "app/resource_finder.h"
37 #include "app/send_crash.h"
38 #include "app/site.h"
39 #include "app/tools/active_tool.h"
40 #include "app/tools/tool_box.h"
41 #include "app/ui/backup_indicator.h"
42 #include "app/ui/color_bar.h"
43 #include "app/ui/doc_view.h"
44 #include "app/ui/editor/editor.h"
45 #include "app/ui/editor/editor_view.h"
46 #include "app/ui/input_chain.h"
47 #include "app/ui/keyboard_shortcuts.h"
48 #include "app/ui/main_window.h"
49 #include "app/ui/status_bar.h"
50 #include "app/ui/toolbar.h"
51 #include "app/ui/workspace.h"
52 #include "app/ui_context.h"
53 #include "app/util/clipboard.h"
54 #include "app/webserver.h"
55 #include "base/exception.h"
56 #include "base/fs.h"
57 #include "base/scoped_lock.h"
58 #include "base/split_string.h"
59 #include "base/unique_ptr.h"
60 #include "doc/sprite.h"
61 #include "fmt/format.h"
62 #include "render/render.h"
63 #include "she/display.h"
64 #include "she/error.h"
65 #include "she/surface.h"
66 #include "she/system.h"
67 #include "ui/intern.h"
68 #include "ui/ui.h"
69 
70 #include <iostream>
71 
72 #ifdef ENABLE_SCRIPTING
73   #include "app/script/app_scripting.h"
74   #include "app/shell.h"
75   #include "script/engine_delegate.h"
76 #endif
77 
78 #ifdef ENABLE_STEAM
79   #include "steam/steam.h"
80 #endif
81 
82 namespace app {
83 
84 using namespace ui;
85 
86 class App::CoreModules {
87 public:
88   ConfigModule m_configModule;
89   Preferences m_preferences;
90 };
91 
92 class App::LoadLanguage {
93 public:
LoadLanguage(Preferences & pref,Extensions & exts)94   LoadLanguage(Preferences& pref,
95                Extensions& exts) {
96     Strings::createInstance(pref, exts);
97   }
98 };
99 
100 class App::Modules {
101 public:
102 #ifdef ENABLE_UI
103   typedef app::UIContext ContextT;
104 #else
105   typedef app::Context ContextT;
106 #endif
107 
108   LoggerModule m_loggerModule;
109   FileSystemModule m_file_system_module;
110   Extensions m_extensions;
111   // Load main language (after loading the extensions)
112   LoadLanguage m_loadLanguage;
113 #ifdef ENABLE_UI
114   tools::ToolBox m_toolbox;
115   tools::ActiveToolManager m_activeToolManager;
116 #endif
117   Commands m_commands;
118   ContextT m_context;
119 #ifdef ENABLE_UI
120   RecentFiles m_recent_files;
121   InputChain m_inputChain;
122   clipboard::ClipboardManager m_clipboardManager;
123 #endif
124   // This is a raw pointer because we want to delete this explicitly.
125   app::crash::DataRecovery* m_recovery;
126 
Modules(const bool createLogInDesktop,Preferences & pref)127   Modules(const bool createLogInDesktop,
128           Preferences& pref)
129     : m_loggerModule(createLogInDesktop)
130     , m_loadLanguage(pref, m_extensions)
131 #ifdef ENABLE_UI
132     , m_activeToolManager(&m_toolbox)
133     , m_recent_files(pref.general.recentItems())
134 #endif
135     , m_recovery(nullptr) {
136   }
137 
recovery()138   app::crash::DataRecovery* recovery() {
139     return m_recovery;
140   }
141 
hasRecoverySessions() const142   bool hasRecoverySessions() const {
143     return m_recovery && !m_recovery->sessions().empty();
144   }
145 
createDataRecovery()146   void createDataRecovery() {
147 #ifdef ENABLE_DATA_RECOVERY
148     m_recovery = new app::crash::DataRecovery(&m_context);
149 #endif
150   }
151 
deleteDataRecovery()152   void deleteDataRecovery() {
153 #ifdef ENABLE_DATA_RECOVERY
154     delete m_recovery;
155     m_recovery = nullptr;
156 #endif
157   }
158 
159 };
160 
161 App* App::m_instance = NULL;
162 
App()163 App::App()
164   : m_coreModules(NULL)
165   , m_modules(NULL)
166   , m_legacy(NULL)
167   , m_isGui(false)
168   , m_isShell(false)
169 #ifdef ENABLE_UI
170   , m_backupIndicator(nullptr)
171 #endif
172 {
173   ASSERT(m_instance == NULL);
174   m_instance = this;
175 }
176 
initialize(const AppOptions & options)177 void App::initialize(const AppOptions& options)
178 {
179 #ifdef ENABLE_UI
180   m_isGui = options.startUI() && !options.previewCLI();
181 #else
182   m_isGui = false;
183 #endif
184   m_isShell = options.startShell();
185   m_coreModules = new CoreModules;
186 
187 #ifdef _WIN32
188   if (options.disableWintab() ||
189       !preferences().experimental.loadWintabDriver()) {
190     she::instance()->useWintabAPI(false);
191   }
192 #endif
193 
194   if (m_isGui)
195     m_uiSystem.reset(new ui::UISystem);
196 
197   bool createLogInDesktop = false;
198   switch (options.verboseLevel()) {
199     case AppOptions::kNoVerbose:
200       base::set_log_level(ERROR);
201       break;
202     case AppOptions::kVerbose:
203       base::set_log_level(INFO);
204       break;
205     case AppOptions::kHighlyVerbose:
206       base::set_log_level(VERBOSE);
207       createLogInDesktop = true;
208       break;
209   }
210 
211   // Load modules
212   m_modules = new Modules(createLogInDesktop, preferences());
213   m_legacy = new LegacyModules(isGui() ? REQUIRE_INTERFACE: 0);
214 #ifdef ENABLE_UI
215   m_brushes.reset(new AppBrushes);
216 #endif
217 
218   // Data recovery is enabled only in GUI mode
219   if (isGui() && preferences().general.dataRecovery())
220     m_modules->createDataRecovery();
221 
222   if (isPortable())
223     LOG("APP: Running in portable mode\n");
224 
225   // Load or create the default palette, or migrate the default
226   // palette from an old format palette to the new one, etc.
227   load_default_palette();
228 
229 #ifdef ENABLE_UI
230   // Initialize GUI interface
231   if (isGui()) {
232     LOG("APP: GUI mode\n");
233 
234     // Set the ClipboardDelegate impl to copy/paste text in the native
235     // clipboard from the ui::Entry control.
236     m_uiSystem->setClipboardDelegate(&m_modules->m_clipboardManager);
237 
238     // Setup the GUI cursor and redraw screen
239     ui::set_use_native_cursors(preferences().cursor.useNativeCursor());
240     ui::set_mouse_cursor_scale(preferences().cursor.cursorScale());
241     ui::set_mouse_cursor(kArrowCursor);
242 
243     ui::Manager::getDefault()->invalidate();
244 
245     // Create the main window and show it.
246     m_mainWindow.reset(new MainWindow);
247 
248     // Default status of the main window.
249     app_rebuild_documents_tabs();
250     app_default_statusbar_message();
251 
252     // Recover data
253     if (m_modules->hasRecoverySessions())
254       m_mainWindow->showDataRecovery(m_modules->recovery());
255 
256     m_mainWindow->openWindow();
257 
258     // Redraw the whole screen.
259     ui::Manager::getDefault()->invalidate();
260   }
261 #endif  // ENABLE_UI
262 
263   // Process options
264   LOG("APP: Processing options...\n");
265   {
266     base::UniquePtr<CliDelegate> delegate;
267     if (options.previewCLI())
268       delegate.reset(new PreviewCliDelegate);
269     else
270       delegate.reset(new DefaultCliDelegate);
271 
272     CliProcessor cli(delegate.get(), options);
273     cli.process(&m_modules->m_context);
274   }
275 
276   she::instance()->finishLaunching();
277 }
278 
run()279 void App::run()
280 {
281 #ifdef ENABLE_UI
282   // Run the GUI
283   if (isGui()) {
284 #ifdef _WIN32
285     // How to interpret one finger on Windows tablets.
286     ui::Manager::getDefault()->getDisplay()
287       ->setInterpretOneFingerGestureAsMouseMovement(
288         Preferences::instance().experimental.oneFingerAsMouseMovement());
289 #endif
290 
291 #if !defined(_WIN32) && !defined(__APPLE__)
292     // Setup app icon for Linux window managers
293     try {
294       she::Display* display = she::instance()->defaultDisplay();
295       she::SurfaceList icons;
296 
297       for (const int size : { 32, 64, 128 }) {
298         ResourceFinder rf;
299         rf.includeDataDir(fmt::format("icons/ase{0}.png", size).c_str());
300         if (rf.findFirst()) {
301           she::Surface* surf = she::instance()->loadRgbaSurface(rf.filename().c_str());
302           if (surf)
303             icons.push_back(surf);
304         }
305       }
306 
307       display->setIcons(icons);
308 
309       for (auto surf : icons)
310         surf->dispose();
311     }
312     catch (const std::exception&) {
313       // Just ignore the exception, we couldn't change the app icon, no
314       // big deal.
315     }
316 #endif
317 
318     // Initialize Steam API
319 #ifdef ENABLE_STEAM
320     steam::SteamAPI steam;
321     if (steam.initialized())
322       she::instance()->activateApp();
323 #endif
324 
325 #if ENABLE_DEVMODE
326     // On OS X, when we compile Aseprite on devmode, we're using it
327     // outside an app bundle, so we must active the app explicitly.
328     she::instance()->activateApp();
329 #endif
330 
331 #ifdef ENABLE_UPDATER
332     // Launch the thread to check for updates.
333     app::CheckUpdateThreadLauncher checkUpdate(
334       m_mainWindow->getCheckUpdateDelegate());
335     checkUpdate.launch();
336 #endif
337 
338 #ifdef ENABLE_WEBSERVER
339     // Launch the webserver.
340     app::WebServer webServer;
341     webServer.start();
342 #endif
343 
344     app::SendCrash sendCrash;
345     sendCrash.search();
346 
347     // Run the GUI main message loop
348     ui::Manager::getDefault()->run();
349   }
350 #endif  // ENABLE_UI
351 
352 #ifdef ENABLE_SCRIPTING
353   // Start shell to execute scripts.
354   if (m_isShell) {
355     script::StdoutEngineDelegate delegate;
356     AppScripting engine(&delegate);
357     engine.printLastResult();
358     Shell shell;
359     shell.run(engine);
360   }
361 #endif  // ENABLE_SCRIPTING
362 
363   // Destroy all documents in the UIContext.
364   const Docs& docs = m_modules->m_context.documents();
365   while (!docs.empty()) {
366     Doc* doc = docs.back();
367 
368     // First we close the document. In this way we receive recent
369     // notifications related to the document as a app::Doc. If
370     // we delete the document directly, we destroy the app::Doc
371     // too early, and then doc::~Document() call
372     // DocsObserver::onRemoveDocument(). In this way, observers
373     // could think that they have a fully created app::Doc when
374     // in reality it's a doc::Document (in the middle of a
375     // destruction process).
376     //
377     // TODO: This problem is because we're extending doc::Document,
378     // in the future, we should remove app::Doc.
379     doc->close();
380     delete doc;
381   }
382 
383 #ifdef ENABLE_UI
384   if (isGui()) {
385     // Destroy the window.
386     m_mainWindow.reset(NULL);
387   }
388 #endif
389 
390   // Delete backups (this is a normal shutdown, we are not handling
391   // exceptions, and we are not in a destructor).
392   m_modules->deleteDataRecovery();
393 }
394 
395 // Finishes the Aseprite application.
~App()396 App::~App()
397 {
398   try {
399     LOG("APP: Exit\n");
400     ASSERT(m_instance == this);
401 
402     // Delete file formats.
403     FileFormatsManager::destroyInstance();
404 
405     // Fire App Exit signal.
406     App::instance()->Exit();
407 
408 #ifdef ENABLE_UI
409     // Finalize modules, configuration and core.
410     Editor::destroyEditorSharedInternals();
411 
412     if (m_backupIndicator) {
413       delete m_backupIndicator;
414       m_backupIndicator = nullptr;
415     }
416 
417     // Save brushes
418     m_brushes.reset(nullptr);
419 #endif
420 
421     delete m_legacy;
422     delete m_modules;
423     delete m_coreModules;
424 
425 #ifdef ENABLE_UI
426     // Destroy the loaded gui.xml data.
427     delete KeyboardShortcuts::instance();
428     delete GuiXml::instance();
429 #endif
430 
431     m_instance = NULL;
432   }
433   catch (const std::exception& e) {
434     LOG(ERROR) << "APP: Error: " << e.what() << "\n";
435     she::error_message(e.what());
436 
437     // no re-throw
438   }
439   catch (...) {
440     she::error_message("Error closing " PACKAGE ".\n(uncaught exception)");
441 
442     // no re-throw
443   }
444 }
445 
context()446 Context* App::context()
447 {
448   return &m_modules->m_context;
449 }
450 
isPortable()451 bool App::isPortable()
452 {
453   static bool* is_portable = NULL;
454   if (!is_portable) {
455     is_portable =
456       new bool(
457         base::is_file(base::join_path(
458             base::get_file_path(base::get_app_path()),
459             "aseprite.ini")));
460   }
461   return *is_portable;
462 }
463 
toolBox() const464 tools::ToolBox* App::toolBox() const
465 {
466   ASSERT(m_modules != NULL);
467 #ifdef ENABLE_UI
468   return &m_modules->m_toolbox;
469 #else
470   return nullptr;
471 #endif
472 }
473 
activeTool() const474 tools::Tool* App::activeTool() const
475 {
476 #ifdef ENABLE_UI
477   return m_modules->m_activeToolManager.activeTool();
478 #else
479   return nullptr;
480 #endif
481 }
482 
activeToolManager() const483 tools::ActiveToolManager* App::activeToolManager() const
484 {
485 #ifdef ENABLE_UI
486   return &m_modules->m_activeToolManager;
487 #else
488   return nullptr;
489 #endif
490 }
491 
recentFiles() const492 RecentFiles* App::recentFiles() const
493 {
494 #ifdef ENABLE_UI
495   ASSERT(m_modules != NULL);
496   return &m_modules->m_recent_files;
497 #else
498   return nullptr;
499 #endif
500 }
501 
workspace() const502 Workspace* App::workspace() const
503 {
504   if (m_mainWindow)
505     return m_mainWindow->getWorkspace();
506   else
507     return nullptr;
508 }
509 
contextBar() const510 ContextBar* App::contextBar() const
511 {
512   if (m_mainWindow)
513     return m_mainWindow->getContextBar();
514   else
515     return nullptr;
516 }
517 
timeline() const518 Timeline* App::timeline() const
519 {
520   if (m_mainWindow)
521     return m_mainWindow->getTimeline();
522   else
523     return nullptr;
524 }
525 
preferences() const526 Preferences& App::preferences() const
527 {
528   return m_coreModules->m_preferences;
529 }
530 
extensions() const531 Extensions& App::extensions() const
532 {
533   return m_modules->m_extensions;
534 }
535 
dataRecovery() const536 crash::DataRecovery* App::dataRecovery() const
537 {
538   return m_modules->recovery();
539 }
540 
541 #ifdef ENABLE_UI
showNotification(INotificationDelegate * del)542 void App::showNotification(INotificationDelegate* del)
543 {
544   m_mainWindow->showNotification(del);
545 }
546 
showBackupNotification(bool state)547 void App::showBackupNotification(bool state)
548 {
549   base::scoped_lock lock(m_backupIndicatorMutex);
550   if (state) {
551     if (!m_backupIndicator)
552       m_backupIndicator = new BackupIndicator;
553     m_backupIndicator->start();
554   }
555   else {
556     if (m_backupIndicator)
557       m_backupIndicator->stop();
558   }
559 }
560 
updateDisplayTitleBar()561 void App::updateDisplayTitleBar()
562 {
563   std::string defaultTitle = PACKAGE " v" VERSION;
564   std::string title;
565 
566   DocView* docView = UIContext::instance()->activeView();
567   if (docView) {
568     // Prepend the document's filename.
569     title += docView->document()->name();
570     title += " - ";
571   }
572 
573   title += defaultTitle;
574   she::instance()->defaultDisplay()->setTitleBar(title);
575 }
576 
inputChain()577 InputChain& App::inputChain()
578 {
579   return m_modules->m_inputChain;
580 }
581 #endif
582 
583 // Updates palette and redraw the screen.
app_refresh_screen()584 void app_refresh_screen()
585 {
586 #ifdef ENABLE_UI
587   Context* context = UIContext::instance();
588   ASSERT(context != NULL);
589 
590   Site site = context->activeSite();
591 
592   if (Palette* pal = site.palette())
593     set_current_palette(pal, false);
594   else
595     set_current_palette(NULL, false);
596 
597   // Invalidate the whole screen.
598   ui::Manager::getDefault()->invalidate();
599 #endif // ENABLE_UI
600 }
601 
602 // TODO remove app_rebuild_documents_tabs() and replace it by
603 // observable events in the document (so a tab can observe if the
604 // document is modified).
app_rebuild_documents_tabs()605 void app_rebuild_documents_tabs()
606 {
607 #ifdef ENABLE_UI
608   if (App::instance()->isGui()) {
609     App::instance()->workspace()->updateTabs();
610     App::instance()->updateDisplayTitleBar();
611   }
612 #endif // ENABLE_UI
613 }
614 
app_get_current_pixel_format()615 PixelFormat app_get_current_pixel_format()
616 {
617 #ifdef ENABLE_UI
618   Context* context = UIContext::instance();
619   ASSERT(context != NULL);
620 
621   Doc* document = context->activeDocument();
622   if (document != NULL)
623     return document->sprite()->pixelFormat();
624   else
625     return IMAGE_RGB;
626 #else // ENABLE_UI
627   return IMAGE_RGB;
628 #endif
629 }
630 
app_default_statusbar_message()631 void app_default_statusbar_message()
632 {
633 #ifdef ENABLE_UI
634   StatusBar::instance()
635     ->setStatusText(250, "%s %s | %s", PACKAGE, VERSION, COPYRIGHT);
636 #endif
637 }
638 
app_get_color_to_clear_layer(Layer * layer)639 int app_get_color_to_clear_layer(Layer* layer)
640 {
641   ASSERT(layer != NULL);
642 
643   app::Color color;
644 
645   // The `Background' is erased with the `Background Color'
646   if (layer->isBackground()) {
647 #ifdef ENABLE_UI
648     if (ColorBar::instance())
649       color = ColorBar::instance()->getBgColor();
650     else
651 #endif
652       color = app::Color::fromRgb(0, 0, 0); // TODO get background color color from doc::Settings
653   }
654   else // All transparent layers are cleared with the mask color
655     color = app::Color::fromMask();
656 
657   return color_utils::color_for_layer(color, layer);
658 }
659 
memory_dump_filename()660 std::string memory_dump_filename()
661 {
662 #ifdef _WIN32
663   static const char* kDefaultCrashName = PACKAGE "-crash-" VERSION ".dmp";
664 
665   app::ResourceFinder rf;
666   rf.includeUserDir(kDefaultCrashName);
667   return rf.getFirstOrCreateDefault();
668 
669 #else
670   return "";
671 #endif
672 }
673 
674 } // namespace app
675