diff --git a/examples/accounts/account_ex.nim b/examples/accounts/account_ex.nim index 60db940..0732f7a 100644 --- a/examples/accounts/account_ex.nim +++ b/examples/accounts/account_ex.nim @@ -1,8 +1,10 @@ +import sets, strutils import libnx/graphics import libnx/wrapper/hid import libnx/wrapper/console import libnx/ext/integer128 import libnx/account +import libnx/input import libnx/app proc main() = @@ -25,10 +27,22 @@ proc main() = let msg = getCurrentExceptionMsg() echo "\x1b[6;2H" & msg - mainLoop: - let keysDown = hidKeysDown(CONTROLLER_P1_AUTO) + try: + let users = listAllUsers() + echo "" + echo " There are $# users:" % $users.len() + for user in users: + echo " User: " & user.username + except AccountUserListError: + let msg = getCurrentExceptionMsg() + echo msg - if (keysDown and KEY_PLUS.uint64) > 0.uint64: + + + mainLoop: + let keysDown = keysDown(Controller.P1_AUTO) + + if ControllerKey.Plus in keysDown: break main() diff --git a/libnxGen.cfg b/libnxGen.cfg index 1880157..ab71d91 100644 --- a/libnxGen.cfg +++ b/libnxGen.cfg @@ -84,6 +84,8 @@ defines = true defines = true search.static_assert = "static_assert" replace.static_assert = "// static_assert" +search.touch = "touchPosition" +replace.touch = "TouchPosition" [ipc.h] defines = true diff --git a/src/libnx/account.nim b/src/libnx/account.nim index 7ec03a9..f8d9942 100644 --- a/src/libnx/account.nim +++ b/src/libnx/account.nim @@ -11,6 +11,8 @@ type AccountInitError* = object of AccountError AccountImageSizeError* = object of AccountError AccountUserProfileError* = object of AccountError + AccountUserCountError* = object of AccountError + AccountUserListError* = object of AccountError AccountUserNotSelectedError* = object of AccountError AccountUserDataError* = object of AccountError AccountActiveUserError* = object of AccountError @@ -120,6 +122,32 @@ proc loadImage*(user: User): AccountImage = proc userID*(user: User): string = $user.id +proc getUser*(userID: u128): User = + ensureEnabled() + result = new(User) + + result.id = userID + result.selected = false + + var + prof: AccountProfile = getProfileHelper(userID) + userData: AccountUserData + profBase: AccountProfileBase + + let res = accountProfileGet(prof.addr, userData.addr, profBase.addr).newResult + + if res.failed: + raiseEx(AccountUserDataError, "Error, could not get user data: " & res.description) + + result.username = profBase.username.join("") + result.lastEdited = profBase.lastEditTimestamp + result.iconID = userData.iconID + result.iconBgColorID = userData.iconBackgroundColorID + result.miiID = userData.miiID + + prof.close() + + proc getActiveUser*(): User = ensureEnabled() result = new(User) @@ -171,3 +199,31 @@ proc getProfile*(user: User): Profile = result.service = newService(prof.s) prof.close() + + +proc getUserCount*(): int32 = + var count: int32 + let res = accountGetUserCount(count.addr).newResult + if res.failed: + raiseEx( + AccountUserCountError, + "Error, could not get user count: " & res.description + ) + result = count + + +proc listAllUsers*(): seq[User] = + result = @[] + + let numUsers = getUserCount() + var userIDs: array[ACC_USER_LIST_SIZE, u128] + let res = accountListAllUsers(userIDs[0].addr).newResult + + if res.failed: + raiseEx( + AccountUserListError, + "Error, could not list users: " & res.description + ) + + for i in 0 ..< numUsers: + result.add(getUser(userIDs[i])) diff --git a/src/libnx/input.nim b/src/libnx/input.nim new file mode 100644 index 0000000..c3740d6 --- /dev/null +++ b/src/libnx/input.nim @@ -0,0 +1,346 @@ +import macros, strutils, sets +import libnx/wrapper/hid +from libnx/wrapper/types import BIT +import libnx/utils +import libnx/results +import libnx/service +import libnx/results + +niceify(HidMouseButton, "Hid:MOUSE_") +niceify(HidKeyboardModifier, "Hid:KBD_MOD_") +niceify(HidKeyboardScancode, "HidKeyboardScancode:KeyboardKey:KBD_:") +niceify(HidControllerType, "Hid:TYPE_") +niceify(HidControllerLayoutType, "Hid:LAYOUT_") +niceify(HidControllerColorDescription, "Hid:COLORS_") +niceify(HidControllerKeys, "HidControllerKeys:ControllerKey:KEY_:") +niceify(HidControllerJoystick, "Hid:JOYSTICK_") +niceify(HidControllerID, "HidControllerID:Controller:CONTROLLER_:") + +export TouchPosition, JoystickPosition, MousePosition +export JOYSTICK_MAX, JOYSTICK_MIN + +type + InputError* = object of Exception + ControllerSelectError* = object of InputError + + TouchScreenHeader* = ref object + timestampTicks*: uint64 + numEntries*: uint64 + latestEntry*: uint64 + maxEntryIndex*: uint64 + timestamp*: uint64 + + TouchScreenEntryHeader* = ref object + timestamp*: uint64 + numTouches*: uint64 + + TouchScreenEntryTouch* = ref object + timestamp*: uint64 + padding*: uint32 + touchIndex*: uint32 + x*: uint32 + y*: uint32 + diameterX*: uint32 + diameterY*: uint32 + angle*: uint32 + padding2*: uint32 + + TouchScreenEntry* = ref object + header*: TouchScreenEntryHeader + touches*: Buffer[HidTouchScreenEntryTouch] + unk*: uint64 + + TouchScreen* = ref object + header*: TouchScreenHeader + entries*: Buffer[HidTouchScreenEntry] + padding*: Buffer[uint8] + + MouseHeader* = ref object + timestampTicks*: uint64 + numEntries*: uint64 + latestEntry*: uint64 + maxEntryIndex*: uint64 + + +type + MouseEntry* = ref object + timestamp*: uint64 + timestamp2*: uint64 + position*: MousePosition + buttons*: uint64 + + +type + Mouse* = ref object + header*: MouseHeader + entries*: Buffer[MouseEntry] + padding*: Buffer[uint8] + + KeyboardHeader* = ref object + timestampTicks*: uint64 + numEntries*: uint64 + latestEntry*: uint64 + maxEntryIndex*: uint64 + + KeyboardEntry* = ref object + timestamp*: uint64 + timestamp2*: uint64 + modifier*: uint64 + keys*: Buffer[uint32] + + + KeyboardSection* = ref object + header*: KeyboardHeader + entries*: array[17, KeyboardEntry] + padding*: array[0x00000028, uint8] + + ControllerMAC* = object + timestamp*: uint64 + mac*: array[0x00000008, uint8] + unk*: uint64 + timestamp2*: uint64 + + ControllerHeader* = object + `type`* {.importc: "type".}: uint32 + isHalf* {.importc: "isHalf".}: uint32 + singleColorsDescriptor* {.importc: "singleColorsDescriptor".}: uint32 + singleColorBody* {.importc: "singleColorBody".}: uint32 + singleColorButtons* {.importc: "singleColorButtons".}: uint32 + splitColorsDescriptor* {.importc: "splitColorsDescriptor".}: uint32 + leftColorBody* {.importc: "leftColorBody".}: uint32 + leftColorButtons* {.importc: "leftColorButtons".}: uint32 + rightColorBody* {.importc: "rightColorBody".}: uint32 + rightColorbuttons* {.importc: "rightColorbuttons".}: uint32 + + + ControllerLayoutHeader* = object + timestampTicks* {.importc: "timestampTicks".}: uint64 + numEntries* {.importc: "numEntries".}: uint64 + latestEntry* {.importc: "latestEntry".}: uint64 + maxEntryIndex* {.importc: "maxEntryIndex".}: uint64 + + + HidControllerInputEntry* = object + timestamp* {.importc: "timestamp".}: uint64 + timestamp_2* {.importc: "timestamp_2".}: uint64 + buttons* {.importc: "buttons".}: uint64 + joysticks* {.importc: "joysticks".}: array[JOYSTICK_NUM_STICKS, JoystickPosition] + connectionState* {.importc: "connectionState".}: uint64 + + + ControllerLayoutSection* = ref object + header*: ControllerLayoutHeader + entries* {.importc: "entries".}: array[17, HidControllerInputEntry] + + + ControllerSection* = ref object + header* {.importc: "header".}: ControllerHeader + layouts* {.importc: "layouts".}: Buffer[ControllerLayoutSection] + unk_1*: Buffer[uint8] + macLeft*: HidControllerMAC + macRight*: HidControllerMAC + unk_2* {.importc: "unk_2".}: array[0x00000DF8, uint8] + + + InputSharedMemory* = ref object + header*: Buffer[uint8] + touchscreen*: TouchScreen + mouse*: Mouse + keyboard*: KeyboardSection + controllerSerials*: Buffer[uint8] + controllers*: Buffer[ControllerSection] + + VibrationDeviceInfo* = ref object + unk_x0*: uint32 + unk_x4*: uint32 ## /< 0x1 for left-joycon, 0x2 for right-joycon. + + VibrationValue* = ref object + ampLow*: float ## /< Low Band amplitude. 1.0f: Max amplitude. + freqLow*: float ## /< Low Band frequency in Hz. + ampHigh*: float ## /< High Band amplitude. 1.0f: Max amplitude. + freqHigh*: float ## /< High Band frequency in Hz. + + +proc init*(): Result = hidInitialize().newResult +proc exit*() = hidExit() +proc reset*() = hidReset() + +proc getSessionService*(): Service = + newService(hidGetSessionService()[]) + +proc getSharedMemory*(): ptr HidSharedMemory = + result = cast[ptr HidSharedMemory](hidGetSharedMemAddr()) + +proc setControllerLayout*(id: Controller, layoutType: ControllerLayoutType) = + hidSetControllerLayout(HidControllerID(id), HidControllerLayoutType(layoutType)) + +proc getControllerLayout*(id: Controller): ControllerLayoutType = + hidGetControllerLayout(HidControllerID(id)).ControllerLayoutType + +proc scanInput*() = scanInput() + +proc keysHeld*(id: Controller): HashSet[ControllerKey] = + result = initSet[ControllerKey]() + + var raw = hidKeysHeld(HidControllerID(id)) + for i in ControllerKey.low.int ..< ControllerKey.size: + let bit = raw and 0x1 + if bit == 1: + result.incl(ControllerKey(BIT(i))) + raw = raw shr 1 + +proc keysDown*(id: Controller): HashSet[ControllerKey] = + result = initSet[ControllerKey]() + + var raw = hidKeysDown(HidControllerID(id)) + for i in ControllerKey.low.int ..< ControllerKey.size: + let bit = raw and 0x1 + if bit == 1: + result.incl(ControllerKey(BIT(i))) + raw = raw shr 1 + +proc keysUp*(id: Controller): HashSet[ControllerKey] = + result = initSet[ControllerKey]() + + var raw = hidKeysUp(HidControllerID(id)) + for i in ControllerKey.low.int ..< ControllerKey.size: + let bit = raw and 0x1 + if bit == 1: + result.incl(ControllerKey(BIT(i))) + raw = raw shr 1 + +proc mouseButtonsHeld*(): HashSet[MouseButton] = + result = initSet[MouseButton]() + + var raw = hidMouseButtonsHeld() + for i in MouseButton.low.int ..< MouseButton.size: + let bit = raw and 0x1 + if bit == 1: + result.incl(MouseButton(BIT(i))) + raw = raw shr 1 + + +proc mouseButtonsDown*(): HashSet[MouseButton] = + result = initSet[MouseButton]() + + var raw = hidMouseButtonsDown() + for i in MouseButton.low.int ..< MouseButton.size: + let bit = raw and 0x1 + if bit == 1: + result.incl(MouseButton(BIT(i))) + raw = raw shr 1 + +proc mouseButtonsUp*(): HashSet[MouseButton] = + result = initSet[MouseButton]() + + var raw = hidMouseButtonsUp() + for i in MouseButton.low.int ..< MouseButton.size: + let bit = raw and 0x1 + if bit == 1: + result.incl(MouseButton(BIT(i))) + raw = raw shr 1 + +proc mouseRead*(): MousePosition = + var pos: ptr MousePosition + hidMouseRead(pos) + result = pos[] + +proc keyboardModifierHeld*(modifier: KeyboardModifier): bool = + hidKeyboardModifierHeld(modifier.HidKeyboardModifier) + +proc keyboardModifierDown*(modifier: KeyboardModifier): bool = + hidKeyboardModifierDown(modifier.HidKeyboardModifier) + +proc keyboardModifierUp*(modifier: KeyboardModifier): bool = + hidKeyboardModifierUp(modifier.HidKeyboardModifier) + +proc keyboardHeld*(key: KeyboardKey): bool = + hidKeyboardHeld(key.HidKeyboardScancode) + +proc keyboardDown*(key: KeyboardKey): bool = + hidKeyboardDown(key.HidKeyboardScancode) + +proc keyboardUp*(key: KeyboardKey): bool = + hidKeyboardUp(key.HidKeyboardScancode) + +proc touchCount*(): uint32 = + hidTouchCount() + +proc touchRead*(pointId: uint32): TouchPosition = + var touch: ptr TouchPosition + hidTouchRead(touch, pointId) + result = touch[] + +proc joystickRead*(id: Controller, stick: ControllerJoystick): JoystickPosition = + var pos: ptr JoystickPosition + hidJoystickRead(pos, id.HidControllerID, stick.HidControllerJoystick) + result = pos[] + +## / This can be used to check what CONTROLLER_P1_AUTO uses. +## / Returns 0 when CONTROLLER_PLAYER_1 is connected, otherwise returns 1 for +## handheld-mode. +proc getHandheldMode*(): bool = + hidGetHandheldMode() + +## / Use this if you want to use a single joy-con as a dedicated CONTROLLER_PLAYER_*. +## / When used, both joy-cons in a pair should be used with this (CONTROLLER_PLAYER_1 +## and CONTROLLER_PLAYER_2 for example). +## / id must be CONTROLLER_PLAYER_*. +proc setJoyConModeSingle*(id: Controller): Result = + if not (id in {Controller.Player1 .. Controller.Player8}): + raiseEx(ControllerSelectError, "Must be controller 1-8") + hidSetNpadJoyAssignmentModeSingleByDefault(id.HidControllerId).newResult + +## / Use this if you want to use a pair of joy-cons as a single CONTROLLER_PLAYER_*. +## Only necessary if you want to use this mode in your application after \ref +## hidSetNpadJoyAssignmentModeSingleByDefault was used with this pair of joy-cons. +## / Used automatically during app startup/exit for all controllers. +## / When used, both joy-cons in a pair should be used with this (CONTROLLER_PLAYER_1 +## and CONTROLLER_PLAYER_2 for example). +## / id must be CONTROLLER_PLAYER_*. +proc setJoyconModeDual*(id: Controller): Result = + if not (id in {Controller.Player1 .. Controller.Player8}): + raiseEx(ControllerSelectError, "Must be controller 1-8") + hidSetNpadJoyAssignmentModeDual(id.HidControllerId).newResult + +## / Merge two single joy-cons into a dual-mode controller. Use this after \ref +## hidSetNpadJoyAssignmentModeDual, when \ref hidSetNpadJoyAssignmentModeSingleByDefault +## was previously used (this includes using this manually at application exit). +proc mergeSingleJoyAsDualJoy*(id1: Controller; id2: Controller): Result = + hidMergeSingleJoyAsDualJoy(id1.HidControllerID, id2.HidControllerId).newResult + + +proc initializeVibrationDevices*(VibrationDeviceHandles: ptr uint32; + total_handles: csize; id: HidControllerID; + `type`: HidControllerType): Result = + discard + +## / Gets HidVibrationDeviceInfo for the specified VibrationDeviceHandle. +proc getVibrationDeviceInfo*(VibrationDeviceHandle: ptr uint32; + VibrationDeviceInfo: ptr HidVibrationDeviceInfo): Result = + newResult(0) + +## / Send the VibrationValue to the specified VibrationDeviceHandle. +proc sendVibrationValue*(VibrationDeviceHandle: ptr uint32; + VibrationValue: ptr HidVibrationValue): Result = + discard + +## / Gets the current HidVibrationValue for the specified VibrationDeviceHandle. +proc getActualVibrationValue*(VibrationDeviceHandle: ptr uint32; + VibrationValue: ptr HidVibrationValue): Result = + discard + +## / Sets whether vibration is allowed, this also affects the config displayed by +## System Settings. +proc permitVibration*(flag: bool): Result = + discard + +## / Gets whether vibration is allowed. +proc isVibrationPermitted*(flag: ptr bool): Result = + discard + +## / Send VibrationValues[index] to VibrationDeviceHandles[index], where count is the +## number of entries in the VibrationDeviceHandles/VibrationValues arrays. +proc sendVibrationValues*(VibrationDeviceHandles: ptr uint32; + VibrationValues: ptr HidVibrationValue; count: csize): Result = + discard diff --git a/src/libnx/results.nim b/src/libnx/results.nim index 74198c0..f1d2b61 100644 --- a/src/libnx/results.nim +++ b/src/libnx/results.nim @@ -109,19 +109,26 @@ proc newResult*(code: uint32): Result = let resCode = MAKERESULT(moduleCode, descCode) var description = "" - - case module - of Module.Invalid: - discard - of Module.Kernel: - result.kernelError = KernelError(resCode) - description = $result.kernelError - of Module.Libnx: - result.libnxError = LibnxError(resCode) - description = $result.libnxError - of Module.LibnxNvidia: - result.libnxNvidiaError = LibnxNvidiaError(resCode) - description = $result.libnxNvidiaError + try: + case module + of Module.Invalid: + discard + of Module.Kernel: + result.kernelError = KernelError(resCode) + description = $result.kernelError + of Module.Libnx: + result.libnxError = LibnxError(resCode) + description = $result.libnxError + of Module.LibnxNvidia: + result.libnxNvidiaError = LibnxNvidiaError(resCode) + description = $result.libnxNvidiaError + except: + echo "Converting to result failed: " & getCurrentExceptionMsg() + echo "Code: " & $code + echo "ModuleCode: " & $moduleCode + echo "Module: " & $module + echo "DescCode: " & $descCode + echo "ResultCode: " & $resCode result.module = $module result.description = description diff --git a/src/libnx/utils.nim b/src/libnx/utils.nim index 97a5a58..cb47b79 100644 --- a/src/libnx/utils.nim +++ b/src/libnx/utils.nim @@ -1,4 +1,84 @@ +import macros, strutils, math +proc size*(enumTy: typedesc): int = + # Returns the number of items in a bit set enum + log2(float64(enumTy.high)).int + 1 + +template recursive(node, action: untyped): untyped {.dirty.} = + ## recursively iterate over AST nodes and perform an + ## action on them + proc helper(child: NimNode): NimNode {.gensym.}= + action + result = child.copy() + for c in child.children: + if child.kind == nnkCall and c.kind == nnkDotExpr: + # ignore dot expressions that are also calls + continue + result.add helper(c) + discard helper(node) + +macro niceify*(enumType: typed, replaceNameField: string): untyped = + ## + ## niceify(HidMouseButton, "Hid:MOUSE_") transforms this: + ## + ## HidMouseButton* {.size: sizeof(cint)} = enum + ## MOUSE_LEFT = BIT(0), MOUSE_RIGHT = BIT(1), MOUSE_MIDDLE = BIT(2), + ## MOUSE_FORWARD = BIT(3), MOUSE_BACK = BIT(4) + ## + ## into this: + ## + ## MouseButton {.size: 4, pure.} = enum + ## Left = MOUSE_LEFT, Right = MOUSE_RIGHT, Middle = MOUSE_MIDDLE, + ## Forward = MOUSE_FORWARD, + ## Back = MOUSE_BACK + ## + ## If any fields begin with a number, the first letter of the name after + ## the colon will be used in addition. (128 -> M128 in this case) + + let replaceSplit = replaceNameField.strVal.split(":") + var + replaceName: string + replaceField: string + replaceNameWith = "" + replaceFieldWith = "" + + if replaceSplit.len == 2: + replaceName = replaceSplit[0] + replaceField = replaceSplit[1] + else: + replaceName = replaceSplit[0] + replaceNameWith = replaceSplit[1] + replaceField = replaceSplit[2] + replaceFieldWith = replaceSplit[3] + + var node = enumType.getImpl().copy() + recursive(node): + if child.kind == nnkPragmaExpr: + child[0] = postFix( + ident(child[0].strVal.replace($replaceName, replaceNameWith)), + "*" + ) + + if child.kind == nnkPragma: + child.add(ident("pure")) + + if child.kind == nnkEnumFieldDef: + let + lhs = child[0] + rhs = child[1] + name = lhs.strVal + var + newName = name.replace($replaceField, replaceFieldWith) + .normalize().capitalizeAscii() + firstChar = newName[0] + + if firstChar in Digits: + newName = replaceField[0] & newName + + child[0] = ident(newName) + child[1] = ident(name) + + return newNimNode(nnkTypeSection).add(node) type BufferError* = object of Exception @@ -8,6 +88,7 @@ type len*: int data*: ptr UncheckedArray[T] + template raiseEx*(ty: untyped, message: string): untyped = raise newException(ty, message)