Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Switch.profiles, Switch.currentProfile(), Switch.selectProfile() #77

Merged
merged 1 commit into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rare-seals-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'nxjs-runtime': patch
---

Add `Switch.profiles`, `Switch.currentProfile()`, `Switch.selectProfile()`
8 changes: 8 additions & 0 deletions packages/runtime/src/$.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
Application,
IRSensor,
NetworkInfo,
Profile,
Stats,
Versions,
} from './switch';
Expand Down Expand Up @@ -32,6 +33,13 @@ type ClassOf<T> = {
};

export interface Init {
// account.c
accountInitialize(): () => void;
accountProfileInit(c: ClassOf<Profile>): void;
accountCurrentProfile(): Profile | null;
accountSelectProfile(): Profile | null;
accountProfiles(): Profile[];

// applet.c
appletIlluminance(): number;
appletGetAppletType(): number;
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './inspect';
export * from './switch/nifm';
export * from './switch/ns';
export * from './switch/irsensor';
export * from './switch/profile';
export { Socket, Server };

export type PathLike = string | URL;
Expand Down
84 changes: 84 additions & 0 deletions packages/runtime/src/switch/profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { $ } from '../$';
import { assertInternalConstructor } from '../utils';

let init = false;

function _init() {
if (init) return;
addEventListener('unload', $.accountInitialize());
init = true;
}

/**
* Represents a user profile that exists on the system.
*/
export class Profile {
/**
* The unique ID of the profile, represented as an array of two `bigint` values.
*/
declare readonly uid: [bigint, bigint];

/**
* The human readable nickname of the profile.
*/
declare readonly nickname: string;

/**
* The raw JPEG data for the profile image. Can be decoded with the `Image` class.
*/
declare readonly image: ArrayBuffer;

/**
* @ignore
*/
constructor() {
assertInternalConstructor(arguments);
}
}
$.accountProfileInit(Profile);

/**
* Return a {@link Profile} instance if there was a preselected user
* when launching the application, or `null` if there was none.
*/
export function currentProfile() {
_init();
const p = $.accountCurrentProfile();
if (p) Object.setPrototypeOf(p, Profile.prototype);
return p;
}

/**
* Shows the user selection interface and returns a {@link Profile}
* instance representing the user that was selected.
*
* @note This function blocks the event loop until the user has made their selection.
*/
export function selectProfile() {
_init();
const p = $.accountSelectProfile();
if (p) Object.setPrototypeOf(p, Profile.prototype);
return p;
}

/**
* Can be used as an iterator to retrieve the list of user profiles.
*
* @example
*
* ```typescript
* for (const profile of Switch.profiles) {
* console.log(profile.nickname);
* }
* ```
*/
export const profiles = {
*[Symbol.iterator]() {
_init();
const pr = $.accountProfiles();
for (const p of pr) {
Object.setPrototypeOf(p, Profile.prototype);
yield p;
}
},
};
235 changes: 235 additions & 0 deletions source/account.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#include "account.h"

static JSClassID nx_profile_class_id;

typedef struct
{
AccountUid uid;
AccountProfile profile;
AccountUserData userdata;
AccountProfileBase profilebase;
bool profile_loaded;
bool userdata_loaded;
} nx_profile_t;

static void finalizer_profile(JSRuntime *rt, JSValue val)
{
nx_profile_t *profile = JS_GetOpaque(val, nx_profile_class_id);
if (profile)
{
if (profile->profile_loaded)
{
accountProfileClose(&profile->profile);
}
js_free_rt(rt, profile);
}
}

JSValue profile_new(JSContext *ctx, AccountUid uid)
{
JSValue obj = JS_NewObjectClass(ctx, nx_profile_class_id);
if (JS_IsException(obj))
{
return obj;
}
nx_profile_t *profile = js_mallocz(ctx, sizeof(nx_profile_t));
if (!profile)
{
return JS_EXCEPTION;
}
profile->uid = uid;
JS_SetOpaque(obj, profile);
JSValue uid_arr = JS_NewArray(ctx);
for (int i = 0; i < 2; i++)
{
JS_SetPropertyUint32(ctx, uid_arr, i, JS_NewBigUint64(ctx, uid.uid[i]));
}
JS_SetPropertyStr(ctx, obj, "uid", uid_arr);
return obj;
}

nx_profile_t *nx_get_profile(JSContext *ctx, JSValueConst obj)
{
return JS_GetOpaque2(ctx, obj, nx_profile_class_id);
}

static JSValue nx_account_exit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
accountExit();
return JS_UNDEFINED;
}

static JSValue nx_account_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Result rc = accountInitialize(AccountServiceType_System);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountInitialize() returned 0x%x", rc);
return JS_EXCEPTION;
}
return JS_NewCFunction(ctx, nx_account_exit, "", 0);
}

static JSValue nx_account_current_profile(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
AccountUid uid;
Result rc = accountGetPreselectedUser(&uid);
if (R_FAILED(rc))
{
fprintf(stderr, "accountGetPreselectedUser() returned 0x%x\n", rc);
return JS_NULL;
}
return profile_new(ctx, uid);
}

