mirror of https://gitlab.com/nakst/essence
				
				
				
			general: use contentType field for file types
This commit is contained in:
		
							parent
							
								
									4f9b5194cd
								
							
						
					
					
						commit
						910bc1bbb0
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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++);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<FileTypeApplicationEntry> 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<FileType> knownFileTypes; 
 | 
			
		||||
HashStore<char, uintptr_t /* index into knownFileTypes */> knownFileTypesByExtension;
 | 
			
		||||
HashStore<char, EsUniqueIdentifier> 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];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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++) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 }
 | 
			
		||||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								start.sh
								
								
								
								
							
							
						
						
									
										2
									
								
								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 $@`"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue