Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,28 @@ arguments = defos.get_arguments()

---

**Prevent screen dimming and sleep** when the game is active (e.g. when using a gamepad). On HTML5, requires an active/visible tab and a browser that supports the Screen Wake Lock API (Chrome 84+, Firefox 126+, Safari 16.4+). Use `defos.is_keep_awake_supported()` to check browser support before calling `defos.set_keep_awake(true)` on HTML5.

```lua
defos.set_keep_awake(bool_value)
local supported = defos.is_keep_awake_supported()
```

Example usage with gamepad input:
```lua
local keep_awake = false
function on_input(self, action_id, action)
if action.gamepad ~= nil then
if not keep_awake then
keep_awake = true
defos.set_keep_awake(true)
end
end
end
```

---

If you'd like to see any other features, open an issue.

## Example
Expand Down
16 changes: 16 additions & 0 deletions defos/api/defos.script_api
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,22 @@
type: table
desc: Table of command line arguments

- name: set_keep_awake
type: function
desc: Prevents or allows the screen from dimming or going to sleep. Useful when the game is controlled by a gamepad. On HTML5, requires an active/visible tab and browser support (Chrome 84+, Firefox 126+, Safari 16.4+). Platforms - Linux, Windows, OSX, HTML5.
parameters:
- name: keep_awake
type: boolean
desc: Whether to prevent screen dimming and sleep

- name: is_keep_awake_supported
type: function
desc: Checks if the keep awake feature is supported on the current platform/browser. Platforms - Linux, Windows, OSX, HTML5.
returns:
- name: supported
type: boolean
desc: Whether keep awake is supported

- name: CURSOR_ARROW
type: number
desc: Default arrow cursor
Expand Down
4 changes: 2 additions & 2 deletions defos/ext.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: "defos"
platforms:
osx:
context:
frameworks: ["AppKit"]
frameworks: ["AppKit", "IOKit"]

linux:
context:
frameworks: ["X11", "Xrender"]
libs: ["Xrender", "Xfixes"]
libs: ["Xrender", "Xfixes", "Xss"]
17 changes: 17 additions & 0 deletions defos/src/defos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,20 @@ void defos_emit_event(DefosEvent event)
assert(top == lua_gettop(L));
}

// Keep awake

static int set_keep_awake(lua_State *L)
{
defos_set_keep_awake(checkboolean(L, 1));
return 0;
}

static int is_keep_awake_supported(lua_State *L)
{
lua_pushboolean(L, defos_is_keep_awake_supported());
return 1;
}

// Lua module initialization

static const luaL_reg Module_methods[] =
Expand Down Expand Up @@ -685,6 +699,8 @@ static const luaL_reg Module_methods[] =
{"get_bundle_root", get_bundle_root},
{"get_arguments", get_arguments},
{"get_parameters", get_arguments}, // For backwards compatibility
{"set_keep_awake", set_keep_awake},
{"is_keep_awake_supported", is_keep_awake_supported},
{0, 0}};

static void LuaInit(lua_State *L)
Expand Down Expand Up @@ -754,6 +770,7 @@ dmExtension::Result InitializeDefos(dmExtension::Params *params)

dmExtension::Result FinalizeDefos(dmExtension::Params *params)
{
defos_set_keep_awake(false);
defos_final();
return dmExtension::RESULT_OK;
}
Expand Down
67 changes: 67 additions & 0 deletions defos/src/defos_html5.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,71 @@ DisplayID defos_get_current_display() {
return NULL;
}

