Skip to content

Commit

Permalink
Add Icon.loadResource()
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbuchan committed Mar 28, 2019
1 parent 66875ac commit 4f991e5
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 120 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export namespace Icon {

export function load(pathOrId: string | BuiltinId, size: Readonly<Size>): Icon;

export function loadResource(size: Readonly<Size>, id?: number, path?: string): Icon;
/** Native API to load a built-in icon at a specific size. */
export function loadBuiltin(id: BuiltinId, size: Readonly<Size>): Icon;
export function loadFile(path: string, size: Readonly<Size>): Icon;
Expand Down
136 changes: 80 additions & 56 deletions src/icon-object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,83 @@ napi_status napi_get_value(napi_env env, napi_value value,
return napi_ok;
}

napi_status load_icon(napi_env env, HINSTANCE hinstance, LPCWSTR path,
icon_size_t size, DWORD flags, napi_value* result) {
auto env_data = get_env_data(env);
auto shared = (flags & LR_SHARED) != 0;

auto icon = (HICON)LoadImageW(hinstance, path, IMAGE_ICON, size.width,
size.height, flags);
if (!icon) {
napi_throw_win32_error(env, "LoadImageW");
return napi_pending_exception;
}

Unique<HICON, DestroyIcon> owned_icon = shared ? icon : nullptr;

NAPI_RETURN_IF_NOT_OK(
IconObject::new_instance(env, env_data->icon_constructor, result));
IconObject* wrapped = nullptr;
NAPI_RETURN_IF_NOT_OK(IconObject::try_unwrap(env, *result, &wrapped));
wrapped->icon = icon;
wrapped->shared = shared;
wrapped->width = size.width;
wrapped->height = size.height;
return napi_ok;
}

napi_value export_Icon_loadBuiltin(napi_env env, napi_callback_info info) {
uint32_t id;
icon_size_t size;
NAPI_RETURN_NULL_IF_NOT_OK(napi_get_required_args(env, info, &id, &size));

auto env_data = get_env_data(env);
napi_value result;
NAPI_RETURN_NULL_IF_NOT_OK(IconObject::load(env_data, MAKEINTRESOURCE(id),
size, LR_SHARED, &result));
NAPI_RETURN_NULL_IF_NOT_OK(
load_icon(env, nullptr, MAKEINTRESOURCE(id), size, LR_SHARED, &result));
return result;
}

napi_value export_Icon_loadResource(napi_env env, napi_callback_info info) {
icon_size_t size;
std::optional<uint32_t> id;
std::optional<std::wstring> path;
NAPI_RETURN_NULL_IF_NOT_OK(napi_get_args(env, info, 1, &size, &id, &path));

Unique<HMODULE, FreeLibrary> library;
HINSTANCE hinstance;
if (!path) {
hinstance = GetModuleHandle(nullptr);
} else {
library = hinstance = LoadLibraryExW(
path->c_str(), nullptr,
LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (!hinstance) {
napi_throw_win32_error(env, "LoadLibraryExW");
return nullptr;
}
}

LPWSTR resource;
if (id) {
resource = MAKEINTRESOURCEW(id.value());
} else {
// Use first RT_GROUP_ICON, the same as Windows uses for an .exe icon.
EnumResourceNamesW(
hinstance, RT_GROUP_ICON,
[](HMODULE hModule, LPCWSTR lpType, LPWSTR lpName, LONG_PTR lParam) {
*reinterpret_cast<LPWSTR*>(lParam) = lpName;
return FALSE;
},
reinterpret_cast<LONG_PTR>(&resource));
if (auto error = GetLastError(); error != ERROR_RESOURCE_ENUM_USER_STOP) {
napi_throw_win32_error(env, "EnumResourceNamesW", error);
return nullptr;
}
}

napi_value result;
NAPI_RETURN_NULL_IF_NOT_OK(
load_icon(env, hinstance, resource, size, 0, &result));
return result;
}

Expand All @@ -26,10 +94,9 @@ napi_value export_Icon_loadFile(napi_env env, napi_callback_info info) {
icon_size_t size;
NAPI_RETURN_NULL_IF_NOT_OK(napi_get_required_args(env, info, &path, &size));

auto env_data = get_env_data(env);
napi_value result;
NAPI_RETURN_NULL_IF_NOT_OK(
IconObject::load(env_data, path.c_str(), size, LR_LOADFROMFILE, &result));
load_icon(env, nullptr, path.c_str(), size, LR_LOADFROMFILE, &result));
return result;
}

Expand All @@ -55,25 +122,12 @@ napi_property_descriptor member_getter_property(
const char* utf8name,
napi_property_attributes attributes = napi_enumerable) {
auto getter = [](napi_env env, napi_callback_info info) -> napi_value {
napi_value this_value;
IconObject* icon_object = nullptr;
NAPI_RETURN_NULL_IF_NOT_OK(napi_get_this_arg(env, info, &icon_object));
napi_value result;
NAPI_THROW_RETURN_NULL_IF_NOT_OK(
env,
napi_get_cb_info(env, info, nullptr, nullptr, &this_value, nullptr));

if (auto [status, wrapped] = IconObject::try_unwrap(env, this_value);
status != napi_ok) {
napi_throw_last_error(env);
return nullptr;
} else if (!wrapped) {
napi_throw_type_error(env, nullptr,
"'this' must be an instance of Icon.");
return nullptr;
} else {
napi_value result;
NAPI_THROW_RETURN_NULL_IF_NOT_OK(
env, napi_create(env, wrapped->*member, &result));
return result;
}
env, napi_create(env, icon_object->*member, &result));
return result;
};

return napi_getter_property(utf8name, getter, attributes, nullptr);
Expand Down Expand Up @@ -107,43 +161,13 @@ napi_status IconObject::define_class(EnvData* env_data,
{
napi_value_property("small", small_value, napi_static),
napi_value_property("large", large_value, napi_static),
napi_method_property("loadResource", export_Icon_loadResource,
napi_static),
napi_method_property("loadBuiltin", export_Icon_loadBuiltin,
napi_static),
napi_method_property("loadFile", export_Icon_loadFile, napi_static),

member_getter_property<&IconObject::width>("width"),
member_getter_property<&IconObject::height>("height"),
});
}

