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