void defos_set_keep_awake(bool keep_awake) {
EM_ASM({
var keep = $0 !== 0;

// Initialize shared state and helpers once.
if (!Module.__defosjs_keepAwakeInitialized) {
Module.__defosjs_keepAwakeInitialized = true;
Module.__defosjs_keepAwakeDesired = false;
Module.__defosjs_wakeLock = null;

Module.__defosjs_requestWakeLock = function() {
if (!Module.__defosjs_keepAwakeDesired) { return; }
if (!('wakeLock' in navigator)) { return; }
if (Module.__defosjs_wakeLock) { return; }
navigator.wakeLock.request('screen').then(function(lock) {
// Check whether the desired state changed while the promise was in flight.
if (!Module.__defosjs_keepAwakeDesired) {
lock.release();
return;
}
Module.__defosjs_wakeLock = lock;
lock.addEventListener('release', function() {
Module.__defosjs_wakeLock = null;
// Re-acquire if still desired (e.g. browser released due to tab hide).
if (Module.__defosjs_keepAwakeDesired) {
Module.__defosjs_requestWakeLock();
}
});
}).catch(function(err) {
console.error('defos: wake lock request failed:', err);
});
};

// Re-try when the document becomes visible again.
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible' &&
Module.__defosjs_keepAwakeDesired &&
!Module.__defosjs_wakeLock) {
Module.__defosjs_requestWakeLock();
}
});
}

Module.__defosjs_keepAwakeDesired = keep;

if (keep) {
Module.__defosjs_requestWakeLock();
} else {
if (Module.__defosjs_wakeLock) {
Module.__defosjs_wakeLock.release().then(function() {
Module.__defosjs_wakeLock = null;
}).catch(function(err) {
console.error('defos: wake lock release failed:', err);
Module.__defosjs_wakeLock = null;
});
}
}
}, keep_awake ? 1 : 0);
}

bool defos_is_keep_awake_supported() {
int supported = EM_ASM_INT({
return ('wakeLock' in navigator) ? 1 : 0;
});
return supported != 0;
}

#endif
25 changes: 25 additions & 0 deletions defos/src/defos_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <X11/Xos.h>
#include <X11/cursorfont.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/scrnsaver.h>
#include <Xcursor.h>
#include <Xrandr.h>

Expand All @@ -43,6 +44,7 @@ static Display *disp;
static int screen;
static Window win;
static Window root;
static bool g_xss_supported = false;

// TODO: add support checking
static Atom UTF8_STRING;
Expand Down Expand Up @@ -104,6 +106,13 @@ void defos_init()
is_cursor_actually_visible = true;
window_has_focus = true;

// Probe for XScreenSaver extension availability.
// event/error base and version outputs are required by the API but not used beyond the check.
int xss_event_base, xss_error_base;
int xss_major = 0, xss_minor = 0;
g_xss_supported = XScreenSaverQueryExtension(disp, &xss_event_base, &xss_error_base) &&
XScreenSaverQueryVersion(disp, &xss_major, &xss_minor);

current_cursor = NULL;
memset(default_cursors, 0, DEFOS_CURSOR_INTMAX * sizeof(CustomCursor*));
}
Expand Down Expand Up @@ -926,4 +935,20 @@ static void send_message(Window &window, Atom type, long a, long b, long c, long
XSendEvent(disp, root, False, SubstructureNotifyMask | SubstructureRedirectMask, &event);
}

static bool g_keep_awake = false;

void defos_set_keep_awake(bool keep_awake)
{
if (keep_awake == g_keep_awake) { return; }
g_keep_awake = keep_awake;
if (!g_xss_supported) { return; }
XScreenSaverSuspend(disp, keep_awake ? True : False);
XFlush(disp);
}

bool defos_is_keep_awake_supported()
{
return g_xss_supported;
}
Comment on lines +940 to +952
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Linux, defos_is_keep_awake_supported() currently always returns true, but XScreenSaverSuspend() requires the XScreenSaver extension to be present on the connected X11 display. Consider querying extension availability (e.g. XScreenSaverQueryExtension / XScreenSaverQueryVersion) during init and returning an accurate supported flag; additionally, skip calling XScreenSaverSuspend when unsupported to avoid X errors on systems without the extension.

Copilot uses AI. Check for mistakes.

#endif
31 changes: 31 additions & 0 deletions defos/src/defos_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -817,4 +817,35 @@ static void disable_mouse_tracking() {
mouse_tracker = nil;
}

#import <IOKit/pwr_mgt/IOPMLib.h>

static IOPMAssertionID g_assertion_id = kIOPMNullAssertionID;

