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

Virtual keyboard functionality #54

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions docs/apps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ List of Functions

* :func:`apps.getCurrentGameLanguage`
* :func:`apps.isDlcInstalled`
* :func:`apps.getLaunchCommandLineParam`
* :func:`apps.getLaunchCommandLine`


List of Callbacks
Expand Down Expand Up @@ -37,7 +37,7 @@ Function Reference

:param number appID: The App ID of the DLC to check.
:returns: (`boolean`) true if the user owns the DLC and it's currently installed, otherwise false.
:SteamWorks: `GetCurrentGameLanguage <https://partner.steamgames.com/doc/api/ISteamApps#BIsDlcInstalled>`_
:SteamWorks: `BIsDlcInstalled <https://partner.steamgames.com/doc/api/ISteamApps#BIsDlcInstalled>`_

Checks if the user owns a specific DLC and if the DLC is installed.

Expand Down
115 changes: 113 additions & 2 deletions docs/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,130 @@ List of Functions
-----------------

* :func:`utils.getAppID`
* :func:`utils.getEnteredGamepadTextInput`
* :func:`utils.getEnteredGamepadTextLength`
* :func:`utils.isSteamInBigPictureMode`
* :func:`utils.isSteamRunningOnSteamDeck`
* :func:`utils.showGamepadTextInput`
* :func:`utils.showFloatingGamepadTextInput`

List of Callbacks
-----------------

* :func:`utils.onGamepadTextInputDismissed`
* :func:`utils.onFloatingGamepadTextInputDismissed`

Function Reference
------------------

.. function:: utils.getAppID ()
.. function:: utils.getAppID()

:returns: (`number`) The AppID.
:SteamWorks: `GetAppID <https://partner.steamgames.com/doc/api/ISteamUtils#GetAppID>`_

Gets the App ID of the current process.
Gets the App ID of the current process.

**Example**::

print("My app id is " .. Steam.utils.getAppID())

.. function:: utils.getEnteredGamepadTextInput(buffer,length)

:param number buffer: 1024
:param number length: Number returned by getEnteredGamepadTextLength().
:returns: (`string`)
:SteamWorks: `GetEnteredGamepadTextInput <https://partner.steamgames.com/doc/api/ISteamUtils#GetEnteredGamepadTextInput>`_

Called within onGamepadTextInputDismissed: see that example.

.. function:: utils.getEnteredGamepadTextLength()

:returns: (`number`)
:SteamWorks: `GetEnteredGamepadTextLength <https://partner.steamgames.com/doc/api/ISteamUtils#GetEnteredGamepadTextLength>`_

Called within onGamepadTextInputDismissed: see that example.

.. function:: utils.isSteamInBigPictureMode()

:returns: (`boolean`)
:SteamWorks: `IsSteamInBigPictureMode <https://partner.steamgames.com/doc/api/ISteamUtils#IsSteamInBigPictureMode>`_

.. function:: utils.isSteamRunningOnSteamDeck()

:returns: (`boolean`)
:SteamWorks: `IsSteamRunningOnSteamDeck <https://partner.steamgames.com/doc/api/ISteamUtils#IsSteamRunningOnSteamDeck>`_

.. function:: utils.showGamepadTextInput(input_mode,input_line_mode,description,char_max,existing_text)

:param string input_mode: Valid options are: "Normal", "Password".
:param string input_line_mode: Valid options are: "SingleLine", "MultipleLines".
:param string description: Sets the description that should inform the user what the input dialog is for.
:param number char_max: 1024
:param string existing_text: Sets the preexisting text which the user can edit.
:returns: (`boolean`) true if the big picture overlay is running; otherwise, false
:SteamWorks: `ShowGamepadTextInput <https://partner.steamgames.com/doc/api/ISteamUtils#ShowGamepadTextInput>`_

Activates the Big Picture text input dialog which only supports gamepad input.

Notes: Steam must be in Big Picture Mode. Non-Steam games ran through Steam will not overlay properly; however, you can minimize Steam BPMode and test with steam_api64.dll.

**Example**::

Steam.utils.showGamepadTextInput("Normal","SingleLine",description,1024,existing_text)

.. function:: utils.showFloatingGamepadTextInput(floating_input_mode,TextFieldXPosition,TextFieldYPosition,TextFieldWidth,TextFieldHeight)

:param string floating_input_mode: Valid options are: "SingleLine", "MultipleLines", "Email", "Numeric".
:param number TextFieldXPosition: X coordinate of text field which shouldn't be obscured by the floating keyboard.
:param number TextFieldYPosition: Y coordinate of text field which shouldn't be obscured by the floating keyboard.
:param number TextFieldWidth: Width of text field which shouldn't be obscured by the floating keyboard.
:param number TextFieldHeight: Height of text field which shouldn't be obscured by the floating keyboard.
:returns: (`boolean`) true if the floating keyboard was shown, otherwise, false
:SteamWorks: `ShowFloatingGamepadTextInput <https://partner.steamgames.com/doc/api/ISteamUtils#ShowFloatingGamepadTextInput>`_

