diff --git a/README.md b/README.md index ba68878..b2028b1 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,9 @@ You can download and test the latest nightly build from https://github.com/nakst These builds are configured to run on emulators only, to make testing easier. Builds for real hardware are coming soon :) -## Source +## Building -See `help/Building.md` for a description of how to build and test the system from source. - -For an overview of the files in the source tree, see `help/Source Map.md`. +See `help/Building.md` for a description of how to build and test the system. ## Features diff --git a/apps/file_manager.ini b/apps/file_manager.ini index ede7957..6f2487d 100644 --- a/apps/file_manager.ini +++ b/apps/file_manager.ini @@ -13,108 +13,131 @@ background_service=1 source=apps/file_manager/main.cpp [file_type] -extension=esx +match=ext:esx name=Executable icon=icon_application_default_icon +uuid=BF-88-E4-DD-71-E8-5F-DE-16-C5-AF-33-41-C7-A2-96 [file_type] -extension=ini +match=ext:ini name=Configuration file icon=icon_application_x_desktop +uuid=81-72-43-29-E5-37-89-51-8E-22-A0-67-A5-B9-42-2C +textual=1 [file_type] -extension=esx_symbols -name=Debugging data -icon=icon_extension - -[file_type] -extension=exe -name=PC executable -icon=icon_application_x_ms_dos_executable - -[file_type] -extension=png +match=ext:png name=PNG image icon=icon_image_x_generic has_thumbnail_generator=1 +uuid=59-21-05-4D-34-40-AB-61-EC-F9-7D-5C-6E-04-96-AE [file_type] -extension=jpg +match=ext:jpg,ext:jpeg name=JPG image icon=icon_image_x_generic has_thumbnail_generator=1 +uuid=D8-C2-13-B0-53-64-82-11-48-7B-5B-64-0F-92-B9-38 [file_type] -extension=ttf +match=ext:tga +name=TGA image +icon=icon_image_x_generic +has_thumbnail_generator=1 +uuid=69-62-4E-28-A1-E1-7B-35-64-2E-36-01-65-91-BE-A1 + +[file_type] +match=ext:bmp +name=BMP image +icon=icon_image_x_generic +has_thumbnail_generator=1 +uuid=40-15-B7-82-99-6D-D5-41-96-D5-3B-6D-A6-5F-07-34 + +[file_type] +match=ext:ttf name=TrueType font icon=icon_font_x_generic +uuid=DA-BF-EC-06-31-8A-67-B6-E3-95-BC-D1-92-D2-9A-56 [file_type] -extension=otf +match=ext:otf name=OpenType font icon=icon_font_x_generic +uuid=E7-2B-BB-E7-FF-75-32-E0-4E-75-41-C0-D2-ED-80-56 [file_type] -extension=a +match=ext:a name=Static library icon=icon_extension +uuid=7D-39-BF-18-9E-07-BC-D3-4F-9F-87-EC-6F-70-65-5D [file_type] -extension=dat -name=Database +match=ext:dat +name=Application data icon=icon_office_database +uuid=B5-32-22-14-01-CB-D0-1D-A2-55-08-C7-0D-46-86-53 [file_type] -extension=icon_pack -name=Icon pack -icon=icon_package_x_generic - -[file_type] -extension=ekm +match=ext:ekm name=Kernel module icon=icon_extension +uuid=80-6D-8E-D6-C7-45-AD-A7-03-5B-B3-64-C5-0A-EE-C6 [file_type] -extension=o +match=ext:o name=Binary object icon=icon_extension +uuid=B5-19-F2-0D-AD-4F-D4-C9-A4-BF-97-E4-5E-4C-55-00 [file_type] -extension=c +match=ext:c name=C source icon=icon_text_x_csrc +uuid=36-02-D3-AC-6C-DE-8D-31-FA-70-B2-DA-FA-81-53-69 +textual=1 [file_type] -extension=cpp +match=ext:cpp name=C++ source icon=icon_text_x_csrc +uuid=35-84-A6-0E-52-28-BA-AB-CA-74-14-F4-E1-15-CA-5F +textual=1 [file_type] -extension=h +match=ext:h name=C header icon=icon_text_x_csrc +uuid=D1-16-C4-C1-7B-C0-ED-4F-CA-AC-18-05-32-1D-56-32 +textual=1 [file_type] -extension=txt +match=ext:txt name=Text file icon=icon_text +uuid=25-65-4C-89-E7-29-EA-9E-B5-BE-B5-CA-A7-60-BD-3D +textual=1 [file_type] -extension=md +match=ext:md name=Markdown file icon=icon_text_markdown +uuid=B1-C3-9E-56-C9-B0-85-D6-AF-98-12-1C-2E-29-8D-24 +textual=1 [file_type] -extension=ogg +match=ext:ogg name=OGG audio icon=icon_audio_x_generic +uuid=E5-14-14-ED-4D-67-C0-D0-62-A1-A4-D4-F5-82-A7-88 [file_type] -extension=mp4 +match=ext:mp4 name=MP4 video icon=icon_view_list_video_symbolic +uuid=02-76-E1-41-B8-54-19-A9-05-8D-C1-3F-6E-8F-D7-9E [file_type] -extension=wav +match=ext:wav name=Wave audio icon=icon_audio_x_generic +uuid=87-BC-A1-A1-25-76-26-5C-8F-94-D6-D8-35-B6-7A-9F diff --git a/apps/file_manager/commands.cpp b/apps/file_manager/commands.cpp index e407593..b82627f 100644 --- a/apps/file_manager/commands.cpp +++ b/apps/file_manager/commands.cpp @@ -22,10 +22,11 @@ void CommandRename(Instance *instance, EsElement *, EsCommand *) { instance->rename.index = index; FolderEntry *entry = instance->listContents[index].entry; + ptrdiff_t extensionOffset = PathGetExtension(entry->GetName()).text - entry->name; - if (entry->extensionOffset != entry->nameBytes) { + if (extensionOffset != entry->nameBytes) { // Don't include the file extension in the initial selection. - EsTextboxSetSelection(instance->rename.textbox, 0, 0, 0, entry->extensionOffset - 1); + EsTextboxSetSelection(instance->rename.textbox, 0, 0, 0, extensionOffset - 1); } instance->rename.textbox->messageUser = [] (EsElement *element, EsMessage *message) { @@ -499,23 +500,32 @@ void InstanceRegisterCommands(Instance *instance) { EsCommandRegister(&instance->commandRename, instance, INTERFACE_STRING(FileManagerRenameAction), CommandRename, stableCommandID++, "F2"); EsCommandRegister(&instance->commandViewDetails, instance, INTERFACE_STRING(CommonListViewTypeDetails), [] (Instance *instance, EsElement *, EsCommand *) { - uint8_t old = instance->viewSettings.viewType; + if (instance->viewSettings.viewType != VIEW_DETAILS) { + EsElementStartTransition(instance->list, ES_TRANSITION_FADE, ES_ELEMENT_TRANSITION_CONTENT_ONLY, 1.0f); + } + instance->viewSettings.viewType = VIEW_DETAILS; - InstanceRefreshViewType(instance, old != instance->viewSettings.viewType); + InstanceRefreshViewType(instance); InstanceViewSettingsUpdated(instance); }, stableCommandID++); EsCommandRegister(&instance->commandViewTiles, instance, INTERFACE_STRING(CommonListViewTypeTiles), [] (Instance *instance, EsElement *, EsCommand *) { - uint8_t old = instance->viewSettings.viewType; + if (instance->viewSettings.viewType != VIEW_TILES) { + EsElementStartTransition(instance->list, ES_TRANSITION_FADE, ES_ELEMENT_TRANSITION_CONTENT_ONLY, 1.0f); + } + instance->viewSettings.viewType = VIEW_TILES; - InstanceRefreshViewType(instance, old != instance->viewSettings.viewType); + InstanceRefreshViewType(instance); InstanceViewSettingsUpdated(instance); }, stableCommandID++); EsCommandRegister(&instance->commandViewThumbnails, instance, INTERFACE_STRING(CommonListViewTypeThumbnails), [] (Instance *instance, EsElement *, EsCommand *) { - uint8_t old = instance->viewSettings.viewType; + if (instance->viewSettings.viewType != VIEW_THUMBNAILS) { + EsElementStartTransition(instance->list, ES_TRANSITION_FADE, ES_ELEMENT_TRANSITION_CONTENT_ONLY, 1.0f); + } + instance->viewSettings.viewType = VIEW_THUMBNAILS; - InstanceRefreshViewType(instance, old != instance->viewSettings.viewType); + InstanceRefreshViewType(instance); InstanceViewSettingsUpdated(instance); }, stableCommandID++); diff --git a/apps/file_manager/folder.cpp b/apps/file_manager/folder.cpp index df46230..1590be9 100644 --- a/apps/file_manager/folder.cpp +++ b/apps/file_manager/folder.cpp @@ -394,7 +394,6 @@ FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, entry->handles = 1; entry->name = name; entry->nameBytes = nameBytes; - entry->extensionOffset = PathGetExtension(entry->GetName()).text - name; entry->internalName = name; entry->internalNameBytes = nameBytes; @@ -417,6 +416,7 @@ FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, entry->size = information->fileSize; entry->isFolder = information->type == ES_NODE_DIRECTORY; entry->sizeUnknown = entry->isFolder && information->directoryChildren == ES_DIRECTORY_CHILDREN_UNKNOWN; + entry->contentType = information->contentType; return entry; } @@ -459,12 +459,26 @@ uint64_t FolderRemoveEntryAndUpdateInstances(Folder *folder, const char *name, s return id; } -void FolderFileUpdatedAtPath(String path, Instance *instance) { +void FolderFileUpdatedAtPath(String path, Instance *instance, bool setGuessedContentType = false) { path = PathRemoveTrailingSlash(path); String file = PathGetName(path); String folder = PathGetParent(path); EsDirectoryChild information = {}; bool add = ES_SUCCESS == EsPathQueryInformation(STRING(path), &information); + EsUniqueIdentifier zeroIdentifier = {}; + + if (setGuessedContentType && 0 == EsMemoryCompare(&information.contentType, &zeroIdentifier, sizeof(EsUniqueIdentifier))) { + EsUniqueIdentifier identifier = FileTypeMatchByExtension(path); + + if (EsMemoryCompare(&identifier, &zeroIdentifier, sizeof(EsUniqueIdentifier))) { + EsFileInformation information = EsFileOpen(STRING(path), ES_FILE_WRITE_SHARED | ES_NODE_FAIL_IF_NOT_FOUND); + + if (information.error == ES_SUCCESS) { + EsFileControl(information.handle, ES_FILE_CONTROL_SET_CONTENT_TYPE, &identifier, sizeof(identifier)); + EsHandleClose(information.handle); + } + } + } for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { if (loadedFolders[i]->itemHandler->type != NAMESPACE_HANDLER_FILE_SYSTEM) continue; diff --git a/apps/file_manager/main.cpp b/apps/file_manager/main.cpp index f5cdd5c..be0b177 100644 --- a/apps/file_manager/main.cpp +++ b/apps/file_manager/main.cpp @@ -53,12 +53,14 @@ const char *errorTypeStrings[] = { #define LOAD_FOLDER_NO_FOCUS (1 << 8) struct FolderEntry { + // TODO Can this structure be made smaller? uint16_t handles; - bool isFolder, sizeUnknown; - uint8_t nameBytes, extensionOffset, internalNameBytes; // 0 -> 256. + bool isFolder, sizeUnknown, guessedContentType; + uint8_t nameBytes, internalNameBytes; // 0 -> 256. char *name, *internalName; EsFileOffset size, previousSize; uint64_t id; + EsUniqueIdentifier contentType; inline String GetName() { return { .text = name, .bytes = nameBytes ?: 256u, .allocated = nameBytes ?: 256u }; @@ -67,11 +69,6 @@ struct FolderEntry { inline String GetInternalName() { return { .text = internalName, .bytes = internalNameBytes ?: 256u, .allocated = internalNameBytes ?: 256u }; } - - inline String GetExtension() { - uintptr_t offset = extensionOffset ?: 256u; - return { .text = name + offset, .bytes = (nameBytes ?: 256u) - offset }; - } }; struct ListEntry { @@ -227,7 +224,7 @@ void InstanceReportError(struct Instance *instance, int error, EsError code); bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, int historyMode = 0); void InstanceUpdateStatusString(Instance *instance); void InstanceViewSettingsUpdated(Instance *instance); -void InstanceRefreshViewType(Instance *instance, bool startTransition); +void InstanceRefreshViewType(Instance *instance); void InstanceFolderPathChanged(Instance *instance, bool fromLoadFolder); void InstanceAddContents(struct Instance *instance, HashTable *newEntries); void InstanceAddSingle(struct Instance *instance, ListEntry newEntry); @@ -583,6 +580,10 @@ void _start() { StringDestroy(&openDocuments[i]); } + for (uintptr_t i = 0; i < knownFileTypes.Length(); i++) { + knownFileTypes[i].applicationEntries.Free(); + } + EsAssert(!instances.Length()); EsHeapFree(fileTypesBuffer.out); @@ -608,7 +609,7 @@ void _start() { size_t pathSectionCount = PathCountSections(fullPath); for (uintptr_t i = 0; i < pathSectionCount; i++) { - FolderFileUpdatedAtPath(PathGetParent(fullPath, i + 1), nullptr); + FolderFileUpdatedAtPath(PathGetParent(fullPath, i + 1), nullptr, true); } EsHandleClose(message->user.context1.u); diff --git a/apps/file_manager/type_database.cpp b/apps/file_manager/type_database.cpp index 9c690fa..f0eb6f5 100644 --- a/apps/file_manager/type_database.cpp +++ b/apps/file_manager/type_database.cpp @@ -2,101 +2,242 @@ // It is released under the terms of the MIT license -- see LICENSE.md. // Written by: nakst. +struct FileTypeApplicationEntry { + int64_t application; + bool open; +}; + struct FileType { + Array applicationEntries; // One per application that is involved with this type. + EsUniqueIdentifier identifier; char *name; size_t nameBytes; - char *extension; - size_t extensionBytes; uint32_t iconID; - int64_t openHandler; - - // TODO Allow applications to register their own thumbnail generators. - bool hasThumbnailGenerator; + bool textual; + bool hasThumbnailGenerator; // TODO Allow applications to register their own thumbnail generators. }; Array knownFileTypes; -HashStore knownFileTypesByExtension; +HashStore knownFileTypesByExtension; EsBuffer fileTypesBuffer; void AddKnownFileTypes() { -#define ADD_FILE_TYPE(_extension, _name, _iconID) \ +#define ADD_FILE_TYPE(_name, _iconID) \ { \ FileType type = {}; \ type.name = (char *) _name; \ type.nameBytes = EsCStringLength(_name); \ - type.extension = (char *) _extension; \ - type.extensionBytes = EsCStringLength(_extension); \ type.iconID = _iconID; \ knownFileTypes.Add(type); \ } #define KNOWN_FILE_TYPE_DIRECTORY (0) - ADD_FILE_TYPE("", interfaceString_CommonItemFolder, ES_ICON_FOLDER); + ADD_FILE_TYPE(interfaceString_CommonItemFolder, ES_ICON_FOLDER); #define KNOWN_FILE_TYPE_UNKNOWN (1) - ADD_FILE_TYPE("", interfaceString_CommonItemFile, ES_ICON_UNKNOWN); + ADD_FILE_TYPE(interfaceString_CommonItemFile, ES_ICON_UNKNOWN); #define KNOWN_FILE_TYPE_DRIVE_HDD (2) - ADD_FILE_TYPE("", interfaceString_CommonDriveHDD, ES_ICON_DRIVE_HARDDISK); + ADD_FILE_TYPE(interfaceString_CommonDriveHDD, ES_ICON_DRIVE_HARDDISK); #define KNOWN_FILE_TYPE_DRIVE_SSD (3) - ADD_FILE_TYPE("", interfaceString_CommonDriveSSD, ES_ICON_DRIVE_HARDDISK_SOLIDSTATE); + ADD_FILE_TYPE(interfaceString_CommonDriveSSD, ES_ICON_DRIVE_HARDDISK_SOLIDSTATE); #define KNOWN_FILE_TYPE_DRIVE_CDROM (4) - ADD_FILE_TYPE("", interfaceString_CommonDriveCDROM, ES_ICON_MEDIA_OPTICAL); + ADD_FILE_TYPE(interfaceString_CommonDriveCDROM, ES_ICON_MEDIA_OPTICAL); #define KNOWN_FILE_TYPE_DRIVE_USB_MASS_STORAGE (5) - ADD_FILE_TYPE("", interfaceString_CommonDriveUSBMassStorage, ES_ICON_DRIVE_REMOVABLE_MEDIA_USB); + ADD_FILE_TYPE(interfaceString_CommonDriveUSBMassStorage, ES_ICON_DRIVE_REMOVABLE_MEDIA_USB); #define KNOWN_FILE_TYPE_DRIVES_PAGE (6) - ADD_FILE_TYPE("", interfaceString_FileManagerDrivesPage, ES_ICON_COMPUTER_LAPTOP); + ADD_FILE_TYPE(interfaceString_FileManagerDrivesPage, ES_ICON_COMPUTER_LAPTOP); +#define KNOWN_FILE_TYPE_SPECIAL_COUNT (7) fileTypesBuffer = { .canGrow = true }; EsSystemConfigurationReadFileTypes(&fileTypesBuffer); + EsINIState s = { .buffer = (char *) fileTypesBuffer.out, .bytes = fileTypesBuffer.bytes }; FileType type = {}; + FileTypeApplicationEntry applicationEntry = {}; + bool specifiedTextual = false, specifiedHasThumbnailGenerator = false, hasUUID = false; + const char *matchValue; + size_t matchValueBytes = 0; while (EsINIParse(&s)) { if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("name"))) { type.name = s.value, type.nameBytes = s.valueBytes; - } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("extension"))) { - type.extension = s.value, type.extensionBytes = s.valueBytes; - } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("open"))) { - type.openHandler = EsIntegerParse(s.value, s.valueBytes); + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("uuid"))) { + type.identifier = EsUniqueIdentifierParse(s.value, s.valueBytes); + hasUUID = true; } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("icon"))) { type.iconID = EsIconIDFromString(s.value, s.valueBytes); - } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("has_thumbnail_generator")) && EsIntegerParse(s.value, s.valueBytes)) { + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("has_thumbnail_generator"))) { // TODO Proper thumbnail generator registrations. - type.hasThumbnailGenerator = true; + type.hasThumbnailGenerator = EsIntegerParse(s.value, s.valueBytes) != 0; + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("textual"))) { + type.textual = EsIntegerParse(s.value, s.valueBytes) != 0; + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("application"))) { + applicationEntry.application = EsIntegerParse(s.value, s.valueBytes); + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("actions"))) { + uintptr_t p = 0, q = 0; + + while (q <= s.valueBytes) { + if (q == s.valueBytes || s.value[q] == ',') { + String string = StringFromLiteralWithSize(&s.value[p], q - p); + p = q + 1; + + if (StringEquals(string, StringFromLiteral("open"))) { + applicationEntry.open = true; + } else { + EsPrint("File Manager: Unknown file type entry action '%s'.\n", STRFMT(string)); + } + } + + q++; + } + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("match"))) { + matchValue = s.value; + matchValueBytes = s.valueBytes; } if (!EsINIPeek(&s) || !s.keyBytes) { - uintptr_t index = knownFileTypes.Length(); - knownFileTypes.Add(type); - *knownFileTypesByExtension.Put(type.extension, type.extensionBytes) = index; + EsUniqueIdentifier zeroIdentifier = {}; + + if (EsMemoryCompare(&type.identifier, &zeroIdentifier, sizeof(EsUniqueIdentifier))) { + bool typeFound = false; + uint32_t typeIndex = 0; + + ES_MACRO_SEARCH(knownFileTypes.Length(), { + result = EsMemoryCompare(&type.identifier, &knownFileTypes[index].identifier, sizeof(EsUniqueIdentifier)); + }, typeIndex, typeFound); + + EsAssert(typeIndex >= KNOWN_FILE_TYPE_SPECIAL_COUNT); + FileType *existing; + + if (!typeFound) { + existing = knownFileTypes.Insert(type, typeIndex); + EsPrint("File Manager: Type %I registered by application %d with name '%s'.\n", + type.identifier, applicationEntry.application, type.nameBytes, type.name); + } else { + // TODO Priority system. + existing = &knownFileTypes[typeIndex]; + EsAssert(0 == EsMemoryCompare(&existing->identifier, &type.identifier, sizeof(EsUniqueIdentifier))); + if (type.nameBytes) existing->name = type.name, existing->nameBytes = type.nameBytes; + if (type.iconID) existing->iconID = type.iconID; + if (specifiedTextual) existing->textual = type.textual; + if (specifiedHasThumbnailGenerator) existing->hasThumbnailGenerator = type.hasThumbnailGenerator; + EsPrint("File Manager: Type %I updated by application %d with name '%s'.\n", + type.identifier, applicationEntry.application, type.nameBytes, type.name); + } + + existing->applicationEntries.Add(applicationEntry); + + if (matchValueBytes) { + uintptr_t p = 0, q = 0; + + while (q <= matchValueBytes) { + if (q == matchValueBytes || matchValue[q] == ',') { + String string = StringFromLiteralWithSize(&matchValue[p], q - p); + p = q + 1; + + if (StringStartsWith(string, StringFromLiteral("ext:"))) { + String extension = StringSlice(string, 4, -1); + bool isLower = true; + + for (uintptr_t i = 0; i < extension.bytes; i++) { + if (EsCRTisupper(extension.text[i])) { + isLower = false; + } + } + + if (extension.bytes && isLower) { + EsPrint("File Manager: Matching extension '%s' as %I.\n", + STRFMT(string), type.identifier); + *knownFileTypesByExtension.Put(STRING(extension)) = type.identifier; + } else { + EsPrint("File Manager: Invalid file type entry extension match '%s'.\n", + STRFMT(string)); + } + } else { + EsPrint("File Manager: Unknown file type entry match '%s'.\n", STRFMT(string)); + } + } + + q++; + } + } + } else { + // The UUID is invalid, ignore the entry. + if (hasUUID) EsPrint("File Manager: Discarding file type entry with invalid UUID.\n"); + } + EsMemoryZero(&type, sizeof(type)); + EsMemoryZero(&applicationEntry, sizeof(applicationEntry)); + specifiedTextual = false, specifiedHasThumbnailGenerator = false, hasUUID = false; + matchValueBytes = 0; + } + } + + s = { .buffer = (char *) fileTypesBuffer.out, .bytes = fileTypesBuffer.bytes }; + + while (EsINIParse(&s)) { + if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("application"))) { + applicationEntry.application = EsIntegerParse(s.value, s.valueBytes); + } else if (0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("opens_any_textual_file"))) { + applicationEntry.open = true; + } + + if (!EsINIPeek(&s) || !s.keyBytes) { + if (applicationEntry.open) { + for (uintptr_t i = 0; i < knownFileTypes.Length(); i++) { + if (knownFileTypes[i].textual) { + knownFileTypes[i].applicationEntries.Insert(applicationEntry, 0); + EsPrint("File Manager: Type %I is textual, so application %d can open it.\n", + knownFileTypes[i].identifier, applicationEntry.application); + } + } + } + + EsMemoryZero(&applicationEntry, sizeof(applicationEntry)); } } } +EsUniqueIdentifier FileTypeMatchByExtension(String name) { + String extension = PathGetExtension(name); + char buffer[32]; + uintptr_t i = 0; + + for (; i < extension.bytes && i < 32; i++) { + buffer[i] = EsCRTtolower(extension.text[i]); + } + + return knownFileTypesByExtension.Get1(buffer, i); +} + FileType *FolderEntryGetType(Folder *folder, FolderEntry *entry) { + EsUniqueIdentifier zeroIdentifier = {}; + if (entry->isFolder) { if (folder->itemHandler->getFileType != NamespaceDefaultGetFileType) { String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetInternalName())); FileType *type = &knownFileTypes[folder->itemHandler->getFileType(path)]; StringDestroy(&path); return type; - } else { - return &knownFileTypes[KNOWN_FILE_TYPE_DIRECTORY]; - } - } else { - String extension = entry->GetExtension(); - char buffer[32]; - uintptr_t i = 0; - - for (; i < extension.bytes && i < 32; i++) { - if (EsCRTisupper(extension.text[i])) { - buffer[i] = EsCRTtolower(extension.text[i]); - } else { - buffer[i] = extension.text[i]; - } } - uintptr_t index = knownFileTypesByExtension.Get1(buffer, i); - return &knownFileTypes[index ? index : KNOWN_FILE_TYPE_UNKNOWN]; + return &knownFileTypes[KNOWN_FILE_TYPE_DIRECTORY]; + } + + if (0 == EsMemoryCompare(&entry->contentType, &zeroIdentifier, sizeof(EsUniqueIdentifier))) { + entry->contentType = FileTypeMatchByExtension(entry->GetName()); + entry->guessedContentType = true; + } + + if (0 == EsMemoryCompare(&entry->contentType, &zeroIdentifier, sizeof(EsUniqueIdentifier))) { + return &knownFileTypes[KNOWN_FILE_TYPE_UNKNOWN]; } + + bool typeFound = false; + uint32_t typeIndex = 0; + + ES_MACRO_SEARCH(knownFileTypes.Length(), { + result = EsMemoryCompare(&entry->contentType, &knownFileTypes[index].identifier, sizeof(EsUniqueIdentifier)); + }, typeIndex, typeFound); + + return &knownFileTypes[typeFound ? typeIndex : KNOWN_FILE_TYPE_UNKNOWN]; } diff --git a/apps/file_manager/ui.cpp b/apps/file_manager/ui.cpp index 516b277..37a0b18 100644 --- a/apps/file_manager/ui.cpp +++ b/apps/file_manager/ui.cpp @@ -153,7 +153,7 @@ bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, i } } - InstanceRefreshViewType(instance, false); + InstanceRefreshViewType(instance); // Update the user interface. @@ -168,11 +168,7 @@ bool InstanceLoadFolder(Instance *instance, String path /* takes ownership */, i return true; } -void InstanceRefreshViewType(Instance *instance, bool startTransition) { - if (startTransition) { - EsElementStartTransition(instance->list, ES_TRANSITION_FADE, ES_ELEMENT_TRANSITION_CONTENT_ONLY, 1.0f); - } - +void InstanceRefreshViewType(Instance *instance) { EsCommandSetCheck(&instance->commandViewDetails, instance->viewSettings.viewType == VIEW_DETAILS ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false); EsCommandSetCheck(&instance->commandViewTiles, instance->viewSettings.viewType == VIEW_TILES ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false); EsCommandSetCheck(&instance->commandViewThumbnails, instance->viewSettings.viewType == VIEW_THUMBNAILS ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false); @@ -222,7 +218,11 @@ int InstanceCompareFolderEntries(FolderEntry *left, FolderEntry *right, uint16_t if (sortColumn == COLUMN_NAME) { result = EsStringCompare(STRING(left->GetName()), STRING(right->GetName())); } else if (sortColumn == COLUMN_TYPE) { - result = EsStringCompare(STRING(left->GetExtension()), STRING(right->GetExtension())); + if (!left->isFolder) { + FileType *leftType = FolderEntryGetType(nullptr, left); + FileType *rightType = FolderEntryGetType(nullptr, right); + result = EsStringCompare(leftType->name, leftType->nameBytes, rightType->name, rightType->nameBytes); + } } else if (sortColumn == COLUMN_SIZE) { if (right->size < left->size) result = 1; else if (right->size > left->size) result = -1; @@ -385,6 +385,8 @@ void InstanceAddContents(Instance *instance, HashTable *newEntries) { EsListViewRemove(instance->list, 0, 0, oldListEntryCount); } + // TODO Optimize for each sorting mode. + // For example, sorting by type is pretty bad because it has to lookup file entry types to do each comparison. InstanceSortListContents(instance->listContents.array, instance->listContents.Length(), instance->viewSettings.sortColumn); if (instance->listContents.Length()) { @@ -835,6 +837,8 @@ int ListCallback(EsElement *element, EsMessage *message) { if (listEntry) { FolderEntry *entry = listEntry->entry; + EsUniqueIdentifier applicationContentType = (EsUniqueIdentifier) {{ 0xBF, 0x88, 0xE4, 0xDD, 0x71, 0xE8, 0x5F, 0xDE, + 0x16, 0xC5, 0xAF, 0x33, 0x41, 0xC7, 0xA2, 0x96 }}; if (entry->isFolder) { String path = instance->folder->itemHandler->getPathForChild(instance->folder, entry); @@ -850,7 +854,7 @@ int ListCallback(EsElement *element, EsMessage *message) { } else { InstanceLoadFolder(instance, path); } - } else if (StringEquals(entry->GetExtension(), StringFromLiteral("esx"))) { + } else if (0 == EsMemoryCompare(&entry->contentType, &applicationContentType, sizeof(EsUniqueIdentifier))) { // TODO Temporary. String path = StringAllocateAndFormat("%s%s", STRFMT(instance->folder->path), STRFMT(entry->GetInternalName())); @@ -866,11 +870,18 @@ int ListCallback(EsElement *element, EsMessage *message) { StringDestroy(&path); } else { FileType *fileType = FolderEntryGetType(instance->folder, entry); + FileTypeApplicationEntry *typeEntry = nullptr; - if (fileType->openHandler) { + for (uintptr_t i = 0; i < fileType->applicationEntries.Length(); i++) { + if (fileType->applicationEntries[i].open) { + typeEntry = &fileType->applicationEntries[i]; + } + } + + if (typeEntry) { String path = StringAllocateAndFormat("%s%s", STRFMT(instance->folder->path), STRFMT(entry->GetInternalName())); EsApplicationStartupRequest request = {}; - request.id = fileType->openHandler; + request.id = typeEntry->application; request.filePath = path.text; request.filePathBytes = path.bytes; request.flags = EsKeyboardIsCtrlHeld() || message->chooseItem.source == ES_LIST_VIEW_CHOOSE_ITEM_MIDDLE_CLICK diff --git a/apps/font_book.ini b/apps/font_book.ini index c011979..e329bc2 100644 --- a/apps/font_book.ini +++ b/apps/font_book.ini @@ -5,10 +5,10 @@ icon=icon_applications_fonts [build] source=apps/font_book.cpp -[handler] -extension=ttf -action=open +[file_type] +actions=open +uuid=DA-BF-EC-06-31-8A-67-B6-E3-95-BC-D1-92-D2-9A-56 -[handler] -extension=otf -action=open +[file_type] +actions=open +uuid=E7-2B-BB-E7-FF-75-32-E0-4E-75-41-C0-D2-ED-80-56 diff --git a/apps/image_editor.cpp b/apps/image_editor.cpp index a3e2530..afd6991 100644 --- a/apps/image_editor.cpp +++ b/apps/image_editor.cpp @@ -736,14 +736,27 @@ int InstanceCallback(Instance *instance, EsMessage *message) { EsBuffer buffer = { .out = _buffer, .bytes = _bufferBytes }; buffer.fileStore = message->instanceSave.file; + EsUniqueIdentifier typeJPG = (EsUniqueIdentifier) + {{ 0xD8, 0xC2, 0x13, 0xB0, 0x53, 0x64, 0x82, 0x11, 0x48, 0x7B, 0x5B, 0x64, 0x0F, 0x92, 0xB9, 0x38 }}; + EsUniqueIdentifier typeBMP = (EsUniqueIdentifier) + {{ 0x40, 0x15, 0xB7, 0x82, 0x99, 0x6D, 0xD5, 0x41, 0x96, 0xD5, 0x3B, 0x6D, 0xA6, 0x5F, 0x07, 0x34 }}; + EsUniqueIdentifier typeTGA = (EsUniqueIdentifier) + {{ 0x69, 0x62, 0x4E, 0x28, 0xA1, 0xE1, 0x7B, 0x35, 0x64, 0x2E, 0x36, 0x01, 0x65, 0x91, 0xBE, 0xA1 }}; + EsUniqueIdentifier typePNG = (EsUniqueIdentifier) + {{ 0x59, 0x21, 0x05, 0x4D, 0x34, 0x40, 0xAB, 0x61, 0xEC, 0xF9, 0x7D, 0x5C, 0x6E, 0x04, 0x96, 0xAE }}; + if (0 == EsStringCompare(extension, extensionBytes, EsLiteral("jpg")) || 0 == EsStringCompare(extension, extensionBytes, EsLiteral("jpeg"))) { + EsFileStoreSetContentType(message->instanceSave.file, typeJPG); stbi_write_jpg_to_func(WriteCallback, &buffer, width, height, 4, bits, 90); } else if (0 == EsStringCompare(extension, extensionBytes, EsLiteral("bmp"))) { + EsFileStoreSetContentType(message->instanceSave.file, typeBMP); stbi_write_bmp_to_func(WriteCallback, &buffer, width, height, 4, bits); } else if (0 == EsStringCompare(extension, extensionBytes, EsLiteral("tga"))) { + EsFileStoreSetContentType(message->instanceSave.file, typeTGA); stbi_write_tga_to_func(WriteCallback, &buffer, width, height, 4, bits); } else { + EsFileStoreSetContentType(message->instanceSave.file, typePNG); stbi_write_png_to_func(WriteCallback, &buffer, width, height, 4, bits, stride); } diff --git a/apps/image_editor.ini b/apps/image_editor.ini index 4c7c1fa..bef533e 100644 --- a/apps/image_editor.ini +++ b/apps/image_editor.ini @@ -6,10 +6,18 @@ use_single_process=1 [build] source=apps/image_editor.cpp -[handler] -extension=jpg -action=open +[file_type] +actions=open +uuid=D8-C2-13-B0-53-64-82-11-48-7B-5B-64-0F-92-B9-38 -[handler] -extension=png -action=open +[file_type] +actions=open +uuid=59-21-05-4D-34-40-AB-61-EC-F9-7D-5C-6E-04-96-AE + +[file_type] +actions=open +uuid=40-15-B7-82-99-6D-D5-41-96-D5-3B-6D-A6-5F-07-34 + +[file_type] +actions=open +uuid=69-62-4E-28-A1-E1-7B-35-64-2E-36-01-65-91-BE-A1 diff --git a/apps/markdown_viewer.ini b/apps/markdown_viewer.ini index d642b8d..2476fcd 100644 --- a/apps/markdown_viewer.ini +++ b/apps/markdown_viewer.ini @@ -7,6 +7,6 @@ hidden=1 [build] source=apps/markdown_viewer.cpp -[handler] -extension=md -action=open +[file_type] +actions=open +uuid=B1-C3-9E-56-C9-B0-85-D6-AF-98-12-1C-2E-29-8D-24 diff --git a/apps/text_editor.cpp b/apps/text_editor.cpp index 7ecc786..34c6f2c 100644 --- a/apps/text_editor.cpp +++ b/apps/text_editor.cpp @@ -269,6 +269,23 @@ int InstanceCallback(Instance *instance, EsMessage *message) { size_t byteCount; char *contents = EsTextboxGetContents(instance->textboxDocument, &byteCount); EsFileStoreWriteAll(message->instanceSave.file, contents, byteCount); + + bool fileNameContainsExtension = false; + + for (uintptr_t i = 0; i < (uintptr_t) message->instanceSave.nameOrPathBytes && i < 5; i++) { + if (message->instanceSave.nameOrPath[message->instanceSave.nameOrPathBytes - i - 1] == '.') { + fileNameContainsExtension = true; + } + } + + if (fileNameContainsExtension) { + // Don't set a content type, it should be matched from the file extension. + } else { + EsUniqueIdentifier plainText = (EsUniqueIdentifier) + {{ 0x25, 0x65, 0x4C, 0x89, 0xE7, 0x29, 0xEA, 0x9E, 0xB5, 0xBE, 0xB5, 0xCA, 0xA7, 0x60, 0xBD, 0x3D }}; + EsFileStoreSetContentType(message->instanceSave.file, plainText); + } + EsHeapFree(contents); EsInstanceSaveComplete(instance, message->instanceSave.file, true); } else if (message->type == ES_MSG_INSTANCE_OPEN) { diff --git a/apps/text_editor.ini b/apps/text_editor.ini index 4765acc..b9de785 100644 --- a/apps/text_editor.ini +++ b/apps/text_editor.ini @@ -3,25 +3,8 @@ name=Text Editor icon=icon_accessories_text_editor use_single_process=1 +[file_type] +opens_any_textual_file=1 + [build] source=apps/text_editor.cpp - -[handler] -extension=txt -action=open - -[handler] -extension=cpp -action=open - -[handler] -extension=h -action=open - -[handler] -extension=c -action=open - -[handler] -extension=ini -action=open diff --git a/desktop/api.cpp b/desktop/api.cpp index f6c7aa1..340bedf 100644 --- a/desktop/api.cpp +++ b/desktop/api.cpp @@ -1132,6 +1132,10 @@ EsMessage *EsMessageReceive() { } } else if (type == ES_MSG_INSTANCE_OPEN_DELAYED) { InstanceSendOpenMessage((EsInstance *) message.message._argument, false); + } else if (type == ES_MSG_INSTANCE_SAVE_COMPLETE_DELAYED) { + char buffer[1]; + buffer[0] = DESKTOP_MSG_COMPLETE_SAVE; + MessageDesktop(buffer, 1, ((EsInstance *) message.message._argument)->window->handle); } else if (type == ES_MSG_PRIMARY_CLIPBOARD_UPDATED) { EsInstance *instance = InstanceFromWindowID(message.message.tabOperation.id); if (instance) UIRefreshPrimaryClipboard(instance->window); @@ -1237,9 +1241,9 @@ void EsInstanceSaveComplete(EsInstance *instance, EsFileStore *file, bool succes APIInstance *apiInstance = (APIInstance *) instance->_private; if (instance) { - char buffer[1]; - buffer[0] = DESKTOP_MSG_COMPLETE_SAVE; - MessageDesktop(buffer, 1, instance->window->handle); + // HACK Post this message so that our handle to the file is (hopefully) closed first. + EsMessage m = { .type = ES_MSG_INSTANCE_SAVE_COMPLETE_DELAYED, ._argument = instance }; + EsMessagePost(nullptr, &m); if (success) { const char *name; diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp index 2af9873..0e82c9a 100644 --- a/desktop/desktop.cpp +++ b/desktop/desktop.cpp @@ -98,6 +98,7 @@ struct OpenDocument { size_t pathBytes; char *temporarySavePath; size_t temporarySavePathBytes; + EsUniqueIdentifier temporarySavePreviousContentType; // The previous content type. Used as the default if the save operation does not set a new content type. This is useful for text/hex editors, where there is no intrinsic content type. EsHandle readHandle; EsObjectID id; EsObjectID currentWriter; @@ -2312,6 +2313,11 @@ void ApplicationInstanceRequestSave(ApplicationInstance *instance, const char *n EsHeapFree(document->temporarySavePath); document->temporarySavePath = nullptr; + if (ES_SUCCESS != EsFileControl(document->readHandle, ES_FILE_CONTROL_GET_CONTENT_TYPE, + &document->temporarySavePreviousContentType, sizeof(EsUniqueIdentifier))) { + document->temporarySavePreviousContentType = {}; + } + EsHandle fileHandle; m.tabOperation.error = TemporaryFileCreate(&fileHandle, &document->temporarySavePath, &document->temporarySavePathBytes, ES_FILE_WRITE); @@ -2410,6 +2416,22 @@ void ApplicationInstanceCompleteSave(ApplicationInstance *fromInstance) { document->readHandle = file.handle; } + EsUniqueIdentifier newContentType = {}, zeroContentType = {}; + + if (ES_SUCCESS == EsFileControl(document->readHandle, ES_FILE_CONTROL_GET_CONTENT_TYPE, &newContentType, sizeof(EsUniqueIdentifier))) { + if (0 == EsMemoryCompare(&newContentType, &zeroContentType, sizeof(EsUniqueIdentifier)) + && EsMemoryCompare(&document->temporarySavePreviousContentType, &zeroContentType, sizeof(EsUniqueIdentifier))) { + // The application did not set a content type, so use the previous content type of the file. + EsFileInformation write = EsFileOpen(document->path, document->pathBytes, ES_FILE_WRITE_SHARED | ES_NODE_FAIL_IF_NOT_FOUND); + + if (write.error == ES_SUCCESS) { + EsFileControl(write.handle, ES_FILE_CONTROL_SET_CONTENT_TYPE, + &document->temporarySavePreviousContentType, sizeof(EsUniqueIdentifier)); + EsHandleClose(write.handle); + } + } + } + document->currentWriter = 0; if (desktop.fileManager && desktop.fileManager->singleProcess) { diff --git a/desktop/files.cpp b/desktop/files.cpp index aee47ae..36100c6 100644 --- a/desktop/files.cpp +++ b/desktop/files.cpp @@ -95,7 +95,7 @@ EsError EsPathCreate(const char *path, ptrdiff_t pathBytes, EsNodeType type, boo return ES_SUCCESS; } -EsError EsFileControl(EsHandle file, EsFileControlOperation operation, const void *data, size_t dataBytes) { +EsError EsFileControl(EsHandle file, EsFileControlOperation operation, void *data, size_t dataBytes) { return EsSyscall(ES_SYSCALL_FILE_CONTROL, file, operation, (uintptr_t) data, dataBytes); } @@ -278,6 +278,21 @@ void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flags) { } } +void EsFileStoreSetContentType(EsFileStore *file, EsUniqueIdentifier identifier) { + if (file->type == FILE_STORE_HANDLE) { + EsFileControl(file->handle, ES_FILE_CONTROL_SET_CONTENT_TYPE, &identifier, sizeof(identifier)); + } else if (file->type == FILE_STORE_PATH) { + EsFileInformation information = EsFileOpen(file->path, file->pathBytes, ES_FILE_WRITE | ES_NODE_CREATE_DIRECTORIES); + + if (information.error == ES_SUCCESS) { + EsFileControl(file->handle, ES_FILE_CONTROL_SET_CONTENT_TYPE, &identifier, sizeof(identifier)); + EsHandleClose(information.handle); + } + } else { + // The file store backend doesn't support this operation. + } +} + EsError MountPointAdd(const char *prefix, size_t prefixBytes, EsHandle base, bool addedByApplication) { EsMutexAcquire(&api.mountPointsMutex); bool duplicate = NodeFindMountPoint(prefix, prefixBytes, nullptr, true); @@ -545,7 +560,7 @@ EsError EsFileWriteAllGather(const char *filePath, ptrdiff_t filePathLength, con filePathLength = EsCStringLength(filePath); } - EsFileInformation information = EsFileOpen((char *) filePath, filePathLength, ES_FILE_WRITE | ES_NODE_CREATE_DIRECTORIES); + EsFileInformation information = EsFileOpen(filePath, filePathLength, ES_FILE_WRITE | ES_NODE_CREATE_DIRECTORIES); if (ES_SUCCESS != information.error) { return information.error; diff --git a/desktop/os.header b/desktop/os.header index 05cb15a..e0bc812 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -763,6 +763,7 @@ inttype EsConnectionOpenFlags uint32_t none { inttype EsFileControlOperation uint32_t none { ES_FILE_CONTROL_FLUSH = 0 // data/dataBytes ignored ES_FILE_CONTROL_SET_CONTENT_TYPE = 1 // data EsUniqueIdentifier; dataBytes ignored + ES_FILE_CONTROL_GET_CONTENT_TYPE = 2 // data EsUniqueIdentifier; dataBytes ignored }; inttype EsElementUpdateContentFlags uint32_t none { @@ -2142,7 +2143,7 @@ function EsError EsFileWriteAllGatherFromHandle(EsHandle handle, const void **da function void *EsFileMap(STRING filePath, size_t *fileSize, uint32_t flags) @native(); function EsError EsFileCopy(STRING source, STRING destination, void **copyBuffer = ES_NULL, EsFileCopyCallback callback = ES_NULL, EsGeneric data = ES_NULL) @todo(); // If you are copying lots of files, you can reuse the temporary copy buffer by storing the output copyBuffer; call EsHeapFree on it after the last copy. -function EsError EsFileControl(EsHandle file, EsFileControlOperation operation, const void *data, size_t dataBytes) @buffer_in(data, dataBytes); +function EsError EsFileControl(EsHandle file, EsFileControlOperation operation, void *data, size_t dataBytes) @buffer_out(data, dataBytes); // TODO This annotation is incorrect, it might be in or out depending on the operation. function EsFileInformation EsFileOpen(STRING path, EsFileOpenFlags flags); function EsFileOffset EsFileGetSize(EsHandle handle); function size_t EsFileReadSync(EsHandle file, EsFileOffset offset, size_t size, void *buffer) @buffer_out(buffer, size); @@ -2162,6 +2163,7 @@ function bool EsFileStoreWriteAll(EsFileStore *file, const void *data, size_t da function bool EsFileStoreAppend(EsFileStore *file, const void *data, size_t dataBytes) @buffer_in(data, dataBytes); function EsFileOffsetDifference EsFileStoreGetSize(EsFileStore *file); // Returns -1 on error. function void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flags) @native(); +function void EsFileStoreSetContentType(EsFileStore *file, EsUniqueIdentifier identifier); // These calls require permission_all_files. function bool EsMountPointGetVolumeInformation(STRING prefix, EsVolumeInformation *information) @out(information); // Returns false if the mount point does not exist. @@ -2394,6 +2396,7 @@ function bool EsUTF8IsValid(STRING input); // Does not check for surrogate chara function double EsDoubleParse(STRING string, char **endptr) @native(); function int64_t EsIntegerParse(STRING text); // Parses in hexadecimal if the first two characters are '0x'. +function EsUniqueIdentifier EsUniqueIdentifierParse(STRING text); // CRT functions. diff --git a/desktop/prefix.h b/desktop/prefix.h index 662adcf..41de85e 100644 --- a/desktop/prefix.h +++ b/desktop/prefix.h @@ -425,6 +425,7 @@ extern "C" void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writ #define ES_MSG_PING ((EsMessageType) (ES_MSG_SYSTEM_START + 0x203)) /* Sent by Desktop to check processes are processing messages. */ #define ES_MSG_WAKEUP ((EsMessageType) (ES_MSG_SYSTEM_START + 0x204)) /* Sent to wakeup the message thread, so that it can process locally posted messages. */ #define ES_MSG_INSTANCE_OPEN_DELAYED ((EsMessageType) (ES_MSG_SYSTEM_START + 0x205)) +#define ES_MSG_INSTANCE_SAVE_COMPLETE_DELAYED ((EsMessageType) (ES_MSG_SYSTEM_START + 0x206)) #endif diff --git a/desktop/text.cpp b/desktop/text.cpp index 49ff1c8..28b6106 100644 --- a/desktop/text.cpp +++ b/desktop/text.cpp @@ -87,7 +87,7 @@ struct TextPiece { uintptr_t glyphOffset; size_t glyphCount; uintptr_t start, end; - bool isTabPiece; + bool isTabPiece, isEllipsisPiece; }; struct TextLine { @@ -1829,6 +1829,7 @@ void TextAddEllipsis(EsTextPlan *plan, int32_t maximumLineWidth, bool needFinalE piece.glyphOffset = plan->glyphInfos.Length(); piece.ascent = FontGetAscent (&plan->font) + plan->currentTextStyle->baselineOffset, piece.descent = -FontGetDescent(&plan->font) - plan->currentTextStyle->baselineOffset; + piece.isEllipsisPiece = true; for (uintptr_t i = 0; i < glyphCount; i++) { if (!plan->glyphInfos.Add(glyphInfos[i])) break; @@ -2295,7 +2296,7 @@ void DrawTextPiece(EsPainter *painter, EsTextPlan *plan, TextPiece *piece, TextL // Draw the selection background. - if (selection->caret0 != selection->caret1 && !selection->hideCaret) { + if (selection->caret0 != selection->caret1 && !selection->hideCaret && !piece->isEllipsisPiece) { int sCursorX = cursorX, selectionStartX = -1, selectionEndX = -1; for (uintptr_t i = 0; i < piece->glyphCount; i++) { diff --git a/help/Build System.md b/help/Build System.md index 6cd7846..131c36d 100644 --- a/help/Build System.md +++ b/help/Build System.md @@ -97,15 +97,13 @@ In the `[embed]` section there is a list of files that should be embedded into t Each `[file_type]` section provides information about a file type. -- `extension` Gives the file name extension for the file type. +- `match` Gives the file name extensions to match for the file type. - `name` Gives the readable name of the file type, which will be shown to the user. TODO Translations. - `icon` Gives the name of the icon from `desktop/icons.header` to show for files of this type. TODO Bundled icons. - `has_thumbnail_generator` Set to 1 if the file type has a thumbnail generator. Only images are supported at the moment. TODO Custom thumbnail generators. - -Each `[handler]` section describes this application's support to manage files of a given file type. - -- `extension` The file name extension of the file type. -- `action` The action that this application support for the file type. Currently only "open" is supported. +- `uuid` The EsUniqueIdentifier of the file type. +- `textual` Set to 1 if the file type can be read by plain text editors. +- `actions` The actions the application supports with this file type, e.g. `open`. ### Standard build configuration options diff --git a/help/Content Types.txt b/help/Content Types.txt new file mode 100644 index 0000000..a20afa7 --- /dev/null +++ b/help/Content Types.txt @@ -0,0 +1,119 @@ +These are the EsUniqueIdentifier values for different file content types. +TODO Write up how the content type system works. + +ELF executable +{ 0xAB, 0xDE, 0x98, 0xB5, 0x56, 0x2C, 0x04, 0xDF, 0x1E, 0x43, 0xC8, 0x10, 0x24, 0x63, 0xDB, 0xB8 } + +esx application +{ 0xBF, 0x88, 0xE4, 0xDD, 0x71, 0xE8, 0x5F, 0xDE, 0x16, 0xC5, 0xAF, 0x33, 0x41, 0xC7, 0xA2, 0x96 } + +plain text files +{ 0x25, 0x65, 0x4C, 0x89, 0xE7, 0x29, 0xEA, 0x9E, 0xB5, 0xBE, 0xB5, 0xCA, 0xA7, 0x60, 0xBD, 0x3D } + +bochs config file +{ 0xFF, 0x92, 0xF0, 0x53, 0x28, 0x83, 0xDC, 0xF1, 0xBA, 0xDD, 0xDE, 0x79, 0x92, 0x33, 0x3F, 0x73 } + +build core file +{ 0x6F, 0x2A, 0x23, 0x16, 0xAF, 0xA3, 0xD0, 0xFB, 0x95, 0x06, 0x52, 0xB3, 0x67, 0xFB, 0x37, 0xFA } + +theme designer source file +{ 0x26, 0x4A, 0xE0, 0x6C, 0x0F, 0xE8, 0x2C, 0xE7, 0xF4, 0x6E, 0x4E, 0x76, 0x5B, 0x4A, 0xCF, 0x4F } + +uxn rom +{ 0x76, 0xC2, 0xC1, 0x73, 0xB1, 0x20, 0x3D, 0x42, 0x70, 0xFD, 0xD4, 0xBD, 0x66, 0xE9, 0x2A, 0x39 } + +static library .a +{ 0x7D, 0x39, 0xBF, 0x18, 0x9E, 0x07, 0xBC, 0xD3, 0x4F, 0x9F, 0x87, 0xEC, 0x6F, 0x70, 0x65, 0x5D } + +libtool archive .la +{ 0x8F, 0x10, 0x4F, 0x33, 0x4A, 0xE5, 0x2D, 0xA7, 0x2A, 0x80, 0x2C, 0x4F, 0xEA, 0xE4, 0x99, 0xB1 } + +compiled object .o +{ 0xB5, 0x19, 0xF2, 0x0D, 0xAD, 0x4F, 0xD4, 0xC9, 0xA4, 0xBF, 0x97, 0xE4, 0x5E, 0x4C, 0x55, 0x00 } + +c source file +{ 0x36, 0x02, 0xD3, 0xAC, 0x6C, 0xDE, 0x8D, 0x31, 0xFA, 0x70, 0xB2, 0xDA, 0xFA, 0x81, 0x53, 0x69 } + +c++ source file +{ 0x35, 0x84, 0xA6, 0x0E, 0x52, 0x28, 0xBA, 0xAB, 0xCA, 0x74, 0x14, 0xF4, 0xE1, 0x15, 0xCA, 0x5F } + +c header file +{ 0xD1, 0x16, 0xC4, 0xC1, 0x7B, 0xC0, 0xED, 0x4F, 0xCA, 0xAC, 0x18, 0x05, 0x32, 0x1D, 0x56, 0x32 } + +html file +{ 0x4A, 0x5A, 0x1C, 0xE5, 0xEE, 0x08, 0x6E, 0x04, 0x13, 0x9C, 0x6D, 0x05, 0x48, 0x5C, 0xE5, 0xD2 } + +source template +{ 0x76, 0x35, 0xF3, 0xF5, 0xC2, 0x69, 0x8A, 0xA4, 0xE4, 0x68, 0x5F, 0xE5, 0x2C, 0x73, 0x10, 0xF9 } + +linker script +{ 0x23, 0x56, 0xBC, 0xD4, 0x5A, 0xD6, 0x56, 0x08, 0x06, 0x61, 0x7C, 0x33, 0x38, 0x66, 0xD5, 0x1F } + +markdown +{ 0xB1, 0xC3, 0x9E, 0x56, 0xC9, 0xB0, 0x85, 0xD6, 0xAF, 0x98, 0x12, 0x1C, 0x2E, 0x29, 0x8D, 0x24 } + +UNIX package configuration +{ 0x74, 0x72, 0x01, 0x17, 0x97, 0x4B, 0x6B, 0x4B, 0x74, 0xCD, 0x18, 0x7C, 0x8F, 0x3C, 0x58, 0xBC } + +python script +{ 0x77, 0x19, 0xBA, 0x87, 0x2E, 0xDB, 0x51, 0xA4, 0x71, 0x5D, 0xFF, 0x8D, 0x39, 0x86, 0x96, 0xF0 } + +UNIX manual file +{ 0xDB, 0x72, 0xDD, 0x5D, 0x92, 0x54, 0x09, 0x1A, 0xC4, 0x16, 0xDD, 0x89, 0x0B, 0x13, 0x12, 0x1F } + +application configuration file +{ 0x18, 0x8D, 0xD3, 0xAC, 0x35, 0xF5, 0xD3, 0x01, 0x8C, 0x66, 0xFD, 0x12, 0xE1, 0xED, 0xE2, 0x6F } + +INI configuration file +{ 0x81, 0x72, 0x43, 0x29, 0xE5, 0x37, 0x89, 0x51, 0x8E, 0x22, 0xA0, 0x67, 0xA5, 0xB9, 0x42, 0x2C } + +gzip archive +{ 0x7D, 0x93, 0x66, 0x74, 0x80, 0x0B, 0x64, 0x9F, 0x16, 0x5D, 0x44, 0xA5, 0x45, 0xFF, 0x80, 0xBF } + +virtual drive image +{ 0xE1, 0x61, 0x4D, 0x0D, 0x32, 0xEC, 0xE0, 0x0F, 0x36, 0x7F, 0xE9, 0x7B, 0x3F, 0x16, 0x43, 0x02 } + +jpeg image +{ 0xD8, 0xC2, 0x13, 0xB0, 0x53, 0x64, 0x82, 0x11, 0x48, 0x7B, 0x5B, 0x64, 0x0F, 0x92, 0xB9, 0x38 } + +matroska video +{ 0x0D, 0xCA, 0x26, 0x92, 0x22, 0x44, 0xEB, 0x6B, 0xC6, 0xE9, 0x6C, 0x8E, 0xFC, 0x28, 0xD2, 0x8E } + +3d object .obj +{ 0xF7, 0x81, 0xE9, 0x21, 0xDF, 0x89, 0x77, 0xD0, 0xC6, 0x97, 0xA8, 0x63, 0xF1, 0xF3, 0x4F, 0x63 } + +png image +{ 0x59, 0x21, 0x05, 0x4D, 0x34, 0x40, 0xAB, 0x61, 0xEC, 0xF9, 0x7D, 0x5C, 0x6E, 0x04, 0x96, 0xAE } + +tga image +{ 0x69, 0x62, 0x4E, 0x28, 0xA1, 0xE1, 0x7B, 0x35, 0x64, 0x2E, 0x36, 0x01, 0x65, 0x91, 0xBE, 0xA1 } + +bmp image +{ 0x40, 0x15, 0xB7, 0x82, 0x99, 0x6D, 0xD5, 0x41, 0x96, 0xD5, 0x3B, 0x6D, 0xA6, 0x5F, 0x07, 0x34 } + +truetype font +{ 0xDA, 0xBF, 0xEC, 0x06, 0x31, 0x8A, 0x67, 0xB6, 0xE3, 0x95, 0xBC, 0xD1, 0x92, 0xD2, 0x9A, 0x56 } + +opentype font +{ 0xE7, 0x2B, 0xBB, 0xE7, 0xFF, 0x75, 0x32, 0xE0, 0x4E, 0x75, 0x41, 0xC0, 0xD2, 0xED, 0x80, 0x56 } + +application data +{ 0xB5, 0x32, 0x22, 0x14, 0x01, 0xCB, 0xD0, 0x1D, 0xA2, 0x55, 0x08, 0xC7, 0x0D, 0x46, 0x86, 0x53 } + +bash script +{ 0x66, 0x42, 0x88, 0xF9, 0xD1, 0x68, 0x47, 0xBF, 0x7F, 0x48, 0x82, 0x77, 0x2B, 0xE3, 0x39, 0xBA } + +makefile +{ 0x3A, 0x58, 0x08, 0x24, 0x00, 0x75, 0xB5, 0x8B, 0xA8, 0x39, 0xD8, 0x37, 0x03, 0x6B, 0x20, 0x85 } + +kernel module +{ 0x80, 0x6D, 0x8E, 0xD6, 0xC7, 0x45, 0xAD, 0xA7, 0x03, 0x5B, 0xB3, 0x64, 0xC5, 0x0A, 0xEE, 0xC6 } + +ogg audio +{ 0xE5, 0x14, 0x14, 0xED, 0x4D, 0x67, 0xC0, 0xD0, 0x62, 0xA1, 0xA4, 0xD4, 0xF5, 0x82, 0xA7, 0x88 } + +mp4 video +{ 0x02, 0x76, 0xE1, 0x41, 0xB8, 0x54, 0x19, 0xA9, 0x05, 0x8D, 0xC1, 0x3F, 0x6E, 0x8F, 0xD7, 0x9E } + +wav audio +{ 0x87, 0xBC, 0xA1, 0xA1, 0x25, 0x76, 0x26, 0x5C, 0x8F, 0x94, 0xD6, 0xD8, 0x35, 0xB6, 0x7A, 0x9F } diff --git a/kernel/files.cpp b/kernel/files.cpp index 2b0c629..c6629ac 100644 --- a/kernel/files.cpp +++ b/kernel/files.cpp @@ -65,7 +65,7 @@ EsError FSNodeOpenHandle(KNode *node, uint32_t flags, uint8_t mode); void FSNodeCloseHandle(KNode *node, uint32_t flags); EsError FSNodeDelete(KNode *node); EsError FSNodeMove(KNode *node, KNode *destination, const char *newName, size_t nameNameBytes); -EsError FSFileResize(KNode *node, EsFileOffset newSizeBytes); +EsError FSFileResize(KNode *node, EsFileOffset newSizeBytes, bool growOnly = false); ptrdiff_t FSDirectoryEnumerate(KNode *node, K_USER_BUFFER EsDirectoryChild *buffer, size_t bufferSize); EsError FSFileControlFlush(KNode *node); EsError FSFileControlSetContentType(KNode *node, EsUniqueIdentifier identifier); @@ -136,6 +136,7 @@ bool FSCheckPathForIllegalCharacters(const char *path, size_t pathBytes) { EsError FSReadIntoCache(CCSpace *fileCache, void *buffer, EsFileOffset offset, EsFileOffset count) { FSFile *node = EsContainerOf(FSFile, cache, fileCache); + // KWriterLockAssertLocked(&node->resizeLock); KWriterLockTake(&node->writerLock, K_LOCK_SHARED); EsDefer(KWriterLockReturn(&node->writerLock, K_LOCK_SHARED)); @@ -238,6 +239,7 @@ EsError FSFileCreateAndResizeOnFileSystem(FSFile *node, EsFileOffset fileSize) { EsError FSWriteFromCache(CCSpace *fileCache, const void *buffer, EsFileOffset offset, EsFileOffset count) { FSFile *node = EsContainerOf(FSFile, cache, fileCache); + // KWriterLockAssertLocked(&node->resizeLock); KWriterLockTake(&node->writerLock, K_LOCK_EXCLUSIVE); EsDefer(KWriterLockReturn(&node->writerLock, K_LOCK_EXCLUSIVE)); @@ -335,7 +337,7 @@ void _FSFileResize(FSFile *file, EsFileOffset newSize) { KMutexRelease(&file->fileSystem->moveMutex); } -EsError FSFileResize(KNode *node, EsFileOffset newSize) { +EsError FSFileResize(KNode *node, EsFileOffset newSize, bool growOnly) { if (fs.shutdown) KernelPanic("FSFileResize - Attempting to resize a file after FSShutdown called.\n"); if (newSize > (EsFileOffset) 1 << 60) { @@ -350,7 +352,9 @@ EsError FSFileResize(KNode *node, EsFileOffset newSize) { EsError error = ES_SUCCESS; KWriterLockTake(&file->resizeLock, K_LOCK_EXCLUSIVE); - if (file->blockResize) { + if (growOnly && newSize <= file->directoryEntry->totalSize) { + // Nothing to do. + } else if (file->blockResize) { error = ES_ERROR_OPERATION_BLOCKED; } else if (!file->fileSystem->resize) { error = ES_ERROR_FILE_ON_READ_ONLY_VOLUME; @@ -365,10 +369,8 @@ EsError FSFileResize(KNode *node, EsFileOffset newSize) { ptrdiff_t FSFileWriteSync(KNode *node, K_USER_BUFFER const void *buffer, EsFileOffset offset, EsFileOffset bytes, uint32_t flags) { if (fs.shutdown) KernelPanic("FSFileWriteSync - Attempting to write to a file after FSShutdown called.\n"); - if (offset + bytes > node->directoryEntry->totalSize) { - if (ES_SUCCESS != FSFileResize(node, offset + bytes)) { - return ES_ERROR_COULD_NOT_RESIZE_FILE; - } + if (ES_SUCCESS != FSFileResize(node, offset + bytes, true /* growOnly */)) { + return ES_ERROR_COULD_NOT_RESIZE_FILE; } FSFile *file = (FSFile *) node; @@ -426,8 +428,10 @@ EsError FSFileControlSetContentType(KNode *node, EsUniqueIdentifier identifier) } else if (~node->fileSystem->flags & ES_VOLUME_STORES_CONTENT_TYPE) { return ES_ERROR_UNSUPPORTED; } else { + KWriterLockTake(&node->writerLock, K_LOCK_EXCLUSIVE); node->directoryEntry->contentType = identifier; __sync_fetch_and_or(&node->flags, NODE_MODIFIED); // Set the modified flag *after* making the modification. + KWriterLockReturn(&node->writerLock, K_LOCK_EXCLUSIVE); return ES_SUCCESS; } } @@ -1015,9 +1019,11 @@ void FSNodeSynchronize(KNode *node) { if (node->directoryEntry->type == ES_NODE_FILE) { FSFile *file = (FSFile *) node; CCSpaceFlush(&file->cache); - KWriterLockTake(&node->writerLock, K_LOCK_EXCLUSIVE); + KWriterLockTake(&file->resizeLock, K_LOCK_EXCLUSIVE); + KWriterLockTake(&file->writerLock, K_LOCK_EXCLUSIVE); FSFileCreateAndResizeOnFileSystem(file, file->directoryEntry->totalSize); - KWriterLockReturn(&node->writerLock, K_LOCK_EXCLUSIVE); + KWriterLockReturn(&file->writerLock, K_LOCK_EXCLUSIVE); + KWriterLockReturn(&file->resizeLock, K_LOCK_EXCLUSIVE); } if (node->flags & NODE_MODIFIED) { diff --git a/kernel/memory.cpp b/kernel/memory.cpp index 0f4e094..9f842b2 100644 --- a/kernel/memory.cpp +++ b/kernel/memory.cpp @@ -697,10 +697,15 @@ bool MMHandlePageFault(MMSpace *space, uintptr_t address, unsigned faultFlags) { pagesToRead = region->pageCount - (offsetIntoRegion + region->data.file.zeroedBytes) / K_PAGE_SIZE; } + // This shouldn't block, because mapped files cannot be resized. + KWriterLockTake(®ion->data.file.node->resizeLock, K_LOCK_SHARED); + EsError error = CCSpaceAccess(®ion->data.file.node->cache, (void *) address, offsetIntoRegion + region->data.file.offset, pagesToRead * K_PAGE_SIZE, CC_ACCESS_MAP, space, flags); + KWriterLockReturn(®ion->data.file.node->resizeLock, K_LOCK_SHARED); + KMutexAcquire(®ion->data.mapMutex); if (error != ES_SUCCESS) { diff --git a/kernel/syscall.cpp b/kernel/syscall.cpp index 33e9e70..3c2f661 100644 --- a/kernel/syscall.cpp +++ b/kernel/syscall.cpp @@ -1193,18 +1193,38 @@ SYSCALL_IMPLEMENT(ES_SYSCALL_FILE_CONTROL) { if (file->directoryEntry->type != ES_NODE_FILE) { SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true); - } else if ((handle.flags & (ES_FILE_WRITE_SHARED | ES_FILE_WRITE)) == 0) { - SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); } EsError error = ES_ERROR_UNSUPPORTED; if (argument1 == ES_FILE_CONTROL_FLUSH) { + if ((handle.flags & (ES_FILE_WRITE_SHARED | ES_FILE_WRITE)) == 0) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); + } + error = FSFileControlFlush(file); } else if (argument1 == ES_FILE_CONTROL_SET_CONTENT_TYPE) { + if ((handle.flags & (ES_FILE_WRITE_SHARED | ES_FILE_WRITE)) == 0) { + SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_FILE_ACCESS, true); + } + EsUniqueIdentifier identifier; SYSCALL_READ(&identifier, argument2, sizeof(identifier)); error = FSFileControlSetContentType(file, identifier); + } else if (argument1 == ES_FILE_CONTROL_GET_CONTENT_TYPE) { + EsUniqueIdentifier identifier; + KWriterLockTake(&file->writerLock, K_LOCK_SHARED); + + if (file->fileSystem->flags & ES_VOLUME_STORES_CONTENT_TYPE) { + identifier = file->directoryEntry->contentType; + error = ES_SUCCESS; + } + + KWriterLockReturn(&file->writerLock, K_LOCK_SHARED); + + if (error == ES_SUCCESS) { + SYSCALL_WRITE(argument2, &identifier, sizeof(EsUniqueIdentifier)); + } } SYSCALL_RETURN(error, false); diff --git a/ports/bochs/bochs.ini b/ports/bochs/bochs.ini index a33cfbd..0aaf507 100644 --- a/ports/bochs/bochs.ini +++ b/ports/bochs/bochs.ini @@ -9,10 +9,9 @@ custom_compile_command=cp root/Applications/POSIX/bin/bochs bin/Bochs require=root/Applications/POSIX/bin/bochs [file_type] -extension=bochsrc +match=ext:bochsrc name=Bochs configuration icon=icon_applications_development - -[handler] -extension=bochsrc -action=open +uuid=FF-92-F0-53-28-83-DC-F1-BA-DD-DE-79-92-33-3F-73 +actions=open +textual=1 diff --git a/ports/mesa/obj_viewer.ini b/ports/mesa/obj_viewer.ini index 38fc3ee..6086511 100644 --- a/ports/mesa/obj_viewer.ini +++ b/ports/mesa/obj_viewer.ini @@ -9,11 +9,10 @@ link_flags=-lOSMesa -lstdc++ -lz with_cstdlib=1 source=ports/mesa/obj_viewer.c -[handler] -extension=obj -action=open - [file_type] -extension=obj +match=ext:obj name=3D model icon=icon_model +uuid=F7-81-E9-21-DF-89-77-D0-C6-97-A8-63-F1-F3-4F-63 +actions=open +textual=1 diff --git a/ports/uxn/emulator.ini b/ports/uxn/emulator.ini index bff167c..3e151ee 100644 --- a/ports/uxn/emulator.ini +++ b/ports/uxn/emulator.ini @@ -6,11 +6,9 @@ hidden=1 source=ports/uxn/emulator.c compile_flags=-Wno-unknown-pragmas -Wno-unused-parameter -[handler] -extension=uxn -action=open - [file_type] -extension=uxn +match=ext:uxn name=Uxn ROM icon=icon_unknown +uuid=76-C2-C1-73-B1-20-3D-42-70-FD-D4-BD-66-E9-2A-39 +actions=open diff --git a/shared/common.cpp b/shared/common.cpp index dd19b92..341c5e2 100644 --- a/shared/common.cpp +++ b/shared/common.cpp @@ -589,6 +589,16 @@ void _StringFormat(FormatCallback callback, void *callbackData, const char *form callback('}', callbackData); } break; + case 'I': { + EsUniqueIdentifier value = va_arg(arguments, EsUniqueIdentifier); + + for (uintptr_t i = 0; i < 16; i++) { + if (i) callback('-', callbackData); + callback(hexChars[(value.d[i] & 0xF0) >> 4], callbackData); + callback(hexChars[(value.d[i] & 0xF)], callbackData); + } + } break; + case 'X': { uintptr_t value = va_arg(arguments, uintptr_t); callback(hexChars[(value & 0xF0) >> 4], callbackData); @@ -920,6 +930,7 @@ int64_t EsStringParseInteger(const char **string, size_t *length, int base) { int EsStringCompareRaw(const char *s1, ptrdiff_t length1, const char *s2, ptrdiff_t length2) { if (length1 == -1) length1 = EsCStringLength(s1); if (length2 == -1) length2 = EsCStringLength(s2); + if (s1 == s2 && length1 == length2) return 0; while (length1 || length2) { if (!length1) return -1; @@ -945,6 +956,7 @@ int EsStringCompare(const char *s1, ptrdiff_t _length1, const char *s2, ptrdiff_ if (_length1 == -1) _length1 = EsCStringLength(s1); if (_length2 == -1) _length2 = EsCStringLength(s2); size_t length1 = _length1, length2 = _length2; + if (s1 == s2 && length1 == length2) return 0; while (length1 || length2) { if (!length1) return -1; @@ -1072,6 +1084,28 @@ static int64_t ConvertCharacterToDigit(int character, int base) { return result; } +EsUniqueIdentifier EsUniqueIdentifierParse(const char *text, ptrdiff_t bytes) { + if (bytes == -1) bytes = EsCStringLength(text); + if (bytes != 3 * 16 - 1) return {}; + + for (uintptr_t i = 0; i < 15; i++) { + if (text[i * 3 + 2] != '-') { + return {}; + } + } + + EsUniqueIdentifier identifier; + + for (uintptr_t i = 0; i < 16; i++) { + int64_t a = ConvertCharacterToDigit(text[i * 3 + 0], 16); + int64_t b = ConvertCharacterToDigit(text[i * 3 + 1], 16); + if (a == -1 || b == -1) return {}; + identifier.d[i] = a * 16 + b; + } + + return identifier; +} + int64_t EsIntegerParse(const char *text, ptrdiff_t bytes) { if (bytes == -1) bytes = EsCStringLength(text); diff --git a/shared/esfs2.h b/shared/esfs2.h index 1bb8373..de188a2 100644 --- a/shared/esfs2.h +++ b/shared/esfs2.h @@ -694,12 +694,13 @@ void PrintTree(uint64_t block, int indent = 2, uint64_t lowerThan = -1) { } #endif -void NewDirectoryEntry(DirectoryEntry *entry, uint8_t nodeType, EsUniqueIdentifier parentUID, const char *name) { +void NewDirectoryEntry(DirectoryEntry *entry, uint8_t nodeType, EsUniqueIdentifier parentUID, const char *name, EsUniqueIdentifier contentType) { memcpy(entry->signature, ESFS_DIRECTORY_ENTRY_SIGNATURE, 8); GenerateUniqueIdentifier(&entry->identifier, false); entry->attributeOffset = ESFS_ATTRIBUTE_OFFSET; entry->nodeType = nodeType; entry->parent = parentUID; + entry->contentType = contentType; uint8_t *position = (uint8_t *) entry + entry->attributeOffset; size_t newFilenameSize = ((strlen(name) + ESFS_FILENAME_HEADER_SIZE - 1) & ~7) + 8; // Size of name + size of header, rounded up to the nearest 8 bytes. @@ -736,7 +737,7 @@ void NewDirectoryEntry(DirectoryEntry *entry, uint8_t nodeType, EsUniqueIdentifi } bool AddNode(const char *name, uint8_t nodeType, DirectoryEntry *outputEntry, DirectoryEntryReference *outputReference, - DirectoryEntryReference directoryReference) { + DirectoryEntryReference directoryReference, EsUniqueIdentifier contentType) { // Log("add %s to %s\n", name, path); // Step 1: Resize the directory so that it can fit another directory entry. @@ -770,7 +771,7 @@ bool AddNode(const char *name, uint8_t nodeType, DirectoryEntry *outputEntry, Di DirectoryEntry entry = {}; { - NewDirectoryEntry(&entry, nodeType, directory.identifier, name); + NewDirectoryEntry(&entry, nodeType, directory.identifier, name, contentType); // Log("\tchild nodes: %ld\n", directoryAttribute->childNodes); if (!AccessNode(&directory, &entry, (directoryAttribute->childNodes - 1) * sizeof(DirectoryEntry), sizeof(DirectoryEntry), &reference, false)) { @@ -1041,6 +1042,7 @@ typedef struct ImportNode { const char *name, *path; struct ImportNode *children; bool isFile; + EsUniqueIdentifier contentType; } ImportNode; int64_t Import(ImportNode node, DirectoryEntryReference parentDirectory) { @@ -1059,7 +1061,7 @@ int64_t Import(ImportNode node, DirectoryEntryReference parentDirectory) { DirectoryEntryReference reference; DirectoryEntry entry; - if (!AddNode(node.children[i].name, ESFS_NODE_TYPE_FILE, &entry, &reference, parentDirectory)) { + if (!AddNode(node.children[i].name, ESFS_NODE_TYPE_FILE, &entry, &reference, parentDirectory, node.children[i].contentType)) { return -1; } @@ -1081,7 +1083,7 @@ int64_t Import(ImportNode node, DirectoryEntryReference parentDirectory) { } } else { DirectoryEntryReference reference; - if (!AddNode(node.children[i].name, ESFS_NODE_TYPE_DIRECTORY, NULL, &reference, parentDirectory)) return -1; + if (!AddNode(node.children[i].name, ESFS_NODE_TYPE_DIRECTORY, NULL, &reference, parentDirectory, node.children[i].contentType)) return -1; int64_t size = Import(node.children[i], reference); if (size == -1) return -1; DirectoryEntry directory; @@ -1241,7 +1243,8 @@ bool Format(uint64_t driveSize, const char *volumeName, EsUniqueIdentifier osIns DirectoryEntry entry; EsUniqueIdentifier unused = {}; - NewDirectoryEntry(&entry, ESFS_NODE_TYPE_FILE, unused, "Kernel"); + EsUniqueIdentifier elf = (EsUniqueIdentifier) {{ 0xAB, 0xDE, 0x98, 0xB5, 0x56, 0x2C, 0x04, 0xDF, 0x1E, 0x43, 0xC8, 0x10, 0x24, 0x63, 0xDB, 0xB8 }}; + NewDirectoryEntry(&entry, ESFS_NODE_TYPE_FILE, unused, "Kernel", elf); if (WriteDirectoryEntryReference(reference, &entry)) { if (ResizeNode(&entry, kernelBytes)) { diff --git a/start.sh b/start.sh index 5e74063..ce33daf 100755 --- a/start.sh +++ b/start.sh @@ -1 +1 @@ -cd "$(dirname "$0")" && mkdir -p bin && gcc -o bin/script util/script.c -g -Wall -Wextra -O2 -pthread -ldl && bin/script util/start.script options="`echo $@`" +cd "$(dirname "$0")" && mkdir -p bin && gcc -o bin/script util/script.c -g -Wall -Wextra -O2 -pthread && bin/script util/start.script options="`echo $@`" diff --git a/util/api_table.ini b/util/api_table.ini index 256ec74..00a5423 100644 --- a/util/api_table.ini +++ b/util/api_table.ini @@ -37,6 +37,7 @@ EsCommandSetCheck=35 EsTextboxGetContentsAsDouble=36 EsFileStoreAppend=37 EsBufferFlushToFileStore=38 +EsFileStoreSetContentType=39 EsPathExists=40 EsInstanceSetClassViewer=41 EsPathDelete=42 @@ -494,3 +495,4 @@ EsRectangleContainsAll=493 EsListViewFixedItemSetEnumStringsForColumn=494 EsImageDisplayGetImageHeight=495 EsDirectoryEnumerate=496 +EsUniqueIdentifierParse=497 diff --git a/util/build_core.c b/util/build_core.c index 0a8372d..9d9eed4 100644 --- a/util/build_core.c +++ b/util/build_core.c @@ -406,6 +406,91 @@ bool FileExists(const char *path) { #endif } +#ifndef OS_ESSENCE +EsUniqueIdentifier MatchFileContentType(const char *pathBuffer) { + FILE *f = fopen(pathBuffer, "rb"); + assert(f); + uint32_t elfSignature; + fread(&elfSignature, 1, sizeof(elfSignature), f); + bool isELFExecutable = elfSignature == 0x464C457F; + fclose(f); + + EsUniqueIdentifier t = { 0 }; + +#define ENDS_WITH(s) (strlen(pathBuffer) >= strlen(s) && 0 == strcmp(pathBuffer + strlen(pathBuffer) - strlen(s), s)) +#define STARTS_WITH(s) (strlen(pathBuffer) >= strlen(s) && 0 == memcmp(pathBuffer, s, strlen(s))) + if (ENDS_WITH(".bochsrc")) + t = (EsUniqueIdentifier) {{ 0xFF, 0x92, 0xF0, 0x53, 0x28, 0x83, 0xDC, 0xF1, 0xBA, 0xDD, 0xDE, 0x79, 0x92, 0x33, 0x3F, 0x73 }}; + else if (ENDS_WITH(".build_core")) + t = (EsUniqueIdentifier) {{ 0x6F, 0x2A, 0x23, 0x16, 0xAF, 0xA3, 0xD0, 0xFB, 0x95, 0x06, 0x52, 0xB3, 0x67, 0xFB, 0x37, 0xFA }}; + else if (ENDS_WITH(".designer")) + t = (EsUniqueIdentifier) {{ 0x26, 0x4A, 0xE0, 0x6C, 0x0F, 0xE8, 0x2C, 0xE7, 0xF4, 0x6E, 0x4E, 0x76, 0x5B, 0x4A, 0xCF, 0x4F }}; + else if (ENDS_WITH(".uxn")) + t = (EsUniqueIdentifier) {{ 0x76, 0xC2, 0xC1, 0x73, 0xB1, 0x20, 0x3D, 0x42, 0x70, 0xFD, 0xD4, 0xBD, 0x66, 0xE9, 0x2A, 0x39 }}; + else if (ENDS_WITH(".a")) + t = (EsUniqueIdentifier) {{ 0x7D, 0x39, 0xBF, 0x18, 0x9E, 0x07, 0xBC, 0xD3, 0x4F, 0x9F, 0x87, 0xEC, 0x6F, 0x70, 0x65, 0x5D }}; + else if (ENDS_WITH(".la")) + t = (EsUniqueIdentifier) {{ 0x8F, 0x10, 0x4F, 0x33, 0x4A, 0xE5, 0x2D, 0xA7, 0x2A, 0x80, 0x2C, 0x4F, 0xEA, 0xE4, 0x99, 0xB1 }}; + else if (ENDS_WITH(".esx")) + t = (EsUniqueIdentifier) {{ 0xBF, 0x88, 0xE4, 0xDD, 0x71, 0xE8, 0x5F, 0xDE, 0x16, 0xC5, 0xAF, 0x33, 0x41, 0xC7, 0xA2, 0x96 }}; + else if (ENDS_WITH(".o")) + t = (EsUniqueIdentifier) {{ 0xB5, 0x19, 0xF2, 0x0D, 0xAD, 0x4F, 0xD4, 0xC9, 0xA4, 0xBF, 0x97, 0xE4, 0x5E, 0x4C, 0x55, 0x00 }}; + else if (ENDS_WITH(".c")) + t = (EsUniqueIdentifier) {{ 0x36, 0x02, 0xD3, 0xAC, 0x6C, 0xDE, 0x8D, 0x31, 0xFA, 0x70, 0xB2, 0xDA, 0xFA, 0x81, 0x53, 0x69 }}; + else if (ENDS_WITH(".cpp")) + t = (EsUniqueIdentifier) {{ 0x35, 0x84, 0xA6, 0x0E, 0x52, 0x28, 0xBA, 0xAB, 0xCA, 0x74, 0x14, 0xF4, 0xE1, 0x15, 0xCA, 0x5F }}; + else if (ENDS_WITH(".h")) + t = (EsUniqueIdentifier) {{ 0xD1, 0x16, 0xC4, 0xC1, 0x7B, 0xC0, 0xED, 0x4F, 0xCA, 0xAC, 0x18, 0x05, 0x32, 0x1D, 0x56, 0x32 }}; + else if (ENDS_WITH(".html")) + t = (EsUniqueIdentifier) {{ 0x4A, 0x5A, 0x1C, 0xE5, 0xEE, 0x08, 0x6E, 0x04, 0x13, 0x9C, 0x6D, 0x05, 0x48, 0x5C, 0xE5, 0xD2 }}; + else if (ENDS_WITH(".in")) + t = (EsUniqueIdentifier) {{ 0x76, 0x35, 0xF3, 0xF5, 0xC2, 0x69, 0x8A, 0xA4, 0xE4, 0x68, 0x5F, 0xE5, 0x2C, 0x73, 0x10, 0xF9 }}; + else if (ENDS_WITH("/Makefile")) + t = (EsUniqueIdentifier) {{ 0x3A, 0x58, 0x08, 0x24, 0x00, 0x75, 0xB5, 0x8B, 0xA8, 0x39, 0xD8, 0x37, 0x03, 0x6B, 0x20, 0x85 }}; + else if (ENDS_WITH(".ld") || STARTS_WITH("root/Applications/POSIX/x86_64-essence/lib/ldscripts/")) + t = (EsUniqueIdentifier) {{ 0x23, 0x56, 0xBC, 0xD4, 0x5A, 0xD6, 0x56, 0x08, 0x06, 0x61, 0x7C, 0x33, 0x38, 0x66, 0xD5, 0x1F }}; + else if (ENDS_WITH(".md")) + t = (EsUniqueIdentifier) {{ 0xB1, 0xC3, 0x9E, 0x56, 0xC9, 0xB0, 0x85, 0xD6, 0xAF, 0x98, 0x12, 0x1C, 0x2E, 0x29, 0x8D, 0x24 }}; + else if (ENDS_WITH(".pc")) + t = (EsUniqueIdentifier) {{ 0x74, 0x72, 0x01, 0x17, 0x97, 0x4B, 0x6B, 0x4B, 0x74, 0xCD, 0x18, 0x7C, 0x8F, 0x3C, 0x58, 0xBC }}; + else if (ENDS_WITH(".py")) + t = (EsUniqueIdentifier) {{ 0x77, 0x19, 0xBA, 0x87, 0x2E, 0xDB, 0x51, 0xA4, 0x71, 0x5D, 0xFF, 0x8D, 0x39, 0x86, 0x96, 0xF0 }}; + else if (ENDS_WITH(".sh")) + t = (EsUniqueIdentifier) {{ 0x66, 0x42, 0x88, 0xF9, 0xD1, 0x68, 0x47, 0xBF, 0x7F, 0x48, 0x82, 0x77, 0x2B, 0xE3, 0x39, 0xBA }}; + else if (ENDS_WITH(".txt") || ENDS_WITH("/README") || ENDS_WITH("/LICENSE") || ENDS_WITH("/COPYING") || ENDS_WITH("/AUTHORS") || ENDS_WITH("/TODO") + || ENDS_WITH("/BUGS") || ENDS_WITH("/NEWS") || ENDS_WITH("/CHANGES") || ENDS_WITH("/ReadMe") || ENDS_WITH(".LESSER")) + t = (EsUniqueIdentifier) {{ 0x25, 0x65, 0x4C, 0x89, 0xE7, 0x29, 0xEA, 0x9E, 0xB5, 0xBE, 0xB5, 0xCA, 0xA7, 0x60, 0xBD, 0x3D }}; + else if (ENDS_WITH(".1") || ENDS_WITH(".7") || ENDS_WITH(".info") || ENDS_WITH(".info-1") || ENDS_WITH(".info-2")) + t = (EsUniqueIdentifier) {{ 0xDB, 0x72, 0xDD, 0x5D, 0x92, 0x54, 0x09, 0x1A, 0xC4, 0x16, 0xDD, 0x89, 0x0B, 0x13, 0x12, 0x1F }}; + else if (ENDS_WITH(".conf") || ENDS_WITH(".bfd")) + t = (EsUniqueIdentifier) {{ 0x18, 0x8D, 0xD3, 0xAC, 0x35, 0xF5, 0xD3, 0x01, 0x8C, 0x66, 0xFD, 0x12, 0xE1, 0xED, 0xE2, 0x6F }}; + else if (ENDS_WITH(".ini")) + t = (EsUniqueIdentifier) {{ 0x81, 0x72, 0x43, 0x29, 0xE5, 0x37, 0x89, 0x51, 0x8E, 0x22, 0xA0, 0x67, 0xA5, 0xB9, 0x42, 0x2C }}; + else if (ENDS_WITH(".gz")) + t = (EsUniqueIdentifier) {{ 0x7D, 0x93, 0x66, 0x74, 0x80, 0x0B, 0x64, 0x9F, 0x16, 0x5D, 0x44, 0xA5, 0x45, 0xFF, 0x80, 0xBF }}; + else if (ENDS_WITH(".img")) + t = (EsUniqueIdentifier) {{ 0xE1, 0x61, 0x4D, 0x0D, 0x32, 0xEC, 0xE0, 0x0F, 0x36, 0x7F, 0xE9, 0x7B, 0x3F, 0x16, 0x43, 0x02 }}; + else if (ENDS_WITH(".jpg")) + t = (EsUniqueIdentifier) {{ 0xD8, 0xC2, 0x13, 0xB0, 0x53, 0x64, 0x82, 0x11, 0x48, 0x7B, 0x5B, 0x64, 0x0F, 0x92, 0xB9, 0x38 }}; + else if (ENDS_WITH(".mkv")) + t = (EsUniqueIdentifier) {{ 0x0D, 0xCA, 0x26, 0x92, 0x22, 0x44, 0xEB, 0x6B, 0xC6, 0xE9, 0x6C, 0x8E, 0xFC, 0x28, 0xD2, 0x8E }}; + else if (ENDS_WITH(".obj")) + t = (EsUniqueIdentifier) {{ 0xF7, 0x81, 0xE9, 0x21, 0xDF, 0x89, 0x77, 0xD0, 0xC6, 0x97, 0xA8, 0x63, 0xF1, 0xF3, 0x4F, 0x63 }}; + else if (ENDS_WITH(".png")) + t = (EsUniqueIdentifier) {{ 0x59, 0x21, 0x05, 0x4D, 0x34, 0x40, 0xAB, 0x61, 0xEC, 0xF9, 0x7D, 0x5C, 0x6E, 0x04, 0x96, 0xAE }}; + else if (ENDS_WITH(".ttf")) + t = (EsUniqueIdentifier) {{ 0xDA, 0xBF, 0xEC, 0x06, 0x31, 0x8A, 0x67, 0xB6, 0xE3, 0x95, 0xBC, 0xD1, 0x92, 0xD2, 0x9A, 0x56 }}; + else if (STARTS_WITH("root/Applications/POSIX/share/bochs/")) + t = (EsUniqueIdentifier) {{ 0xB5, 0x32, 0x22, 0x14, 0x01, 0xCB, 0xD0, 0x1D, 0xA2, 0x55, 0x08, 0xC7, 0x0D, 0x46, 0x86, 0x53 }}; + else if (isELFExecutable) + t = (EsUniqueIdentifier) {{ 0xAB, 0xDE, 0x98, 0xB5, 0x56, 0x2C, 0x04, 0xDF, 0x1E, 0x43, 0xC8, 0x10, 0x24, 0x63, 0xDB, 0xB8 }}; +#undef ENDS_WITH +#undef STARTS_WITH + + return t; +} +#endif + void CreateImportNode(const char *path, ImportNode *node) { char pathBuffer[256]; @@ -429,6 +514,7 @@ void CreateImportNode(const char *path, ImportNode *node) { CreateImportNode(pathBuffer, &child); } else { child.isFile = true; + child.contentType = children[i].contentType; } child.name = EsStringAllocateAndFormat(NULL, "%s", children[i].nameBytes, children[i].name); @@ -464,6 +550,7 @@ void CreateImportNode(const char *path, ImportNode *node) { continue; } else { child.isFile = true; + child.contentType = MatchFileContentType(pathBuffer); } child.name = strdup(dir->d_name); @@ -549,20 +636,6 @@ bool MakeBundle(const char *outputFile, BundleInput *inputFiles, size_t inputFil ////////////////////////////////// -typedef struct FileType { - const char *extension; - const char *name; - const char *icon; - int id, openID; - bool hasThumbnailGenerator; -} FileType; - -typedef struct Handler { - const char *extension; - const char *action; - int fileTypeID; -} Handler; - typedef struct DependencyFile { char path[256]; const char *name; @@ -573,11 +646,9 @@ typedef struct Application { const char *name; EsINIState *properties; + EsINIState *fileTypeLines; int id; - FileType *fileTypes; - Handler *handlers; - bool install, builtin, withCStdLib; const char **sources; @@ -794,8 +865,6 @@ void ParseApplicationManifest(const char *manifestPath) { application.manifestPath = manifestPath; application.compileFlags = ""; application.linkFlags = ""; - Handler *handler = NULL; - FileType *fileType = NULL; while (EsINIParse(&s)) { EsINIZeroTerminate(&s); @@ -815,27 +884,8 @@ void ParseApplicationManifest(const char *manifestPath) { else INI_READ_BOOL(disabled, disabled); else INI_READ_BOOL(needs_native_toolchain, needsNativeToolchain); else if (s.keyBytes && s.valueBytes) arrput(application.properties, s); - } else if (0 == strcmp(s.section, "handler")) { - if (!s.keyBytes) { - Handler _handler = {}; - arrput(application.handlers, _handler); - handler = &arrlast(application.handlers); - } - - INI_READ_STRING_PTR(extension, handler->extension); - INI_READ_STRING_PTR(action, handler->action); } else if (0 == strcmp(s.section, "file_type")) { - if (!s.keyBytes) { - FileType _fileType = {}; - _fileType.id = nextID++; - arrput(application.fileTypes, _fileType); - fileType = &arrlast(application.fileTypes); - } - - INI_READ_STRING_PTR(extension, fileType->extension); - INI_READ_STRING_PTR(name, fileType->name); - INI_READ_STRING_PTR(icon, fileType->icon); - INI_READ_BOOL(has_thumbnail_generator, fileType->hasThumbnailGenerator); + arrput(application.fileTypeLines, s); } else if (0 == strcmp(s.section, "embed") && s.key[0] != ';' && s.value[0]) { BundleInput input = { 0 }; input.path = s.value; @@ -884,38 +934,6 @@ void OutputSystemConfiguration() { FileWrite(file, EsINIFormat(generalOptions + i, buffer, sizeof(buffer)), buffer); } - for (uintptr_t i = 0; i < arrlenu(applications); i++) { - if (!applications[i].install) { - continue; - } - - for (uintptr_t j = 0; j < arrlenu(applications[i].handlers); j++) { - Handler *handler = applications[i].handlers + j; - int handlerID = applications[i].id; - - for (uintptr_t i = 0; i < arrlenu(applications); i++) { - for (uintptr_t j = 0; j < arrlenu(applications[i].fileTypes); j++) { - FileType *fileType = applications[i].fileTypes + j; - - if (0 == strcmp(handler->extension, fileType->extension)) { - handler->fileTypeID = fileType->id; - - if (0 == strcmp(handler->action, "open")) { - fileType->openID = handlerID; - } else { - Log("Warning: unrecognised handler action '%s'.\n", handler->action); - } - } - } - } - - if (!handler->fileTypeID) { - Log("Warning: could not find a file_type entry for handler with extension '%s' in application '%s'.\n", - handler->extension, applications[i].name); - } - } - } - for (uintptr_t i = 0; i < arrlenu(applications); i++) { if (!applications[i].install) { continue; @@ -931,21 +949,13 @@ void OutputSystemConfiguration() { FilePrintFormat(file, "%s=%s\n", applications[i].properties[j].key, applications[i].properties[j].value); } - for (uintptr_t j = 0; j < arrlenu(applications[i].fileTypes); j++) { - FilePrintFormat(file, "\n[file_type]\n"); - FilePrintFormat(file, "id=%d\n", applications[i].fileTypes[j].id); - FilePrintFormat(file, "extension=%s\n", applications[i].fileTypes[j].extension); - FilePrintFormat(file, "name=%s\n", applications[i].fileTypes[j].name); - FilePrintFormat(file, "icon=%s\n", applications[i].fileTypes[j].icon); - FilePrintFormat(file, "open=%d\n", applications[i].fileTypes[j].openID); - FilePrintFormat(file, "has_thumbnail_generator=%d\n", applications[i].fileTypes[j].hasThumbnailGenerator); - } + for (uintptr_t j = 0; j < arrlenu(applications[i].fileTypeLines); j++) { + char buffer[4096]; + FileWrite(file, EsINIFormat(applications[i].fileTypeLines + j, buffer, sizeof(buffer)), buffer); - for (uintptr_t j = 0; j < arrlenu(applications[i].handlers); j++) { - FilePrintFormat(file, "\n[handler]\n"); - FilePrintFormat(file, "action=%s\n", applications[i].handlers[j].action); - FilePrintFormat(file, "application=%d\n", applications[i].id); - FilePrintFormat(file, "file_type=%d\n", applications[i].handlers[j].fileTypeID); + if (!applications[i].fileTypeLines[j].keyBytes) { + FilePrintFormat(file, "application=%d\n", applications[i].id); + } } } @@ -1752,8 +1762,7 @@ int main(int argc, char **argv) { arrfree(applications[i].sources); arrfree(applications[i].dependencyFiles); arrfree(applications[i].bundleInputFiles); - arrfree(applications[i].fileTypes); - arrfree(applications[i].handlers); + arrfree(applications[i].fileTypeLines); } arrfree(applicationManifests); diff --git a/util/build_core.ini b/util/build_core.ini index 232b032..8b020a8 100644 --- a/util/build_core.ini +++ b/util/build_core.ini @@ -9,10 +9,9 @@ permission_run_temporary_application=1 source=util/build_core.c [file_type] -extension=build_core +match=ext:build_core name=Build config icon=icon_text_x_makefile - -[handler] -extension=build_core -action=open +uuid=6F-2A-23-16-AF-A3-D0-FB-95-06-52-B3-67-FB-37-FA +actions=open +textual=1 diff --git a/util/designer2.cpp b/util/designer2.cpp index a8dcfe8..94a98ec 100644 --- a/util/designer2.cpp +++ b/util/designer2.cpp @@ -3758,6 +3758,9 @@ int _UIInstanceCallback(EsInstance *instance, EsMessage *message) { if (message->type == ES_MSG_INSTANCE_SAVE) { fileStore = message->instanceSave.file; DocumentSave(nullptr); + EsUniqueIdentifier type = (EsUniqueIdentifier) + {{ 0x26, 0x4A, 0xE0, 0x6C, 0x0F, 0xE8, 0x2C, 0xE7, 0xF4, 0x6E, 0x4E, 0x76, 0x5B, 0x4A, 0xCF, 0x4F }}; + EsFileStoreSetContentType(fileStore, type); EsInstanceSaveComplete(instance, fileStore, true); fileStore = nullptr; } else if (message->type == ES_MSG_INSTANCE_OPEN) { diff --git a/util/designer2.ini b/util/designer2.ini index 95c21df..810a43a 100644 --- a/util/designer2.ini +++ b/util/designer2.ini @@ -7,10 +7,8 @@ source=util/designer2.cpp compile_flags=-D UI_ESSENCE -Wno-unused-parameter [file_type] -extension=designer +match=ext:designer name=Designer file icon=icon_text_css - -[handler] -extension=designer -action=open +uuid=26-4A-E0-6C-0F-E8-2C-E7-F4-6E-4E-76-5B-4A-CF-4F +actions=open