- Added oc_file_open_with_request() to request new file capability for a given path. This presents user with a message box allowing them to accept or deny the request.

- Added oc_file_open_with_dialog() to ask user to grant new file capabilities through an open/save dialog.
This commit is contained in:
Martin Fouilleul 2023-08-30 16:45:56 +02:00
parent 0db1589dc6
commit 0205d90941
42 changed files with 1495 additions and 511 deletions

View File

@ -215,3 +215,12 @@ void oc_window_set_frame_size(oc_window window, oc_vec2 size)
frame.h = size.y; frame.h = size.y;
oc_window_set_frame_rect(window, frame); oc_window_set_frame_rect(window, frame);
} }
//---------------------------------------------------------------
// dialogs
//---------------------------------------------------------------
oc_file_dialog_result oc_file_dialog(oc_arena* arena, oc_file_dialog_desc* desc)
{
return (oc_file_dialog_for_table(arena, desc, oc_file_table_get_global()));
}

View File

@ -389,6 +389,60 @@ ORCA_API oc_str8 oc_save_dialog(oc_arena* arena,
oc_str8 defaultPath, oc_str8 defaultPath,
oc_str8_list filters); oc_str8_list filters);
#endif // !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA)
#include "platform/platform_io.h"
typedef enum
{
OC_FILE_DIALOG_SAVE,
OC_FILE_DIALOG_OPEN,
} oc_file_dialog_kind;
typedef u32 oc_file_dialog_flags;
enum _oc_file_dialog_flags
{
OC_FILE_DIALOG_FILES = 1,
OC_FILE_DIALOG_DIRECTORIES = 1 << 1,
OC_FILE_DIALOG_MULTIPLE = 1 << 2,
OC_FILE_DIALOG_CREATE_DIRECTORIES = 1 << 3,
};
typedef struct oc_file_dialog_desc
{
oc_file_dialog_kind kind;
oc_file_dialog_flags flags;
oc_str8 title;
oc_str8 okLabel;
oc_file startAt;
oc_str8 startPath;
oc_str8_list filters;
//... later customization options with checkboxes / radiobuttons
} oc_file_dialog_desc;
typedef enum
{
OC_FILE_DIALOG_CANCEL = 0,
OC_FILE_DIALOG_OK,
} oc_file_dialog_button;
typedef struct oc_file_dialog_result
{
oc_file_dialog_button button;
oc_str8 path;
oc_str8_list selection;
} oc_file_dialog_result;
#if !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA)
ORCA_API oc_file_dialog_result oc_file_dialog(oc_arena* arena, oc_file_dialog_desc* desc);
typedef struct oc_file_table oc_file_table;
ORCA_API oc_file_dialog_result oc_file_dialog_for_table(oc_arena* arena, oc_file_dialog_desc* desc, oc_file_table* table);
ORCA_API int oc_alert_popup(oc_str8 title, ORCA_API int oc_alert_popup(oc_str8 title,
oc_str8 message, oc_str8 message,
oc_str8_list options); oc_str8_list options);
@ -404,9 +458,7 @@ ORCA_API int oc_directory_create(oc_str8 path);
#else #else
void ORCA_IMPORT(oc_request_quit)(void); void ORCA_IMPORT(oc_request_quit)(void);
void ORCA_IMPORT(oc_runtime_window_set_title)(oc_str8 title); void ORCA_IMPORT(oc_runtime_window_set_title)(oc_str8 title);
void ORCA_IMPORT(oc_runtime_window_set_size)(oc_vec2 size); void ORCA_IMPORT(oc_runtime_window_set_size)(oc_vec2 size);
#endif // !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA) #endif // !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA)

View File

@ -12,6 +12,7 @@
#include "app.h" #include "app.h"
#include "platform/platform.h" #include "platform/platform.h"
#include "platform/platform_io_internal.h"
#include "util/ringbuffer.h" #include "util/ringbuffer.h"
#if OC_PLATFORM_WINDOWS #if OC_PLATFORM_WINDOWS

View File

@ -2,3 +2,8 @@
//This is used to pass raw events from the runtime //This is used to pass raw events from the runtime
ORCA_EXPORT oc_event oc_rawEvent; ORCA_EXPORT oc_event oc_rawEvent;
ORCA_EXPORT void* oc_arena_push_stub(oc_arena* arena, u64 size)
{
return (oc_arena_push(arena, size));
}

View File