Opens a floating keyboard over the game content and sends OS keyboard keys directly to the game.
The text field position is specified in pixels relative the origin of the game window and is used to position the floating keyboard in a way that doesn't cover the text field.

Notes: Steam must be in Big Picture Mode. Non-Steam games ran through Steam will not overlay properly; however, you can minimize Steam BPMode and test with steam_api64.dll.

**Example**::

-- For bottom of window use 0,0,0,0
-- For top of window use 0,window height/2,window width,window height/2
Steam.utils.showFloatingGamepadTextInput("SingleLine",x,y,w,h)

Callbacks Reference
-------------------

.. warning::

Remember callbacks are functions that you should override in order to receive the events, and not call directly.

Also, you **must** constantly call ``Steam.runCallbacks()`` (preferably in your game loop) in order for your callbacks to be called.

.. function:: utils.onGamepadTextInputDismissed(data)

:param table data: A table similar to `GamepadTextInputDismissed_t <https://partner.steamgames.com/doc/api/ISteamUtils#GamepadTextInputDismissed_t>`_

* **data.submitted** (`boolean`) -- true if user entered & accepted text (Call utils.getEnteredGamepadTextInput to receive the text), false if input was canceled.
* **data.submittedText** (`number`) -- Contains the length in bytes if there was text submitted.
:returns: nothing
:SteamWorks: `GamepadTextInputDismissed_t <https://partner.steamgames.com/doc/api/ISteamUtils#GamepadTextInputDismissed_t>`_

Called when the big picture gamepad text input has been closed.

**Example**::

function Steam.utils.onGamepadTextInputDismissed(data)
if not data.submitted then return end-- The user canceled
local length = Steam.utils.getEnteredGamepadTextLength();
local newstring = Steam.utils.getEnteredGamepadTextInput(1024, length);
-- Use the newstring made by the user.
end

.. function:: utils.onFloatingGamepadTextInputDismissed()

:returns: nothing
:SteamWorks: `FloatingGamepadTextInputDismissed_t <https://partner.steamgames.com/doc/api/ISteamUtils#FloatingGamepadTextInputDismissed_t>`_

Called when the floating keyboard invoked from utils.showFloatingGamepadTextInput has been closed.
132 changes: 129 additions & 3 deletions src/utils.cpp
Original file line number Diff line number Diff line change
@@ -1,25 +1,151 @@
#include "utils.hpp"
//#include <cstdio>

// ==========================
// ======= SteamUtils =======
// ==========================

using luasteam::CallResultListener;

namespace {

class CallbackListener;
CallbackListener *callback_listener = nullptr;
int utils_ref = LUA_NOREF;

const char *input_modes[] = {"Normal", "Password", nullptr};
const char *input_line_modes[] = {"SingleLine", "MultipleLines", nullptr};
const char *floating_input_modes[] = {"SingleLine", "MultipleLines", "Email", "Numeric", nullptr};

class CallbackListener {
private:
STEAM_CALLBACK(CallbackListener, OnGamepadTextInputDismissed, GamepadTextInputDismissed_t);
STEAM_CALLBACK(CallbackListener, OnFloatingGamepadTextInputDismissed, FloatingGamepadTextInputDismissed_t);
};

void CallbackListener::OnGamepadTextInputDismissed(GamepadTextInputDismissed_t *data) {
if (data == nullptr) {
return;
}
lua_State *L = luasteam::global_lua_state;
if (!lua_checkstack(L, 4)) {
return;
}

lua_rawgeti(L, LUA_REGISTRYINDEX, utils_ref);
lua_getfield(L, -1, "onGamepadTextInputDismissed");
if (lua_isnil(L, -1)) {
lua_pop(L, 2);
} else {
lua_createtable(L, 0, 2);
lua_pushboolean(L, data->m_bSubmitted);
lua_setfield(L, -2, "submitted");
lua_pushnumber(L, data->m_unSubmittedText);
lua_setfield(L, -2, "submittedText"); // len in bytes
lua_call(L, 1, 0);
lua_pop(L, 1);
}
}

void CallbackListener::OnFloatingGamepadTextInputDismissed(FloatingGamepadTextInputDismissed_t *data) {
if (data == nullptr) {
return;
}
lua_State *L = luasteam::global_lua_state;
if (!lua_checkstack(L, 4)) {
return;
}
lua_rawgeti(L, LUA_REGISTRYINDEX, utils_ref);
lua_getfield(L, -1, "onFloatingGamepadTextInputDismissed");
if (lua_isnil(L, -1)) {
lua_pop(L, 2);
} else {
lua_call(L, 1, 0);
lua_pop(L, 1);
}
}

} // namespace