napi_status IconObject::load(EnvData* env_data, LPCWSTR path, icon_size_t size,
DWORD flags, napi_value* result) {
auto env = env_data->env;
auto shared = (flags & LR_SHARED) != 0;

auto icon = (HICON)LoadImageW(nullptr, path, IMAGE_ICON, size.width,
size.height, flags);

if (!icon) {
napi_throw_win32_error(env, "LoadImageW");
return napi_pending_exception;
}

if (auto [status, wrapper, wrapped] =
NapiWrapped::new_instance(env, env_data->icon_constructor);
status != napi_ok) {
if (!shared) {
DestroyIcon(icon);
}

napi_throw_last_error(env);
return napi_pending_exception;
} else {
wrapped->icon = icon;
wrapped->shared = shared;
wrapped->width = size.width;
wrapped->height = size.height;
*result = wrapper;
return napi_ok;
}
}
}
3 changes: 0 additions & 3 deletions src/icon-object.hh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,4 @@ struct IconObject : NapiWrapped<IconObject> {

static napi_status define_class(EnvData* env_data,
napi_value* constructor_value);

static napi_status load(EnvData* env_data, LPCWSTR path, icon_size_t size,
DWORD flags, napi_value* result);
};
30 changes: 14 additions & 16 deletions src/menu-object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,10 @@ static napi_value wrap_menu(napi_env env, MenuHandle menu) {

auto env_data = get_env_data(env);

if (auto [status, wrapper, wrapped] =
MenuObject::new_instance(env_data, std::move(menu));
status != napi_ok) {
napi_throw_last_error(env);
return nullptr;
} else {
return wrapper;
}
napi_value result;
NAPI_THROW_RETURN_NULL_IF_NOT_OK(
env, MenuObject::new_instance(env_data, std::move(menu), &result));
return result;
}

napi_value export_Menu_create(napi_env env, napi_callback_info info) {
Expand Down Expand Up @@ -238,7 +234,7 @@ napi_value export_Menu_showSync(napi_env env, napi_callback_info info) {
mouse_x, mouse_y, env_data->msg_hwnd, nullptr);
if (!item_id) {
if (auto code = GetLastError(); code) {
napi_throw_win32_error(env, "TrackPopupMenuEx", (HRESULT) code);
napi_throw_win32_error(env, "TrackPopupMenuEx", (HRESULT)code);
return nullptr;
}
}
Expand Down Expand Up @@ -370,13 +366,15 @@ auto MenuObject::define_class(EnvData* env_data, napi_value* constructor_value)
});
}