@ -19,7 +19,7 @@
#include "platform_clock.h" #include "platform_clock.h"
#include "platform_debug.h" #include "platform_debug.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "platform/platform_path.h"
#include "app.c" #include "app.c"
//-------------------------------------------------------------------- //--------------------------------------------------------------------
@ -2133,73 +2133,87 @@ oc_str8 oc_open_dialog(oc_arena* arena,
oc_str8_list filters, oc_str8_list filters,
bool directory) bool directory)
{ {
@autoreleasepool __block oc_str8 path = { 0 };
dispatch_block_t block = ^{
@autoreleasepool
{
NSWindow* keyWindow = [NSApp keyWindow];
NSOpenPanel* dialog = [NSOpenPanel openPanel];
NSString* nsTitle = [[NSString alloc] initWithBytes:title.ptr length:title.len encoding:NSUTF8StringEncoding];
//NOTE: title is not displayed since OS X 10.11, now use setMessage instead.
// see https://stackoverflow.com/questions/36879212/title-bar-missing-in-nsopenpanel
[dialog setMessage:nsTitle];
[dialog setLevel:NSModalPanelWindowLevel];
if(filters.eltCount)
{
NSMutableArray* fileTypesArray = [NSMutableArray array];
oc_list_for((oc_list*)&filters.list, elt, oc_str8_elt, listElt)
{
oc_str8 string = elt->string;
NSString* filter = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
[fileTypesArray addObject:filter];
}
[dialog setAllowedFileTypes:fileTypesArray];
}
// Enable options in the dialog.
if(directory)
{
[dialog setCanChooseDirectories:YES];
}
else
{
[dialog setCanChooseFiles:YES];
}
[dialog setAllowsMultipleSelection:FALSE];
NSString* nsPath = 0;
;
if(defaultPath.len)
{
nsPath = [[NSString alloc] initWithBytes:defaultPath.ptr length:defaultPath.len encoding:NSUTF8StringEncoding];
}
else
{
nsPath = [NSString stringWithUTF8String:"~"];
}
nsPath = [nsPath stringByExpandingTildeInPath];
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
// Display the dialog box. If the OK pressed,
// process the files.
if([dialog runModal] == NSModalResponseOK)
{
// Gets list of all files selected
NSArray* files = [dialog URLs];
//TODO: Loop through the files and process them.
const char* result = [[[files objectAtIndex:0] path] UTF8String];
path = oc_str8_push_cstring(arena, result);
}
[keyWindow makeKeyWindow];
}
};
if([NSThread isMainThread])
{ {
NSWindow* keyWindow = [NSApp keyWindow]; block();
NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setLevel:CGShieldingWindowLevel()];
if(filters.eltCount)
{
NSMutableArray* fileTypesArray = [NSMutableArray array];
oc_list_for(&filters.list, elt, oc_str8_elt, listElt)
{
oc_str8 string = elt->string;
NSString* filter = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
[fileTypesArray addObject:filter];
}
[dialog setAllowedFileTypes:fileTypesArray];
}
// Enable options in the dialog.
if(directory)
{
[dialog setCanChooseDirectories:YES];
}
else
{
[dialog setCanChooseFiles:YES];
}
[dialog setAllowsMultipleSelection:FALSE];
NSString* nsPath = 0;
;
if(defaultPath.len)
{
nsPath = [[NSString alloc] initWithBytes:defaultPath.ptr length:defaultPath.len encoding:NSUTF8StringEncoding];
}
else
{
nsPath = [NSString stringWithUTF8String:"~"];
}
nsPath = [nsPath stringByExpandingTildeInPath];
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
// Display the dialog box. If the OK pressed,
// process the files.
if([dialog runModal] == NSModalResponseOK)
{
// Gets list of all files selected
NSArray* files = [dialog URLs];
//TODO: Loop through the files and process them.
const char* result = [[[files objectAtIndex:0] path] UTF8String];
oc_str8 path = oc_str8_push_cstring(arena, result);
[keyWindow makeKeyWindow];
return (path);
}
else
{
[keyWindow makeKeyWindow];
return ((oc_str8){ 0, 0 });
}
} }
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
return (path);
} }
oc_str8 oc_save_dialog(oc_arena* arena, oc_str8 oc_save_dialog(oc_arena* arena,
@ -2207,61 +2221,221 @@ oc_str8 oc_save_dialog(oc_arena* arena,
oc_str8 defaultPath, oc_str8 defaultPath,
oc_str8_list filters) oc_str8_list filters)
{ {
@autoreleasepool __block oc_str8 path = { 0 };
dispatch_block_t block = ^{
@autoreleasepool
{
NSWindow* keyWindow = [NSApp keyWindow];
NSSavePanel* dialog = [NSSavePanel savePanel];
[dialog setLevel:CGShieldingWindowLevel()];
if(filters.eltCount)
{
NSMutableArray* fileTypesArray = [NSMutableArray array];
oc_list_for((oc_list*)&filters.list, elt, oc_str8_elt, listElt)
{
oc_str8 string = elt->string;
NSString* filter = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
[fileTypesArray addObject:filter];
}
[dialog setAllowedFileTypes:fileTypesArray];
}
NSString* nsPath = 0;
;
if(defaultPath.len)
{
nsPath = [[NSString alloc] initWithBytes:defaultPath.ptr length:defaultPath.len encoding:NSUTF8StringEncoding];
}
else
{
nsPath = [NSString stringWithUTF8String:"~"];
}
nsPath = [nsPath stringByExpandingTildeInPath];
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
// Display the dialog box. If the OK pressed,
// process the files.
if([dialog runModal] == NSModalResponseOK)
{
// Gets list of all files selected
NSURL* files = [dialog URL];
// Loop through the files and process them.
const char* result = [[files path] UTF8String];
path = oc_str8_push_cstring(arena, result);
}
[keyWindow makeKeyWindow];
}
};
if([NSThread isMainThread])
{ {
NSWindow* keyWindow = [NSApp keyWindow]; block();
NSSavePanel* dialog = [NSSavePanel savePanel];
[dialog setLevel:CGShieldingWindowLevel()];
if(filters.eltCount)
{
NSMutableArray* fileTypesArray = [NSMutableArray array];
oc_list_for(&filters.list, elt, oc_str8_elt, listElt)
{
oc_str8 string = elt->string;
NSString* filter = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
[fileTypesArray addObject:filter];
}
[dialog setAllowedFileTypes:fileTypesArray];
}
NSString* nsPath = 0;
;
if(defaultPath.len)
{
nsPath = [[NSString alloc] initWithBytes:defaultPath.ptr length:defaultPath.len encoding:NSUTF8StringEncoding];
}
else
{
nsPath = [NSString stringWithUTF8String:"~"];
}
nsPath = [nsPath stringByExpandingTildeInPath];
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
// Display the dialog box. If the OK pressed,
// process the files.
if([dialog runModal] == NSModalResponseOK)
{
// Gets list of all files selected
NSURL* files = [dialog URL];
// Loop through the files and process them.
const char* result = [[files path] UTF8String];
oc_str8 path = oc_str8_push_cstring(arena, result);
[keyWindow makeKeyWindow];
return (path);
}
else
{
[keyWindow makeKeyWindow];
return ((oc_str8){ 0, 0 });
}
} }
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
return (path);
}
ORCA_API oc_file_dialog_result oc_file_dialog_for_table(oc_arena* arena, oc_file_dialog_desc* desc, oc_file_table* table)
{
__block oc_file_dialog_result result = { 0 };
dispatch_block_t block = ^{
@autoreleasepool
{
oc_arena_scope scratch = oc_scratch_begin_next(arena);
NSWindow* keyWindow = [NSApp keyWindow];
NSSavePanel* dialog = 0;
if(desc->kind == OC_FILE_DIALOG_OPEN)
{
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
dialog = (NSSavePanel*)openPanel;
openPanel.canChooseFiles = (desc->flags & OC_FILE_DIALOG_FILES) ? YES : NO;
openPanel.canChooseDirectories = (desc->flags & OC_FILE_DIALOG_DIRECTORIES) ? YES : NO;
openPanel.allowsMultipleSelection = (desc->flags & OC_FILE_DIALOG_MULTIPLE) ? YES : NO;
}
else
{
dialog = [NSSavePanel savePanel];
dialog.canCreateDirectories = (desc->flags & OC_FILE_DIALOG_CREATE_DIRECTORIES) ? YES : NO;
}
//NOTE: set title. "title" property is not displayed since OS X 10.11, now use setMessage instead.
// see https://stackoverflow.com/questions/36879212/title-bar-missing-in-nsopenpanel
NSString* nsTitle = [[NSString alloc] initWithBytes:desc->title.ptr
length:desc->title.len
encoding:NSUTF8StringEncoding];
[dialog setMessage:nsTitle];
//NOTE: set ok button
if(desc->okLabel.len)
{
NSString* label = [[NSString alloc] initWithBytes:desc->okLabel.ptr
length:desc->okLabel.len
encoding:NSUTF8StringEncoding];
[dialog setPrompt:label];
}
//NOTE: set starting path
oc_str8 startPath = { 0 };
{
oc_str8_list list = { 0 };
if(!oc_file_is_nil(desc->startAt))
{
oc_file_slot* slot = oc_file_slot_from_handle(table, desc->startAt);
if(slot)
{
char path[PATH_MAX];
if(fcntl(slot->fd, F_GETPATH, path) != -1)
{
oc_str8 string = oc_str8_push_cstring(scratch.arena, path);
oc_str8_list_push(scratch.arena, &list, string);
}
}
}
if(desc->startPath.len)
{
oc_str8_list_push(scratch.arena, &list, desc->startPath);
}
startPath = oc_path_join(scratch.arena, list);
}
NSString* nsPath = 0;
if(startPath.len)
{
nsPath = [[NSString alloc] initWithBytes:startPath.ptr
length:startPath.len
encoding:NSUTF8StringEncoding];
}
else
{
nsPath = [NSString stringWithUTF8String:"~"];
}
nsPath = [nsPath stringByExpandingTildeInPath];
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
//NOTE: set filters
if(desc->filters.eltCount)
{
NSMutableArray* fileTypesArray = [NSMutableArray array];
oc_list_for((oc_list*)&desc->filters.list, elt, oc_str8_elt, listElt)
{
oc_str8 string = elt->string;
NSString* filter = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
[fileTypesArray addObject:filter];
}
[dialog setAllowedFileTypes:fileTypesArray];
}
// Display the dialog box. If the OK pressed,
// process the files.
[dialog validateVisibleColumns];
[dialog setLevel:NSModalPanelWindowLevel];
if([dialog runModal] == NSModalResponseOK)
{
if(desc->kind == OC_FILE_DIALOG_OPEN && (desc->flags & OC_FILE_DIALOG_MULTIPLE))
{
// Gets list of all files selected
NSArray* files = [((NSOpenPanel*)dialog) URLs];
const char* path = [[[files objectAtIndex:0] path] UTF8String];
result.path = oc_str8_push_cstring(arena, path);
for(int i = 0; i < [files count]; i++)
{
const char* path = [[[files objectAtIndex:i] path] UTF8String];
oc_str8 string = oc_str8_push_cstring(arena, path);
oc_str8_list_push(arena, &result.selection, string);
}
}
else
{
const char* path = [[[dialog URL] path] UTF8String];
result.path = oc_str8_push_cstring(arena, path);
oc_str8_list_push(arena, &result.selection, result.path);
}
result.button = OC_FILE_DIALOG_OK;
}
else
{
result.button = OC_FILE_DIALOG_CANCEL;
}
[keyWindow makeKeyWindow];
oc_scratch_end(scratch);
}
};
if([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
return (result);
} }
int oc_alert_popup(oc_str8 title, int oc_alert_popup(oc_str8 title,

View File

@ -1189,16 +1189,16 @@ void oc_win32_surface_set_hidden(oc_surface_data* surface, bool hidden)
void oc_win32_surface_bring_to_front(oc_surface_data* surface) void oc_win32_surface_bring_to_front(oc_surface_data* surface)
{ {
oc_list_remove(&surface->layer.parent->win32.layers, &surface->layer.listElt); oc_list_remove(&surface->layer.parent->win32.layers, &surface->layer.listElt);
oc_list_push(&surface->layer.parent->win32.layers, &surface->layer.listElt); oc_list_push(&surface->layer.parent->win32.layers, &surface->layer.listElt);
oc_win32_update_child_layers_zorder(surface->layer.parent); oc_win32_update_child_layers_zorder(surface->layer.parent);
} }
void oc_win32_surface_send_to_back(oc_surface_data* surface) void oc_win32_surface_send_to_back(oc_surface_data* surface)
{ {
oc_list_remove(&surface->layer.parent->win32.layers, &surface->layer.listElt); oc_list_remove(&surface->layer.parent->win32.layers, &surface->layer.listElt);
oc_list_push_back(&surface->layer.parent->win32.layers, &surface->layer.listElt); oc_list_push_back(&surface->layer.parent->win32.layers, &surface->layer.listElt);
oc_win32_update_child_layers_zorder(surface->layer.parent); oc_win32_update_child_layers_zorder(surface->layer.parent);
} }
void* oc_win32_surface_native_layer(oc_surface_data* surface) void* oc_win32_surface_native_layer(oc_surface_data* surface)
@ -1565,6 +1565,207 @@ oc_str8 oc_save_dialog(oc_arena* arena,
return (res); return (res);
} }
#include "platform/platform_io_internal.h"
oc_str16 win32_path_from_handle_null_terminated(oc_arena* arena, HANDLE handle); // defined in win32_io.c
oc_file_dialog_result oc_file_dialog_for_table(oc_arena* arena, oc_file_dialog_desc* desc, oc_file_table* table)
{
oc_arena_scope scratch = oc_scratch_begin_next(arena);
oc_file_dialog_result result = { 0 };
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if(SUCCEEDED(hr))
{
IFileDialog* dialog = 0;
if(desc->kind == OC_FILE_DIALOG_OPEN)
{
hr = CoCreateInstance(&CLSID_FileOpenDialog, NULL, CLSCTX_ALL, &IID_IFileOpenDialog, (void**)&dialog);
if(SUCCEEDED(hr))
{
FILEOPENDIALOGOPTIONS opt;
dialog->lpVtbl->GetOptions(dialog, &opt);
//NOTE: OC_FILE_DIALOG_FILES is always implied, since IFileDialog offers no way to pick _only_ folders
if(desc->flags & OC_FILE_DIALOG_DIRECTORIES)
{
opt |= FOS_PICKFOLDERS;
}
else
{
opt &= ~FOS_PICKFOLDERS;
}
if(desc->flags & OC_FILE_DIALOG_MULTIPLE)
{
opt |= FOS_ALLOWMULTISELECT;
}
else
{
opt &= ~FOS_ALLOWMULTISELECT;
}
dialog->lpVtbl->SetOptions(dialog, opt);
}
}
else
{
hr = CoCreateInstance(&CLSID_FileSaveDialog, NULL, CLSCTX_ALL, &IID_IFileSaveDialog, (void**)&dialog);
//NOTE: OC_FILE_DIALOG_CREATE_DIRECTORIES is implied, IFileSaveDialog offers no way of disabling it...
}
if(SUCCEEDED(hr))
{
//NOTE set title
if(desc->title.len)
{
oc_str16 titleWide = oc_win32_utf8_to_wide(scratch.arena, desc->title);
dialog->lpVtbl->SetTitle(dialog, (LPCWSTR)titleWide.ptr);
}
//NOTE set ok button
if(desc->okLabel.len)
{
oc_str16 okLabelWide = oc_win32_utf8_to_wide(scratch.arena, desc->okLabel);
dialog->lpVtbl->SetOkButtonLabel(dialog, (LPCWSTR)okLabelWide.ptr);
}
//NOTE: set starting path
oc_str8 startPath = { 0 };
{
oc_str8_list list = { 0 };
if(!oc_file_is_nil(desc->startAt))
{
oc_file_slot* slot = oc_file_slot_from_handle(table, desc->startAt);
if(slot)
{
oc_str16 pathWide = win32_path_from_handle_null_terminated(scratch.arena, slot->fd);
oc_str8 path = oc_win32_wide_to_utf8(scratch.arena, pathWide);
//NOTE: remove potential \\?\ prefix which doesn't work with SHCreateItemFromParsingName()
if(!oc_str8_cmp(oc_str8_slice(path, 0, 4), OC_STR8("\\\\?\\")))
{
path = oc_str8_slice(path, 4, path.len);
}
oc_str8_list_push(scratch.arena, &list, path);
}
}
if(desc->startPath.len)
{
oc_str8_list_push(scratch.arena, &list, desc->startPath);
}
startPath = oc_path_join(scratch.arena, list);
}
if(startPath.len)
{
oc_str16 pathWide = oc_win32_utf8_to_wide(scratch.arena, startPath);
IShellItem* item = 0;
hr = SHCreateItemFromParsingName((LPCWSTR)pathWide.ptr, NULL, &IID_IShellItem, (void**)&item);
if(SUCCEEDED(hr))
{
hr = dialog->lpVtbl->SetFolder(dialog, item);
item->lpVtbl->Release(item);
}
}
//NOTE: set filters
if(desc->filters.eltCount)
{
COMDLG_FILTERSPEC* filterSpecs = oc_arena_push_array(scratch.arena, COMDLG_FILTERSPEC, desc->filters.eltCount);
int i = 0;
oc_list_for(&desc->filters.list, elt, oc_str8_elt, listElt)
{
oc_str8_list list = { 0 };
oc_str8_list_push(scratch.arena, &list, OC_STR8("*."));
oc_str8_list_push(scratch.arena, &list, elt->string);
oc_str8 filter = oc_str8_list_join(scratch.arena, list);
int filterWideSize = 1 + MultiByteToWideChar(CP_UTF8, 0, filter.ptr, filter.len, NULL, 0);
filterSpecs[i].pszSpec = oc_arena_push_array(scratch.arena, wchar_t, filterWideSize);
MultiByteToWideChar(CP_UTF8, 0, filter.ptr, filter.len, (LPWSTR)filterSpecs[i].pszSpec, filterWideSize);
((LPWSTR)(filterSpecs[i].pszSpec))[filterWideSize - 1] = 0;
filterSpecs[i].pszName = filterSpecs[i].pszSpec;
i++;
}
hr = dialog->lpVtbl->SetFileTypes(dialog, i, filterSpecs);
}
hr = dialog->lpVtbl->Show(dialog, NULL);
if(SUCCEEDED(hr))
{
if(desc->kind == OC_FILE_DIALOG_OPEN && (desc->flags & OC_FILE_DIALOG_MULTIPLE))
{
IShellItemArray* array = 0;
hr = ((IFileOpenDialog*)dialog)->lpVtbl->GetResults((IFileOpenDialog*)dialog, &array);
if(SUCCEEDED(hr))
{
int count = 0;
array->lpVtbl->GetCount(array, &count);
for(int i = 0; i < count; i++)
{
IShellItem* item = 0;
hr = array->lpVtbl->GetItemAt(array, i, &item);
if(SUCCEEDED(hr))
{
PWSTR pathWCStr = 0;
hr = item->lpVtbl->GetDisplayName(item, SIGDN_FILESYSPATH, &pathWCStr);
if(SUCCEEDED(hr))
{
oc_str16 pathWide = oc_str16_from_buffer(lstrlenW(pathWCStr), pathWCStr);
oc_str8 path = oc_win32_wide_to_utf8(arena, pathWide);
oc_str8_list_push(arena, &result.selection, path);
if(i == 0)
{
result.path = path;
}
CoTaskMemFree(pathWCStr);
}
item->lpVtbl->Release(item);
}
}
result.button = OC_FILE_DIALOG_OK;
}
}
else
{
IShellItem* item;
hr = dialog->lpVtbl->GetResult(dialog, &item);
if(SUCCEEDED(hr))
{
PWSTR pathWCStr;
hr = item->lpVtbl->GetDisplayName(item, SIGDN_FILESYSPATH, &pathWCStr);
if(SUCCEEDED(hr))
{
oc_str16 pathWide = oc_str16_from_buffer(lstrlenW(pathWCStr), pathWCStr);
result.path = oc_win32_wide_to_utf8(arena, pathWide);
oc_str8_list_push(arena, &result.selection, result.path);
CoTaskMemFree(pathWCStr);
}
item->lpVtbl->Release(item);
result.button = OC_FILE_DIALOG_OK;
}
}
}
}
dialog->lpVtbl->Release(dialog);
}
CoUninitialize();
oc_scratch_end(scratch);
return (result);
}
#include <commctrl.h> #include <commctrl.h>
int oc_alert_popup(oc_str8 title, int oc_alert_popup(oc_str8 title,

View File

@ -22,6 +22,7 @@
#include "platform/platform.h" #include "platform/platform.h"
#include "platform/platform_clock.h" #include "platform/platform_clock.h"
#include "platform/platform_io.h" #include "platform/platform_io.h"
#include "platform/platform_io_dialog.h"
#include "platform/platform_path.h" #include "platform/platform_path.h"
#if !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA) #if !defined(OC_PLATFORM_ORCA) || !(OC_PLATFORM_ORCA)

View File

@ -222,4 +222,10 @@ ORCA_API u64 oc_file_size(oc_file file);
//TODO: Complete as needed... //TODO: Complete as needed...
//----------------------------------------------------------------
// Acquiring new file capabilities through user interaction
//----------------------------------------------------------------
ORCA_API oc_file oc_file_open_with_request(oc_str8 path, oc_file_access rights, oc_file_open_flags flags);
#endif //__PLATFORM_IO_H_ #endif //__PLATFORM_IO_H_

View File

@ -7,6 +7,7 @@
*****************************************************************/ *****************************************************************/
#include "platform_io.h" #include "platform_io.h"
#include "platform_io_dialog.h"
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// File stream read/write API // File stream read/write API

View File

@ -0,0 +1,29 @@
/************************************************************/ /**
*
* @file: platform_io_dialog.h
* @author: Martin Fouilleul
* @date: 01/09/2023
*
*****************************************************************/
#ifndef __PLATFORM_IO_DIALOG_H_
#define __PLATFORM_IO_DIALOG_H_
#include "platform_io.h"
#include "app/app.h"
typedef struct oc_file_open_with_dialog_elt
{
oc_list_elt listElt;
oc_file file;
} oc_file_open_with_dialog_elt;
typedef struct oc_file_open_with_dialog_result
{
oc_file_dialog_button button;
oc_file file;
oc_list selection;
} oc_file_open_with_dialog_result;
ORCA_API oc_file_open_with_dialog_result oc_file_open_with_dialog(oc_arena* arena, oc_file_access rights, oc_file_open_flags flags, oc_file_dialog_desc* desc);
#endif //__PLATFORM_IO_DIALOG_H_

View File

@ -5,12 +5,17 @@
* @date: 11/06/2023 * @date: 11/06/2023
* *
*****************************************************************/ *****************************************************************/
#include "app/app.h"
#include "platform_io_internal.h" #include "platform/platform_io_internal.h"
#include "platform_path.h" #include "platform/platform_path.h"
oc_file_table oc_globalFileTable = { 0 }; oc_file_table oc_globalFileTable = { 0 };
oc_file_table* oc_file_table_get_global()
{
return (&oc_globalFileTable);
}
oc_file_slot* oc_file_slot_alloc(oc_file_table* table) oc_file_slot* oc_file_slot_alloc(oc_file_table* table)
{ {
oc_file_slot* slot = oc_list_pop_entry(&table->freeList, oc_file_slot, freeListElt); oc_file_slot* slot = oc_list_pop_entry(&table->freeList, oc_file_slot, freeListElt);
@ -62,7 +67,7 @@ oc_file_slot* oc_file_slot_from_handle(oc_file_table* table, oc_file handle)
oc_io_cmp oc_io_wait_single_req(oc_io_req* req) oc_io_cmp oc_io_wait_single_req(oc_io_req* req)
{ {
return (oc_io_wait_single_req_with_table(req, &oc_globalFileTable)); return (oc_io_wait_single_req_for_table(req, &oc_globalFileTable));
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -341,3 +346,100 @@ oc_io_cmp oc_io_open_at(oc_file_slot* atSlot, oc_io_req* req, oc_file_table* tab
} }
return (cmp); return (cmp);
} }
oc_file oc_file_open_with_request_for_table(oc_str8 path, oc_file_access rights, oc_file_open_flags flags, oc_file_table* table)
{
oc_arena_scope scratch = oc_scratch_begin();
oc_str8 msg = oc_str8_pushf(scratch.arena, "Application wants to access file '%.*s'.", (int)path.len, path.ptr);
oc_str8_list options = { 0 };
oc_str8_list_push(scratch.arena, &options, OC_STR8("Deny"));
oc_str8_list_push(scratch.arena, &options, OC_STR8("Accept"));
int res = oc_alert_popup(OC_STR8("File Access"), msg, options);
oc_file file = oc_file_nil();
if(res == 1)
{
oc_io_cmp cmp = { 0 };
oc_io_req req = {
.op = OC_IO_OPEN_AT,
.size = path.len,
.buffer = path.ptr,
.open.rights = rights,
.open.flags = flags
};
cmp = oc_io_open_at(0, &req, table);
if(cmp.error == OC_IO_OK)
{
file = cmp.handle;
}
}
oc_scratch_end(scratch);
return (file);
}
oc_file oc_file_open_with_request(oc_str8 path, oc_file_access rights, oc_file_open_flags flags)
{
return (oc_file_open_with_request_for_table(path, rights, flags, &oc_globalFileTable));
}
oc_file_open_with_dialog_result oc_file_open_with_dialog_for_table(oc_arena* arena,
oc_file_access rights,
oc_file_open_flags flags,
oc_file_dialog_desc* desc,
oc_file_table* table)
{
oc_arena_scope scratch = oc_scratch_begin_next(arena);
oc_file_dialog_result dialogResult = oc_file_dialog_for_table(scratch.arena, desc, table);
oc_file_open_with_dialog_result result = {
.button = dialogResult.button
};
if(dialogResult.button == OC_FILE_DIALOG_OK)
{
int i = 0;
oc_list_for(&dialogResult.selection.list, elt, oc_str8_elt, listElt)
{
oc_file file = oc_file_nil();
if(elt->string.len)
{
oc_io_req req = {
.op = OC_IO_OPEN_AT,
.size = elt->string.len,
.buffer = elt->string.ptr,
.open.rights = rights,
.open.flags = flags
};
oc_io_cmp cmp = oc_io_wait_single_req_for_table(&req, table);
file = cmp.handle;
}
oc_file_open_with_dialog_elt* resElt = oc_arena_push_type(arena, oc_file_open_with_dialog_elt);
memset(resElt, 0, sizeof(*resElt));
resElt->file = file;
oc_list_push_back(&result.selection, &resElt->listElt);
if(i == 0)
{
result.file = file;
i++;
}
}
}
oc_scratch_end(scratch);
return (result);
}
oc_file_open_with_dialog_result oc_file_open_with_dialog(oc_arena* arena, oc_file_access rights, oc_file_open_flags flags, oc_file_dialog_desc* desc)
{
return (oc_file_open_with_dialog_for_table(arena, rights, flags, desc, &oc_globalFileTable));
}

View File

@ -10,6 +10,7 @@
#include "platform.h" #include "platform.h"
#include "platform_io.h" #include "platform_io.h"
#include "platform_io_dialog.h"
#if OC_PLATFORM_MACOS || PLATFORM_LINUX #if OC_PLATFORM_MACOS || PLATFORM_LINUX
typedef int oc_file_desc; typedef int oc_file_desc;
@ -46,12 +47,22 @@ typedef struct oc_file_table
oc_list freeList; oc_list freeList;
} oc_file_table; } oc_file_table;
ORCA_API oc_file_table* oc_file_table_get_global();
oc_file_slot* oc_file_slot_alloc(oc_file_table* table); oc_file_slot* oc_file_slot_alloc(oc_file_table* table);
void oc_file_slot_recycle(oc_file_table* table, oc_file_slot* slot); void oc_file_slot_recycle(oc_file_table* table, oc_file_slot* slot);
oc_file oc_file_from_slot(oc_file_table* table, oc_file_slot* slot); oc_file oc_file_from_slot(oc_file_table* table, oc_file_slot* slot);
oc_file_slot* oc_file_slot_from_handle(oc_file_table* table, oc_file handle); oc_file_slot* oc_file_slot_from_handle(oc_file_table* table, oc_file handle);
ORCA_API oc_io_cmp oc_io_wait_single_req_with_table(oc_io_req* req, oc_file_table* table); ORCA_API oc_io_cmp oc_io_wait_single_req_for_table(oc_io_req* req, oc_file_table* table);
ORCA_API oc_file oc_file_open_with_request_for_table(oc_str8 path, oc_file_access rights, oc_file_open_flags flags, oc_file_table* table);
ORCA_API oc_file_open_with_dialog_result oc_file_open_with_dialog_for_table(oc_arena* arena,
oc_file_access rights,
oc_file_open_flags flags,
oc_file_dialog_desc* desc,
oc_file_table* table);
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// raw io primitives // raw io primitives

View File

@ -54,7 +54,7 @@ oc_str8_list oc_path_split(oc_arena* arena, oc_str8 path)
oc_str8 oc_path_join(oc_arena* arena, oc_str8_list elements) oc_str8 oc_path_join(oc_arena* arena, oc_str8_list elements)
{ {
//TODO: check if elements have ending/begining '/' ? //TODO: check if elements have ending/begining '/' ?
oc_str8 res = oc_str8_list_collate(arena, elements, OC_STR8("/"), OC_STR8("/"), (oc_str8){ 0 }); oc_str8 res = oc_str8_list_collate(arena, elements, OC_STR8(""), OC_STR8("/"), (oc_str8){ 0 });
return (res); return (res);
} }

View File

@ -518,7 +518,7 @@ oc_io_cmp oc_io_get_error(oc_file_slot* slot, oc_io_req* req)
return (cmp); return (cmp);
} }
oc_io_cmp oc_io_wait_single_req_with_table(oc_io_req* req, oc_file_table* table) oc_io_cmp oc_io_wait_single_req_for_table(oc_io_req* req, oc_file_table* table)
{ {
oc_io_cmp cmp = { 0 }; oc_io_cmp cmp = { 0 };

View File

@ -73,7 +73,7 @@ oc_io_error oc_io_raw_last_error()
return (error); return (error);
} }
static oc_str16 win32_path_from_handle_null_terminated(oc_arena* arena, HANDLE handle) oc_str16 win32_path_from_handle_null_terminated(oc_arena* arena, HANDLE handle)
{ {
oc_str16 res = { 0 }; oc_str16 res = { 0 };
@ -108,7 +108,7 @@ static oc_str16 win32_get_path_at_null_terminated(oc_arena* arena, oc_file_desc
oc_arena_scope scratch = oc_scratch_begin_next(arena); oc_arena_scope scratch = oc_scratch_begin_next(arena);
oc_str16 dirPathW = win32_path_from_handle_null_terminated(scratch.arena, dirFd); oc_str16 dirPathW = win32_path_from_handle_null_terminated(scratch.arena, dirFd);
oc_str16 pathW = oc_win32_utf8_to_wide_null_terminated(scratch.arena, path); oc_str16 pathW = oc_win32_utf8_to_wide(scratch.arena, path);
if(dirPathW.len && pathW.len) if(dirPathW.len && pathW.len)
{ {
@ -194,7 +194,7 @@ oc_file_desc oc_io_raw_open_at(oc_file_desc dirFd, oc_str8 path, oc_file_access
} }
oc_arena_scope scratch = oc_scratch_begin(); oc_arena_scope scratch = oc_scratch_begin();
oc_str16 pathW = oc_win32_utf8_to_wide_null_terminated(scratch.arena, path); oc_str16 pathW = oc_win32_utf8_to_wide(scratch.arena, path);
if(dirFd == NULL || dirFd == INVALID_HANDLE_VALUE) if(dirFd == NULL || dirFd == INVALID_HANDLE_VALUE)
{ {
@ -505,7 +505,7 @@ static oc_io_cmp oc_io_get_error(oc_file_slot* slot, oc_io_req* req)
return (cmp); return (cmp);
} }
oc_io_cmp oc_io_wait_single_req_with_table(oc_io_req* req, oc_file_table* table) oc_io_cmp oc_io_wait_single_req_for_table(oc_io_req* req, oc_file_table* table)
{ {
oc_io_cmp cmp = { 0 }; oc_io_cmp cmp = { 0 };

View File

@ -13,7 +13,7 @@
bool oc_path_is_absolute(oc_str8 path) bool oc_path_is_absolute(oc_str8 path)
{ {
oc_arena_scope scratch = oc_scratch_begin(); oc_arena_scope scratch = oc_scratch_begin();
oc_str16 pathW = oc_win32_utf8_to_wide_null_terminated(scratch.arena, path); oc_str16 pathW = oc_win32_utf8_to_wide(scratch.arena, path);
bool result = !PathIsRelativeW(pathW.ptr); bool result = !PathIsRelativeW(pathW.ptr);
oc_scratch_end(scratch); oc_scratch_end(scratch);

View File

@ -11,7 +11,7 @@
#include "win32_string_helpers.h" #include "win32_string_helpers.h"
oc_str16 oc_win32_utf8_to_wide_null_terminated(oc_arena* arena, oc_str8 s) oc_str16 oc_win32_utf8_to_wide(oc_arena* arena, oc_str8 s)
{ {
oc_str16 res = { 0 }; oc_str16 res = { 0 };
res.len = 1 + MultiByteToWideChar(CP_UTF8, 0, s.ptr, s.len, NULL, 0); res.len = 1 + MultiByteToWideChar(CP_UTF8, 0, s.ptr, s.len, NULL, 0);

View File

@ -10,7 +10,7 @@
#include "util/strings.h" #include "util/strings.h"
oc_str16 oc_win32_utf8_to_wide_null_terminated(oc_arena* arena, oc_str8 s); oc_str16 oc_win32_utf8_to_wide(oc_arena* arena, oc_str8 s);
oc_str8 oc_win32_wide_to_utf8(oc_arena* arena, oc_str16 s); oc_str8 oc_win32_wide_to_utf8(oc_arena* arena, oc_str16 s);
#endif // __WIN32_STRING_HELPERS_H_ #endif // __WIN32_STRING_HELPERS_H_

View File

@ -19,7 +19,7 @@
oc_font orca_font_create(const char* resourcePath) oc_font orca_font_create(const char* resourcePath)
{ {
//NOTE(martin): create default font //NOTE(martin): create default fonts
oc_str8 fontPath = oc_path_executable_relative(oc_scratch(), OC_STR8(resourcePath)); oc_str8 fontPath = oc_path_executable_relative(oc_scratch(), OC_STR8(resourcePath));
FILE* fontFile = fopen(fontPath.ptr, "r"); FILE* fontFile = fopen(fontPath.ptr, "r");
@ -49,11 +49,6 @@ oc_font orca_font_create(const char* resourcePath)
return (font); return (font);
} }
oc_font oc_font_create_default()
{
return (orca_font_create("../resources/OpenSansLatinSubset.ttf"));
}
oc_runtime __orcaApp = { 0 }; oc_runtime __orcaApp = { 0 };
oc_runtime* oc_runtime_get() oc_runtime* oc_runtime_get()
@ -63,26 +58,69 @@ oc_runtime* oc_runtime_get()
oc_runtime_env* oc_runtime_env_get() oc_runtime_env* oc_runtime_env_get()
{ {
return (&__orcaApp.runtime); return (&__orcaApp.env);
} }
void* oc_runtime_ptr_to_native(oc_runtime* orca, void* wasmPtr, u32 length) void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const char* function, int line, const char* msg)
{
M3ErrorInfo errInfo = { 0 };
m3_GetErrorInfo(runtime, &errInfo);
if(errInfo.message && res == errInfo.result)
{
oc_abort_ext(file, function, line, "%s: %s (%s)", msg, res, errInfo.message);
}
else
{
oc_abort_ext(file, function, line, "%s: %s", msg, res);
}
}
#define ORCA_WASM3_ABORT(runtime, err, msg) orca_wasm3_abort(runtime, err, __FILE__, __FUNCTION__, __LINE__, msg)
void* oc_runtime_env_ptr_to_native(oc_runtime_env* env, void* wasmPtr, u32 length)
{ {
// We can't use the runtime's memory pointer directly because wasm3 embeds a // We can't use the runtime's memory pointer directly because wasm3 embeds a
// header at the beginning of the block we give it. // header at the beginning of the block we give it.
u64 bufferIndex = (u64)wasmPtr & 0xffffffff; u64 bufferIndex = (u64)wasmPtr & 0xffffffff;
u32 memSize = 0; u32 memSize = 0;
char* memory = (char*)m3_GetMemory(orca->runtime.m3Runtime, &memSize, 0); char* memory = (char*)m3_GetMemory(env->m3Runtime, &memSize, 0);
if(bufferIndex + length < memSize) if(bufferIndex + length < memSize)
{ {
char* nativePtr = memory + bufferIndex; char* nativePtr = memory + bufferIndex;
return nativePtr; return nativePtr;
} }
//TODO directly abort here?
return NULL; return NULL;
} }
void* oc_runtime_ptr_to_native(oc_runtime* orca, void* wasmPtr, u32 length)
{
return (oc_runtime_env_ptr_to_native(&orca->env, wasmPtr, length));
}
void* oc_wasm_arena_push(oc_runtime_env* env, i32 arenaIndex, u64 size)
{
void* retValues[1] = { 0 };
const void* retPointers[1] = { &retValues[0] };
const void* args[2] = { &arenaIndex, &size };
M3Result res = m3_Call(env->exports[OC_EXPORT_ARENA_PUSH], 2, args);
if(res)
{
ORCA_WASM3_ABORT(env->m3Runtime, res, "Runtime error");
}
res = m3_GetResults(env->exports[OC_EXPORT_ARENA_PUSH], 1, retPointers);
if(res)
{
ORCA_WASM3_ABORT(env->m3Runtime, res, "Runtime error");
}
void* ptr = oc_runtime_env_ptr_to_native(env, retValues[0], size);
return (ptr);
}
void oc_runtime_window_set_title(oc_str8 title) void oc_runtime_window_set_title(oc_str8 title)
{ {
title.ptr = oc_runtime_ptr_to_native(oc_runtime_get(), title.ptr, title.len); title.ptr = oc_runtime_ptr_to_native(oc_runtime_get(), title.ptr, title.len);
@ -231,8 +269,8 @@ void orca_surface_render_commands(oc_surface surface,
{ {
oc_runtime* app = &__orcaApp; oc_runtime* app = &__orcaApp;
char* memBase = app->runtime.wasmMemory.ptr; char* memBase = app->env.wasmMemory.ptr;
u32 memSize = app->runtime.wasmMemory.committed; u32 memSize = app->env.wasmMemory.committed;
if(((char*)primitives > memBase) if(((char*)primitives > memBase)
&& ((char*)primitives + primitiveCount * sizeof(oc_primitive) - memBase <= memSize) && ((char*)primitives + primitiveCount * sizeof(oc_primitive) - memBase <= memSize)
&& ((char*)elements > memBase) && ((char*)elements > memBase)
@ -350,27 +388,11 @@ void oc_runtime_env_init(oc_runtime_env* runtime)
#include "wasmbind/io_api_bind_gen.c" #include "wasmbind/io_api_bind_gen.c"
#include "wasmbind/surface_api_bind_gen.c" #include "wasmbind/surface_api_bind_gen.c"
void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const char* function, int line, const char* msg)
{
M3ErrorInfo errInfo = { 0 };
m3_GetErrorInfo(runtime, &errInfo);
if(errInfo.message && res == errInfo.result)
{
oc_abort_ext(file, function, line, "%s: %s (%s)", msg, res, errInfo.message);
}
else
{
oc_abort_ext(file, function, line, "%s: %s", msg, res);
}
}
#define ORCA_WASM3_ABORT(runtime, err, msg) orca_wasm3_abort(runtime, err, __FILE__, __FUNCTION__, __LINE__, msg)
i32 orca_runloop(void* user) i32 orca_runloop(void* user)
{ {
oc_runtime* app = &__orcaApp; oc_runtime* app = &__orcaApp;
oc_runtime_env_init(&app->runtime); oc_runtime_env_init(&app->env);
//NOTE: loads wasm module //NOTE: loads wasm module
const char* bundleNameCString = "module"; const char* bundleNameCString = "module";
@ -386,42 +408,42 @@ i32 orca_runloop(void* user)
u64 wasmSize = ftell(file); u64 wasmSize = ftell(file);
rewind(file); rewind(file);
app->runtime.wasmBytecode.len = wasmSize; app->env.wasmBytecode.len = wasmSize;
app->runtime.wasmBytecode.ptr = oc_malloc_array(char, wasmSize); app->env.wasmBytecode.ptr = oc_malloc_array(char, wasmSize);
fread(app->runtime.wasmBytecode.ptr, 1, app->runtime.wasmBytecode.len, file); fread(app->env.wasmBytecode.ptr, 1, app->env.wasmBytecode.len, file);
fclose(file); fclose(file);
u32 stackSize = 65536; u32 stackSize = 65536;
app->runtime.m3Env = m3_NewEnvironment(); app->env.m3Env = m3_NewEnvironment();
app->runtime.m3Runtime = m3_NewRuntime(app->runtime.m3Env, stackSize, NULL); app->env.m3Runtime = m3_NewRuntime(app->env.m3Env, stackSize, NULL);
//NOTE: host memory will be freed when runtime is freed. //NOTE: host memory will be freed when runtime is freed.
m3_RuntimeSetMemoryCallbacks(app->runtime.m3Runtime, wasm_memory_resize_callback, wasm_memory_free_callback, &app->runtime.wasmMemory); m3_RuntimeSetMemoryCallbacks(app->env.m3Runtime, wasm_memory_resize_callback, wasm_memory_free_callback, &app->env.wasmMemory);
M3Result res = m3_ParseModule(app->runtime.m3Env, &app->runtime.m3Module, (u8*)app->runtime.wasmBytecode.ptr, app->runtime.wasmBytecode.len); M3Result res = m3_ParseModule(app->env.m3Env, &app->env.m3Module, (u8*)app->env.wasmBytecode.ptr, app->env.wasmBytecode.len);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "The application couldn't parse its web assembly module"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "The application couldn't parse its web assembly module");
} }
res = m3_LoadModule(app->runtime.m3Runtime, app->runtime.m3Module); res = m3_LoadModule(app->env.m3Runtime, app->env.m3Module);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "The application couldn't load its web assembly module into the runtime"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "The application couldn't load its web assembly module into the runtime");
} }
m3_SetModuleName(app->runtime.m3Module, bundleNameCString); m3_SetModuleName(app->env.m3Module, bundleNameCString);
oc_arena_clear(oc_scratch()); oc_arena_clear(oc_scratch());
//NOTE: bind orca APIs //NOTE: bind orca APIs
{ {
int err = 0; int err = 0;
err |= bindgen_link_core_api(app->runtime.m3Module); err |= bindgen_link_core_api(app->env.m3Module);
err |= bindgen_link_surface_api(app->runtime.m3Module); err |= bindgen_link_surface_api(app->env.m3Module);
err |= bindgen_link_clock_api(app->runtime.m3Module); err |= bindgen_link_clock_api(app->env.m3Module);
err |= bindgen_link_io_api(app->runtime.m3Module); err |= bindgen_link_io_api(app->env.m3Module);
err |= bindgen_link_gles_api(app->runtime.m3Module); err |= bindgen_link_gles_api(app->env.m3Module);
err |= manual_link_gles_api(app->runtime.m3Module); err |= manual_link_gles_api(app->env.m3Module);
if(err) if(err)
{ {
@ -429,10 +451,10 @@ i32 orca_runloop(void* user)
} }
} }
//NOTE: compile //NOTE: compile
res = m3_CompileModule(app->runtime.m3Module); res = m3_CompileModule(app->env.m3Module);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "The application couldn't compile its web assembly module"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "The application couldn't compile its web assembly module");
} }
//NOTE: Find and type check event handlers. //NOTE: Find and type check event handlers.
@ -440,7 +462,7 @@ i32 orca_runloop(void* user)
{ {
const oc_export_desc* desc = &OC_EXPORT_DESC[i]; const oc_export_desc* desc = &OC_EXPORT_DESC[i];
IM3Function handler = 0; IM3Function handler = 0;
m3_FindFunction(&handler, app->runtime.m3Runtime, desc->name.ptr); m3_FindFunction(&handler, app->env.m3Runtime, desc->name.ptr);
if(handler) if(handler)
{ {
@ -481,7 +503,7 @@ i32 orca_runloop(void* user)
if(checked) if(checked)
{ {
app->runtime.exports[i] = handler; app->env.exports[i] = handler;
} }
else else
{ {
@ -491,8 +513,8 @@ i32 orca_runloop(void* user)
} }
//NOTE: get location of the raw event slot //NOTE: get location of the raw event slot
IM3Global rawEventGlobal = m3_FindGlobal(app->runtime.m3Module, "oc_rawEvent"); IM3Global rawEventGlobal = m3_FindGlobal(app->env.m3Module, "oc_rawEvent");
app->runtime.rawEventOffset = (u32)rawEventGlobal->intValue; app->env.rawEventOffset = (u32)rawEventGlobal->intValue;
//NOTE: preopen the app local root dir //NOTE: preopen the app local root dir
{ {
@ -502,11 +524,11 @@ i32 orca_runloop(void* user)
.open.rights = OC_FILE_ACCESS_READ | OC_FILE_ACCESS_WRITE, .open.rights = OC_FILE_ACCESS_READ | OC_FILE_ACCESS_WRITE,
.size = localRootPath.len, .size = localRootPath.len,
.buffer = localRootPath.ptr }; .buffer = localRootPath.ptr };
oc_io_cmp cmp = oc_io_wait_single_req_with_table(&req, &app->fileTable); oc_io_cmp cmp = oc_io_wait_single_req_for_table(&req, &app->fileTable);
app->rootDir = cmp.handle; app->rootDir = cmp.handle;
} }
IM3Function* exports = app->runtime.exports; IM3Function* exports = app->env.exports;
//NOTE: call init handler //NOTE: call init handler
if(exports[OC_EXPORT_ON_INIT]) if(exports[OC_EXPORT_ON_INIT])
@ -514,7 +536,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_ON_INIT], 0, 0); M3Result res = m3_Call(exports[OC_EXPORT_ON_INIT], 0, 0);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
@ -527,7 +549,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_FRAME_RESIZE], 2, args); M3Result res = m3_Call(exports[OC_EXPORT_FRAME_RESIZE], 2, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
@ -547,14 +569,14 @@ i32 orca_runloop(void* user)
if(exports[OC_EXPORT_RAW_EVENT]) if(exports[OC_EXPORT_RAW_EVENT])
{ {
#ifndef M3_BIG_ENDIAN #ifndef M3_BIG_ENDIAN
oc_event* eventPtr = (oc_event*)wasm_memory_offset_to_ptr(&app->runtime.wasmMemory, app->runtime.rawEventOffset); oc_event* eventPtr = (oc_event*)wasm_memory_offset_to_ptr(&app->env.wasmMemory, app->env.rawEventOffset);
memcpy(eventPtr, event, sizeof(*event)); memcpy(eventPtr, event, sizeof(*event));
const void* args[1] = { &app->runtime.rawEventOffset }; const void* args[1] = { &app->env.rawEventOffset };
M3Result res = m3_Call(exports[OC_EXPORT_RAW_EVENT], 1, args); M3Result res = m3_Call(exports[OC_EXPORT_RAW_EVENT], 1, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
#else #else
oc_log_error("oc_on_raw_event() is not supported on big endian platforms"); oc_log_error("oc_on_raw_event() is not supported on big endian platforms");
@ -565,7 +587,7 @@ i32 orca_runloop(void* user)
{ {
case OC_EVENT_WINDOW_CLOSE: case OC_EVENT_WINDOW_CLOSE:
{ {
oc_request_quit(); oc_request_quit();
} }
break; break;
@ -581,7 +603,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_FRAME_RESIZE], 2, args); M3Result res = m3_Call(exports[OC_EXPORT_FRAME_RESIZE], 2, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
} }
@ -598,7 +620,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_DOWN], 1, args); M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_DOWN], 1, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
} }
@ -611,7 +633,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_UP], 1, args); M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_UP], 1, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
} }
@ -626,7 +648,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_MOVE], 4, args); M3Result res = m3_Call(exports[OC_EXPORT_MOUSE_MOVE], 4, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
} }
@ -649,7 +671,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_KEY_DOWN], 1, args); M3Result res = m3_Call(exports[OC_EXPORT_KEY_DOWN], 1, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
} }
@ -661,7 +683,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_KEY_UP], 1, args); M3Result res = m3_Call(exports[OC_EXPORT_KEY_UP], 1, args);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
} }
@ -680,7 +702,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_FRAME_REFRESH], 0, 0); M3Result res = m3_Call(exports[OC_EXPORT_FRAME_REFRESH], 0, 0);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }
@ -837,7 +859,7 @@ i32 orca_runloop(void* user)
M3Result res = m3_Call(exports[OC_EXPORT_TERMINATE], 0, 0); M3Result res = m3_Call(exports[OC_EXPORT_TERMINATE], 0, 0);
if(res) if(res)
{ {
ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); ORCA_WASM3_ABORT(app->env.m3Runtime, res, "Runtime error");
} }
} }