// uint32 GetAppID();
EXTERN int luasteam_getAppID(lua_State *L) {
lua_pushnumber(L, SteamUtils()->GetAppID());
return 1;
}

// bool GetEnteredGamepadTextInput( char *pchText, uint32 cchText );
EXTERN int luasteam_getEnteredGamepadTextInput(lua_State *L) {
char pchText[1024];
SteamUtils()->GetEnteredGamepadTextInput(pchText, 1024);
lua_pushstring(L, pchText);
return 1;
}

// uint32 GetEnteredGamepadTextLength();
EXTERN int luasteam_getEnteredGamepadTextLength(lua_State *L) {
lua_pushnumber(L, SteamUtils()->GetEnteredGamepadTextLength());
return 1;
}

//bool IsSteamInBigPictureMode();
EXTERN int luasteam_isSteamInBigPictureMode(lua_State *L) {
lua_pushboolean(L, SteamUtils()->IsSteamInBigPictureMode());
return 1;
}

//bool IsSteamRunningOnSteamDeck();
EXTERN int luasteam_isSteamRunningOnSteamDeck(lua_State *L) {
lua_pushboolean(L, SteamUtils()->IsSteamRunningOnSteamDeck());
return 1;
}

// bool ShowGamepadTextInput( EGamepadTextInputMode eInputMode, EGamepadTextInputLineMode eLineInputMode, const char *pchDescription, uint32 unCharMax, const char *pchExistingText );
EXTERN int luasteam_showGamepadTextInput(lua_State *L) {

int input_mode = luaL_checkoption(L, 1, nullptr, input_modes);
int input_line_mode = luaL_checkoption(L, 2, nullptr, input_line_modes);
// char pchDescription[1024];
const char *pchDescription = luaL_checkstring(L, 3);
const char *pchExistingText = luaL_checkstring(L, 5);
lua_pushboolean(L, SteamUtils()->ShowGamepadTextInput(static_cast<EGamepadTextInputMode>(input_mode), static_cast<EGamepadTextInputLineMode>(input_line_mode), pchDescription, 1024, pchExistingText));
return 1;
}

//bool ShowFloatingGamepadTextInput(EFloatingGamepadTextInputMode eKeyboardMode, int nTextFieldXPosition, int nTextFieldYPosition, int nTextFieldWidth, int nTextFieldHeight);
EXTERN int luasteam_showFloatingGamepadTextInput(lua_State *L) {
int floating_input_mode = luaL_checkoption(L, 1, nullptr, floating_input_modes);
int nTextFieldXPosition = luaL_checkint(L, 2);
int nTextFieldYPosition = luaL_checkint(L, 3);
int nTextFieldWidth = luaL_checkint(L, 4);
int nTextFieldHeight = luaL_checkint(L, 5);
lua_pushboolean(L, SteamUtils()->ShowFloatingGamepadTextInput(static_cast<EFloatingGamepadTextInputMode>(floating_input_mode), nTextFieldXPosition, nTextFieldYPosition, nTextFieldWidth, nTextFieldHeight));
return 1;
}

namespace luasteam {

void add_utils(lua_State *L) {
lua_createtable(L, 0, 1);
lua_createtable(L, 0, 7);
add_func(L, "getAppID", luasteam_getAppID);
add_func(L, "getEnteredGamepadTextInput", luasteam_getEnteredGamepadTextInput);
add_func(L, "getEnteredGamepadTextLength", luasteam_getEnteredGamepadTextLength);
add_func(L, "isSteamInBigPictureMode", luasteam_isSteamInBigPictureMode);
add_func(L, "isSteamRunningOnSteamDeck", luasteam_isSteamRunningOnSteamDeck);
add_func(L, "showGamepadTextInput", luasteam_showGamepadTextInput);
add_func(L, "showFloatingGamepadTextInput", luasteam_showFloatingGamepadTextInput);
lua_pushvalue(L, -1);

utils_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_setfield(L, -2, "utils");
}

void init_utils(lua_State *L) {}
void init_utils(lua_State *L) { callback_listener = new CallbackListener(); }

void shutdown_utils(lua_State *L) {}
void shutdown_utils(lua_State *L) {
luaL_unref(L, LUA_REGISTRYINDEX, utils_ref);
utils_ref = LUA_NOREF;
delete callback_listener;
callback_listener = nullptr;
}

} // namespace luasteam