auto MenuObject::new_instance(EnvData* env_data, MenuHandle menu) -> NewResult {
auto result =
NapiWrapped::new_instance(env_data->env, env_data->menu_constructor);
if (result.wrapped) {
result.wrapped->menu = std::move(menu);
}
return result;
napi_status MenuObject::new_instance(EnvData* env_data, MenuHandle menu,
napi_value* result) {
NAPI_RETURN_IF_NOT_OK(NapiWrapped::new_instance(
env_data->env, env_data->menu_constructor, result));
MenuObject* wrapped = nullptr;
NAPI_RETURN_IF_NOT_OK(
NapiWrapped::try_unwrap(env_data->env, *result, &wrapped));
wrapped->menu = std::move(menu);
return napi_ok;
}

napi_status MenuObject::init(napi_env env, napi_callback_info info,
Expand Down
3 changes: 2 additions & 1 deletion src/menu-object.hh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ struct MenuObject : NapiWrapped<MenuObject> {

static napi_status define_class(EnvData* env_data,
napi_value* constructor_value);
static NewResult new_instance(EnvData* env_data, MenuHandle menu);
static napi_status new_instance(EnvData* env_data, MenuHandle menu,
napi_value* result);

protected:
friend NapiWrapped;
Expand Down
67 changes: 24 additions & 43 deletions src/napi/wrap.hh
Original file line number Diff line number Diff line change
Expand Up @@ -52,58 +52,41 @@ struct NapiWrapped {
delete (TypeWrapper*)finalize_data;
}

struct NewResult {
napi_status status;
napi_value wrapper;
T* wrapped;
};

static NewResult new_instance(napi_env env, napi_ref constructor_ref,
std::initializer_list<napi_value> args = {}) {
static napi_status new_instance(napi_env env, napi_ref constructor_ref,
napi_value* result,
std::initializer_list<napi_value> args = {}) {
napi_value constructor = nullptr;
NAPI_RETURN_INIT_IF_NOT_OK(
NAPI_RETURN_IF_NOT_OK(
napi_get_reference_value(env, constructor_ref, &constructor));
return new_instance(env, constructor, args);
return napi_new_instance(env, constructor, args.size(), args.begin(),
result);
}

static NewResult new_instance(napi_env env, napi_value constructor,
std::initializer_list<napi_value> args) {
napi_value wrapper;
NAPI_RETURN_INIT_IF_NOT_OK(napi_new_instance(env, constructor, args.size(),
args.begin(), &wrapper));

auto [status, wrapped] = try_unwrap(env, wrapper);
return {status, wrapper, wrapped};
static napi_status new_instance(napi_env env, napi_value constructor,
napi_value* result,
std::initializer_list<napi_value> args = {}) {
return napi_new_instance(env, constructor, args.size(), args.begin(),
result);
}

struct UnwrapResult {
napi_status status;
T* wrapped;
};

static UnwrapResult try_unwrap(napi_env env, napi_value value) {
void* void_wrapped = nullptr;
NAPI_RETURN_INIT_IF_NOT_OK(napi_unwrap(env, value, &void_wrapped));
auto type_wrapped = (TypeWrapper*)void_wrapped;
if (type_wrapped->type_ptr != &type_object) {
return {napi_ok};
}

return {napi_ok, &type_wrapped->value};
static napi_status try_unwrap(napi_env env, napi_value value, T** result) {
void* raw = nullptr;
NAPI_RETURN_IF_NOT_OK(napi_unwrap(env, value, &raw));
auto typed = static_cast<TypeWrapper*>(raw);
*result = typed->type_ptr != &type_object ? nullptr : &typed->value;
return napi_ok;
}

using Ref = NapiUnwrappedRef<T>;

static napi_status try_create_ref(napi_env env, napi_value value,
Ref* result) {
if (auto [status, wrapped] = try_unwrap(env, value); !wrapped) {
result->wrapped = nullptr;
if (auto status = try_unwrap(env, value, &result->wrapped);
!result->wrapped) {
return status;
} else {
NAPI_RETURN_IF_NOT_OK(result->create(env, value));
result->wrapped = wrapped;
return napi_ok;
}
NAPI_RETURN_IF_NOT_OK(result->create(env, value));
return napi_ok;
}

protected:
Expand Down Expand Up @@ -151,17 +134,15 @@ constexpr bool is_complete_base_of_v = std::is_base_of_v<Base, Derived>;
template <typename T, typename = std::enable_if_t<
detail::is_complete_base_of_v<NapiWrapped<T>, T>>>
napi_status napi_get_value(napi_env env, napi_value value, T** result) {
auto unwrap = NapiWrapped<T>::try_unwrap(env, value);
if (unwrap.status != napi_ok) {
auto status = NapiWrapped<T>::try_unwrap(env, value, result);
if (status != napi_ok) {
return napi_throw_last_error(env);
}

if (!unwrap.wrapped) {
if (!*result) {
napi_throw_type_error(env, nullptr, "Invalid native object type");
return napi_pending_exception;
}

*result = unwrap.wrapped;

return napi_ok;
}
3 changes: 2 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ catchErrors(() => {
const guid = "ded73175-c489-4f7e-acdc-3dbdde784468";
const guid2 = "ded73175-c489-4f7e-acdc-3dbdde784469";

const icon = Icon.load(`${__dirname}/stop.ico`, Icon.small);
// const icon = Icon.load(`${__dirname}/stop.ico`, Icon.small);
const icon = Icon.loadResource(Icon.small, undefined, "cmd.exe");
const altIcon = Icon.load(Icon.ids.warning, Icon.small);
const notificationIcon = Icon.load(`${__dirname}/lightbulb.ico`, Icon.large);

Expand Down

0 comments on commit 4f991e5

Please sign in to comment.