View File

@ -27,7 +27,8 @@
X(OC_EXPORT_FRAME_REFRESH, "oc_on_frame_refresh", "", "") \ X(OC_EXPORT_FRAME_REFRESH, "oc_on_frame_refresh", "", "") \
X(OC_EXPORT_FRAME_RESIZE, "oc_on_resize", "", "ii") \ X(OC_EXPORT_FRAME_RESIZE, "oc_on_resize", "", "ii") \
X(OC_EXPORT_RAW_EVENT, "oc_on_raw_event", "", "i") \ X(OC_EXPORT_RAW_EVENT, "oc_on_raw_event", "", "i") \
X(OC_EXPORT_TERMINATE, "oc_on_terminate", "", "") X(OC_EXPORT_TERMINATE, "oc_on_terminate", "", "") \
X(OC_EXPORT_ARENA_PUSH, "oc_arena_push_stub", "i", "iI")
typedef enum typedef enum
{ {
@ -111,13 +112,13 @@ typedef struct oc_debug_overlay
typedef struct oc_runtime typedef struct oc_runtime
{ {
bool quit; bool quit;
oc_window window; oc_window window;
oc_file_table fileTable; oc_file_table fileTable;
oc_file rootDir; oc_file rootDir;
oc_runtime_env runtime; oc_runtime_env env;
oc_debug_overlay debugOverlay; oc_debug_overlay debugOverlay;
@ -127,5 +128,6 @@ oc_runtime* oc_runtime_get();
oc_runtime_env* oc_runtime_env_get(); oc_runtime_env* oc_runtime_env_get();
void* oc_runtime_ptr_to_native(oc_runtime* runtime, void* wasmPtr, u32 length); void* oc_runtime_ptr_to_native(oc_runtime* runtime, void* wasmPtr, u32 length);
void* oc_wasm_arena_push(oc_runtime_env* env, i32 arenaIndex, u64 size);
#endif //__RUNTIME_H_ #endif //__RUNTIME_H_

View File

@ -30,7 +30,7 @@ oc_io_cmp oc_runtime_io_wait_single_req(oc_io_req* wasmReq)
req.open.flags |= OC_FILE_OPEN_RESTRICT; req.open.flags |= OC_FILE_OPEN_RESTRICT;
} }
} }
cmp = oc_io_wait_single_req_with_table(&req, &orca->fileTable); cmp = oc_io_wait_single_req_for_table(&req, &orca->fileTable);
} }
else else
{ {
@ -39,3 +39,158 @@ oc_io_cmp oc_runtime_io_wait_single_req(oc_io_req* wasmReq)
return (cmp); return (cmp);
} }
oc_file oc_file_open_with_request_bridge(oc_str8 path, oc_file_access rights, oc_file_open_flags flags)
{
oc_file file = oc_file_nil();
oc_runtime* orca = oc_runtime_get();
path.ptr = oc_runtime_ptr_to_native(orca, path.ptr, path.len);
if(path.ptr)
{
file = oc_file_open_with_request_for_table(path, rights, flags, &orca->fileTable);
}
return (file);
}
typedef struct oc_wasm_list
{
u32 first;
u32 last;
} oc_wasm_list;
typedef struct oc_wasm_list_elt
{
u32 prev;
u32 next;
} oc_wasm_list_elt;
typedef struct oc_wasm_str8
{
u64 len;
u32 ptr;
} oc_wasm_str8;
typedef struct oc_wasm_str8_elt
{
oc_wasm_list_elt listElt;
oc_wasm_str8 string;
} oc_wasm_str8_elt;
typedef struct oc_wasm_str8_list
{
oc_wasm_list list;
u64 eltCount;
u64 len;
} oc_wasm_str8_list;
typedef struct oc_wasm_file_dialog_desc
{
oc_file_dialog_kind kind;
oc_file_dialog_flags flags;
oc_wasm_str8 title;
oc_wasm_str8 okLabel;
oc_file startAt;
oc_wasm_str8 startPath;
oc_wasm_str8_list filters;
} oc_wasm_file_dialog_desc;
typedef struct oc_wasm_file_open_with_dialog_elt
{
oc_wasm_list_elt listElt;
oc_file file;
} oc_wasm_file_open_with_dialog_elt;
typedef struct oc_wasm_file_open_with_dialog_result
{
oc_file_dialog_button button;
oc_file file;
oc_wasm_list selection;
} oc_wasm_file_open_with_dialog_result;
oc_wasm_file_open_with_dialog_result oc_file_open_with_dialog_bridge(i32 wasmArenaIndex,
oc_file_access rights,
oc_file_open_flags flags,
oc_wasm_file_dialog_desc* desc)
{
oc_runtime* orca = oc_runtime_get();
oc_arena_scope scratch = oc_scratch_begin();
oc_file_dialog_desc nativeDesc = {
.kind = desc->kind,
.flags = desc->flags
};
nativeDesc.title.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)desc->title.ptr, desc->title.len);
nativeDesc.title.len = desc->title.len;
nativeDesc.okLabel.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)desc->okLabel.ptr, desc->okLabel.len);
nativeDesc.okLabel.len = desc->okLabel.len;
if(oc_file_is_nil(desc->startAt) && desc->startPath.len)
{
nativeDesc.startAt = orca->rootDir;
}
else
{
nativeDesc.startAt = desc->startAt;
}
nativeDesc.startPath.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)desc->startPath.ptr, desc->startPath.len);
nativeDesc.startPath.len = desc->startPath.len;
u32 eltIndex = desc->filters.list.first;
while(eltIndex)
{
oc_wasm_str8_elt* elt = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)eltIndex, sizeof(oc_wasm_str8_elt));
oc_str8 filter = { 0 };
filter.ptr = oc_runtime_ptr_to_native(orca, (char*)(uintptr_t)elt->string.ptr, elt->string.len);
filter.len = elt->string.len;
oc_str8_list_push(scratch.arena, &nativeDesc.filters, filter);
oc_log_info("filter: %.*s\n", (int)filter.len, filter.ptr);
eltIndex = elt->listElt.next;
}
oc_file_open_with_dialog_result nativeResult = oc_file_open_with_dialog_for_table(scratch.arena, rights, flags, &nativeDesc, &orca->fileTable);
oc_wasm_file_open_with_dialog_result result = {
.button = nativeResult.button,
.file = nativeResult.file
};
u32 memSize;
char* wasmMemory = (char*)m3_GetMemory(orca->env.m3Runtime, &memSize, 0);
oc_wasm_file_open_with_dialog_elt* lastElt = 0;
oc_list_for(&nativeResult.selection, elt, oc_file_open_with_dialog_elt, listElt)
{
oc_wasm_file_open_with_dialog_elt* wasmElt = oc_wasm_arena_push(&orca->env, wasmArenaIndex, sizeof(oc_wasm_file_open_with_dialog_elt));
wasmElt->file = elt->file;
if(result.selection.last == 0)
{
result.selection.first = ((char*)wasmElt - wasmMemory);
result.selection.last = result.selection.first;
wasmElt->listElt.prev = 0;
wasmElt->listElt.next = 0;
}
else
{
wasmElt->listElt.prev = result.selection.last;
wasmElt->listElt.next = 0;
lastElt->listElt.next = ((char*)wasmElt - wasmMemory);
result.selection.last = ((char*)wasmElt - wasmMemory);
}
lastElt = wasmElt;
}
oc_scratch_end(scratch);
return (result);
}

