Blender V4.5
userpref_ops.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2009 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cstring>
10#include <fmt/format.h>
11
12#include "DNA_screen_types.h"
13#include "DNA_space_types.h"
14
15#include "BLI_listbase.h"
16#ifdef WIN32
17# include "BLI_winstuff.h"
18#endif
19#include "BLI_fileops.h"
20#include "BLI_path_utils.hh"
21#include "BLI_string.h"
22#include "BLI_string_utf8.h"
23
24#include "BKE_callbacks.hh"
25#include "BKE_context.hh"
26#include "BKE_global.hh"
27#include "BKE_main.hh"
28#include "BKE_preferences.h"
29
30#include "BKE_report.hh"
31
32#include "BLT_translation.hh"
33
34#include "RNA_access.hh"
35#include "RNA_define.hh"
36#include "RNA_prototypes.hh"
37#include "RNA_types.hh"
38
39#include "UI_interface.hh"
40
41#include "WM_api.hh"
42#include "WM_types.hh"
43
44#include "ED_asset.hh"
45#include "ED_userpref.hh"
46
47#include "MEM_guardedalloc.h"
48
49/* -------------------------------------------------------------------- */
52
63
65{
66 /* identifiers */
67 ot->name = "Reset to Default Theme";
68 ot->idname = "PREFERENCES_OT_reset_default_theme";
69 ot->description = "Reset to the default theme colors";
70
71 /* callbacks */
73
74 /* flags */
75 ot->flag = OPTYPE_REGISTER;
76}
77
79
80/* -------------------------------------------------------------------- */
83
85{
86 bPathCompare *path_cmp = MEM_callocN<bPathCompare>("bPathCompare");
87 BLI_addtail(&U.autoexec_paths, path_cmp);
88 U.runtime.is_dirty = true;
89 return OPERATOR_FINISHED;
90}
91
93{
94 ot->name = "Add Auto-Execution Path";
95 ot->idname = "PREFERENCES_OT_autoexec_path_add";
96 ot->description = "Add path to exclude from auto-execution";
97
99
100 ot->flag = OPTYPE_INTERNAL;
101}
102
104
105/* -------------------------------------------------------------------- */
108
110{
111 const int index = RNA_int_get(op->ptr, "index");
112 bPathCompare *path_cmp = static_cast<bPathCompare *>(BLI_findlink(&U.autoexec_paths, index));
113 if (path_cmp) {
114 BLI_freelinkN(&U.autoexec_paths, path_cmp);
115 U.runtime.is_dirty = true;
116 }
117 return OPERATOR_FINISHED;
118}
119
121{
122 ot->name = "Remove Auto-Execution Path";
123 ot->idname = "PREFERENCES_OT_autoexec_path_remove";
124 ot->description = "Remove path to exclude from auto-execution";
125
127
128 ot->flag = OPTYPE_INTERNAL;
129
130 RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
131}
132
134
135/* -------------------------------------------------------------------- */
138
140{
141 char *path = RNA_string_get_alloc(op->ptr, "directory", nullptr, 0, nullptr);
142 char dirname[FILE_MAXFILE];
143
146
147 /* nullptr is a valid directory path here. A library without path will be created then. */
148 const bUserAssetLibrary *new_library = BKE_preferences_asset_library_add(&U, dirname, path);
149 /* Activate new library in the UI for further setup. */
150 U.active_asset_library = BLI_findindex(&U.asset_libraries, new_library);
151 U.runtime.is_dirty = true;
152
153 /* There's no dedicated notifier for the Preferences. */
156
157 MEM_freeN(path);
158 return OPERATOR_FINISHED;
159}
160
162 wmOperator *op,
163 const wmEvent * /*event*/)
164{
165 if (!RNA_struct_property_is_set(op->ptr, "directory")) {
168 }
169
171}
172
174{
175 ot->name = "Add Asset Library";
176 ot->idname = "PREFERENCES_OT_asset_library_add";
177 ot->description = "Add a directory to be used by the Asset Browser as source of assets";
178
181
182 ot->flag = OPTYPE_INTERNAL;
183
191}
192
194
195/* -------------------------------------------------------------------- */
198
200{
201 if (BLI_listbase_is_empty(&U.asset_libraries)) {
202 CTX_wm_operator_poll_msg_set(C, "There is no asset library to remove");
203 return false;
204 }
205 return true;
206}
207
209{
210 const int index = RNA_int_get(op->ptr, "index");
211 bUserAssetLibrary *library = static_cast<bUserAssetLibrary *>(
212 BLI_findlink(&U.asset_libraries, index));
213 if (!library) {
214 return OPERATOR_CANCELLED;
215 }
216
218 const int count_remaining = BLI_listbase_count(&U.asset_libraries);
219 /* Update active library index to be in range. */
220 CLAMP(U.active_asset_library, 0, count_remaining - 1);
221 U.runtime.is_dirty = true;
222
224 /* Trigger refresh for the Asset Browser. */
226
227 return OPERATOR_FINISHED;
228}
229
231{
232 ot->name = "Remove Asset Library";
233 ot->idname = "PREFERENCES_OT_asset_library_remove";
234 ot->description =
235 "Remove a path to a .blend file, so the Asset Browser will not attempt to show it anymore";
236
239
240 ot->flag = OPTYPE_INTERNAL;
241
242 RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
243}
244
246
247/* -------------------------------------------------------------------- */
250
253 Local = 1,
254};
255
257 const bUserExtensionRepoAddType repo_type)
258{
259 switch (repo_type) {
261 return "Remote Repository";
262 }
264 return "User Repository";
265 }
266 }
268 return "";
269}
270
272{
274 RNA_enum_get(op->ptr, "type"));
275
276 Main *bmain = CTX_data_main(C);
278
279 char name[sizeof(bUserExtensionRepo::name)] = "";
280 char remote_url[sizeof(bUserExtensionRepo::remote_url)] = "";
281 char *access_token = nullptr;
282 char custom_directory[sizeof(bUserExtensionRepo::custom_dirpath)] = "";
283
284 const bool use_custom_directory = RNA_boolean_get(op->ptr, "use_custom_directory");
285 const bool use_access_token = RNA_boolean_get(op->ptr, "use_access_token");
286 const bool use_sync_on_startup = RNA_boolean_get(op->ptr, "use_sync_on_startup");
287 if (use_custom_directory) {
288 RNA_string_get(op->ptr, "custom_directory", custom_directory);
289 BLI_path_slash_rstrip(custom_directory);
290 }
291
292 if (repo_type == bUserExtensionRepoAddType::Remote) {
293 RNA_string_get(op->ptr, "remote_url", remote_url);
294
295 if (use_access_token) {
296 if (RNA_string_length(op->ptr, "access_token")) {
297 access_token = RNA_string_get_alloc(op->ptr, "access_token", nullptr, 0, nullptr);
298 }
299 }
300 }
301
302 /* Setup the name using the following logic:
303 * - It has been set so leave as-is.
304 * - Initialize it based on the URL (default for remote repositories).
305 * - Use a default name as a fallback.
306 */
307 {
308 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "name");
309 if (RNA_property_is_set(op->ptr, prop)) {
310 RNA_property_string_get(op->ptr, prop, name);
311 }
312
313 /* Unset or empty, auto-name based on remote URL or local directory. */
314 if (name[0] == '\0') {
315 switch (repo_type) {
318 break;
319 }
321 if (use_custom_directory) {
322 const char *custom_directory_basename = BLI_path_basename(custom_directory);
323 STRNCPY_UTF8(name, custom_directory_basename);
325 }
326 break;
327 }
328 }
329 }
330 if (name[0] == '\0') {
332 }
333 }
334
335 const char *module = custom_directory[0] ? BLI_path_basename(custom_directory) : name;
336 /* Not essential but results in more readable module names.
337 * Otherwise URL's have their '.' removed, making for quite unreadable module names. */
338 char module_buf[FILE_MAX];
339 {
340 STRNCPY(module_buf, module);
341 int i;
342 for (i = 0; module_buf[i]; i++) {
343 if (ELEM(module_buf[i], '.', '-', '/', '\\')) {
344 module_buf[i] = '_';
345 }
346 }
347 /* Strip any trailing underscores. */
348 while ((i > 0) && (module_buf[--i] == '_')) {
349 module_buf[i] = '\0';
350 }
351 module = module_buf;
352 }
353
355 &U, name, module, custom_directory);
356
357 if (use_sync_on_startup) {
359 }
360 if (use_custom_directory) {
362 }
363
364 if (repo_type == bUserExtensionRepoAddType::Remote) {
365 STRNCPY(new_repo->remote_url, remote_url);
367
368 if (use_access_token) {
370 }
371 if (access_token) {
372 new_repo->access_token = access_token;
373 }
374 }
375
376 /* Activate new repository in the UI for further setup. */
377 U.active_extension_repo = BLI_findindex(&U.extension_repos, new_repo);
378 U.runtime.is_dirty = true;
379
380 {
382 nullptr, &RNA_UserExtensionRepo, new_repo);
383 PointerRNA *pointers[] = {&new_repo_ptr};
384
387 }
388
389 /* There's no dedicated notifier for the Preferences. */
391
392 /* Mainly useful when adding a repository from a popup since it's not as obvious
393 * the repository was added compared to the repository popover. */
395 RPT_INFO,
396 "Added %s \"%s\"",
398 new_repo->name);
399
400 return OPERATOR_FINISHED;
401}
402
404 wmOperator *op,
405 const wmEvent *event)
406{
408 RNA_enum_get(op->ptr, "type"));
409 PropertyRNA *prop_name = RNA_struct_find_property(op->ptr, "name");
410 if (!RNA_property_is_set(op->ptr, prop_name)) {
411 const char *name_default = preferences_extension_repo_default_name_from_type(repo_type);
412 /* Leave unset, let this be set by the URL. */
413 if (repo_type == bUserExtensionRepoAddType::Remote) {
414 name_default = nullptr;
415 }
416 RNA_property_string_set(op->ptr, prop_name, name_default);
417 }
418
420 C, op, event, IFACE_("Add New Extension Repository"), IFACE_("Create"));
421}
422
424{
425
426 uiLayout *layout = op->layout;
427 uiLayoutSetPropSep(layout, true);
428 uiLayoutSetPropDecorate(layout, false);
429
430 PointerRNA *ptr = op->ptr;
432
433 switch (repo_type) {
435 layout->prop(op->ptr, "remote_url", UI_ITEM_R_IMMEDIATE, std::nullopt, ICON_NONE);
436 layout->prop(op->ptr, "use_sync_on_startup", UI_ITEM_NONE, std::nullopt, ICON_NONE);
437
439
440 const bool use_access_token = RNA_boolean_get(ptr, "use_access_token");
441 const int token_icon = (use_access_token && RNA_string_length(op->ptr, "access_token")) ?
442 ICON_LOCKED :
443 ICON_UNLOCKED;
444
445 uiLayout *row = &layout->row(true, IFACE_("Authentication"));
446 row->prop(op->ptr, "use_access_token", UI_ITEM_NONE, std::nullopt, ICON_NONE);
447 uiLayout *col = &layout->row(false);
448 uiLayoutSetActive(col, use_access_token);
449 /* Use "immediate" flag to refresh the icon. */
450 col->prop(op->ptr, "access_token", UI_ITEM_R_IMMEDIATE, std::nullopt, token_icon);
451
453
454 break;
455 }
457 layout->prop(op->ptr, "name", UI_ITEM_R_IMMEDIATE, std::nullopt, ICON_NONE);
458 break;
459 }
460 }
461
462 layout->prop(op->ptr, "use_custom_directory", UI_ITEM_NONE, std::nullopt, ICON_NONE);
463 uiLayout *col = &layout->row(false);
464 uiLayoutSetActive(col, RNA_boolean_get(ptr, "use_custom_directory"));
465 col->prop(op->ptr, "custom_directory", UI_ITEM_NONE, std::nullopt, ICON_NONE);
466}
467
469{
470 ot->name = "Add Extension Repository";
471 ot->idname = "PREFERENCES_OT_extension_repo_add";
472 ot->description = "Add a new repository used to store extensions";
473
477
479
480 static const EnumPropertyItem repo_type_items[] = {
482 "REMOTE",
483 ICON_INTERNET,
484 "Add Remote Repository",
485 "Add a repository referencing a remote repository "
486 "with support for listing and updating extensions"},
488 "LOCAL",
489 ICON_DISK_DRIVE,
490 "Add Local Repository",
491 "Add a repository managed manually without referencing an external repository"},
492 {0, nullptr, 0, nullptr, nullptr},
493 };
494
495 /* After creating a new repository some settings can't be easily changed
496 * (especially the custom directory). To avoid showing a partially initialized repository,
497 * set these values upon creation instead of having the user create the repository and change
498 * them afterwards.
499 *
500 * An alternative solution could be implemented by creating an "uninitialized" repository,
501 * setting up all it's properties then running an "initialize" operator however this seems
502 * unnecessarily confusing as in most cases a user can do this in one step by naming and
503 * setting the repositories URL (optionally the custom-directory). */
504
505 /* Copy the RNA values are copied into the operator to avoid repetition. */
506 StructRNA *type_ref = &RNA_UserExtensionRepo;
507
508 { /* Name. */
509 const char *prop_id = "name";
510 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
511 PropertyRNA *prop = RNA_def_string(ot->srna,
512 prop_id,
513 nullptr,
515 RNA_property_ui_name_raw(prop_ref),
518 }
519
520 { /* Remote Path. */
521 const char *prop_id = "remote_url";
522 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
523 PropertyRNA *prop = RNA_def_string(ot->srna,
524 prop_id,
525 nullptr,
527 RNA_property_ui_name_raw(prop_ref),
530 }
531
532 { /* Use Access Token. */
533 const char *prop_id = "use_access_token";
534 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
535 PropertyRNA *prop = RNA_def_boolean(ot->srna,
536 prop_id,
537 false,
538 RNA_property_ui_name_raw(prop_ref),
541 }
542
543 { /* Access Token (dynamic length). */
544 const char *prop_id = "access_token";
545 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
546 PropertyRNA *prop = RNA_def_string(ot->srna,
547 prop_id,
548 nullptr,
549 0,
550 RNA_property_ui_name_raw(prop_ref),
554 }
555
556 { /* Check for Updated on Startup. */
557 const char *prop_id = "use_sync_on_startup";
558 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
559 PropertyRNA *prop = RNA_def_boolean(ot->srna,
560 prop_id,
561 false,
562 RNA_property_ui_name_raw(prop_ref),
565 }
566
567 { /* Use Custom Directory. */
568 const char *prop_id = "use_custom_directory";
569 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
570 PropertyRNA *prop = RNA_def_boolean(ot->srna,
571 prop_id,
572 false,
573 RNA_property_ui_name_raw(prop_ref),
576 }
577
578 { /* Custom Directory. */
579 const char *prop_id = "custom_directory";
580 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
582 prop_id,
583 nullptr,
585 RNA_property_ui_name_raw(prop_ref),
588 }
589
590 ot->prop = RNA_def_enum(
591 ot->srna, "type", repo_type_items, 0, "Type", "The kind of repository to add");
593}
594
596
597/* -------------------------------------------------------------------- */
600
602{
603 if (BLI_listbase_is_empty(&U.extension_repos)) {
604 CTX_wm_operator_poll_msg_set(C, "There is no extension repository to remove");
605 return false;
606 }
607 return true;
608}
609
611 wmOperator *op,
612 const wmEvent * /*event*/)
613{
614 const int index = RNA_int_get(op->ptr, "index");
615 bool remove_files = RNA_boolean_get(op->ptr, "remove_files");
616 const bUserExtensionRepo *repo = static_cast<bUserExtensionRepo *>(
617 BLI_findlink(&U.extension_repos, index));
618
619 if (!repo) {
620 return OPERATOR_CANCELLED;
621 }
622
623 if (remove_files) {
626 remove_files = false;
627 }
628 }
629 }
630
631 std::string message;
632 if (remove_files) {
633 char dirpath[FILE_MAX];
634 char user_dirpath[FILE_MAX];
635 BKE_preferences_extension_repo_dirpath_get(repo, dirpath, sizeof(dirpath));
636 BKE_preferences_extension_repo_user_dirpath_get(repo, user_dirpath, sizeof(user_dirpath));
637
638 if (dirpath[0] || user_dirpath[0]) {
639 message = IFACE_("Remove all files in:");
640 const char *paths[] = {dirpath, user_dirpath};
641 for (int i = 0; i < ARRAY_SIZE(paths); i++) {
642 if (paths[i][0] == '\0') {
643 continue;
644 }
645 message.append(fmt::format("\n\"{}\"", paths[i]));
646 }
647 }
648 else {
649 message = IFACE_("Remove, local files not found.");
650 remove_files = false;
651 }
652 }
653 else {
654 message = IFACE_("Remove, keeping local files.");
655 }
656
657 const char *confirm_text = remove_files ? IFACE_("Remove Repository & Files") :
658 IFACE_("Remove Repository");
659
661 C, op, nullptr, message.c_str(), confirm_text, ALERT_ICON_WARNING, true);
662}
663
665{
666 const int index = RNA_int_get(op->ptr, "index");
667 bool remove_files = RNA_boolean_get(op->ptr, "remove_files");
668 bUserExtensionRepo *repo = static_cast<bUserExtensionRepo *>(
669 BLI_findlink(&U.extension_repos, index));
670 if (!repo) {
671 return OPERATOR_CANCELLED;
672 }
673
674 Main *bmain = CTX_data_main(C);
676
677 if (remove_files) {
680 /* The UI doesn't show this option, if it's accessed disallow it. */
681 BKE_report(op->reports, RPT_WARNING, "Unable to remove files for \"System\" repositories");
682 remove_files = false;
683 }
684 }
685 }
686
687 if (remove_files) {
691 /* Account for it not being null terminated. */
692 "Unable to remove files, the module name \"%.*s\" is invalid and "
693 "could remove non-repository files",
694 int(sizeof(repo->module)),
695 repo->module);
696 remove_files = false;
697 }
698 }
699
700 if (remove_files) {
701 char dirpath[FILE_MAX];
702 BKE_preferences_extension_repo_dirpath_get(repo, dirpath, sizeof(dirpath));
703 if (dirpath[0] && BLI_is_dir(dirpath)) {
704
705 /* Removing custom directories has the potential to remove user data
706 * if users accidentally point this to their home directory or similar.
707 * Even though the UI shows a warning, we better prevent any accidents
708 * caused by recursive removal, see #119481.
709 * Only check custom directories because the non-custom directory is always
710 * a specific location under Blender's local extensions directory. */
711 const bool recursive = (repo->flag & USER_EXTENSION_REPO_FLAG_USE_CUSTOM_DIRECTORY) == 0;
712
713 /* Perform package manager specific clear operations,
714 * needed when `recursive` is false so the empty directory can be removed.
715 * If it's not empty there will be a warning that the directory couldn't be removed.
716 * The user will have to do this manually which is good since unknown files
717 * could be user data. */
719
720 if (BLI_delete(dirpath, true, recursive) != 0) {
723 "Unable to remove directory: %s",
724 errno ? strerror(errno) : "unknown");
725 }
726 }
727
728 BKE_preferences_extension_repo_user_dirpath_get(repo, dirpath, sizeof(dirpath));
729 if (dirpath[0] && BLI_is_dir(dirpath)) {
730 if (BLI_delete(dirpath, true, true) != 0) {
733 "Unable to remove directory: %s",
734 errno ? strerror(errno) : "unknown");
735 }
736 }
737 }
738
740 const int count_remaining = BLI_listbase_count(&U.extension_repos);
741 /* Update active repo index to be in range. */
742 CLAMP(U.active_extension_repo, 0, count_remaining - 1);
743 U.runtime.is_dirty = true;
744
746
747 /* There's no dedicated notifier for the Preferences. */
749
750 return OPERATOR_FINISHED;
751}
752
754{
755 ot->name = "Remove Extension Repository";
756 ot->idname = "PREFERENCES_OT_extension_repo_remove";
757 ot->description = "Remove an extension repository";
758
762
763 ot->flag = OPTYPE_INTERNAL;
764
765 PropertyRNA *prop;
766 prop = RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
768 prop = RNA_def_boolean(ot->srna,
769 "remove_files",
770 false,
771 "Remove Files",
772 "Remove extension files when removing the repository");
774}
775
777
778/* -------------------------------------------------------------------- */
781
783 wmOperator *op,
784 const wmEvent *event)
785{
786 char *url = RNA_string_get_alloc(op->ptr, "url", nullptr, 0, nullptr);
787 const bool url_is_file = STRPREFIX(url, "file://");
788 const bool url_is_online = STRPREFIX(url, "http://") || STRPREFIX(url, "https://");
789 const bool url_is_remote = url_is_file | url_is_online;
790
791 /* NOTE: searching for hard-coded add-on name isn't great.
792 * Needed since #WM_dropbox_add expects the operator to exist on startup. */
793 const char *idname_external = url_is_remote ? "extensions.package_install" :
794 "extensions.package_install_files";
795 bool use_url = true;
796
797 if (url_is_online && (G.f & G_FLAG_INTERNET_ALLOW) == 0) {
798 idname_external = "extensions.userpref_allow_online_popup";
799 use_url = false;
800 }
801
802 wmOperatorType *ot = WM_operatortype_find(idname_external, true);
803 wmOperatorStatus retval;
804 if (ot) {
805 PointerRNA props_ptr;
807 if (use_url) {
808 RNA_string_set(&props_ptr, "url", url);
809 }
811 WM_operator_properties_free(&props_ptr);
812 retval = OPERATOR_FINISHED;
813 }
814 else {
815 BKE_reportf(op->reports, RPT_ERROR, "Extension operator not found \"%s\"", idname_external);
816 retval = OPERATOR_CANCELLED;
817 }
818 MEM_freeN(url);
819 return retval;
820}
821
823{
824 /* identifiers */
825 ot->name = "Drop Extension URL";
826 ot->description = "Handle dropping an extension URL";
827 ot->idname = "PREFERENCES_OT_extension_url_drop";
828
829 /* API callbacks. */
831
832 RNA_def_string(ot->srna, "url", nullptr, 0, "URL", "Location of the extension to install");
833}
834
836
837/* -------------------------------------------------------------------- */
840
842{
843#ifdef WIN32
845 CTX_wm_operator_poll_msg_set(C, "Not available for Microsoft Store installations");
846 return false;
847 }
848 return true;
849#elif defined(__APPLE__)
850 CTX_wm_operator_poll_msg_set(C, "Windows & Linux only operator");
851 return false;
852#else
853 UNUSED_VARS(C);
854 return true;
855#endif
856}
857
858#if !defined(__APPLE__)
859static bool associate_blend(bool do_register, bool all_users, char **r_error_msg)
860{
861 const bool result = WM_platform_associate_set(do_register, all_users, r_error_msg);
862# ifdef WIN32
863 if ((result == false) &&
864 /* For some reason the message box isn't shown in this case. */
865 (all_users == false))
866 {
867 const char *msg = do_register ? "Unable to register file association" :
868 "Unable to unregister file association";
869 MessageBox(0, msg, "Blender", MB_OK | MB_ICONERROR);
870 }
871# endif /* !WIN32 */
872 return result;
873}
874#endif
875
877{
878#ifdef __APPLE__
879 UNUSED_VARS(op);
881 return OPERATOR_CANCELLED;
882#else
883
884# ifdef WIN32
887 op->reports, RPT_ERROR, "Registration not possible from Microsoft Store installations");
888 return OPERATOR_CANCELLED;
889 }
890# endif
891
892 const bool all_users = (U.uiflag & USER_REGISTER_ALL_USERS);
893 char *error_msg = nullptr;
894
895 WM_cursor_wait(true);
896 const bool success = associate_blend(true, all_users, &error_msg);
897 WM_cursor_wait(false);
898
899 if (!success) {
901 op->reports, RPT_ERROR, error_msg ? error_msg : "Unable to register file association");
902 if (error_msg) {
903 MEM_freeN(error_msg);
904 }
905 return OPERATOR_CANCELLED;
906 }
907 BLI_assert(error_msg == nullptr);
908 BKE_report(op->reports, RPT_INFO, "File association registered");
909 return OPERATOR_FINISHED;
910#endif /* !__APPLE__ */
911}
912
914{
915 /* identifiers */
916 ot->name = "Register File Association";
917 ot->description = "Use this installation for .blend files and to display thumbnails";
918 ot->idname = "PREFERENCES_OT_associate_blend";
919
920 /* API callbacks. */
921 ot->exec = associate_blend_exec;
922 ot->poll = associate_blend_poll;
923}
924
926{
927#ifdef __APPLE__
928 UNUSED_VARS(op);
930 return OPERATOR_CANCELLED;
931#else
932# ifdef WIN32
935 op->reports, RPT_ERROR, "Unregistration not possible from Microsoft Store installations");
936 return OPERATOR_CANCELLED;
937 }
938# endif
939
940 const bool all_users = (U.uiflag & USER_REGISTER_ALL_USERS);
941 char *error_msg = nullptr;
942
943 WM_cursor_wait(true);
944 bool success = associate_blend(false, all_users, &error_msg);
945 WM_cursor_wait(false);
946
947 if (!success) {
949 op->reports, RPT_ERROR, error_msg ? error_msg : "Unable to unregister file association");
950 if (error_msg) {
951 MEM_freeN(error_msg);
952 }
953 return OPERATOR_CANCELLED;
954 }
955 BLI_assert(error_msg == nullptr);
956 BKE_report(op->reports, RPT_INFO, "File association unregistered");
957 return OPERATOR_FINISHED;
958#endif /* !__APPLE__ */
959}
960
962{
963 /* identifiers */
964 ot->name = "Remove File Association";
965 ot->description = "Remove this installation's associations with .blend files";
966 ot->idname = "PREFERENCES_OT_unassociate_blend";
967
968 /* API callbacks. */
970 ot->poll = associate_blend_poll;
971}
972
974
975/* -------------------------------------------------------------------- */
978
979static bool drop_extension_url_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/)
980{
981 if (drag->type != WM_DRAG_STRING) {
982 return false;
983 }
984
985 /* NOTE(@ideasman42): it should be possible to drag a URL into the text editor or Python console.
986 * In the future we may support dragging images into Blender by URL, so treating any single-line
987 * URL as an extension could back-fire. Avoid problems in the future by limiting the text which
988 * is accepted as an extension to ZIP's or URL's that reference known repositories. */
989
990 const std::string &str = WM_drag_get_string(drag);
991
992 /* Only URL formatted text. */
993 const char *cstr = str.c_str();
995 return false;
996 }
997
998 /* Only single line strings. */
999 if (str.find('\n') != std::string::npos) {
1000 return false;
1001 }
1002
1003 bool has_known_extension = false;
1004 {
1005 /* Strip parameters from the URL (if they exist) before the file extension is checked.
1006 * This allows for `https://example.org/api/v1/file.zip?repository=/api/v1/`.
1007 * This allows draggable links to specify their repository, see: #120665. */
1008 std::string str_strip;
1009 const char *cstr_maybe_copy = cstr;
1010 size_t param_char = str.find('?');
1011 if (param_char != std::string::npos) {
1012 str_strip = str.substr(0, param_char);
1013 cstr_maybe_copy = str_strip.c_str();
1014 }
1015
1016 const char *cstr_ext = BLI_path_extension(cstr_maybe_copy);
1017 if (cstr_ext && STRCASEEQ(cstr_ext, ".zip")) {
1018 has_known_extension = true;
1019 }
1020 }
1021
1022 /* Check the URL has a `.zip` suffix OR has a known repository as a prefix.
1023 * This is needed to support redirects which don't contain an extension. */
1024 if (!has_known_extension &&
1026 {
1027 return false;
1028 }
1029
1030 return true;
1031}
1032
1033static void drop_extension_url_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
1034{
1035 /* Copy drag URL to properties. */
1036 const std::string &str = WM_drag_get_string(drag);
1037 RNA_string_set(drop->ptr, "url", str.c_str());
1038}
1039
1041
1042/* -------------------------------------------------------------------- */
1045
1046static bool drop_extension_path_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/)
1047{
1048 if (drag->type != WM_DRAG_PATH) {
1049 return false;
1050 }
1051
1052 const char *cstr = WM_drag_get_single_path(drag);
1053 const char *cstr_ext = BLI_path_extension(cstr);
1054 if (!(cstr_ext && STRCASEEQ(cstr_ext, ".zip"))) {
1055 return false;
1056 }
1057
1058 return true;
1059}
1060
1061static void drop_extension_path_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
1062{
1063 /* Copy drag URL to properties. */
1064 const char *cstr = WM_drag_get_single_path(drag);
1065 RNA_string_set(drop->ptr, "url", cstr);
1066}
1067
1069
1071{
1073 WM_dropbox_add(lb,
1074 "PREFERENCES_OT_extension_url_drop",
1077 nullptr,
1078 nullptr);
1079 WM_dropbox_add(lb,
1080 "PREFERENCES_OT_extension_url_drop",
1083 nullptr,
1084 nullptr);
1085}
1086
void BKE_callback_exec(Main *bmain, PointerRNA **pointers, int num_pointers, eCbEvent evt)
Definition callbacks.cc:27
void BKE_callback_exec_string(Main *bmain, eCbEvent evt, const char *str)
Definition callbacks.cc:63
void BKE_callback_exec_null(Main *bmain, eCbEvent evt)
Definition callbacks.cc:38
@ BKE_CB_EVT_EXTENSION_REPOS_FILES_CLEAR
@ BKE_CB_EVT_EXTENSION_REPOS_UPDATE_PRE
@ BKE_CB_EVT_EXTENSION_REPOS_UPDATE_POST
@ BKE_CB_EVT_EXTENSION_REPOS_SYNC
void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg)
Main * CTX_data_main(const bContext *C)
@ G_FLAG_INTERNET_ALLOW
bUserExtensionRepo * BKE_preferences_extension_repo_find_by_remote_url_prefix(const UserDef *userdef, const char *remote_url_full, const bool only_enabled)
bool BKE_preferences_extension_repo_module_is_valid(const bUserExtensionRepo *repo)
size_t BKE_preferences_extension_repo_dirpath_get(const bUserExtensionRepo *repo, char *dirpath, int dirpath_maxncpy)
size_t BKE_preferences_extension_repo_user_dirpath_get(const bUserExtensionRepo *repo, char *dirpath, const int dirpath_maxncpy)
int BKE_preferences_extension_repo_remote_scheme_end(const char *url)
bUserExtensionRepo * BKE_preferences_extension_repo_add(UserDef *userdef, const char *name, const char *module, const char *custom_dirpath)
struct bUserAssetLibrary * BKE_preferences_asset_library_add(struct UserDef *userdef, const char *name, const char *dirpath) ATTR_NONNULL(1)
void BKE_preferences_extension_repo_remove(UserDef *userdef, bUserExtensionRepo *repo)
void BKE_preferences_asset_library_remove(struct UserDef *userdef, struct bUserAssetLibrary *library) ATTR_NONNULL()
void BKE_preferences_extension_remote_to_name(const char *remote_url, char name[64])
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
File and directory operations.
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:456
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void BLI_freelinkN(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:270
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
#define FILE_MAXFILE
#define FILE_MAX
void void void BLI_path_split_file_part(const char *filepath, char *file, size_t file_maxncpy) ATTR_NONNULL(1
void BLI_path_slash_rstrip(char *path) ATTR_NONNULL(1)
const char * BLI_path_extension(const char *filepath) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
#define STRNCPY_UTF8(dst, src)
#define CLAMP(a, b, c)
#define STRPREFIX(a, b)
#define ARRAY_SIZE(arr)
#define UNUSED_VARS(...)
#define ELEM(...)
#define STRCASEEQ(a, b)
Compatibility-like things for windows.
const char * dirname(char *path)
bool BLI_windows_is_store_install(void)
#define IFACE_(msgid)
@ RGN_TYPE_WINDOW
@ FILE_SORT_DEFAULT
@ FILE_SPECIAL
@ FILE_TYPE_FOLDER
@ SPACE_EMPTY
@ FILE_DEFAULTDISPLAY
@ USER_REGISTER_ALL_USERS
@ USER_EXTENSION_REPO_SOURCE_SYSTEM
@ USER_EXTENSION_REPO_FLAG_USE_ACCESS_TOKEN
@ USER_EXTENSION_REPO_FLAG_USE_CUSTOM_DIRECTORY
@ USER_EXTENSION_REPO_FLAG_SYNC_ON_STARTUP
@ USER_EXTENSION_REPO_FLAG_USE_REMOTE_URL
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
Read Guarded memory(de)allocation.
@ PROP_SKIP_SAVE
Definition RNA_types.hh:330
@ PROP_HIDDEN
Definition RNA_types.hh:324
@ PROP_PASSWORD
Definition RNA_types.hh:231
#define C
Definition RandGen.cpp:29
void UI_style_init_default()
void UI_theme_init_default()
@ ALERT_ICON_WARNING
@ UI_ITEM_R_IMMEDIATE
void uiLayoutSetActive(uiLayout *layout, bool active)
void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
#define UI_ITEM_NONE
void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
@ WM_FILESEL_DIRECTORY
Definition WM_api.hh:1073
@ FILE_OPENFILE
Definition WM_api.hh:1084
#define NC_WINDOW
Definition WM_types.hh:372
#define ND_SPACE_ASSET_PARAMS
Definition WM_types.hh:522
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:238
@ WM_DRAG_PATH
Definition WM_types.hh:1205
@ WM_DRAG_STRING
Definition WM_types.hh:1214
@ OPTYPE_INTERNAL
Definition WM_types.hh:202
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define NC_SPACE
Definition WM_types.hh:389
#define U
#define str(s)
uint col
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
#define G(x, y, z)
void clear_all_library(const bContext *C)
static struct PyModuleDef module
Definition python.cpp:796
const char * RNA_property_ui_name_raw(const PropertyRNA *prop)
PropertyRNA * RNA_struct_type_find_property(StructRNA *srna, const char *identifier)
void RNA_string_set(PointerRNA *ptr, const char *name, const char *value)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
const char * RNA_property_ui_description_raw(const PropertyRNA *prop)
bool RNA_property_is_set(PointerRNA *ptr, PropertyRNA *prop)
void RNA_string_get(PointerRNA *ptr, const char *name, char *value)
int RNA_int_get(PointerRNA *ptr, const char *name)
char * RNA_string_get_alloc(PointerRNA *ptr, const char *name, char *fixedbuf, int fixedlen, int *r_len)
int RNA_string_length(PointerRNA *ptr, const char *name)
std::string RNA_property_string_get(PointerRNA *ptr, PropertyRNA *prop)
bool RNA_struct_property_is_set(PointerRNA *ptr, const char *identifier)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
int RNA_enum_get(PointerRNA *ptr, const char *name)
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
PropertyRNA * RNA_def_string(StructOrFunctionRNA *cont_, const char *identifier, const char *default_value, const int maxlen, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
PropertyRNA * RNA_def_string_dir_path(StructOrFunctionRNA *cont_, const char *identifier, const char *default_value, const int maxlen, const char *ui_name, const char *ui_description)
void RNA_def_property_subtype(PropertyRNA *prop, PropertySubType subtype)
PropertyRNA * RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, const int default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
eWM_DragDataType type
Definition WM_types.hh:1327
PointerRNA * ptr
Definition WM_types.hh:1415
struct ReportList * reports
struct uiLayout * layout
struct PointerRNA * ptr
i
Definition text_draw.cc:230
static bool associate_blend_poll(bContext *C)
static bool preferences_asset_library_remove_poll(bContext *C)
static bool preferences_extension_repo_remove_poll(bContext *C)
static wmOperatorStatus unassociate_blend_exec(bContext *, wmOperator *op)
static bool drop_extension_path_poll(bContext *, wmDrag *drag, const wmEvent *)
static void PREFERENCES_OT_associate_blend(wmOperatorType *ot)
static void PREFERENCES_OT_extension_repo_remove(wmOperatorType *ot)
static wmOperatorStatus preferences_extension_repo_remove_invoke(bContext *C, wmOperator *op, const wmEvent *)
static wmOperatorStatus preferences_extension_url_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void drop_extension_url_copy(bContext *, wmDrag *drag, wmDropBox *drop)
static void PREFERENCES_OT_extension_url_drop(wmOperatorType *ot)
static void PREFERENCES_OT_reset_default_theme(wmOperatorType *ot)
static void drop_extension_path_copy(bContext *, wmDrag *drag, wmDropBox *drop)
static wmOperatorStatus associate_blend_exec(bContext *, wmOperator *op)
static wmOperatorStatus preferences_asset_library_add_invoke(bContext *C, wmOperator *op, const wmEvent *)
bUserExtensionRepoAddType
static wmOperatorStatus preferences_autoexec_remove_exec(bContext *, wmOperator *op)
static void PREFERENCES_OT_unassociate_blend(wmOperatorType *ot)
void ED_operatortypes_userpref()
static wmOperatorStatus preferences_autoexec_add_exec(bContext *, wmOperator *)
static wmOperatorStatus preferences_reset_default_theme_exec(bContext *C, wmOperator *)
static wmOperatorStatus preferences_extension_repo_add_exec(bContext *C, wmOperator *op)
static void preferences_extension_repo_add_ui(bContext *, wmOperator *op)
static const char * preferences_extension_repo_default_name_from_type(const bUserExtensionRepoAddType repo_type)
static wmOperatorStatus preferences_asset_library_remove_exec(bContext *C, wmOperator *op)
static void ED_dropbox_drop_extension()
static bool associate_blend(bool do_register, bool all_users, char **r_error_msg)
static void PREFERENCES_OT_asset_library_remove(wmOperatorType *ot)
static wmOperatorStatus preferences_extension_repo_add_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static wmOperatorStatus preferences_asset_library_add_exec(bContext *C, wmOperator *op)
static void PREFERENCES_OT_autoexec_path_remove(wmOperatorType *ot)
static void PREFERENCES_OT_autoexec_path_add(wmOperatorType *ot)
static bool drop_extension_url_poll(bContext *, wmDrag *drag, const wmEvent *)
static void PREFERENCES_OT_asset_library_add(wmOperatorType *ot)
static void PREFERENCES_OT_extension_repo_add(wmOperatorType *ot)
static wmOperatorStatus preferences_extension_repo_remove_exec(bContext *C, wmOperator *op)
void WM_cursor_wait(bool val)
wmDropBox * WM_dropbox_add(ListBase *lb, const char *idname, bool(*poll)(bContext *C, wmDrag *drag, const wmEvent *event), void(*copy)(bContext *C, wmDrag *drag, wmDropBox *drop), void(*cancel)(Main *bmain, wmDrag *drag, wmDropBox *drop), WMDropboxTooltipFunc tooltip)
const std::string & WM_drag_get_string(const wmDrag *drag)
const char * WM_drag_get_single_path(const wmDrag *drag)
ListBase * WM_dropboxmap_find(const char *idname, int spaceid, int regionid)
void WM_event_add_fileselect(bContext *C, wmOperator *op)
void WM_main_add_notifier(uint type, void *reference)
wmOperatorStatus WM_operator_name_call_ptr(bContext *C, wmOperatorType *ot, wmOperatorCallContext context, PointerRNA *properties, const wmEvent *event)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
PointerRNA * ptr
Definition wm_files.cc:4227
wmOperatorType * ot
Definition wm_files.cc:4226
void WM_reinit_gizmomap_all(Main *bmain)
void WM_operator_properties_filesel(wmOperatorType *ot, const int filter, const short type, const eFileSel_Action action, const eFileSel_Flag flag, const short display, const short sort)
void WM_operatortype_append(void(*opfunc)(wmOperatorType *))
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)
wmOperatorStatus WM_operator_props_popup_confirm_ex(bContext *C, wmOperator *op, const wmEvent *, std::optional< std::string > title, std::optional< std::string > confirm_text, const bool cancel_default)
void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot)
wmOperatorStatus WM_operator_confirm_ex(bContext *C, wmOperator *op, const char *title, const char *message, const char *confirm_text, int icon, bool cancel_default)
void WM_operator_properties_free(PointerRNA *ptr)
bool WM_platform_associate_set(bool do_register, bool all_users, char **r_error_msg)