Skip to content

Commit

Permalink
Add Switch.profiles, Switch.currentProfile(), `Switch.selectProfi…
Browse files Browse the repository at this point in the history
…le()`
  • Loading branch information
TooTallNate committed Jan 6, 2024
1 parent 1e699c0 commit 33c4245
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 0 deletions.
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

0 comments on commit 33c4245

Please sign in to comment.