Blender V5.0
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"
41
42#include "WM_api.hh"
43#include "WM_types.hh"
44
45#include "ED_asset.hh"
46#include "ED_userpref.hh"
47
48#include "MEM_guardedalloc.h"
49
50/* -------------------------------------------------------------------- */
53
64
66{
67 /* identifiers */
68 ot->name = "Reset to Default Theme";
69 ot->idname = "PREFERENCES_OT_reset_default_theme";
70 ot->description = "Reset to the default theme colors";
71
72 /* callbacks */
74
75 /* flags */
76 ot->flag = OPTYPE_REGISTER;
77}
78
80
81/* -------------------------------------------------------------------- */
84
86{
87 bPathCompare *path_cmp = MEM_callocN<bPathCompare>("bPathCompare");
88 BLI_addtail(&U.autoexec_paths, path_cmp);
89 U.runtime.is_dirty = true;
90 return OPERATOR_FINISHED;
91}
92
94{
95 ot->name = "Add Auto-Execution Path";
96 ot->idname = "PREFERENCES_OT_autoexec_path_add";
97 ot->description = "Add path to exclude from auto-execution";
98
100
101 ot->flag = OPTYPE_INTERNAL;
102}
103
105
106/* -------------------------------------------------------------------- */
109
111{
112 const int index = RNA_int_get(op->ptr, "index");
113 bPathCompare *path_cmp = static_cast<bPathCompare *>(BLI_findlink(&U.autoexec_paths, index));
114 if (path_cmp) {
115 BLI_freelinkN(&U.autoexec_paths, path_cmp);
116 U.runtime.is_dirty = true;
117 }
118 return OPERATOR_FINISHED;
119}
120
122{
123 ot->name = "Remove Auto-Execution Path";
124 ot->idname = "PREFERENCES_OT_autoexec_path_remove";
125 ot->description = "Remove path to exclude from auto-execution";
126
128
129 ot->flag = OPTYPE_INTERNAL;
130
131 RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
132}
133
135
136/* -------------------------------------------------------------------- */
139
141{
142 char *path = RNA_string_get_alloc(op->ptr, "directory", nullptr, 0, nullptr);
143 char dirname[FILE_MAXFILE];
144
147
148 /* nullptr is a valid directory path here. A library without path will be created then. */
149 const bUserAssetLibrary *new_library = BKE_preferences_asset_library_add(&U, dirname, path);
150 /* Activate new library in the UI for further setup. */
151 U.active_asset_library = BLI_findindex(&U.asset_libraries, new_library);
152 U.runtime.is_dirty = true;
153
154 /* There's no dedicated notifier for the Preferences. */
157
158 MEM_freeN(path);
159 return OPERATOR_FINISHED;
160}
161
163 wmOperator *op,
164 const wmEvent * /*event*/)
165{
166 if (!RNA_struct_property_is_set(op->ptr, "directory")) {
169 }
170
172}
173
175{
176 ot->name = "Add Asset Library";
177 ot->idname = "PREFERENCES_OT_asset_library_add";
178 ot->description = "Add a directory to be used by the Asset Browser as source of assets";
179
182
183 ot->flag = OPTYPE_INTERNAL;
184
192}
193
195
196/* -------------------------------------------------------------------- */
199
201{
202 if (BLI_listbase_is_empty(&U.asset_libraries)) {
203 CTX_wm_operator_poll_msg_set(C, "There is no asset library to remove");
204 return false;
205 }
206 return true;
207}
208
210{
211 const int index = RNA_int_get(op->ptr, "index");
212 bUserAssetLibrary *library = static_cast<bUserAssetLibrary *>(
213 BLI_findlink(&U.asset_libraries, index));
214 if (!library) {
215 return OPERATOR_CANCELLED;
216 }
217
219 const int count_remaining = BLI_listbase_count(&U.asset_libraries);
220 /* Update active library index to be in range. */
221 CLAMP(U.active_asset_library, 0, count_remaining - 1);
222 U.runtime.is_dirty = true;
223
225 /* Trigger refresh for the Asset Browser. */
227
228 return OPERATOR_FINISHED;
229}
230
232{
233 ot->name = "Remove Asset Library";
234 ot->idname = "PREFERENCES_OT_asset_library_remove";
235 ot->description =
236 "Remove a path to a .blend file, so the Asset Browser will not attempt to show it anymore";
237
240
241 ot->flag = OPTYPE_INTERNAL;
242
243 RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
244}
245
247
248/* -------------------------------------------------------------------- */
251
254 Local = 1,
255};
256
258 const bUserExtensionRepoAddType repo_type)
259{
260 switch (repo_type) {
262 return "Remote Repository";
263 }
265 return "User Repository";
266 }
267 }
269 return "";
270}
271
273{
275 RNA_enum_get(op->ptr, "type"));
276
277 Main *bmain = CTX_data_main(C);
279
280 char name[sizeof(bUserExtensionRepo::name)] = "";
281 char remote_url[sizeof(bUserExtensionRepo::remote_url)] = "";
282 char *access_token = nullptr;
283 char custom_directory[sizeof(bUserExtensionRepo::custom_dirpath)] = "";
284
285 const bool use_custom_directory = RNA_boolean_get(op->ptr, "use_custom_directory");
286 const bool use_access_token = RNA_boolean_get(op->ptr, "use_access_token");
287 const bool use_sync_on_startup = RNA_boolean_get(op->ptr, "use_sync_on_startup");
288 if (use_custom_directory) {
289 RNA_string_get(op->ptr, "custom_directory", custom_directory);
290 BLI_path_slash_rstrip(custom_directory);
291 }
292
293 if (repo_type == bUserExtensionRepoAddType::Remote) {
294 RNA_string_get(op->ptr, "remote_url", remote_url);
295
296 if (use_access_token) {
297 if (RNA_string_length(op->ptr, "access_token")) {
298 access_token = RNA_string_get_alloc(op->ptr, "access_token", nullptr, 0, nullptr);
299 }
300 }
301 }
302
303 /* Setup the name using the following logic:
304 * - It has been set so leave as-is.
305 * - Initialize it based on the URL (default for remote repositories).
306 * - Use a default name as a fallback.
307 */
308 {
309 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "name");
310 if (RNA_property_is_set(op->ptr, prop)) {
311 RNA_property_string_get(op->ptr, prop, name);
312 }
313
314 /* Unset or empty, auto-name based on remote URL or local directory. */
315 if (name[0] == '\0') {
316 switch (repo_type) {
319 break;
320 }
322 if (use_custom_directory) {
323 const char *custom_directory_basename = BLI_path_basename(custom_directory);
324 STRNCPY_UTF8(name, custom_directory_basename);
326 }
327 break;
328 }
329 }
330 }
331 if (name[0] == '\0') {
333 }
334 }
335
336 const char *module = custom_directory[0] ? BLI_path_basename(custom_directory) : name;
337 /* Not essential but results in more readable module names.
338 * Otherwise URL's have their '.' removed, making for quite unreadable module names. */
339 char module_buf[FILE_MAX];
340 {
341 STRNCPY_UTF8(module_buf, module);
342 int i;
343 for (i = 0; module_buf[i]; i++) {
344 if (ELEM(module_buf[i], '.', '-', '/', '\\')) {
345 module_buf[i] = '_';
346 }
347 }
348 /* Strip any trailing underscores. */
349 while ((i > 0) && (module_buf[--i] == '_')) {
350 module_buf[i] = '\0';
351 }
352 module = module_buf;
353 }
354
356 &U, name, module, custom_directory);
357
358 if (use_sync_on_startup) {
360 }
361 if (use_custom_directory) {
363 }
364
365 if (repo_type == bUserExtensionRepoAddType::Remote) {
366 STRNCPY_UTF8(new_repo->remote_url, remote_url);
368
369 if (use_access_token) {
371 }
372 if (access_token) {
373 new_repo->access_token = access_token;
374 }
375 }
376
377 /* Activate new repository in the UI for further setup. */
378 U.active_extension_repo = BLI_findindex(&U.extension_repos, new_repo);
379 U.runtime.is_dirty = true;
380
381 {
383 nullptr, &RNA_UserExtensionRepo, new_repo);
384 PointerRNA *pointers[] = {&new_repo_ptr};
385
388 }
389
390 /* There's no dedicated notifier for the Preferences. */
392
393 /* Mainly useful when adding a repository from a popup since it's not as obvious
394 * the repository was added compared to the repository popover. */
396 RPT_INFO,
397 "Added %s \"%s\"",
399 new_repo->name);
400
401 return OPERATOR_FINISHED;
402}
403
405 wmOperator *op,
406 const wmEvent *event)
407{
409 RNA_enum_get(op->ptr, "type"));
410 PropertyRNA *prop_name = RNA_struct_find_property(op->ptr, "name");
411 if (!RNA_property_is_set(op->ptr, prop_name)) {
412 const char *name_default = preferences_extension_repo_default_name_from_type(repo_type);
413 /* Leave unset, let this be set by the URL. */
414 if (repo_type == bUserExtensionRepoAddType::Remote) {
415 name_default = nullptr;
416 }
417 RNA_property_string_set(op->ptr, prop_name, name_default);
418 }
419
421 C, op, event, IFACE_("Add New Extension Repository"), IFACE_("Create"));
422}
423
425{
426
427 uiLayout *layout = op->layout;
428 layout->use_property_split_set(true);
429 layout->use_property_decorate_set(false);
430
431 PointerRNA *ptr = op->ptr;
433
434 switch (repo_type) {
436 layout->prop(op->ptr, "remote_url", UI_ITEM_R_IMMEDIATE, std::nullopt, ICON_NONE);
437 layout->prop(op->ptr, "use_sync_on_startup", UI_ITEM_NONE, std::nullopt, ICON_NONE);
438
440
441 const bool use_access_token = RNA_boolean_get(ptr, "use_access_token");
442 const int token_icon = (use_access_token && RNA_string_length(op->ptr, "access_token")) ?
443 ICON_LOCKED :
444 ICON_UNLOCKED;
445
446 uiLayout *row = &layout->row(true, IFACE_("Authentication"));
447 row->prop(op->ptr, "use_access_token", UI_ITEM_NONE, std::nullopt, ICON_NONE);
448 uiLayout *col = &layout->row(false);
449 col->active_set(use_access_token);
450 /* Use "immediate" flag to refresh the icon. */
451 col->prop(op->ptr, "access_token", UI_ITEM_R_IMMEDIATE, std::nullopt, token_icon);
452
454
455 break;
456 }
458 layout->prop(op->ptr, "name", UI_ITEM_R_IMMEDIATE, std::nullopt, ICON_NONE);
459 break;
460 }
461 }
462
463 layout->prop(op->ptr, "use_custom_directory", UI_ITEM_NONE, std::nullopt, ICON_NONE);
464 uiLayout *col = &layout->row(false);
465 col->active_set(RNA_boolean_get(ptr, "use_custom_directory"));
466 col->prop(op->ptr, "custom_directory", UI_ITEM_NONE, std::nullopt, ICON_NONE);
467}
468
470{
471 ot->name = "Add Extension Repository";
472 ot->idname = "PREFERENCES_OT_extension_repo_add";
473 ot->description = "Add a new repository used to store extensions";
474
478
480
481 static const EnumPropertyItem repo_type_items[] = {
483 "REMOTE",
484 ICON_INTERNET,
485 "Add Remote Repository",
486 "Add a repository referencing a remote repository "
487 "with support for listing and updating extensions"},
489 "LOCAL",
490 ICON_DISK_DRIVE,
491 "Add Local Repository",
492 "Add a repository managed manually without referencing an external repository"},
493 {0, nullptr, 0, nullptr, nullptr},
494 };
495
496 /* After creating a new repository some settings can't be easily changed
497 * (especially the custom directory). To avoid showing a partially initialized repository,
498 * set these values upon creation instead of having the user create the repository and change
499 * them afterwards.
500 *
501 * An alternative solution could be implemented by creating an "uninitialized" repository,
502 * setting up all it's properties then running an "initialize" operator however this seems
503 * unnecessarily confusing as in most cases a user can do this in one step by naming and
504 * setting the repositories URL (optionally the custom-directory). */
505
506 /* Copy the RNA values are copied into the operator to avoid repetition. */
507 StructRNA *type_ref = &RNA_UserExtensionRepo;
508
509 { /* Name. */
510 const char *prop_id = "name";
511 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
512 PropertyRNA *prop = RNA_def_string(ot->srna,
513 prop_id,
514 nullptr,
516 RNA_property_ui_name_raw(prop_ref),
519 }
520
521 { /* Remote Path. */
522 const char *prop_id = "remote_url";
523 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
524 PropertyRNA *prop = RNA_def_string(ot->srna,
525 prop_id,
526 nullptr,
528 RNA_property_ui_name_raw(prop_ref),
531 }
532
533 { /* Use Access Token. */
534 const char *prop_id = "use_access_token";
535 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
536 PropertyRNA *prop = RNA_def_boolean(ot->srna,
537 prop_id,
538 false,
539 RNA_property_ui_name_raw(prop_ref),
542 }
543
544 { /* Access Token (dynamic length). */
545 const char *prop_id = "access_token";
546 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
547 PropertyRNA *prop = RNA_def_string(ot->srna,
548 prop_id,
549 nullptr,
550 0,
551 RNA_property_ui_name_raw(prop_ref),
555 }
556
557 { /* Check for Updated on Startup. */
558 const char *prop_id = "use_sync_on_startup";
559 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
560 PropertyRNA *prop = RNA_def_boolean(ot->srna,
561 prop_id,
562 false,
563 RNA_property_ui_name_raw(prop_ref),
566 }
567
568 { /* Use Custom Directory. */
569 const char *prop_id = "use_custom_directory";
570 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
571 PropertyRNA *prop = RNA_def_boolean(ot->srna,
572 prop_id,
573 false,
574 RNA_property_ui_name_raw(prop_ref),
577 }
578
579 { /* Custom Directory. */
580 const char *prop_id = "custom_directory";
581 const PropertyRNA *prop_ref = RNA_struct_type_find_property(type_ref, prop_id);
583 prop_id,
584 nullptr,
586 RNA_property_ui_name_raw(prop_ref),
589 }
590
591 ot->prop = RNA_def_enum(
592 ot->srna, "type", repo_type_items, 0, "Type", "The kind of repository to add");
594}
595
597
598/* -------------------------------------------------------------------- */
601
603{
604 if (BLI_listbase_is_empty(&U.extension_repos)) {
605 CTX_wm_operator_poll_msg_set(C, "There is no extension repository to remove");
606 return false;
607 }
608 return true;
609}
610
612 wmOperator *op,
613 const wmEvent * /*event*/)
614{
615 const int index = RNA_int_get(op->ptr, "index");
616 bool remove_files = RNA_boolean_get(op->ptr, "remove_files");
617 const bUserExtensionRepo *repo = static_cast<bUserExtensionRepo *>(
618 BLI_findlink(&U.extension_repos, index));
619
620 if (!repo) {
621 return OPERATOR_CANCELLED;
622 }
623
624 if (remove_files) {
627 remove_files = false;
628 }
629 }
630 }
631
632 std::string message;
633 if (remove_files) {
634 char dirpath[FILE_MAX];
635 char user_dirpath[FILE_MAX];
636 BKE_preferences_extension_repo_dirpath_get(repo, dirpath, sizeof(dirpath));
637 BKE_preferences_extension_repo_user_dirpath_get(repo, user_dirpath, sizeof(user_dirpath));
638
639 if (dirpath[0] || user_dirpath[0]) {
640 message = IFACE_("Remove all files in:");
641 const char *paths[] = {dirpath, user_dirpath};
642 for (int i = 0; i < ARRAY_SIZE(paths); i++) {
643 if (paths[i][0] == '\0') {
644 continue;
645 }
646 message.append(fmt::format("\n\"{}\"", paths[i]));
647 }
648 }
649 else {
650 message = IFACE_("Remove, local files not found.");
651 remove_files = false;
652 }
653 }
654 else {
655 message = IFACE_("Remove, keeping local files.");
656 }
657
658 const char *confirm_text = remove_files ? IFACE_("Remove Repository & Files") :
659 IFACE_("Remove Repository");
660
662 C, op, nullptr, message.c_str(), confirm_text, ALERT_ICON_WARNING, true);
663}
664
666{
667 const int index = RNA_int_get(op->ptr, "index");
668 bool remove_files = RNA_boolean_get(op->ptr, "remove_files");
669 bUserExtensionRepo *repo = static_cast<bUserExtensionRepo *>(
670 BLI_findlink(&U.extension_repos, index));
671 if (!repo) {
672 return OPERATOR_CANCELLED;
673 }
674
675 Main *bmain = CTX_data_main(C);
677
678 if (remove_files) {
681 /* The UI doesn't show this option, if it's accessed disallow it. */
682 BKE_report(op->reports, RPT_WARNING, "Unable to remove files for \"System\" repositories");
683 remove_files = false;
684 }
685 }
686 }
687
688 if (remove_files) {
692 /* Account for it not being null terminated. */
693 "Unable to remove files, the module name \"%.*s\" is invalid and "
694 "could remove non-repository files",
695 int(sizeof(repo->module)),
696 repo->module);
697 remove_files = false;
698 }
699 }
700
701 if (remove_files) {
702 char dirpath[FILE_MAX];
703 BKE_preferences_extension_repo_dirpath_get(repo, dirpath, sizeof(dirpath));
704 if (dirpath[0] && BLI_is_dir(dirpath)) {
705
706 /* Removing custom directories has the potential to remove user data
707 * if users accidentally point this to their home directory or similar.
708 * Even though the UI shows a warning, we better prevent any accidents
709 * caused by recursive removal, see #119481.
710 * Only check custom directories because the non-custom directory is always
711 * a specific location under Blender's local extensions directory. */
712 const bool recursive = (repo->flag & USER_EXTENSION_REPO_FLAG_USE_CUSTOM_DIRECTORY) == 0;
713
714 /* Perform package manager specific clear operations,
715 * needed when `recursive` is false so the empty directory can be removed.
716 * If it's not empty there will be a warning that the directory couldn't be removed.
717 * The user will have to do this manually which is good since unknown files
718 * could be user data. */
720
721 if (BLI_delete(dirpath, true, recursive) != 0) {
724 "Unable to remove directory: %s",
725 errno ? strerror(errno) : "unknown");
726 }
727 }
728
729 BKE_preferences_extension_repo_user_dirpath_get(repo, dirpath, sizeof(dirpath));
730 if (dirpath[0] && BLI_is_dir(dirpath)) {
731 if (BLI_delete(dirpath, true, true) != 0) {
734 "Unable to remove directory: %s",
735 errno ? strerror(errno) : "unknown");
736 }
737 }
738 }
739
741 const int count_remaining = BLI_listbase_count(&U.extension_repos);
742 /* Update active repo index to be in range. */
743 CLAMP(U.active_extension_repo, 0, count_remaining - 1);
744 U.runtime.is_dirty = true;
745
747
748 /* There's no dedicated notifier for the Preferences. */
750
751 return OPERATOR_FINISHED;
752}
753
755{
756 ot->name = "Remove Extension Repository";
757 ot->idname = "PREFERENCES_OT_extension_repo_remove";
758 ot->description = "Remove an extension repository";
759
763
764 ot->flag = OPTYPE_INTERNAL;
765
766 PropertyRNA *prop;
767 prop = RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
769 prop = RNA_def_boolean(ot->srna,
770 "remove_files",
771 false,
772 "Remove Files",
773 "Remove extension files when removing the repository");
775}
776
778
779/* -------------------------------------------------------------------- */
782
784 wmOperator *op,
785 const wmEvent *event)
786{
787 std::string url = RNA_string_get(op->ptr, "url");
788 const bool url_is_file = STRPREFIX(url.c_str(), "file://");
789 const bool url_is_online = STRPREFIX(url.c_str(), "http://") ||
790 STRPREFIX(url.c_str(), "https://");
791 const bool url_is_remote = url_is_file | url_is_online;
792
793 /* NOTE: searching for hard-coded add-on name isn't great.
794 * Needed since #WM_dropbox_add expects the operator to exist on startup. */
795 const char *idname_external = url_is_remote ? "extensions.package_install" :
796 "extensions.package_install_files";
797 bool use_url = true;
798
799 if (url_is_online && (G.f & G_FLAG_INTERNET_ALLOW) == 0) {
800 idname_external = "extensions.userpref_allow_online_popup";
801 use_url = false;
802 }
803
804 wmOperatorType *ot = WM_operatortype_find(idname_external, true);
805 wmOperatorStatus retval;
806 if (ot) {
807 PointerRNA props_ptr;
809 if (use_url) {
810 RNA_string_set(&props_ptr, "url", url.c_str());
811 }
813 WM_operator_properties_free(&props_ptr);
814 retval = OPERATOR_FINISHED;
815 }
816 else {
817 BKE_reportf(op->reports, RPT_ERROR, "Extension operator not found \"%s\"", idname_external);
818 retval = OPERATOR_CANCELLED;
819 }
820 return retval;
821}
822
824{
825 /* identifiers */
826 ot->name = "Drop Extension URL";
827 ot->description = "Handle dropping an extension URL";
828 ot->idname = "PREFERENCES_OT_extension_url_drop";
829
830 /* API callbacks. */
832
833 RNA_def_string(ot->srna, "url", nullptr, 0, "URL", "Location of the extension to install");
834}
835
837
838/* -------------------------------------------------------------------- */
841
843{
844#ifdef WIN32
846 CTX_wm_operator_poll_msg_set(C, "Not available for Microsoft Store installations");
847 return false;
848 }
849 return true;
850#elif defined(__APPLE__)
851 CTX_wm_operator_poll_msg_set(C, "Windows & Linux only operator");
852 return false;
853#else
854 UNUSED_VARS(C);
855 return true;
856#endif
857}
858
859#if !defined(__APPLE__)
860static bool associate_blend(bool do_register, bool all_users, char **r_error_msg)
861{
862 const bool result = WM_platform_associate_set(do_register, all_users, r_error_msg);
863# ifdef WIN32
864 if ((result == false) &&
865 /* For some reason the message box isn't shown in this case. */
866 (all_users == false))
867 {
868 const char *msg = do_register ? "Unable to register file association" :
869 "Unable to unregister file association";
870 MessageBox(0, msg, "Blender", MB_OK | MB_ICONERROR);
871 }
872# endif /* !WIN32 */
873 return result;
874}
875#endif
876
878{
879#ifdef __APPLE__
880 UNUSED_VARS(op);
882 return OPERATOR_CANCELLED;
883#else
884
885# ifdef WIN32
888 op->reports, RPT_ERROR, "Registration not possible from Microsoft Store installations");
889 return OPERATOR_CANCELLED;
890 }
891# endif
892
893 const bool all_users = (U.uiflag & USER_REGISTER_ALL_USERS);
894 char *error_msg = nullptr;
895
896 WM_cursor_wait(true);
897 const bool success = associate_blend(true, all_users, &error_msg);
898 WM_cursor_wait(false);
899
900 if (!success) {
902 op->reports, RPT_ERROR, error_msg ? error_msg : "Unable to register file association");
903 if (error_msg) {
904 MEM_freeN(error_msg);
905 }
906 return OPERATOR_CANCELLED;
907 }
908 BLI_assert(error_msg == nullptr);
909 BKE_report(op->reports, RPT_INFO, "File association registered");
910 return OPERATOR_FINISHED;
911#endif /* !__APPLE__ */
912}
913
915{
916 /* identifiers */
917 ot->name = "Register File Association";
918 ot->description = "Use this installation for .blend files and to display thumbnails";
919 ot->idname = "PREFERENCES_OT_associate_blend";
920
921 /* API callbacks. */
922 ot->exec = associate_blend_exec;
923 ot->poll = associate_blend_poll;
924}
925
927{
928#ifdef __APPLE__
929 UNUSED_VARS(op);
931 return OPERATOR_CANCELLED;
932#else
933# ifdef WIN32
936 op->reports, RPT_ERROR, "Unregistration not possible from Microsoft Store installations");
937 return OPERATOR_CANCELLED;
938 }
939# endif
940
941 const bool all_users = (U.uiflag & USER_REGISTER_ALL_USERS);
942 char *error_msg = nullptr;
943
944 WM_cursor_wait(true);
945 bool success = associate_blend(false, all_users, &error_msg);
946 WM_cursor_wait(false);
947
948 if (!success) {
950 op->reports, RPT_ERROR, error_msg ? error_msg : "Unable to unregister file association");
951 if (error_msg) {
952 MEM_freeN(error_msg);
953 }
954 return OPERATOR_CANCELLED;
955 }
956 BLI_assert(error_msg == nullptr);
957 BKE_report(op->reports, RPT_INFO, "File association unregistered");
958 return OPERATOR_FINISHED;
959#endif /* !__APPLE__ */
960}
961
963{
964 /* identifiers */
965 ot->name = "Remove File Association";
966 ot->description = "Remove this installation's associations with .blend files";
967 ot->idname = "PREFERENCES_OT_unassociate_blend";
968
969 /* API callbacks. */
971 ot->poll = associate_blend_poll;
972}
973
975
976/* -------------------------------------------------------------------- */
979
980static bool drop_extension_url_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/)
981{
982 if (drag->type != WM_DRAG_STRING) {
983 return false;
984 }
985
986 /* NOTE(@ideasman42): it should be possible to drag a URL into the text editor or Python console.
987 * In the future we may support dragging images into Blender by URL, so treating any single-line
988 * URL as an extension could back-fire. Avoid problems in the future by limiting the text which
989 * is accepted as an extension to ZIP's or URL's that reference known repositories. */
990
991 const std::string &str = WM_drag_get_string(drag);
992
993 /* Only URL formatted text. */
994 const char *cstr = str.c_str();
996 return false;
997 }
998
999 /* Only single line strings. */
1000 if (str.find('\n') != std::string::npos) {
1001 return false;
1002 }
1003
1004 bool has_known_extension = false;
1005 {
1006 /* Strip parameters from the URL (if they exist) before the file extension is checked.
1007 * This allows for `https://example.org/api/v1/file.zip?repository=/api/v1/`.
1008 * This allows draggable links to specify their repository, see: #120665. */
1009 std::string str_strip;
1010 const char *cstr_maybe_copy = cstr;
1011 size_t param_char = str.find('?');
1012 if (param_char != std::string::npos) {
1013 str_strip = str.substr(0, param_char);
1014 cstr_maybe_copy = str_strip.c_str();
1015 }
1016
1017 const char *cstr_ext = BLI_path_extension(cstr_maybe_copy);
1018 if (cstr_ext && STRCASEEQ(cstr_ext, ".zip")) {
1019 has_known_extension = true;
1020 }
1021 }
1022
1023 /* Check the URL has a `.zip` suffix OR has a known repository as a prefix.
1024 * This is needed to support redirects which don't contain an extension. */
1025 if (!has_known_extension &&
1027 {
1028 return false;
1029 }
1030
1031 return true;
1032}
1033
1034static void drop_extension_url_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
1035{
1036 /* Copy drag URL to properties. */
1037 const std::string &str = WM_drag_get_string(drag);
1038 RNA_string_set(drop->ptr, "url", str.c_str());
1039}
1040
1042
1043/* -------------------------------------------------------------------- */
1046
1047static bool drop_extension_path_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/)
1048{
1049 if (drag->type != WM_DRAG_PATH) {
1050 return false;
1051 }
1052
1053 const char *cstr = WM_drag_get_single_path(drag);
1054 const char *cstr_ext = BLI_path_extension(cstr);
1055 if (!(cstr_ext && STRCASEEQ(cstr_ext, ".zip"))) {
1056 return false;
1057 }
1058
1059 return true;
1060}
1061
1062static void drop_extension_path_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
1063{
1064 /* Copy drag URL to properties. */
1065 const char *cstr = WM_drag_get_single_path(drag);
1066 RNA_string_set(drop->ptr, "url", cstr);
1067}
1068
1070
1072{
1074 WM_dropbox_add(lb,
1075 "PREFERENCES_OT_extension_url_drop",
1078 nullptr,
1079 nullptr);
1080 WM_dropbox_add(lb,
1081 "PREFERENCES_OT_extension_url_drop",
1084 nullptr,
1085 nullptr);
1086}
1087
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
@ RPT_INFO
Definition BKE_report.hh:35
@ RPT_ERROR
Definition BKE_report.hh:39
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#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:443
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
#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:344
@ PROP_HIDDEN
Definition RNA_types.hh:338
@ PROP_PASSWORD
Definition RNA_types.hh:243
#define C
Definition RandGen.cpp:29
void UI_style_init_default()
void UI_theme_init_default()
@ ALERT_ICON_WARNING
@ UI_ITEM_R_IMMEDIATE
#define UI_ITEM_NONE
@ WM_FILESEL_DIRECTORY
Definition WM_api.hh:1122
@ FILE_OPENFILE
Definition WM_api.hh:1133
#define NC_WINDOW
Definition WM_types.hh:375
#define ND_SPACE_ASSET_PARAMS
Definition WM_types.hh:525
@ WM_DRAG_PATH
Definition WM_types.hh:1208
@ WM_DRAG_STRING
Definition WM_types.hh:1217
@ OPTYPE_INTERNAL
Definition WM_types.hh:202
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define NC_SPACE
Definition WM_types.hh:392
#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 * name
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)
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)
std::string RNA_string_get(PointerRNA *ptr, const char *name)
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)
const char * RNA_property_ui_name_raw(const PropertyRNA *prop, const PointerRNA *ptr)
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 use_property_decorate_set(bool is_sep)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
void use_property_split_set(bool value)
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:1331
PointerRNA * ptr
Definition WM_types.hh:1420
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)
wmOperatorStatus WM_operator_name_call_ptr(bContext *C, wmOperatorType *ot, blender::wm::OpCallContext context, PointerRNA *properties, const wmEvent *event)
void WM_event_add_fileselect(bContext *C, wmOperator *op)
void WM_main_add_notifier(uint type, void *reference)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
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)
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)
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, std::optional< std::string > message)
bool WM_platform_associate_set(bool do_register, bool all_users, char **r_error_msg)