void defos_set_keep_awake(bool keep_awake)
{
if (keep_awake && g_assertion_id == kIOPMNullAssertionID)
{
CFStringRef reason = CFSTR("Defold game active");
IOReturn ret = IOPMAssertionCreateWithName(
kIOPMAssertionTypePreventUserIdleDisplaySleep,
kIOPMAssertionLevelOn,
reason,
&g_assertion_id);
if (ret != kIOReturnSuccess)
{
g_assertion_id = kIOPMNullAssertionID;
}
}
else if (!keep_awake && g_assertion_id != kIOPMNullAssertionID)
{
IOPMAssertionRelease(g_assertion_id);
g_assertion_id = kIOPMNullAssertionID;
}
}

bool defos_is_keep_awake_supported()
{
return true;
}

#endif
3 changes: 3 additions & 0 deletions defos/src/defos_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,6 @@ extern void defos_reset_cursor();
extern void defos_get_displays(dmArray<DisplayInfo> &displayList);
extern void defos_get_display_modes(DisplayID displayID, dmArray<DisplayModeInfo> &modeList);
extern DisplayID defos_get_current_display();

extern void defos_set_keep_awake(bool keep_awake);
extern bool defos_is_keep_awake_supported();
21 changes: 21 additions & 0 deletions defos/src/defos_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,27 @@ DisplayID defos_get_current_display()
return copy_string(monitorInfo.szDevice);
}

static bool g_keep_awake = false;

void defos_set_keep_awake(bool keep_awake)
{
if (keep_awake == g_keep_awake) { return; }
g_keep_awake = keep_awake;
if (keep_awake)
{
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED);
}
else
{
SetThreadExecutionState(ES_CONTINUOUS);
}
}

bool defos_is_keep_awake_supported()
{
return true;
}

/********************
* internal functions
********************/
Expand Down
35 changes: 35 additions & 0 deletions example/example.gui
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,41 @@ nodes {
overridden_fields: 18
template_node_child: true
}
nodes {
position {
x: 635.0
y: 350.0
}
scale {
x: 0.75
y: 0.75
}
type: TYPE_TEMPLATE
id: "toggle_keep_awake"
inherit_alpha: true
template: "/dirtylarry/button.gui"
}
nodes {
type: TYPE_BOX
id: "toggle_keep_awake/larrybutton"
parent: "toggle_keep_awake"
template_node_child: true
}
nodes {
size {
x: 250.0
y: 100.0
}
type: TYPE_TEXT
text: "Keep screen awake"
id: "toggle_keep_awake/larrylabel"
line_break: true
parent: "toggle_keep_awake/larrybutton"
overridden_fields: 4
overridden_fields: 8
overridden_fields: 18
template_node_child: true
}
nodes {
position {
x: 525.0
Expand Down
18 changes: 18 additions & 0 deletions example/example.gui_script
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ function init(self)
if gui.get_flipbook(gui.get_node("toggle_borderless/larrybutton")) == hash("button_pressed") then
defos.toggle_borderless()
end
if gui.get_flipbook(gui.get_node("toggle_keep_awake/larrybutton")) == hash("button_pressed") then
if defos.is_keep_awake_supported() then
self.keep_awake = not self.keep_awake
defos.set_keep_awake(self.keep_awake)
gui.set_text(gui.get_node("toggle_keep_awake/larrylabel"), self.keep_awake and "Allow screen sleep" or "Keep screen awake")
else
print("defos: Keep-awake / Wake Lock is not supported on this platform.")
end
end
end)
end

Expand All @@ -106,6 +115,7 @@ function init(self)
self.cursor_visible = true
self.cursor_clipped = false
self.cursor_locked = false
self.keep_awake = false
msg.post(".", "acquire_input_focus")

-- Console related commands only work on Windows and in bundled builds not in editor builds
Expand Down Expand Up @@ -253,4 +263,12 @@ function on_input(self, action_id, action)

defos.set_cursor(self.cursors[self.current_cursor])
end)

dirtylarry:button("toggle_keep_awake", action_id, action, function()
if system_name ~= "HTML5" then
self.keep_awake = not self.keep_awake
defos.set_keep_awake(self.keep_awake)
gui.set_text(gui.get_node("toggle_keep_awake/larrylabel"), self.keep_awake and "Allow screen sleep" or "Keep screen awake")
end
end)
end