1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21#include "wrap_Window.h"
22#include "sdl/Window.h"
23
24namespace love
25{
26namespace window
27{
28
29#define instance() (Module::getInstance<Window>(Module::M_WINDOW))
30
31int w_getDisplayCount(lua_State *L)
32{
33 lua_pushinteger(L, instance()->getDisplayCount());
34 return 1;
35}
36
37int w_getDisplayName(lua_State *L)
38{
39 int index = (int) luaL_checkinteger(L, 1) - 1;
40
41 const char *name = nullptr;
42 luax_catchexcept(L, [&](){ name = instance()->getDisplayName(index); });
43
44 lua_pushstring(L, name);
45 return 1;
46}
47
48static const char *settingName(Window::Setting setting)
49{
50 const char *name = nullptr;
51 Window::getConstant(setting, name);
52 return name;
53}
54
55static int readWindowSettings(lua_State *L, int idx, WindowSettings &settings)
56{
57 luax_checktablefields<Window::Setting>(L, idx, "window setting", Window::getConstant);
58
59 lua_getfield(L, idx, settingName(Window::SETTING_FULLSCREEN_TYPE));
60 if (!lua_isnoneornil(L, -1))
61 {
62 const char *typestr = luaL_checkstring(L, -1);
63 if (!Window::getConstant(typestr, settings.fstype))
64 return luax_enumerror(L, "fullscreen type", Window::getConstants(settings.fstype), typestr);
65 }
66 lua_pop(L, 1);
67
68 settings.fullscreen = luax_boolflag(L, idx, settingName(Window::SETTING_FULLSCREEN), settings.fullscreen);
69 settings.msaa = luax_intflag(L, idx, settingName(Window::SETTING_MSAA), settings.msaa);
70 settings.stencil = luax_boolflag(L, idx, settingName(Window::SETTING_STENCIL), settings.stencil);
71 settings.depth = luax_intflag(L, idx, settingName(Window::SETTING_DEPTH), settings.depth);
72 settings.resizable = luax_boolflag(L, idx, settingName(Window::SETTING_RESIZABLE), settings.resizable);
73 settings.minwidth = luax_intflag(L, idx, settingName(Window::SETTING_MIN_WIDTH), settings.minwidth);
74 settings.minheight = luax_intflag(L, idx, settingName(Window::SETTING_MIN_HEIGHT), settings.minheight);
75 settings.borderless = luax_boolflag(L, idx, settingName(Window::SETTING_BORDERLESS), settings.borderless);
76 settings.centered = luax_boolflag(L, idx, settingName(Window::SETTING_CENTERED), settings.centered);
77 settings.display = luax_intflag(L, idx, settingName(Window::SETTING_DISPLAY), settings.display+1) - 1;
78 settings.highdpi = luax_boolflag(L, idx, settingName(Window::SETTING_HIGHDPI), settings.highdpi);
79 settings.usedpiscale = luax_boolflag(L, idx, settingName(Window::SETTING_USE_DPISCALE), settings.usedpiscale);
80
81 lua_getfield(L, idx, settingName(Window::SETTING_VSYNC));
82 if (lua_isnumber(L, -1))
83 settings.vsync = (int) lua_tointeger(L, -1);
84 else if (lua_isboolean(L, -1))
85 settings.vsync = lua_toboolean(L, -1);
86 lua_pop(L, 1);
87
88 lua_getfield(L, idx, settingName(Window::SETTING_X));
89 lua_getfield(L, idx, settingName(Window::SETTING_Y));
90 settings.useposition = !(lua_isnoneornil(L, -2) && lua_isnoneornil(L, -1));
91 if (settings.useposition)
92 {
93 settings.x = (int) luaL_optinteger(L, -2, 0);
94 settings.y = (int) luaL_optinteger(L, -1, 0);
95 }
96 lua_pop(L, 2);
97
98 // We don't explicitly set the refresh rate, it's "read-only".
99 return 0;
100}
101
102int w_setMode(lua_State *L)
103{
104 int w = (int) luaL_checkinteger(L, 1);
105 int h = (int) luaL_checkinteger(L, 2);
106
107 if (lua_isnoneornil(L, 3))
108 {
109 luax_catchexcept(L, [&](){ luax_pushboolean(L, instance()->setWindow(w, h, nullptr)); });
110 return 1;
111 }
112
113 // Defaults come from WindowSettings itself.
114 WindowSettings settings;
115
116 readWindowSettings(L, 3, settings);
117
118 luax_catchexcept(L,
119 [&](){ luax_pushboolean(L, instance()->setWindow(w, h, &settings)); }
120 );
121
122 return 1;
123}
124
125int w_updateMode(lua_State *L)
126{
127 int w, h;
128 WindowSettings settings;
129 instance()->getWindow(w, h, settings);
130
131 if (lua_gettop(L) == 0)
132 return luaL_error(L, "Expected at least one argument");
133
134 int idx = 1;
135 if (lua_isnumber(L, 1))
136 {
137 idx = 3;
138 w = (int) luaL_checkinteger(L, 1);
139 h = (int) luaL_checkinteger(L, 2);
140 }
141
142 if (!lua_isnoneornil(L, idx))
143 readWindowSettings(L, idx, settings);
144
145 luax_catchexcept(L,
146 [&](){ luax_pushboolean(L, instance()->setWindow(w, h, &settings)); }
147 );
148 return 1;
149}
150
151int w_getMode(lua_State *L)
152{
153 int w, h;
154 WindowSettings settings;
155 instance()->getWindow(w, h, settings);
156 lua_pushnumber(L, w);
157 lua_pushnumber(L, h);
158
159 if (lua_istable(L, 1))
160 lua_pushvalue(L, 1);
161 else
162 lua_newtable(L);
163
164 const char *fstypestr = "desktop";
165 Window::getConstant(settings.fstype, fstypestr);
166
167 lua_pushstring(L, fstypestr);
168 lua_setfield(L, -2, settingName(Window::SETTING_FULLSCREEN_TYPE));
169
170 luax_pushboolean(L, settings.fullscreen);
171 lua_setfield(L, -2, settingName(Window::SETTING_FULLSCREEN));
172
173 lua_pushinteger(L, settings.vsync);
174 lua_setfield(L, -2, settingName(Window::SETTING_VSYNC));
175
176 lua_pushinteger(L, settings.msaa);
177 lua_setfield(L, -2, settingName(Window::SETTING_MSAA));
178
179 luax_pushboolean(L, settings.stencil);
180 lua_setfield(L, -2, settingName(Window::SETTING_STENCIL));
181
182 lua_pushinteger(L, settings.depth);
183 lua_setfield(L, -2, settingName(Window::SETTING_DEPTH));
184
185 luax_pushboolean(L, settings.resizable);
186 lua_setfield(L, -2, settingName(Window::SETTING_RESIZABLE));
187
188 lua_pushinteger(L, settings.minwidth);
189 lua_setfield(L, -2, settingName(Window::SETTING_MIN_WIDTH));
190
191 lua_pushinteger(L, settings.minheight);
192 lua_setfield(L, -2, settingName(Window::SETTING_MIN_HEIGHT));
193
194 luax_pushboolean(L, settings.borderless);
195 lua_setfield(L, -2, settingName(Window::SETTING_BORDERLESS));
196
197 luax_pushboolean(L, settings.centered);
198 lua_setfield(L, -2, settingName(Window::SETTING_CENTERED));
199
200 // Display index is 0-based internally and 1-based in Lua.
201 lua_pushinteger(L, settings.display + 1);
202 lua_setfield(L, -2, settingName(Window::SETTING_DISPLAY));
203
204 luax_pushboolean(L, settings.highdpi);
205 lua_setfield(L, -2, settingName(Window::SETTING_HIGHDPI));
206
207 luax_pushboolean(L, settings.usedpiscale);
208 lua_setfield(L, -2, settingName(Window::SETTING_USE_DPISCALE));
209
210 lua_pushnumber(L, settings.refreshrate);
211 lua_setfield(L, -2, settingName(Window::SETTING_REFRESHRATE));
212
213 lua_pushinteger(L, settings.x);
214 lua_setfield(L, -2, settingName(Window::SETTING_X));
215
216 lua_pushinteger(L, settings.y);
217 lua_setfield(L, -2, settingName(Window::SETTING_Y));
218
219 return 3;
220}
221
222int w_getDisplayOrientation(lua_State *L)
223{
224 int displayindex = 0;
225 if (!lua_isnoneornil(L, 1))
226 displayindex = (int) luaL_checkinteger(L, 1) - 1;
227 else
228 {
229 int x, y;
230 instance()->getPosition(x, y, displayindex);
231 }
232
233 const char *orientationstr = nullptr;
234 if (!Window::getConstant(instance()->getDisplayOrientation(displayindex), orientationstr))
235 return luaL_error(L, "Unknown display orientation type.");
236
237 lua_pushstring(L, orientationstr);
238 return 1;
239}
240
241int w_getFullscreenModes(lua_State *L)
242{
243 int displayindex = 0;
244 if (!lua_isnoneornil(L, 1))
245 displayindex = (int) luaL_checkinteger(L, 1) - 1;
246 else
247 {
248 int x, y;
249 instance()->getPosition(x, y, displayindex);
250 }
251
252 std::vector<Window::WindowSize> modes = instance()->getFullscreenSizes(displayindex);
253
254 lua_createtable(L, (int) modes.size(), 0);
255
256 for (size_t i = 0; i < modes.size(); i++)
257 {
258 lua_pushinteger(L, i + 1);
259 lua_createtable(L, 0, 2);
260
261 // Inner table attribs.
262
263 lua_pushinteger(L, modes[i].width);
264 lua_setfield(L, -2, "width");
265
266 lua_pushinteger(L, modes[i].height);
267 lua_setfield(L, -2, "height");
268
269 // Inner table attribs end.
270
271 lua_settable(L, -3);
272 }
273
274 return 1;
275}
276
277int w_setFullscreen(lua_State *L)
278{
279 bool fullscreen = luax_checkboolean(L, 1);
280 Window::FullscreenType fstype = Window::FULLSCREEN_MAX_ENUM;
281
282 const char *typestr = lua_isnoneornil(L, 2) ? 0 : luaL_checkstring(L, 2);
283 if (typestr && !Window::getConstant(typestr, fstype))
284 return luax_enumerror(L, "fullscreen type", Window::getConstants(fstype), typestr);
285
286 bool success = false;
287 luax_catchexcept(L, [&]() {
288 if (fstype == Window::FULLSCREEN_MAX_ENUM)
289 success = instance()->setFullscreen(fullscreen);
290 else
291 success = instance()->setFullscreen(fullscreen, fstype);
292 });
293
294 luax_pushboolean(L, success);
295 return 1;
296}
297
298int w_getFullscreen(lua_State *L)
299{
300 int w, h;
301 WindowSettings settings;
302 instance()->getWindow(w, h, settings);
303
304 const char *typestr;
305 if (!Window::getConstant(settings.fstype, typestr))
306 luaL_error(L, "Unknown fullscreen type.");
307
308 luax_pushboolean(L, settings.fullscreen);
309 lua_pushstring(L, typestr);
310 return 2;
311}
312
313int w_isOpen(lua_State *L)
314{
315 luax_pushboolean(L, instance()->isOpen());
316 return 1;
317}
318
319int w_close(lua_State *L)
320{
321 luax_catchexcept(L, [&]() { instance()->close(); });
322 return 0;
323}
324
325int w_getDesktopDimensions(lua_State *L)
326{
327 int width = 0, height = 0;
328 int displayindex = 0;
329 if (!lua_isnoneornil(L, 1))
330 displayindex = (int) luaL_checkinteger(L, 1) - 1;
331 else
332 {
333 int x, y;
334 instance()->getPosition(x, y, displayindex);
335 }
336 instance()->getDesktopDimensions(displayindex, width, height);
337 lua_pushinteger(L, width);
338 lua_pushinteger(L, height);
339 return 2;
340}
341
342int w_setPosition(lua_State *L)
343{
344 int x = (int) luaL_checkinteger(L, 1);
345 int y = (int) luaL_checkinteger(L, 2);
346
347 int displayindex = 0;
348 if (!lua_isnoneornil(L, 3))
349 displayindex = (int) luaL_checkinteger(L, 3) - 1;
350 else
351 {
352 int x_unused, y_unused;
353 instance()->getPosition(x_unused, y_unused, displayindex);
354 }
355
356 instance()->setPosition(x, y, displayindex);
357 return 0;
358}
359
360int w_getPosition(lua_State *L)
361{
362 int x = 0;
363 int y = 0;
364 int displayindex = 0;
365 instance()->getPosition(x, y, displayindex);
366 lua_pushinteger(L, x);
367 lua_pushinteger(L, y);
368 lua_pushinteger(L, displayindex + 1);
369 return 3;
370}
371
372int w_getSafeArea(lua_State *L)
373{
374 Rect area = instance()->getSafeArea();
375 lua_pushnumber(L, area.x);
376 lua_pushnumber(L, area.y);
377 lua_pushnumber(L, area.w);
378 lua_pushnumber(L, area.h);
379 return 4;
380}
381
382int w_setIcon(lua_State *L)
383{
384 image::ImageData *i = luax_checktype<image::ImageData>(L, 1);
385 bool success = false;
386 luax_catchexcept(L, [&]() { success = instance()->setIcon(i); });
387 luax_pushboolean(L, success);
388 return 1;
389}
390
391int w_getIcon(lua_State *L)
392{
393 image::ImageData *i = instance()->getIcon();
394 luax_pushtype(L, i);
395 return 1;
396}
397
398int w_setVSync(lua_State *L)
399{
400 int vsync = 0;
401 if (lua_type(L, 1) == LUA_TBOOLEAN)
402 vsync = lua_toboolean(L, 1);
403 else
404 vsync = (int)luaL_checkinteger(L, 1);
405 instance()->setVSync(vsync);
406 return 0;
407}
408
409int w_getVSync(lua_State *L)
410{
411 lua_pushinteger(L, instance()->getVSync());
412 return 1;
413}
414
415int w_setDisplaySleepEnabled(lua_State *L)
416{
417 instance()->setDisplaySleepEnabled(luax_checkboolean(L, 1));
418 return 0;
419}
420
421int w_isDisplaySleepEnabled(lua_State *L)
422{
423 luax_pushboolean(L, instance()->isDisplaySleepEnabled());
424 return 1;
425}
426
427int w_setTitle(lua_State *L)
428{
429 std::string title = luax_checkstring(L, 1);
430 instance()->setWindowTitle(title);
431 return 0;
432}
433
434int w_getTitle(lua_State *L)
435{
436 luax_pushstring(L, instance()->getWindowTitle());
437 return 1;
438}
439
440int w_hasFocus(lua_State *L)
441{
442 luax_pushboolean(L, instance()->hasFocus());
443 return 1;
444}
445
446int w_hasMouseFocus(lua_State *L)
447{
448 luax_pushboolean(L, instance()->hasMouseFocus());
449 return 1;
450}
451
452int w_isVisible(lua_State *L)
453{
454 luax_pushboolean(L, instance()->isVisible());
455 return 1;
456}
457
458int w_getDPIScale(lua_State *L)
459{
460 lua_pushnumber(L, instance()->getDPIScale());
461 return 1;
462}
463
464int w_getNativeDPIScale(lua_State *L)
465{
466 lua_pushnumber(L, instance()->getNativeDPIScale());
467 return 1;
468}
469
470int w_toPixels(lua_State *L)
471{
472 double wx = luaL_checknumber(L, 1);
473
474 if (lua_isnoneornil(L, 2))
475 {
476 lua_pushnumber(L, instance()->toPixels(wx));
477 return 1;
478 }
479
480 double wy = luaL_checknumber(L, 2);
481 double px = 0.0, py = 0.0;
482
483 instance()->toPixels(wx, wy, px, py);
484
485 lua_pushnumber(L, px);
486 lua_pushnumber(L, py);
487
488 return 2;
489}
490
491int w_fromPixels(lua_State *L)
492{
493 double px = luaL_checknumber(L, 1);
494
495 if (lua_isnoneornil(L, 2))
496 {
497 lua_pushnumber(L, instance()->fromPixels(px));
498 return 1;
499 }
500
501 double py = luaL_checknumber(L, 2);
502 double wx = 0.0, wy = 0.0;
503
504 instance()->fromPixels(px, py, wx, wy);
505
506 lua_pushnumber(L, wx);
507 lua_pushnumber(L, wy);
508
509 return 2;
510}
511
512int w_minimize(lua_State* /*L*/)
513{
514 instance()->minimize();
515 return 0;
516}
517
518int w_maximize(lua_State *)
519{
520 instance()->maximize();
521 return 0;
522}
523
524int w_restore(lua_State *)
525{
526 instance()->restore();
527 return 0;
528}
529
530int w_isMaximized(lua_State *L)
531{
532 luax_pushboolean(L, instance()->isMaximized());
533 return 1;
534}
535
536int w_isMinimized(lua_State *L)
537{
538 luax_pushboolean(L, instance()->isMinimized());
539 return 1;
540}
541
542int w_showMessageBox(lua_State *L)
543{
544 Window::MessageBoxData data = {};
545 data.type = Window::MESSAGEBOX_INFO;
546
547 data.title = luaL_checkstring(L, 1);
548 data.message = luaL_checkstring(L, 2);
549
550 // If we have a table argument, we assume a list of button names, which
551 // means we should use the more complex message box API.
552 if (lua_istable(L, 3))
553 {
554 size_t numbuttons = luax_objlen(L, 3);
555 if (numbuttons == 0)
556 return luaL_error(L, "Must have at least one messagebox button.");
557
558 // Array of button names.
559 for (size_t i = 0; i < numbuttons; i++)
560 {
561 lua_rawgeti(L, 3, (int) i + 1);
562 data.buttons.push_back(luax_checkstring(L, -1));
563 lua_pop(L, 1);
564 }
565
566 // Optional table entry specifying the button to use when enter is pressed.
567 lua_getfield(L, 3, "enterbutton");
568 if (!lua_isnoneornil(L, -1))
569 data.enterButtonIndex = (int) luaL_checkinteger(L, -1) - 1;
570 else
571 data.enterButtonIndex = 0;
572 lua_pop(L, 1);
573
574 // Optional table entry specifying the button to use when esc is pressed.
575 lua_getfield(L, 3, "escapebutton");
576 if (!lua_isnoneornil(L, -1))
577 data.escapeButtonIndex = (int) luaL_checkinteger(L, -1) - 1;
578 else
579 data.escapeButtonIndex = (int) data.buttons.size() - 1;
580 lua_pop(L, 1);
581
582 const char *typestr = lua_isnoneornil(L, 4) ? nullptr : luaL_checkstring(L, 4);
583 if (typestr && !Window::getConstant(typestr, data.type))
584 return luax_enumerror(L, "messagebox type", Window::getConstants(data.type), typestr);
585
586 data.attachToWindow = luax_optboolean(L, 5, true);
587
588 int pressedbutton = instance()->showMessageBox(data);
589 lua_pushinteger(L, pressedbutton + 1);
590 }
591 else
592 {
593 const char *typestr = lua_isnoneornil(L, 3) ? nullptr : luaL_checkstring(L, 3);
594 if (typestr && !Window::getConstant(typestr, data.type))
595 return luax_enumerror(L, "messagebox type", Window::getConstants(data.type), typestr);
596
597 data.attachToWindow = luax_optboolean(L, 4, true);
598
599 // Display a simple message box.
600 bool success = instance()->showMessageBox(data.title, data.message, data.type, data.attachToWindow);
601 luax_pushboolean(L, success);
602 }
603
604 return 1;
605}
606
607int w_requestAttention(lua_State *L)
608{
609 bool continuous = luax_optboolean(L, 1, false);
610 instance()->requestAttention(continuous);
611 return 0;
612}
613
614static const luaL_Reg functions[] =
615{
616 { "getDisplayCount", w_getDisplayCount },
617 { "getDisplayName", w_getDisplayName },
618 { "setMode", w_setMode },
619 { "updateMode", w_updateMode },
620 { "getMode", w_getMode },
621 { "getDisplayOrientation", w_getDisplayOrientation },
622 { "getFullscreenModes", w_getFullscreenModes },
623 { "setFullscreen", w_setFullscreen },
624 { "getFullscreen", w_getFullscreen },
625 { "isOpen", w_isOpen },
626 { "close", w_close },
627 { "getDesktopDimensions", w_getDesktopDimensions },
628 { "setPosition", w_setPosition },
629 { "getPosition", w_getPosition },
630 { "getSafeArea", w_getSafeArea },
631 { "setIcon", w_setIcon },
632 { "getIcon", w_getIcon },
633 { "setVSync", w_setVSync },
634 { "getVSync", w_getVSync },
635 { "setDisplaySleepEnabled", w_setDisplaySleepEnabled },
636 { "isDisplaySleepEnabled", w_isDisplaySleepEnabled },
637 { "setTitle", w_setTitle },
638 { "getTitle", w_getTitle },
639 { "hasFocus", w_hasFocus },
640 { "hasMouseFocus", w_hasMouseFocus },
641 { "isVisible", w_isVisible },
642 { "getDPIScale", w_getDPIScale },
643 { "getNativeDPIScale", w_getNativeDPIScale },
644 { "toPixels", w_toPixels },
645 { "fromPixels", w_fromPixels },
646 { "minimize", w_minimize },
647 { "maximize", w_maximize },
648 { "restore", w_restore },
649 { "isMaximized", w_isMaximized },
650 { "isMinimized", w_isMinimized },
651 { "showMessageBox", w_showMessageBox },
652 { "requestAttention", w_requestAttention },
653 { 0, 0 }
654};
655
656extern "C" int luaopen_love_window(lua_State *L)
657{
658 Window *instance = instance();
659 if (instance == nullptr)
660 luax_catchexcept(L, [&](){ instance = new love::window::sdl::Window(); });
661 else
662 instance->retain();
663
664 WrappedModule w;
665 w.module = instance;
666 w.name = "window";
667 w.type = &Module::type;
668 w.functions = functions;
669 w.types = 0;
670
671 return luax_register_module(L, w);
672}
673
674} // window
675} // love
676