View File

@ -14,13 +14,12 @@
#include "util/debug.h" #include "util/debug.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C" {
{
#endif #endif
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Intrusive linked lists // Intrusive linked lists
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
#define oc_list_entry(ptr, type, member) \ #define oc_list_entry(ptr, type, member) \
oc_container_of(ptr, type, member) oc_container_of(ptr, type, member)
@ -62,162 +61,162 @@ extern "C"
#define oc_list_pop_entry(list, type, member) (oc_list_empty(list) ? 0 : oc_list_entry(oc_list_pop(list), type, member)) #define oc_list_pop_entry(list, type, member) (oc_list_empty(list) ? 0 : oc_list_entry(oc_list_pop(list), type, member))
typedef struct oc_list_elt oc_list_elt; typedef struct oc_list_elt oc_list_elt;
struct oc_list_elt struct oc_list_elt
{ {
oc_list_elt* next; oc_list_elt* prev;
oc_list_elt* prev; oc_list_elt* next;
}; };
typedef struct oc_list typedef struct oc_list
{ {
oc_list_elt* first; oc_list_elt* first;
oc_list_elt* last; oc_list_elt* last;
} oc_list; } oc_list;
static inline void oc_list_init(oc_list* list) static inline void oc_list_init(oc_list* list)
{
list->first = list->last = 0;
}
static inline oc_list_elt* oc_list_begin(oc_list* list)
{
return (list->first);
}
static inline oc_list_elt* oc_list_end(oc_list* list)
{
return (0);
}
static inline oc_list_elt* oc_list_last(oc_list* list)
{
return (list->last);
}
static inline void oc_list_insert(oc_list* list, oc_list_elt* afterElt, oc_list_elt* elt)
{
elt->prev = afterElt;
elt->next = afterElt->next;
if(afterElt->next)
{ {
list->first = list->last = 0; afterElt->next->prev = elt;
} }
else
static inline oc_list_elt* oc_list_begin(oc_list* list)
{ {
return (list->first); list->last = elt;
} }
afterElt->next = elt;
static inline oc_list_elt* oc_list_end(oc_list* list) OC_DEBUG_ASSERT(elt->next != elt, "oc_list_insert(): can't insert an element into itself");
}
static inline void oc_list_insert_before(oc_list* list, oc_list_elt* beforeElt, oc_list_elt* elt)
{
elt->next = beforeElt;
elt->prev = beforeElt->prev;
if(beforeElt->prev)
{
beforeElt->prev->next = elt;
}
else
{
list->first = elt;
}
beforeElt->prev = elt;
OC_DEBUG_ASSERT(elt->next != elt, "oc_list_insert_before(): can't insert an element into itself");
}
static inline void oc_list_remove(oc_list* list, oc_list_elt* elt)
{
if(elt->prev)
{
elt->prev->next = elt->next;
}
else
{
OC_DEBUG_ASSERT(list->first == elt);
list->first = elt->next;
}
if(elt->next)
{
elt->next->prev = elt->prev;
}
else
{
OC_DEBUG_ASSERT(list->last == elt);
list->last = elt->prev;
}
elt->prev = elt->next = 0;
}
static inline void oc_list_push(oc_list* list, oc_list_elt* elt)
{
elt->next = list->first;
elt->prev = 0;
if(list->first)
{
list->first->prev = elt;
}
else
{
list->last = elt;
}
list->first = elt;
}
static inline oc_list_elt* oc_list_pop(oc_list* list)
{
oc_list_elt* elt = oc_list_begin(list);
if(elt != oc_list_end(list))
{
oc_list_remove(list, elt);
return (elt);
}
else
{ {
return (0); return (0);
} }
}
static inline oc_list_elt* oc_list_last(oc_list* list) static inline void oc_list_push_back(oc_list* list, oc_list_elt* elt)
{
elt->prev = list->last;
elt->next = 0;
if(list->last)
{ {
return (list->last); list->last->next = elt;
} }
else
static inline void oc_list_insert(oc_list* list, oc_list_elt* afterElt, oc_list_elt* elt)
{ {
elt->prev = afterElt;
elt->next = afterElt->next;
if(afterElt->next)
{
afterElt->next->prev = elt;
}
else
{
list->last = elt;
}
afterElt->next = elt;
OC_DEBUG_ASSERT(elt->next != elt, "oc_list_insert(): can't insert an element into itself");
}
static inline void oc_list_insert_before(oc_list* list, oc_list_elt* beforeElt, oc_list_elt* elt)
{
elt->next = beforeElt;
elt->prev = beforeElt->prev;
if(beforeElt->prev)
{
beforeElt->prev->next = elt;
}
else
{
list->first = elt;
}
beforeElt->prev = elt;
OC_DEBUG_ASSERT(elt->next != elt, "oc_list_insert_before(): can't insert an element into itself");
}
static inline void oc_list_remove(oc_list* list, oc_list_elt* elt)
{
if(elt->prev)
{
elt->prev->next = elt->next;
}
else
{
OC_DEBUG_ASSERT(list->first == elt);
list->first = elt->next;
}
if(elt->next)
{
elt->next->prev = elt->prev;
}
else
{
OC_DEBUG_ASSERT(list->last == elt);
list->last = elt->prev;
}
elt->prev = elt->next = 0;
}
static inline void oc_list_push(oc_list* list, oc_list_elt* elt)
{
elt->next = list->first;
elt->prev = 0;
if(list->first)
{
list->first->prev = elt;
}
else
{
list->last = elt;
}
list->first = elt; list->first = elt;
} }
list->last = elt;
static inline oc_list_elt* oc_list_pop(oc_list* list) }
{
oc_list_elt* elt = oc_list_begin(list);
if(elt != oc_list_end(list))
{
oc_list_remove(list, elt);
return (elt);
}
else
{
return (0);
}
}
static inline void oc_list_push_back(oc_list* list, oc_list_elt* elt)
{
elt->prev = list->last;
elt->next = 0;
if(list->last)
{
list->last->next = elt;
}
else
{
list->first = elt;
}
list->last = elt;
}
#define oc_list_append(a, b) oc_list_push_back(a, b) #define oc_list_append(a, b) oc_list_push_back(a, b)
static inline oc_list_elt* oc_list_pop_back(oc_list* list) static inline oc_list_elt* oc_list_pop_back(oc_list* list)
{
oc_list_elt* elt = oc_list_last(list);
if(elt != oc_list_end(list))
{ {
oc_list_elt* elt = oc_list_last(list); oc_list_remove(list, elt);
if(elt != oc_list_end(list)) return (elt);
{
oc_list_remove(list, elt);
return (elt);
}
else
{
return (0);
}
} }
else
{
return (0);
}
}
static inline bool oc_list_empty(oc_list* list) static inline bool oc_list_empty(oc_list* list)
{ {
return (list->first == 0 || list->last == 0); return (list->first == 0 || list->last == 0);
} }
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View File

@ -241,7 +241,7 @@ ORCA_API oc_arena* oc_scratch_next(oc_arena* used)
return (res); return (res);
} }
ORCA_API oc_arena_scope oc_scratch_begin() ORCA_API oc_arena_scope oc_scratch_begin(void)
{ {
oc_arena* scratch = oc_scratch(); oc_arena* scratch = oc_scratch();
oc_arena_scope scope = oc_arena_scope_begin(scratch); oc_arena_scope scope = oc_arena_scope_begin(scratch);

View File

@ -14,94 +14,93 @@
#include "util/typedefs.h" #include "util/typedefs.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C" {
{
#endif #endif
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
//NOTE(martin): memory arena //NOTE(martin): memory arena
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
typedef struct oc_arena_chunk typedef struct oc_arena_chunk
{ {
oc_list_elt listElt; oc_list_elt listElt;
char* ptr; char* ptr;
u64 offset; u64 offset;
u64 committed; u64 committed;
u64 cap; u64 cap;
} oc_arena_chunk; } oc_arena_chunk;
typedef struct oc_arena typedef struct oc_arena
{ {
oc_base_allocator* base; oc_base_allocator* base;
oc_list chunks; oc_list chunks;
oc_arena_chunk* currentChunk; oc_arena_chunk* currentChunk;
} oc_arena; } oc_arena;
typedef struct oc_arena_scope typedef struct oc_arena_scope
{ {
oc_arena* arena; oc_arena* arena;
oc_arena_chunk* chunk; oc_arena_chunk* chunk;
u64 offset; u64 offset;
} oc_arena_scope; } oc_arena_scope;
typedef struct oc_arena_options typedef struct oc_arena_options
{ {
oc_base_allocator* base; oc_base_allocator* base;
u64 reserve; u64 reserve;
} oc_arena_options; } oc_arena_options;
ORCA_API void oc_arena_init(oc_arena* arena); ORCA_API void oc_arena_init(oc_arena* arena);
ORCA_API void oc_arena_init_with_options(oc_arena* arena, oc_arena_options* options); ORCA_API void oc_arena_init_with_options(oc_arena* arena, oc_arena_options* options);
ORCA_API void oc_arena_cleanup(oc_arena* arena); ORCA_API void oc_arena_cleanup(oc_arena* arena);
ORCA_API void* oc_arena_push(oc_arena* arena, u64 size); ORCA_API void* oc_arena_push(oc_arena* arena, u64 size);
ORCA_API void oc_arena_clear(oc_arena* arena); ORCA_API void oc_arena_clear(oc_arena* arena);
ORCA_API oc_arena_scope oc_arena_scope_begin(oc_arena* arena); ORCA_API oc_arena_scope oc_arena_scope_begin(oc_arena* arena);
ORCA_API void oc_arena_scope_end(oc_arena_scope scope); ORCA_API void oc_arena_scope_end(oc_arena_scope scope);
#define oc_arena_push_type(arena, type) ((type*)oc_arena_push(arena, sizeof(type))) #define oc_arena_push_type(arena, type) ((type*)oc_arena_push(arena, sizeof(type)))
#define oc_arena_push_array(arena, type, count) ((type*)oc_arena_push(arena, sizeof(type) * (count))) #define oc_arena_push_array(arena, type, count) ((type*)oc_arena_push(arena, sizeof(type) * (count)))
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
//NOTE(martin): memory pool //NOTE(martin): memory pool
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
//TODO: we could probably remove pool. Most of the time we construct pool on top of //TODO: we could probably remove pool. Most of the time we construct pool on top of
// arenas "manually" with different free lists per object types... // arenas "manually" with different free lists per object types...
typedef struct oc_pool typedef struct oc_pool
{ {
oc_arena arena; oc_arena arena;
oc_list freeList; oc_list freeList;
u64 blockSize; u64 blockSize;
} oc_pool; } oc_pool;
typedef struct oc_pool_options typedef struct oc_pool_options
{ {
oc_base_allocator* base; oc_base_allocator* base;
u64 reserve; u64 reserve;
} oc_pool_options; } oc_pool_options;
ORCA_API void oc_pool_init(oc_pool* pool, u64 blockSize); ORCA_API void oc_pool_init(oc_pool* pool, u64 blockSize);
ORCA_API void oc_pool_init_with_options(oc_pool* pool, u64 blockSize, oc_pool_options* options); ORCA_API void oc_pool_init_with_options(oc_pool* pool, u64 blockSize, oc_pool_options* options);
ORCA_API void oc_pool_cleanup(oc_pool* pool); ORCA_API void oc_pool_cleanup(oc_pool* pool);
ORCA_API void* oc_pool_alloc(oc_pool* pool); ORCA_API void* oc_pool_alloc(oc_pool* pool);
ORCA_API void oc_pool_recycle(oc_pool* pool, void* ptr); ORCA_API void oc_pool_recycle(oc_pool* pool, void* ptr);
ORCA_API void oc_pool_clear(oc_pool* pool); ORCA_API void oc_pool_clear(oc_pool* pool);
#define oc_pool_alloc_type(arena, type) ((type*)oc_pool_alloc(arena)) #define oc_pool_alloc_type(arena, type) ((type*)oc_pool_alloc(arena))
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
//NOTE(martin): per-thread implicit scratch arena //NOTE(martin): per-thread implicit scratch arena
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
ORCA_API oc_arena* oc_scratch(); ORCA_API oc_arena* oc_scratch();
ORCA_API oc_arena* oc_scratch_next(oc_arena* used); ORCA_API oc_arena* oc_scratch_next(oc_arena* used);
ORCA_API oc_arena_scope oc_scratch_begin(); ORCA_API oc_arena_scope oc_scratch_begin(void);
ORCA_API oc_arena_scope oc_scratch_begin_next(oc_arena* used); ORCA_API oc_arena_scope oc_scratch_begin_next(oc_arena* used);
#define oc_scratch_end(scope) oc_arena_scope_end(scope) #define oc_scratch_end(scope) oc_arena_scope_end(scope)

View File

@ -17,11 +17,10 @@
#include <string.h> #include <string.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C" {
{
#endif #endif
/*NOTE: /*NOTE:
By convention, functions that take an arena and return a string slice allocated on By convention, functions that take an arena and return a string slice allocated on
this arena, always allocate one more element and null-terminate the string. This is this arena, always allocate one more element and null-terminate the string. This is
done so we can pass those strings directly to C APIs that requires C strings without done so we can pass those strings directly to C APIs that requires C strings without
@ -31,14 +30,14 @@ extern "C"
into the original string. Only the _list nodes_ are allocated on the arena. into the original string. Only the _list nodes_ are allocated on the arena.
*/ */
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// u8 strings // u8 strings
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
typedef struct oc_str8 typedef struct oc_str8
{ {
u64 len; u64 len;
char* ptr; char* ptr;
} oc_str8; } oc_str8;
#define OC_STR8(s) ((oc_str8){ .len = (s) ? strlen(s) : 0, .ptr = (char*)s }) #define OC_STR8(s) ((oc_str8){ .len = (s) ? strlen(s) : 0, .ptr = (char*)s })
@ -51,109 +50,109 @@ extern "C"
#define oc_str8_lp(s) ((s).len), ((s).ptr) #define oc_str8_lp(s) ((s).len), ((s).ptr)
#define oc_str8_ip(s) (int)oc_str8_lp(s) #define oc_str8_ip(s) (int)oc_str8_lp(s)
ORCA_API oc_str8 oc_str8_from_buffer(u64 len, char* buffer); ORCA_API oc_str8 oc_str8_from_buffer(u64 len, char* buffer);
ORCA_API oc_str8 oc_str8_slice(oc_str8 s, u64 start, u64 end); ORCA_API oc_str8 oc_str8_slice(oc_str8 s, u64 start, u64 end);
ORCA_API oc_str8 oc_str8_push_buffer(oc_arena* arena, u64 len, char* buffer); ORCA_API oc_str8 oc_str8_push_buffer(oc_arena* arena, u64 len, char* buffer);
ORCA_API oc_str8 oc_str8_push_cstring(oc_arena* arena, const char* str); ORCA_API oc_str8 oc_str8_push_cstring(oc_arena* arena, const char* str);
ORCA_API oc_str8 oc_str8_push_copy(oc_arena* arena, oc_str8 s); ORCA_API oc_str8 oc_str8_push_copy(oc_arena* arena, oc_str8 s);
ORCA_API oc_str8 oc_str8_push_slice(oc_arena* arena, oc_str8 s, u64 start, u64 end); ORCA_API oc_str8 oc_str8_push_slice(oc_arena* arena, oc_str8 s, u64 start, u64 end);
ORCA_API oc_str8 oc_str8_pushfv(oc_arena* arena, const char* format, va_list args); ORCA_API oc_str8 oc_str8_pushfv(oc_arena* arena, const char* format, va_list args);
ORCA_API oc_str8 oc_str8_pushf(oc_arena* arena, const char* format, ...); ORCA_API oc_str8 oc_str8_pushf(oc_arena* arena, const char* format, ...);
ORCA_API int oc_str8_cmp(oc_str8 s1, oc_str8 s2); ORCA_API int oc_str8_cmp(oc_str8 s1, oc_str8 s2);
ORCA_API char* oc_str8_to_cstring(oc_arena* arena, oc_str8 string); ORCA_API char* oc_str8_to_cstring(oc_arena* arena, oc_str8 string);
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// string lists // string lists
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
typedef struct oc_str8_elt typedef struct oc_str8_elt
{ {
oc_list_elt listElt; oc_list_elt listElt;
oc_str8 string; oc_str8 string;
} oc_str8_elt; } oc_str8_elt;
typedef struct oc_str8_list typedef struct oc_str8_list
{ {
oc_list list; oc_list list;
u64 eltCount; u64 eltCount;
u64 len; u64 len;
} oc_str8_list; } oc_str8_list;
ORCA_API void oc_str8_list_push(oc_arena* arena, oc_str8_list* list, oc_str8 str); ORCA_API void oc_str8_list_push(oc_arena* arena, oc_str8_list* list, oc_str8 str);
ORCA_API void oc_str8_list_pushf(oc_arena* arena, oc_str8_list* list, const char* format, ...); ORCA_API void oc_str8_list_pushf(oc_arena* arena, oc_str8_list* list, const char* format, ...);
ORCA_API oc_str8 oc_str8_list_collate(oc_arena* arena, oc_str8_list list, oc_str8 prefix, oc_str8 separator, oc_str8 postfix); ORCA_API oc_str8 oc_str8_list_collate(oc_arena* arena, oc_str8_list list, oc_str8 prefix, oc_str8 separator, oc_str8 postfix);
ORCA_API oc_str8 oc_str8_list_join(oc_arena* arena, oc_str8_list list); ORCA_API oc_str8 oc_str8_list_join(oc_arena* arena, oc_str8_list list);
ORCA_API oc_str8_list oc_str8_split(oc_arena* arena, oc_str8 str, oc_str8_list separators); ORCA_API oc_str8_list oc_str8_split(oc_arena* arena, oc_str8 str, oc_str8_list separators);
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// u16 strings // u16 strings
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
typedef struct oc_str16 typedef struct oc_str16
{ {
u64 len; u64 len;
u16* ptr; u16* ptr;
} oc_str16; } oc_str16;
ORCA_API oc_str16 oc_str16_from_buffer(u64 len, u16* buffer); ORCA_API oc_str16 oc_str16_from_buffer(u64 len, u16* buffer);
ORCA_API oc_str16 oc_str16_slice(oc_str16 s, u64 start, u64 end); ORCA_API oc_str16 oc_str16_slice(oc_str16 s, u64 start, u64 end);
ORCA_API oc_str16 oc_str16_push_buffer(oc_arena* arena, u64 len, u16* buffer); ORCA_API oc_str16 oc_str16_push_buffer(oc_arena* arena, u64 len, u16* buffer);
ORCA_API oc_str16 oc_str16_push_copy(oc_arena* arena, oc_str16 s); ORCA_API oc_str16 oc_str16_push_copy(oc_arena* arena, oc_str16 s);
ORCA_API oc_str16 oc_str16_push_slice(oc_arena* arena, oc_str16 s, u64 start, u64 end); ORCA_API oc_str16 oc_str16_push_slice(oc_arena* arena, oc_str16 s, u64 start, u64 end);
typedef struct oc_str16_elt typedef struct oc_str16_elt
{ {
oc_list_elt listElt; oc_list_elt listElt;
oc_str16 string; oc_str16 string;
} oc_str16_elt; } oc_str16_elt;
typedef struct oc_str16_list typedef struct oc_str16_list
{ {
oc_list list; oc_list list;
u64 eltCount; u64 eltCount;
u64 len; u64 len;
} oc_str16_list; } oc_str16_list;
ORCA_API void oc_str16_list_push(oc_arena* arena, oc_str16_list* list, oc_str16 str); ORCA_API void oc_str16_list_push(oc_arena* arena, oc_str16_list* list, oc_str16 str);
ORCA_API oc_str16 oc_str16_list_join(oc_arena* arena, oc_str16_list list); ORCA_API oc_str16 oc_str16_list_join(oc_arena* arena, oc_str16_list list);
ORCA_API oc_str16_list oc_str16_split(oc_arena* arena, oc_str16 str, oc_str16_list separators); ORCA_API oc_str16_list oc_str16_split(oc_arena* arena, oc_str16 str, oc_str16_list separators);
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// u32 strings // u32 strings
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
typedef struct oc_str32 typedef struct oc_str32
{ {
u64 len; u64 len;
u32* ptr; u32* ptr;
} oc_str32; } oc_str32;
ORCA_API oc_str32 oc_str32_from_buffer(u64 len, u32* buffer); ORCA_API oc_str32 oc_str32_from_buffer(u64 len, u32* buffer);
ORCA_API oc_str32 oc_str32_slice(oc_str32 s, u64 start, u64 end); ORCA_API oc_str32 oc_str32_slice(oc_str32 s, u64 start, u64 end);
ORCA_API oc_str32 oc_str32_push_buffer(oc_arena* arena, u64 len, u32* buffer); ORCA_API oc_str32 oc_str32_push_buffer(oc_arena* arena, u64 len, u32* buffer);
ORCA_API oc_str32 oc_str32_push_copy(oc_arena* arena, oc_str32 s); ORCA_API oc_str32 oc_str32_push_copy(oc_arena* arena, oc_str32 s);
ORCA_API oc_str32 oc_str32_push_slice(oc_arena* arena, oc_str32 s, u64 start, u64 end); ORCA_API oc_str32 oc_str32_push_slice(oc_arena* arena, oc_str32 s, u64 start, u64 end);
typedef struct oc_str32_elt typedef struct oc_str32_elt
{ {
oc_list_elt listElt; oc_list_elt listElt;
oc_str32 string; oc_str32 string;
} oc_str32_elt; } oc_str32_elt;
typedef struct oc_str32_list typedef struct oc_str32_list
{ {
oc_list list; oc_list list;
u64 eltCount; u64 eltCount;
u64 len; u64 len;
} oc_str32_list; } oc_str32_list;
ORCA_API void oc_str32_list_push(oc_arena* arena, oc_str32_list* list, oc_str32 str); ORCA_API void oc_str32_list_push(oc_arena* arena, oc_str32_list* list, oc_str32 str);
ORCA_API oc_str32 oc_str32_list_join(oc_arena* arena, oc_str32_list list); ORCA_API oc_str32 oc_str32_list_join(oc_arena* arena, oc_str32_list list);
ORCA_API oc_str32_list oc_str32_split(oc_arena* arena, oc_str32 str, oc_str32_list separators); ORCA_API oc_str32_list oc_str32_split(oc_arena* arena, oc_str32 str, oc_str32_list separators);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View File

@ -5,5 +5,33 @@
"ret": {"name": "oc_io_cmp", "tag": "S"}, "ret": {"name": "oc_io_cmp", "tag": "S"},
"args": [ {"name": "req", "args": [ {"name": "req",
"type": {"name": "oc_io_req*", "tag": "p"}}] "type": {"name": "oc_io_req*", "tag": "p"}}]
},
{
"name": "oc_file_open_with_request",
"cname": "oc_file_open_with_request_bridge",
"ret": {"name": "oc_file", "tag": "S"},
"args": [
{"name": "path",
"type": {"name": "oc_str8", "tag": "S"}},
{"name": "rights",
"type": {"name": "oc_file_access", "tag": "i"}},
{"name": "flags",
"type": {"name": "oc_file_open_flags", "tag": "i"}}
]
},
{
"name": "oc_file_open_with_dialog",
"cname": "oc_file_open_with_dialog_bridge",
"ret": {"name": "oc_file_open_with_dialog_result", "cname": "oc_wasm_file_open_with_dialog_result", "tag": "S"},
"args": [
{"name": "arena",
"type": {"name": "oc_arena*", "cname": "i32", "tag": "i"}},
{"name": "rights",
"type": {"name": "oc_file_access", "tag": "i"}},
{"name": "flags",
"type": {"name": "oc_file_open_flags", "tag": "i"}},
{"name": "desc",
"type": {"name": "oc_file_dialog_desc*", "cname": "oc_wasm_file_dialog_desc*", "tag": "p"}}
]
} }
] ]

