86da2ef32ba190656d85185fcde319ee89093f88
[blender.git] / source / blender / windowmanager / intern / wm_files.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * Contributor(s): Blender Foundation 2007
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/windowmanager/intern/wm_files.c
27  *  \ingroup wm
28  *
29  * User level access for blend file read/write, file-history and userprefs.
30  */
31
32
33 /* placed up here because of crappy
34  * winsock stuff.
35  */
36 #include <stddef.h>
37 #include <string.h>
38 #include <errno.h>
39
40 #include "zlib.h" /* wm_read_exotic() */
41
42 #ifdef WIN32
43 #  include <windows.h> /* need to include windows.h so _WIN32_IE is defined  */
44 #  ifndef _WIN32_IE
45 #    define _WIN32_IE 0x0400 /* minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already */
46 #  endif
47 #  include <shlobj.h>  /* for SHGetSpecialFolderPath, has to be done before BLI_winstuff
48                         * because 'near' is disabled through BLI_windstuff */
49 #  include "BLI_winstuff.h"
50 #endif
51
52 #include "MEM_guardedalloc.h"
53 #include "MEM_CacheLimiterC-Api.h"
54
55 #include "BLI_blenlib.h"
56 #include "BLI_linklist.h"
57 #include "BLI_utildefines.h"
58 #include "BLI_threads.h"
59 #include "BLI_callbacks.h"
60 #include "BLI_system.h"
61 #include BLI_SYSTEM_PID_H
62
63 #include "BLT_translation.h"
64
65 #include "DNA_object_types.h"
66 #include "DNA_space_types.h"
67 #include "DNA_userdef_types.h"
68 #include "DNA_scene_types.h"
69 #include "DNA_screen_types.h"
70 #include "DNA_windowmanager_types.h"
71
72 #include "BKE_appdir.h"
73 #include "BKE_utildefines.h"
74 #include "BKE_autoexec.h"
75 #include "BKE_blender.h"
76 #include "BKE_context.h"
77 #include "BKE_depsgraph.h"
78 #include "BKE_global.h"
79 #include "BKE_library.h"
80 #include "BKE_main.h"
81 #include "BKE_packedFile.h"
82 #include "BKE_report.h"
83 #include "BKE_sound.h"
84 #include "BKE_scene.h"
85 #include "BKE_screen.h"
86
87 #include "BLO_readfile.h"
88 #include "BLO_writefile.h"
89
90 #include "RNA_access.h"
91
92 #include "IMB_imbuf.h"
93 #include "IMB_imbuf_types.h"
94 #include "IMB_thumbs.h"
95
96 #include "ED_datafiles.h"
97 #include "ED_fileselect.h"
98 #include "ED_screen.h"
99 #include "ED_view3d.h"
100 #include "ED_util.h"
101
102 #include "GHOST_C-api.h"
103 #include "GHOST_Path-api.h"
104
105 #include "UI_interface.h"
106 #include "UI_view2d.h"
107
108 #include "GPU_draw.h"
109
110 /* only to report a missing engine */
111 #include "RE_engine.h"
112
113 #ifdef WITH_PYTHON
114 #include "BPY_extern.h"
115 #endif
116
117 #include "WM_api.h"
118 #include "WM_types.h"
119 #include "wm.h"
120 #include "wm_files.h"
121 #include "wm_window.h"
122 #include "wm_event_system.h"
123
124 static RecentFile *wm_file_history_find(const char *filepath);
125 static void wm_history_file_free(RecentFile *recent);
126 static void wm_history_file_update(void);
127 static void wm_history_file_write(void);
128
129
130 /* To be able to read files without windows closing, opening, moving
131  * we try to prepare for worst case:
132  * - active window gets active screen from file
133  * - restoring the screens from non-active windows
134  * Best case is all screens match, in that case they get assigned to proper window
135  */
136 static void wm_window_match_init(bContext *C, ListBase *wmlist)
137 {
138         wmWindowManager *wm;
139         wmWindow *win, *active_win;
140         
141         *wmlist = G.main->wm;
142         BLI_listbase_clear(&G.main->wm);
143         
144         active_win = CTX_wm_window(C);
145
146         /* first wrap up running stuff */
147         /* code copied from wm_init_exit.c */
148         for (wm = wmlist->first; wm; wm = wm->id.next) {
149                 
150                 WM_jobs_kill_all(wm);
151                 
152                 for (win = wm->windows.first; win; win = win->next) {
153                 
154                         CTX_wm_window_set(C, win);  /* needed by operator close callbacks */
155                         WM_event_remove_handlers(C, &win->handlers);
156                         WM_event_remove_handlers(C, &win->modalhandlers);
157                         ED_screen_exit(C, win, win->screen);
158                 }
159         }
160         
161         /* reset active window */
162         CTX_wm_window_set(C, active_win);
163
164         ED_editors_exit(C);
165
166         /* just had return; here from r12991, this code could just get removed?*/
167 #if 0
168         if (wm == NULL) return;
169         if (G.fileflags & G_FILE_NO_UI) return;
170         
171         /* we take apart the used screens from non-active window */
172         for (win = wm->windows.first; win; win = win->next) {
173                 BLI_strncpy(win->screenname, win->screen->id.name, MAX_ID_NAME);
174                 if (win != wm->winactive) {
175                         BLI_remlink(&G.main->screen, win->screen);
176                         //BLI_addtail(screenbase, win->screen);
177                 }
178         }
179 #endif
180 }
181
182 static void wm_window_substitute_old(wmWindowManager *wm, wmWindow *oldwin, wmWindow *win)
183 {
184         win->ghostwin = oldwin->ghostwin;
185         win->active = oldwin->active;
186         if (win->active)
187                 wm->winactive = win;
188
189         if (!G.background) /* file loading in background mode still calls this */
190                 GHOST_SetWindowUserData(win->ghostwin, win);    /* pointer back */
191
192         oldwin->ghostwin = NULL;
193
194         win->eventstate = oldwin->eventstate;
195         oldwin->eventstate = NULL;
196
197         /* ensure proper screen rescaling */
198         win->sizex = oldwin->sizex;
199         win->sizey = oldwin->sizey;
200         win->posx = oldwin->posx;
201         win->posy = oldwin->posy;
202 }
203
204 /* match old WM with new, 4 cases:
205  * 1- no current wm, no read wm: make new default
206  * 2- no current wm, but read wm: that's OK, do nothing
207  * 3- current wm, but not in file: try match screen names
208  * 4- current wm, and wm in file: try match ghostwin
209  */
210
211 static void wm_window_match_do(bContext *C, ListBase *oldwmlist)
212 {
213         wmWindowManager *oldwm, *wm;
214         wmWindow *oldwin, *win;
215         
216         /* cases 1 and 2 */
217         if (BLI_listbase_is_empty(oldwmlist)) {
218                 if (G.main->wm.first) {
219                         /* nothing todo */
220                 }
221                 else {
222                         wm_add_default(C);
223                 }
224         }
225         else {
226                 /* cases 3 and 4 */
227                 
228                 /* we've read file without wm..., keep current one entirely alive */
229                 if (BLI_listbase_is_empty(&G.main->wm)) {
230                         bScreen *screen = NULL;
231
232                         /* when loading without UI, no matching needed */
233                         if (!(G.fileflags & G_FILE_NO_UI) && (screen = CTX_wm_screen(C))) {
234
235                                 /* match oldwm to new dbase, only old files */
236                                 for (wm = oldwmlist->first; wm; wm = wm->id.next) {
237                                         
238                                         for (win = wm->windows.first; win; win = win->next) {
239                                                 /* all windows get active screen from file */
240                                                 if (screen->winid == 0)
241                                                         win->screen = screen;
242                                                 else 
243                                                         win->screen = ED_screen_duplicate(win, screen);
244                                                 
245                                                 BLI_strncpy(win->screenname, win->screen->id.name + 2, sizeof(win->screenname));
246                                                 win->screen->winid = win->winid;
247                                         }
248                                 }
249                         }
250                         
251                         G.main->wm = *oldwmlist;
252                         
253                         /* screens were read from file! */
254                         ED_screens_initialize(G.main->wm.first);
255                 }
256                 else {
257                         bool has_match = false;
258
259                         /* what if old was 3, and loaded 1? */
260                         /* this code could move to setup_appdata */
261                         oldwm = oldwmlist->first;
262                         wm = G.main->wm.first;
263
264                         /* preserve key configurations in new wm, to preserve their keymaps */
265                         wm->keyconfigs = oldwm->keyconfigs;
266                         wm->addonconf = oldwm->addonconf;
267                         wm->defaultconf = oldwm->defaultconf;
268                         wm->userconf = oldwm->userconf;
269
270                         BLI_listbase_clear(&oldwm->keyconfigs);
271                         oldwm->addonconf = NULL;
272                         oldwm->defaultconf = NULL;
273                         oldwm->userconf = NULL;
274
275                         /* ensure making new keymaps and set space types */
276                         wm->initialized = 0;
277                         wm->winactive = NULL;
278
279                         /* only first wm in list has ghostwins */
280                         for (win = wm->windows.first; win; win = win->next) {
281                                 for (oldwin = oldwm->windows.first; oldwin; oldwin = oldwin->next) {
282
283                                         if (oldwin->winid == win->winid) {
284                                                 has_match = true;
285
286                                                 wm_window_substitute_old(wm, oldwin, win);
287                                         }
288                                 }
289                         }
290
291                         /* make sure at least one window is kept open so we don't lose the context, check T42303 */
292                         if (!has_match) {
293                                 oldwin = oldwm->windows.first;
294                                 win = wm->windows.first;
295
296                                 wm_window_substitute_old(wm, oldwin, win);
297                         }
298
299                         wm_close_and_free_all(C, oldwmlist);
300                 }
301         }
302 }
303
304 /* in case UserDef was read, we re-initialize all, and do versioning */
305 static void wm_init_userdef(bContext *C, const bool from_memory)
306 {
307         Main *bmain = CTX_data_main(C);
308
309         /* versioning is here */
310         UI_init_userdef();
311         
312         MEM_CacheLimiter_set_maximum(((size_t)U.memcachelimit) * 1024 * 1024);
313         BKE_sound_init(bmain);
314
315         /* needed so loading a file from the command line respects user-pref [#26156] */
316         BKE_BIT_TEST_SET(G.fileflags, U.flag & USER_FILENOUI, G_FILE_NO_UI);
317
318         /* set the python auto-execute setting from user prefs */
319         /* enabled by default, unless explicitly enabled in the command line which overrides */
320         if ((G.f & G_SCRIPT_OVERRIDE_PREF) == 0) {
321                 BKE_BIT_TEST_SET(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_SCRIPT_AUTOEXEC);
322         }
323
324         /* avoid re-saving for every small change to our prefs, allow overrides */
325         if (from_memory) {
326                 BLO_update_defaults_userpref_blend();
327         }
328
329         /* update tempdir from user preferences */
330         BKE_tempdir_init(U.tempdir);
331
332         BKE_userdef_state();
333 }
334
335
336
337 /* return codes */
338 #define BKE_READ_EXOTIC_FAIL_PATH       -3 /* file format is not supported */
339 #define BKE_READ_EXOTIC_FAIL_FORMAT     -2 /* file format is not supported */
340 #define BKE_READ_EXOTIC_FAIL_OPEN       -1 /* Can't open the file */
341 #define BKE_READ_EXOTIC_OK_BLEND         0 /* .blend file */
342 #if 0
343 #define BKE_READ_EXOTIC_OK_OTHER         1 /* other supported formats */
344 #endif
345
346
347 /* intended to check for non-blender formats but for now it only reads blends */
348 static int wm_read_exotic(const char *name)
349 {
350         int len;
351         gzFile gzfile;
352         char header[7];
353         int retval;
354
355         /* make sure we're not trying to read a directory.... */
356
357         len = strlen(name);
358         if (len > 0 && ELEM(name[len - 1], '/', '\\')) {
359                 retval = BKE_READ_EXOTIC_FAIL_PATH;
360         }
361         else {
362                 gzfile = BLI_gzopen(name, "rb");
363                 if (gzfile == NULL) {
364                         retval = BKE_READ_EXOTIC_FAIL_OPEN;
365                 }
366                 else {
367                         len = gzread(gzfile, header, sizeof(header));
368                         gzclose(gzfile);
369                         if (len == sizeof(header) && STREQLEN(header, "BLENDER", 7)) {
370                                 retval = BKE_READ_EXOTIC_OK_BLEND;
371                         }
372                         else {
373 #if 0           /* historic stuff - no longer used */
374                                 WM_cursor_wait(true);
375
376                                 if (is_foo_format(name)) {
377                                         read_foo(name);
378                                         retval = BKE_READ_EXOTIC_OK_OTHER;
379                                 }
380                                 else
381 #endif
382                                 {
383                                         retval = BKE_READ_EXOTIC_FAIL_FORMAT;
384                                 }
385 #if 0
386                                 WM_cursor_wait(false);
387 #endif
388                         }
389                 }
390         }
391
392         return retval;
393 }
394
395 void WM_file_autoexec_init(const char *filepath)
396 {
397         if (G.f & G_SCRIPT_OVERRIDE_PREF) {
398                 return;
399         }
400
401         if (G.f & G_SCRIPT_AUTOEXEC) {
402                 char path[FILE_MAX];
403                 BLI_split_dir_part(filepath, path, sizeof(path));
404                 if (BKE_autoexec_match(path)) {
405                         G.f &= ~G_SCRIPT_AUTOEXEC;
406                 }
407         }
408 }
409
410 void wm_file_read_report(bContext *C)
411 {
412         ReportList *reports = NULL;
413         Scene *sce;
414
415         for (sce = G.main->scene.first; sce; sce = sce->id.next) {
416                 if (sce->r.engine[0] &&
417                     BLI_findstring(&R_engines, sce->r.engine, offsetof(RenderEngineType, idname)) == NULL)
418                 {
419                         if (reports == NULL) {
420                                 reports = CTX_wm_reports(C);
421                         }
422
423                         BKE_reportf(reports, RPT_ERROR,
424                                     "Engine '%s' not available for scene '%s' "
425                                     "(an addon may need to be installed or enabled)",
426                                     sce->r.engine, sce->id.name + 2);
427                 }
428         }
429
430         if (reports) {
431                 if (!G.background) {
432                         WM_report_banner_show(C);
433                 }
434         }
435 }
436
437 /**
438  * Logic shared between #WM_file_read & #wm_homefile_read,
439  * updates to make after reading a file.
440  */
441 static void wm_file_read_post(bContext *C, bool is_startup_file)
442 {
443         bool addons_loaded = false;
444         wmWindowManager *wm = CTX_wm_manager(C);
445
446         if (!G.background) {
447                 /* remove windows which failed to be added via WM_check */
448                 wm_window_ghostwindows_remove_invalid(C, wm);
449         }
450
451         CTX_wm_window_set(C, wm->windows.first);
452
453         ED_editors_init(C);
454         DAG_on_visible_update(CTX_data_main(C), true);
455
456 #ifdef WITH_PYTHON
457         if (is_startup_file) {
458                 /* possible python hasn't been initialized */
459                 if (CTX_py_init_get(C)) {
460                         /* sync addons, these may have changed from the defaults */
461                         BPY_string_exec(C, "__import__('addon_utils').reset_all()");
462
463                         BPY_python_reset(C);
464                         addons_loaded = true;
465                 }
466         }
467         else {
468                 /* run any texts that were loaded in and flagged as modules */
469                 BPY_python_reset(C);
470                 addons_loaded = true;
471         }
472 #endif  /* WITH_PYTHON */
473
474         WM_operatortype_last_properties_clear_all();
475
476         /* important to do before NULL'ing the context */
477         BLI_callback_exec(CTX_data_main(C), NULL, BLI_CB_EVT_VERSION_UPDATE);
478         BLI_callback_exec(CTX_data_main(C), NULL, BLI_CB_EVT_LOAD_POST);
479
480         /* would otherwise be handled by event loop */
481         if (G.background) {
482                 Main *bmain = CTX_data_main(C);
483                 BKE_scene_update_tagged(bmain->eval_ctx, bmain, CTX_data_scene(C));
484         }
485
486         WM_event_add_notifier(C, NC_WM | ND_FILEREAD, NULL);
487
488         /* report any errors.
489          * currently disabled if addons aren't yet loaded */
490         if (addons_loaded) {
491                 wm_file_read_report(C);
492         }
493
494         if (!G.background) {
495                 /* in background mode this makes it hard to load
496                  * a blend file and do anything since the screen
497                  * won't be set to a valid value again */
498                 CTX_wm_window_set(C, NULL); /* exits queues */
499         }
500
501         if (!G.background) {
502 //              undo_editmode_clear();
503                 BKE_undo_reset();
504                 BKE_undo_write(C, "original");  /* save current state */
505         }
506 }
507
508 bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
509 {
510         /* assume automated tasks with background, don't write recent file list */
511         const bool do_history = (G.background == false) && (CTX_wm_manager(C)->op_undo_depth == 0);
512         bool success = false;
513         int retval;
514
515         /* so we can get the error message */
516         errno = 0;
517
518         WM_cursor_wait(1);
519
520         BLI_callback_exec(CTX_data_main(C), NULL, BLI_CB_EVT_LOAD_PRE);
521
522         UI_view2d_zoom_cache_reset();
523
524         /* first try to append data from exotic file formats... */
525         /* it throws error box when file doesn't exist and returns -1 */
526         /* note; it should set some error message somewhere... (ton) */
527         retval = wm_read_exotic(filepath);
528         
529         /* we didn't succeed, now try to read Blender file */
530         if (retval == BKE_READ_EXOTIC_OK_BLEND) {
531                 int G_f = G.f;
532                 ListBase wmbase;
533
534                 /* put aside screens to match with persistent windows later */
535                 /* also exit screens and editors */
536                 wm_window_match_init(C, &wmbase); 
537                 
538                 /* confusing this global... */
539                 G.relbase_valid = 1;
540                 retval = BKE_read_file(C, filepath, reports);
541                 /* when loading startup.blend's, we can be left with a blank path */
542                 if (G.main->name[0]) {
543                         G.save_over = 1;
544                 }
545                 else {
546                         G.save_over = 0;
547                         G.relbase_valid = 0;
548                 }
549
550                 /* this flag is initialized by the operator but overwritten on read.
551                  * need to re-enable it here else drivers + registered scripts wont work. */
552                 if (G.f != G_f) {
553                         const int flags_keep = (G_SCRIPT_AUTOEXEC | G_SCRIPT_OVERRIDE_PREF);
554                         G.f = (G.f & ~flags_keep) | (G_f & flags_keep);
555                 }
556
557                 /* match the read WM with current WM */
558                 wm_window_match_do(C, &wmbase);
559                 WM_check(C); /* opens window(s), checks keymaps */
560
561                 if (retval == BKE_READ_FILE_OK_USERPREFS) {
562                         /* in case a userdef is read from regular .blend */
563                         wm_init_userdef(C, false);
564                 }
565                 
566                 if (retval != BKE_READ_FILE_FAIL) {
567                         if (do_history) {
568                                 wm_history_file_update();
569                         }
570                 }
571
572                 wm_file_read_post(C, false);
573
574                 success = true;
575         }
576 #if 0
577         else if (retval == BKE_READ_EXOTIC_OK_OTHER)
578                 BKE_undo_write(C, "Import file");
579 #endif
580         else if (retval == BKE_READ_EXOTIC_FAIL_OPEN) {
581                 BKE_reportf(reports, RPT_ERROR, "Cannot read file '%s': %s", filepath,
582                             errno ? strerror(errno) : TIP_("unable to open the file"));
583         }
584         else if (retval == BKE_READ_EXOTIC_FAIL_FORMAT) {
585                 BKE_reportf(reports, RPT_ERROR, "File format is not supported in file '%s'", filepath);
586         }
587         else if (retval == BKE_READ_EXOTIC_FAIL_PATH) {
588                 BKE_reportf(reports, RPT_ERROR, "File path '%s' invalid", filepath);
589         }
590         else {
591                 BKE_reportf(reports, RPT_ERROR, "Unknown error loading '%s'", filepath);
592                 BLI_assert(!"invalid 'retval'");
593         }
594
595
596         if (success == false) {
597                 /* remove from recent files list */
598                 if (do_history) {
599                         RecentFile *recent = wm_file_history_find(filepath);
600                         if (recent) {
601                                 wm_history_file_free(recent);
602                                 wm_history_file_write();
603                         }
604                 }
605         }
606
607         WM_cursor_wait(0);
608
609         return success;
610
611 }
612
613
614 /**
615  * called on startup,  (context entirely filled with NULLs)
616  * or called for 'New File'
617  * both startup.blend and userpref.blend are checked
618  * the optional parameter custom_file points to an alternative startup page
619  * custom_file can be NULL
620  */
621 int wm_homefile_read(bContext *C, ReportList *reports, bool from_memory, const char *custom_file)
622 {
623         ListBase wmbase;
624         char startstr[FILE_MAX];
625         char prefstr[FILE_MAX];
626         int success = 0;
627
628         /* Indicates whether user preferences were really load from memory.
629          *
630          * This is used for versioning code, and for this we can not rely on from_memory
631          * passed via argument. This is because there might be configuration folder
632          * exists but it might not have userpref.blend and in this case we fallback to
633          * reading home file from memory.
634          *
635          * And in this case versioning code is to be run.
636          */
637         bool read_userdef_from_memory = true;
638
639         /* options exclude eachother */
640         BLI_assert((from_memory && custom_file) == 0);
641
642         if ((G.f & G_SCRIPT_OVERRIDE_PREF) == 0) {
643                 BKE_BIT_TEST_SET(G.f, (U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0, G_SCRIPT_AUTOEXEC);
644         }
645
646         BLI_callback_exec(CTX_data_main(C), NULL, BLI_CB_EVT_LOAD_PRE);
647
648         UI_view2d_zoom_cache_reset();
649
650         G.relbase_valid = 0;
651         if (!from_memory) {
652                 const char * const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL);
653                 if (custom_file) {
654                         BLI_strncpy(startstr, custom_file, FILE_MAX);
655
656                         if (cfgdir) {
657                                 BLI_make_file_string(G.main->name, prefstr, cfgdir, BLENDER_USERPREF_FILE);
658                         }
659                         else {
660                                 prefstr[0] = '\0';
661                         }
662                 }
663                 else if (cfgdir) {
664                         BLI_make_file_string(G.main->name, startstr, cfgdir, BLENDER_STARTUP_FILE);
665                         BLI_make_file_string(G.main->name, prefstr, cfgdir, BLENDER_USERPREF_FILE);
666                 }
667                 else {
668                         startstr[0] = '\0';
669                         prefstr[0] = '\0';
670                         from_memory = 1;
671                 }
672         }
673         
674         /* put aside screens to match with persistent windows later */
675         wm_window_match_init(C, &wmbase);
676         
677         if (!from_memory) {
678                 if (BLI_access(startstr, R_OK) == 0) {
679                         success = (BKE_read_file(C, startstr, NULL) != BKE_READ_FILE_FAIL);
680                 }
681                 if (BLI_listbase_is_empty(&U.themes)) {
682                         if (G.debug & G_DEBUG)
683                                 printf("\nNote: No (valid) '%s' found, fall back to built-in default.\n\n", startstr);
684                         success = 0;
685                 }
686         }
687
688         if (success == 0 && custom_file && reports) {
689                 BKE_reportf(reports, RPT_ERROR, "Could not read '%s'", custom_file);
690                 /*We can not return from here because wm is already reset*/
691         }
692
693         if (success == 0) {
694                 success = BKE_read_file_from_memory(C, datatoc_startup_blend, datatoc_startup_blend_size, NULL, true);
695                 if (BLI_listbase_is_empty(&wmbase)) {
696                         wm_clear_default_size(C);
697                 }
698                 BKE_tempdir_init(U.tempdir);
699
700 #ifdef WITH_PYTHON_SECURITY
701                 /* use alternative setting for security nuts
702                  * otherwise we'd need to patch the binary blob - startup.blend.c */
703                 U.flag |= USER_SCRIPT_AUTOEXEC_DISABLE;
704 #endif
705         }
706         
707         /* check new prefs only after startup.blend was finished */
708         if (!from_memory && BLI_exists(prefstr)) {
709                 int done = BKE_read_file_userdef(prefstr, NULL);
710                 if (done != BKE_READ_FILE_FAIL) {
711                         read_userdef_from_memory = false;
712                         printf("Read new prefs: %s\n", prefstr);
713                 }
714         }
715         
716         /* prevent buggy files that had G_FILE_RELATIVE_REMAP written out by mistake. Screws up autosaves otherwise
717          * can remove this eventually, only in a 2.53 and older, now its not written */
718         G.fileflags &= ~G_FILE_RELATIVE_REMAP;
719         
720         /* check userdef before open window, keymaps etc */
721         wm_init_userdef(C, read_userdef_from_memory);
722         
723         /* match the read WM with current WM */
724         wm_window_match_do(C, &wmbase); 
725         WM_check(C); /* opens window(s), checks keymaps */
726
727         G.main->name[0] = '\0';
728
729         /* When loading factory settings, the reset solid OpenGL lights need to be applied. */
730         if (!G.background) GPU_default_lights();
731         
732         /* XXX */
733         G.save_over = 0;    // start with save preference untitled.blend
734         G.fileflags &= ~G_FILE_AUTOPLAY;    /*  disable autoplay in startup.blend... */
735
736         wm_file_read_post(C, true);
737
738         return true;
739 }
740
741 int wm_history_file_read_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
742 {
743         ED_file_read_bookmarks();
744         wm_history_file_read();
745         return OPERATOR_FINISHED;
746 }
747
748 int wm_homefile_read_exec(bContext *C, wmOperator *op)
749 {
750         const bool from_memory = (STREQ(op->type->idname, "WM_OT_read_factory_settings"));
751         char filepath_buf[FILE_MAX];
752         const char *filepath = NULL;
753
754         if (!from_memory) {
755                 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "filepath");
756
757                 /* This can be used when loading of a start-up file should only change
758                  * the scene content but keep the blender UI as it is. */
759                 wm_open_init_load_ui(op, true);
760                 BKE_BIT_TEST_SET(G.fileflags, !RNA_boolean_get(op->ptr, "load_ui"), G_FILE_NO_UI);
761
762                 if (RNA_property_is_set(op->ptr, prop)) {
763                         RNA_property_string_get(op->ptr, prop, filepath_buf);
764                         filepath = filepath_buf;
765                         if (BLI_access(filepath, R_OK)) {
766                                 BKE_reportf(op->reports, RPT_ERROR, "Can't read alternative start-up file: '%s'", filepath);
767                                 return OPERATOR_CANCELLED;
768                         }
769                 }
770         }
771         else {
772                 /* always load UI for factory settings (prefs will re-init) */
773                 G.fileflags &= ~G_FILE_NO_UI;
774         }
775
776         return wm_homefile_read(C, op->reports, from_memory, filepath) ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
777 }
778
779 /** \name WM History File API
780  * \{ */
781
782 void wm_history_file_read(void)
783 {
784         char name[FILE_MAX];
785         LinkNode *l, *lines;
786         struct RecentFile *recent;
787         const char *line;
788         int num;
789         const char * const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL);
790
791         if (!cfgdir) return;
792
793         BLI_make_file_string("/", name, cfgdir, BLENDER_HISTORY_FILE);
794
795         lines = BLI_file_read_as_lines(name);
796
797         BLI_listbase_clear(&G.recent_files);
798
799         /* read list of recent opened files from recent-files.txt to memory */
800         for (l = lines, num = 0; l && (num < U.recent_files); l = l->next) {
801                 line = l->link;
802                 /* don't check if files exist, causes slow startup for remote/external drives */
803                 if (line[0]) {
804                         recent = (RecentFile *)MEM_mallocN(sizeof(RecentFile), "RecentFile");
805                         BLI_addtail(&(G.recent_files), recent);
806                         recent->filepath = BLI_strdup(line);
807                         num++;
808                 }
809         }
810         
811         BLI_file_free_lines(lines);
812 }
813
814 static RecentFile *wm_history_file_new(const char *filepath)
815 {
816         RecentFile *recent = MEM_mallocN(sizeof(RecentFile), "RecentFile");
817         recent->filepath = BLI_strdup(filepath);
818         return recent;
819 }
820
821 static void wm_history_file_free(RecentFile *recent)
822 {
823         BLI_assert(BLI_findindex(&G.recent_files, recent) != -1);
824         MEM_freeN(recent->filepath);
825         BLI_freelinkN(&G.recent_files, recent);
826 }
827
828 static RecentFile *wm_file_history_find(const char *filepath)
829 {
830         return BLI_findstring_ptr(&G.recent_files, filepath, offsetof(RecentFile, filepath));
831 }
832
833 /**
834  * Write #BLENDER_HISTORY_FILE as-is, without checking the environment
835  * (thats handled by #wm_history_file_update).
836  */
837 static void wm_history_file_write(void)
838 {
839         const char *user_config_dir;
840         char name[FILE_MAX];
841         FILE *fp;
842
843         /* will be NULL in background mode */
844         user_config_dir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL);
845         if (!user_config_dir)
846                 return;
847
848         BLI_make_file_string("/", name, user_config_dir, BLENDER_HISTORY_FILE);
849
850         fp = BLI_fopen(name, "w");
851         if (fp) {
852                 struct RecentFile *recent;
853                 for (recent = G.recent_files.first; recent; recent = recent->next) {
854                         fprintf(fp, "%s\n", recent->filepath);
855                 }
856                 fclose(fp);
857         }
858 }
859
860 /**
861  * Run after saving a file to refresh the #BLENDER_HISTORY_FILE list.
862  */
863 static void wm_history_file_update(void)
864 {
865         RecentFile *recent;
866
867         /* no write history for recovered startup files */
868         if (G.main->name[0] == 0)
869                 return;
870
871         recent = G.recent_files.first;
872         /* refresh recent-files.txt of recent opened files, when current file was changed */
873         if (!(recent) || (BLI_path_cmp(recent->filepath, G.main->name) != 0)) {
874
875                 recent = wm_file_history_find(G.main->name);
876                 if (recent) {
877                         BLI_remlink(&G.recent_files, recent);
878                 }
879                 else {
880                         RecentFile *recent_next;
881                         for (recent = BLI_findlink(&G.recent_files, U.recent_files - 1); recent; recent = recent_next) {
882                                 recent_next = recent->next;
883                                 wm_history_file_free(recent);
884                         }
885                         recent = wm_history_file_new(G.main->name);
886                 }
887
888                 /* add current file to the beginning of list */
889                 BLI_addhead(&(G.recent_files), recent);
890
891                 /* write current file to recent-files.txt */
892                 wm_history_file_write();
893
894                 /* also update most recent files on System */
895                 GHOST_addToSystemRecentFiles(G.main->name);
896         }
897 }
898
899 /** \} */
900
901
902 /* screen can be NULL */
903 static ImBuf *blend_file_thumb(Scene *scene, bScreen *screen, BlendThumbnail **thumb_pt)
904 {
905         /* will be scaled down, but gives some nice oversampling */
906         ImBuf *ibuf;
907         BlendThumbnail *thumb;
908         char err_out[256] = "unknown";
909
910         /* screen if no camera found */
911         ScrArea *sa = NULL;
912         ARegion *ar = NULL;
913         View3D *v3d = NULL;
914
915         /* In case we are given a valid thumbnail data, just generate image from it. */
916         if (*thumb_pt) {
917                 thumb = *thumb_pt;
918                 return BKE_main_thumbnail_to_imbuf(NULL, thumb);
919         }
920
921         /* scene can be NULL if running a script at startup and calling the save operator */
922         if (G.background || scene == NULL)
923                 return NULL;
924
925         if ((scene->camera == NULL) && (screen != NULL)) {
926                 sa = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
927                 ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
928                 if (ar) {
929                         v3d = sa->spacedata.first;
930                 }
931         }
932
933         if (scene->camera == NULL && v3d == NULL) {
934                 return NULL;
935         }
936
937         /* gets scaled to BLEN_THUMB_SIZE */
938         if (scene->camera) {
939                 ibuf = ED_view3d_draw_offscreen_imbuf_simple(
940                         scene, scene->camera,
941                         BLEN_THUMB_SIZE * 2, BLEN_THUMB_SIZE * 2,
942                         IB_rect, OB_SOLID, false, false, false, R_ALPHAPREMUL, 0, false, NULL,
943                         NULL, NULL, err_out);
944         }
945         else {
946                 ibuf = ED_view3d_draw_offscreen_imbuf(
947                         scene, v3d, ar,
948                         BLEN_THUMB_SIZE * 2, BLEN_THUMB_SIZE * 2,
949                         IB_rect, false, R_ALPHAPREMUL, 0, false, NULL,
950                         NULL, NULL, err_out);
951         }
952
953         if (ibuf) {
954                 float aspect = (scene->r.xsch * scene->r.xasp) / (scene->r.ysch * scene->r.yasp);
955
956                 /* dirty oversampling */
957                 IMB_scaleImBuf(ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE);
958
959                 /* add pretty overlay */
960                 IMB_thumb_overlay_blend(ibuf->rect, ibuf->x, ibuf->y, aspect);
961                 
962                 thumb = BKE_main_thumbnail_from_imbuf(NULL, ibuf);
963         }
964         else {
965                 /* '*thumb_pt' needs to stay NULL to prevent a bad thumbnail from being handled */
966                 fprintf(stderr, "blend_file_thumb failed to create thumbnail: %s\n", err_out);
967                 thumb = NULL;
968         }
969         
970         /* must be freed by caller */
971         *thumb_pt = thumb;
972         
973         return ibuf;
974 }
975
976 /* easy access from gdb */
977 bool write_crash_blend(void)
978 {
979         char path[FILE_MAX];
980         int fileflags = G.fileflags & ~(G_FILE_HISTORY); /* don't do file history on crash file */
981
982         BLI_strncpy(path, G.main->name, sizeof(path));
983         BLI_replace_extension(path, sizeof(path), "_crash.blend");
984         if (BLO_write_file(G.main, path, fileflags, NULL, NULL)) {
985                 printf("written: %s\n", path);
986                 return 1;
987         }
988         else {
989                 printf("failed: %s\n", path);
990                 return 0;
991         }
992 }
993
994 /**
995  * \see #wm_homefile_write_exec wraps #BLO_write_file in a similar way.
996  */
997 int wm_file_write(bContext *C, const char *filepath, int fileflags, ReportList *reports)
998 {
999         Library *li;
1000         int len;
1001         int ret = -1;
1002         BlendThumbnail *thumb, *main_thumb;
1003         ImBuf *ibuf_thumb = NULL;
1004
1005         len = strlen(filepath);
1006         
1007         if (len == 0) {
1008                 BKE_report(reports, RPT_ERROR, "Path is empty, cannot save");
1009                 return ret;
1010         }
1011
1012         if (len >= FILE_MAX) {
1013                 BKE_report(reports, RPT_ERROR, "Path too long, cannot save");
1014                 return ret;
1015         }
1016         
1017         /* Check if file write permission is ok */
1018         if (BLI_exists(filepath) && !BLI_file_is_writable(filepath)) {
1019                 BKE_reportf(reports, RPT_ERROR, "Cannot save blend file, path '%s' is not writable", filepath);
1020                 return ret;
1021         }
1022  
1023         /* note: used to replace the file extension (to ensure '.blend'),
1024          * no need to now because the operator ensures,
1025          * its handy for scripts to save to a predefined name without blender editing it */
1026         
1027         /* send the OnSave event */
1028         for (li = G.main->library.first; li; li = li->id.next) {
1029                 if (BLI_path_cmp(li->filepath, filepath) == 0) {
1030                         BKE_reportf(reports, RPT_ERROR, "Cannot overwrite used library '%.240s'", filepath);
1031                         return ret;
1032                 }
1033         }
1034
1035         /* Call pre-save callbacks befores writing preview, that way you can generate custom file thumbnail... */
1036         BLI_callback_exec(G.main, NULL, BLI_CB_EVT_SAVE_PRE);
1037
1038         /* blend file thumbnail */
1039         /* save before exit_editmode, otherwise derivedmeshes for shared data corrupt #27765) */
1040         /* Main now can store a .blend thumbnail, usefull for background mode or thumbnail customization. */
1041         main_thumb = thumb = CTX_data_main(C)->blen_thumb;
1042         if ((U.flag & USER_SAVE_PREVIEWS) && BLI_thread_is_main()) {
1043                 ibuf_thumb = blend_file_thumb(CTX_data_scene(C), CTX_wm_screen(C), &thumb);
1044         }
1045
1046         /* operator now handles overwrite checks */
1047
1048         if (G.fileflags & G_AUTOPACK) {
1049                 packAll(G.main, reports, false);
1050         }
1051
1052         /* don't forget not to return without! */
1053         WM_cursor_wait(1);
1054         
1055         ED_editors_flush_edits(C, false);
1056
1057         fileflags |= G_FILE_HISTORY; /* write file history */
1058
1059         /* first time saving */
1060         /* XXX temp solution to solve bug, real fix coming (ton) */
1061         if ((G.main->name[0] == '\0') && !(fileflags & G_FILE_SAVE_COPY)) {
1062                 BLI_strncpy(G.main->name, filepath, sizeof(G.main->name));
1063         }
1064
1065         /* XXX temp solution to solve bug, real fix coming (ton) */
1066         G.main->recovered = 0;
1067         
1068         if (BLO_write_file(CTX_data_main(C), filepath, fileflags, reports, thumb)) {
1069                 if (!(fileflags & G_FILE_SAVE_COPY)) {
1070                         G.relbase_valid = 1;
1071                         BLI_strncpy(G.main->name, filepath, sizeof(G.main->name));  /* is guaranteed current file */
1072         
1073                         G.save_over = 1; /* disable untitled.blend convention */
1074                 }
1075
1076                 BKE_BIT_TEST_SET(G.fileflags, fileflags & G_FILE_COMPRESS, G_FILE_COMPRESS);
1077                 BKE_BIT_TEST_SET(G.fileflags, fileflags & G_FILE_AUTOPLAY, G_FILE_AUTOPLAY);
1078
1079                 /* prevent background mode scripts from clobbering history */
1080                 if (!G.background) {
1081                         wm_history_file_update();
1082                 }
1083
1084                 BLI_callback_exec(G.main, NULL, BLI_CB_EVT_SAVE_POST);
1085
1086                 /* run this function after because the file cant be written before the blend is */
1087                 if (ibuf_thumb) {
1088                         IMB_thumb_delete(filepath, THB_FAIL); /* without this a failed thumb overrides */
1089                         ibuf_thumb = IMB_thumb_create(filepath, THB_LARGE, THB_SOURCE_BLEND, ibuf_thumb);
1090                 }
1091
1092                 ret = 0;  /* Success. */
1093         }
1094
1095         if (ibuf_thumb) {
1096                 IMB_freeImBuf(ibuf_thumb);
1097         }
1098         if (thumb && thumb != main_thumb) {
1099                 MEM_freeN(thumb);
1100         }
1101
1102         WM_cursor_wait(0);
1103
1104         return ret;
1105 }
1106
1107 /**
1108  * \see #wm_file_write wraps #BLO_write_file in a similar way.
1109  */
1110 int wm_homefile_write_exec(bContext *C, wmOperator *op)
1111 {
1112         wmWindowManager *wm = CTX_wm_manager(C);
1113         wmWindow *win = CTX_wm_window(C);
1114         char filepath[FILE_MAX];
1115         int fileflags;
1116
1117         BLI_callback_exec(G.main, NULL, BLI_CB_EVT_SAVE_PRE);
1118
1119         /* check current window and close it if temp */
1120         if (win && win->screen->temp)
1121                 wm_window_close(C, wm, win);
1122         
1123         /* update keymaps in user preferences */
1124         WM_keyconfig_update(wm);
1125         
1126         BLI_make_file_string("/", filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_STARTUP_FILE);
1127         printf("trying to save homefile at %s ", filepath);
1128         
1129         ED_editors_flush_edits(C, false);
1130
1131         /*  force save as regular blend file */
1132         fileflags = G.fileflags & ~(G_FILE_COMPRESS | G_FILE_AUTOPLAY | G_FILE_HISTORY);
1133
1134         if (BLO_write_file(CTX_data_main(C), filepath, fileflags | G_FILE_USERPREFS, op->reports, NULL) == 0) {
1135                 printf("fail\n");
1136                 return OPERATOR_CANCELLED;
1137         }
1138         
1139         printf("ok\n");
1140
1141         G.save_over = 0;
1142
1143         BLI_callback_exec(G.main, NULL, BLI_CB_EVT_SAVE_POST);
1144
1145         return OPERATOR_FINISHED;
1146 }
1147
1148 /* Only save the prefs block. operator entry */
1149 int wm_userpref_write_exec(bContext *C, wmOperator *op)
1150 {
1151         wmWindowManager *wm = CTX_wm_manager(C);
1152         char filepath[FILE_MAX];
1153         
1154         /* update keymaps in user preferences */
1155         WM_keyconfig_update(wm);
1156         
1157         BLI_make_file_string("/", filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_USERPREF_FILE);
1158         printf("trying to save userpref at %s ", filepath);
1159         
1160         if (BKE_write_file_userdef(filepath, op->reports) == 0) {
1161                 printf("fail\n");
1162                 return OPERATOR_CANCELLED;
1163         }
1164         
1165         printf("ok\n");
1166         
1167         return OPERATOR_FINISHED;
1168 }
1169
1170 /************************ autosave ****************************/
1171
1172 void wm_autosave_location(char *filepath)
1173 {
1174         const int pid = abs(getpid());
1175         char path[1024];
1176 #ifdef WIN32
1177         const char *savedir;
1178 #endif
1179
1180         if (G.main && G.relbase_valid) {
1181                 const char *basename = BLI_path_basename(G.main->name);
1182                 int len = strlen(basename) - 6;
1183                 BLI_snprintf(path, sizeof(path), "%.*s.blend", len, basename);
1184         }
1185         else {
1186                 BLI_snprintf(path, sizeof(path), "%d.blend", pid);
1187         }
1188
1189 #ifdef WIN32
1190         /* XXX Need to investigate how to handle default location of '/tmp/'
1191          * This is a relative directory on Windows, and it may be
1192          * found. Example:
1193          * Blender installed on D:\ drive, D:\ drive has D:\tmp\
1194          * Now, BLI_exists() will find '/tmp/' exists, but
1195          * BLI_make_file_string will create string that has it most likely on C:\
1196          * through get_default_root().
1197          * If there is no C:\tmp autosave fails. */
1198         if (!BLI_exists(BKE_tempdir_base())) {
1199                 savedir = BKE_appdir_folder_id_create(BLENDER_USER_AUTOSAVE, NULL);
1200                 BLI_make_file_string("/", filepath, savedir, path);
1201                 return;
1202         }
1203 #endif
1204
1205         BLI_make_file_string("/", filepath, BKE_tempdir_base(), path);
1206 }
1207
1208 void WM_autosave_init(wmWindowManager *wm)
1209 {
1210         wm_autosave_timer_ended(wm);
1211
1212         if (U.flag & USER_AUTOSAVE)
1213                 wm->autosavetimer = WM_event_add_timer(wm, NULL, TIMERAUTOSAVE, U.savetime * 60.0);
1214 }
1215
1216 void wm_autosave_timer(const bContext *C, wmWindowManager *wm, wmTimer *UNUSED(wt))
1217 {
1218         wmWindow *win;
1219         wmEventHandler *handler;
1220         char filepath[FILE_MAX];
1221         
1222         WM_event_remove_timer(wm, NULL, wm->autosavetimer);
1223
1224         /* if a modal operator is running, don't autosave, but try again in 10 seconds */
1225         for (win = wm->windows.first; win; win = win->next) {
1226                 for (handler = win->modalhandlers.first; handler; handler = handler->next) {
1227                         if (handler->op) {
1228                                 wm->autosavetimer = WM_event_add_timer(wm, NULL, TIMERAUTOSAVE, 10.0);
1229                                 return;
1230                         }
1231                 }
1232         }
1233
1234         wm_autosave_location(filepath);
1235
1236         if (U.uiflag & USER_GLOBALUNDO) {
1237                 /* fast save of last undobuffer, now with UI */
1238                 BKE_undo_save_file(filepath);
1239         }
1240         else {
1241                 /*  save as regular blend file */
1242                 int fileflags = G.fileflags & ~(G_FILE_COMPRESS | G_FILE_AUTOPLAY | G_FILE_HISTORY);
1243
1244                 ED_editors_flush_edits(C, false);
1245
1246                 /* no error reporting to console */
1247                 BLO_write_file(CTX_data_main(C), filepath, fileflags, NULL, NULL);
1248         }
1249         /* do timer after file write, just in case file write takes a long time */
1250         wm->autosavetimer = WM_event_add_timer(wm, NULL, TIMERAUTOSAVE, U.savetime * 60.0);
1251 }
1252
1253 void wm_autosave_timer_ended(wmWindowManager *wm)
1254 {
1255         if (wm->autosavetimer) {
1256                 WM_event_remove_timer(wm, NULL, wm->autosavetimer);
1257                 wm->autosavetimer = NULL;
1258         }
1259 }
1260
1261 void wm_autosave_delete(void)
1262 {
1263         char filename[FILE_MAX];
1264         
1265         wm_autosave_location(filename);
1266
1267         if (BLI_exists(filename)) {
1268                 char str[FILE_MAX];
1269                 BLI_make_file_string("/", str, BKE_tempdir_base(), BLENDER_QUIT_FILE);
1270
1271                 /* if global undo; remove tempsave, otherwise rename */
1272                 if (U.uiflag & USER_GLOBALUNDO) BLI_delete(filename, false, false);
1273                 else BLI_rename(filename, str);
1274         }
1275 }
1276
1277 void wm_autosave_read(bContext *C, ReportList *reports)
1278 {
1279         char filename[FILE_MAX];
1280
1281         wm_autosave_location(filename);
1282         WM_file_read(C, filename, reports);
1283 }
1284
1285
1286 /** \name Initialize WM_OT_open_xxx properties
1287  *
1288  * Check if load_ui was set by the caller.
1289  * Fall back to user preference when file flags not specified.
1290  *
1291  * \{ */
1292
1293 void wm_open_init_load_ui(wmOperator *op, bool use_prefs)
1294 {
1295         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "load_ui");
1296         if (!RNA_property_is_set(op->ptr, prop)) {
1297                 bool value = use_prefs ?
1298                              ((U.flag & USER_FILENOUI) == 0) :
1299                              ((G.fileflags & G_FILE_NO_UI) == 0);
1300
1301                 RNA_property_boolean_set(op->ptr, prop, value);
1302         }
1303 }
1304
1305 void wm_open_init_use_scripts(wmOperator *op, bool use_prefs)
1306 {
1307         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_scripts");
1308         if (!RNA_property_is_set(op->ptr, prop)) {
1309                 /* use G_SCRIPT_AUTOEXEC rather than the userpref because this means if
1310                  * the flag has been disabled from the command line, then opening
1311                  * from the menu wont enable this setting. */
1312                 bool value = use_prefs ?
1313                              ((U.flag & USER_SCRIPT_AUTOEXEC_DISABLE) == 0) :
1314                              ((G.f & G_SCRIPT_AUTOEXEC) != 0);
1315
1316                 RNA_property_boolean_set(op->ptr, prop, value);
1317         }
1318 }
1319
1320 /** \} */
1321
1322 void WM_file_tag_modified(const bContext *C)
1323 {
1324         wmWindowManager *wm = CTX_wm_manager(C);
1325         if (wm->file_saved) {
1326                 wm->file_saved = 0;
1327                 /* notifier that data changed, for save-over warning or header */
1328                 WM_event_add_notifier(C, NC_WM | ND_DATACHANGED, NULL);
1329         }
1330 }