static JSValue nx_account_select_profile(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
AccountUid uid;
PselUserSelectionSettings settings;
memset(&settings, 0, sizeof(settings));

Result rc = pselShowUserSelector(&uid, &settings);
if (R_FAILED(rc))
{
if (rc == 0x27C /* ResultCancelledByUser*/)
{
return JS_NULL;
}
JS_ThrowInternalError(ctx, "pselShowUserSelector() returned 0x%x", rc);
return JS_EXCEPTION;
}
return profile_new(ctx, uid);
}

static JSValue nx_account_profiles(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Result rc;
s32 user_count;
rc = accountGetUserCount(&user_count);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountGetUserCount() returned 0x%x", rc);
return JS_EXCEPTION;
}

AccountUid uids[user_count];
rc = accountListAllUsers(uids, user_count, &user_count);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountListAllUsers() returned 0x%x", rc);
return JS_EXCEPTION;
}

JSValue arr = JS_NewArray(ctx);
for (int i = 0; i < user_count; i++)
{
JSValue obj = profile_new(ctx, uids[i]);
if (JS_IsException(obj))
{
return obj;
}
JS_SetPropertyUint32(ctx, arr, i, obj);
}
return arr;
}

static JSValue nx_account_nickname(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Result rc;
nx_profile_t *profile = nx_get_profile(ctx, this_val);
if (!profile)
{
return JS_EXCEPTION;
}
if (!profile->profile_loaded)
{
rc = accountGetProfile(&profile->profile, profile->uid);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountGetProfile() returned 0x%x", rc);
return JS_EXCEPTION;
}
profile->profile_loaded = true;
}
if (!profile->userdata_loaded)
{
rc = accountProfileGet(&profile->profile, &profile->userdata, &profile->profilebase);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountProfileGet() returned 0x%x", rc);
return JS_EXCEPTION;
}
profile->userdata_loaded = true;
}
size_t len = strnlen(profile->profilebase.nickname, sizeof(profile->profilebase.nickname));
return JS_NewStringLen(ctx, profile->profilebase.nickname, len);
}

static JSValue nx_account_image(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
Result rc;
nx_profile_t *profile = nx_get_profile(ctx, this_val);
if (!profile)
{
return JS_EXCEPTION;
}
if (!profile->profile_loaded)
{
rc = accountGetProfile(&profile->profile, profile->uid);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountGetProfile() returned 0x%x", rc);
return JS_EXCEPTION;
}
profile->profile_loaded = true;
}

u32 image_size;
rc = accountProfileGetImageSize(&profile->profile, &image_size);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountProfileGetImageSize() returned 0x%x", rc);
return JS_EXCEPTION;
}

u8 buf[image_size];
rc = accountProfileLoadImage(&profile->profile, buf, image_size, &image_size);
if (R_FAILED(rc))
{
JS_ThrowInternalError(ctx, "accountProfileLoadImage() returned 0x%x", rc);
return JS_EXCEPTION;
}
return JS_NewArrayBufferCopy(ctx, buf, image_size);
}

static JSValue nx_account_profile_init(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JSAtom atom;
JSValue proto = JS_GetPropertyStr(ctx, argv[0], "prototype");
NX_DEF_GET(proto, "nickname", nx_account_nickname);
NX_DEF_GET(proto, "image", nx_account_image);
JS_FreeValue(ctx, proto);
return JS_UNDEFINED;
}

static const JSCFunctionListEntry function_list[] = {
JS_CFUNC_DEF("accountInitialize", 0, nx_account_initialize),
JS_CFUNC_DEF("accountProfileInit", 1, nx_account_profile_init),
JS_CFUNC_DEF("accountCurrentProfile", 0, nx_account_current_profile),
JS_CFUNC_DEF("accountSelectProfile", 0, nx_account_select_profile),
JS_CFUNC_DEF("accountProfiles", 0, nx_account_profiles),
};

void nx_init_account(JSContext *ctx, JSValueConst init_obj)
{
JSRuntime *rt = JS_GetRuntime(ctx);

JS_NewClassID(rt, &nx_profile_class_id);
JSClassDef profile_class = {
"Profile",
.finalizer = finalizer_profile,
};
JS_NewClass(rt, nx_profile_class_id, &profile_class);

JS_SetPropertyFunctionList(ctx, init_obj, function_list, countof(function_list));
}
3 changes: 3 additions & 0 deletions source/account.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "types.h"

void nx_init_account(JSContext *ctx, JSValueConst init_obj);
2 changes: 2 additions & 0 deletions source/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <quickjs.h>

#include "types.h"
#include "account.h"
#include "applet.h"
#include "async.h"
#include "battery.h"
Expand Down Expand Up @@ -536,6 +537,7 @@ int main(int argc, char *argv[])
// The internal `$` object contains native functions that are wrapped in the JS runtime
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue init_obj = JS_NewObject(ctx);
nx_init_account(ctx, init_obj);
nx_init_applet(ctx, init_obj);
nx_init_battery(ctx, init_obj);
nx_init_canvas(ctx, init_obj);
Expand Down