1
tests/file_dialog/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bin

View File

@ -0,0 +1,7 @@
set INCLUDES=/I ..\..\src /I ..\..\ext
mkdir bin
cl /we4013 /Zi /Zc:preprocessor /std:c11 /experimental:c11atomics %INCLUDES% main.c /link /LIBPATH:../../build/bin orca.dll.lib /out:bin/test_file_dialog.exe
copy ..\..\build\bin\orca.dll bin

18
tests/file_dialog/build.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
LIBDIR=../../build/bin
SRCDIR=../../src
INCLUDES="-I$SRCDIR"
LIBS="-L$LIBDIR -lorca"
FLAGS="-mmacos-version-min=10.15.4 -DOC_DEBUG -DLOG_COMPILE_DEBUG"
if [ ! \( -e bin \) ] ; then
mkdir ./bin
fi
clang -g $FLAGS $LIBS $INCLUDES -o ./bin/test_file_dialog main.c
cp $LIBDIR/liborca.dylib ./bin/
install_name_tool -add_rpath "@executable_path" ./bin/test_file_dialog

45
tests/file_dialog/main.c Normal file
View File

@ -0,0 +1,45 @@
/************************************************************/ /**
*
* @file: main.c
* @author: Martin Fouilleul
* @date: 26/05/2023
*
*****************************************************************/
#include "orca.h"
int main(int argc, char** argv)
{
oc_init();
oc_str8 path = oc_path_executable_relative(oc_scratch(), OC_STR8("../"));
oc_file dir = oc_file_open(path, OC_FILE_ACCESS_READ, 0);
oc_file_dialog_desc desc = {
.kind = OC_FILE_DIALOG_OPEN,
.flags = OC_FILE_DIALOG_FILES | OC_FILE_DIALOG_MULTIPLE,
.title = OC_STR8("Select Files"),
.okLabel = OC_STR8("Select"),
.startAt = dir,
.startPath = OC_STR8(".."),
};
oc_str8_list_push(oc_scratch(), &desc.filters, OC_STR8("txt"));
oc_file_dialog_result res = oc_file_dialog(oc_scratch(), &desc);
if(res.button == OC_FILE_DIALOG_CANCEL)
{
oc_log_error("Cancel\n");
}
else
{
oc_log_info("Selected files:\n");
oc_list_for(&res.selection.list, elt, oc_str8_elt, listElt)
{
oc_log_info("\t%.*s\n", (int)elt->string.len, elt->string.ptr);
}
}
return (0);
}

View File

@ -0,0 +1 @@
The quick brown fox jumps over the lazy dog

View File

@ -0,0 +1 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit.

1
tests/file_open_dialog/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bin

View File

@ -0,0 +1,5 @@
set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext
cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.dll.lib /out:../../bin/test_open_dialog.exe
copy ..\..\build\bin\orca.dll bin

18
tests/file_open_dialog/build.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
LIBDIR=../../build/bin
SRCDIR=../../src
INCLUDES="-I$SRCDIR"
LIBS="-L$LIBDIR -lorca"
FLAGS="-mmacos-version-min=10.15.4 -DOC_DEBUG -DLOG_COMPILE_DEBUG"
if [ ! \( -e bin \) ] ; then
mkdir ./bin
fi
clang -g $FLAGS $LIBS $INCLUDES -o ./bin/test_open_dialog main.c
cp $LIBDIR/liborca.dylib ./bin/
install_name_tool -add_rpath "@executable_path" ./bin/test_open_dialog

View File

@ -0,0 +1,37 @@
/************************************************************/ /**
*
* @file: main.c
* @author: Martin Fouilleul
* @date: 26/05/2023
*
*****************************************************************/
#include "orca.h"
int main(int argc, char** argv)
{
oc_init();
oc_file_dialog_desc desc = {
.kind = OC_FILE_DIALOG_OPEN,
.flags = OC_FILE_DIALOG_FILES,
.title = OC_STR8("Select Files"),
.okLabel = OC_STR8("Select")
};
oc_str8_list_push(oc_scratch(), &desc.filters, OC_STR8("txt"));
oc_file file = oc_file_open_with_dialog(OC_FILE_ACCESS_READ, 0, &desc);
if(oc_file_is_nil(file))
{
oc_log_error("Couldn't open file\n");
}
else
{
char buffer[1024];
u64 len = oc_file_read(file, 1024, buffer);
oc_log_info("file contents: %.*s\n", (int)len, buffer);
}
return (0);
}

View File

@ -0,0 +1 @@
The quick brown fox jumps over the lazy dog

1
tests/file_open_request/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bin

View File

@ -0,0 +1,5 @@
set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext
cl /we4013 /Zi /Zc:preprocessor /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.dll.lib /out:../../bin/test_open_request.exe
copy ..\..\build\bin\orca.dll bin

View File

@ -0,0 +1,18 @@
#!/bin/bash
LIBDIR=../../build/bin
SRCDIR=../../src
INCLUDES="-I$SRCDIR"
LIBS="-L$LIBDIR -lorca"
FLAGS="-mmacos-version-min=10.15.4 -DOC_DEBUG -DLOG_COMPILE_DEBUG"
if [ ! \( -e bin \) ] ; then
mkdir ./bin
fi
clang -g $FLAGS $LIBS $INCLUDES -o ./bin/test_open_request main.c
cp $LIBDIR/liborca.dylib ./bin/
install_name_tool -add_rpath "@executable_path" ./bin/test_open_request

View File

@ -0,0 +1,28 @@
/************************************************************/ /**
*
* @file: main.c
* @author: Martin Fouilleul
* @date: 26/05/2023
*
*****************************************************************/
#include "orca.h"
int main(int argc, char** argv)
{
oc_init();
oc_file file = oc_file_open_with_request(OC_STR8("./test.txt"), OC_FILE_ACCESS_READ, 0);
if(oc_file_is_nil(file))
{
oc_log_error("Couldn't open file\n");
}
else
{
char buffer[1024];
u64 len = oc_file_read(file, 1024, buffer);
oc_log_info("file contents: %.*s\n", (int)len, buffer);
}
return (0);
}

View File

@ -0,0 +1 @@
The quick brown fox jumps over the lazy dog