1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2005, Günther
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18#pragma once
19
20#include "C4Log.h"
21#include "C4Windows.h"
22#include "StdSync.h"
23#include "StdWindow.h"
24
25#ifdef _WIN32
26
27const int SEC1_TIMER = 1, SEC1_MSEC = 1000;
28#elif defined(USE_X11)
29struct _XIC;
30struct _XIM;
31#include <unordered_map>
32#endif
33
34#ifdef WITH_GLIB
35struct _GMainLoop;
36struct _GIOChannel;
37#endif
38
39#include <thread>
40#include <stdexcept>
41
42#ifdef _WIN32
43#define K_ALT VK_MENU
44#define K_ESCAPE VK_ESCAPE
45#define K_PAUSE VK_PAUSE
46#define K_TAB VK_TAB
47#define K_RETURN VK_RETURN
48#define K_DELETE VK_DELETE
49#define K_INSERT VK_INSERT
50#define K_BACK VK_BACK
51#define K_SPACE VK_SPACE
52#define K_F1 VK_F1
53#define K_F2 VK_F2
54#define K_F3 VK_F3
55#define K_F4 VK_F4
56#define K_F5 VK_F5
57#define K_F6 VK_F6
58#define K_F7 VK_F7
59#define K_F8 VK_F8
60#define K_F9 VK_F9
61#define K_F10 VK_F10
62#define K_F11 VK_F11
63#define K_F12 VK_F12
64#define K_ADD VK_ADD
65#define K_SUBTRACT VK_SUBTRACT
66#define K_MULTIPLY VK_MULTIPLY
67#define K_UP VK_UP
68#define K_DOWN VK_DOWN
69#define K_LEFT VK_LEFT
70#define K_RIGHT VK_RIGHT
71#define K_HOME VK_HOME
72#define K_END VK_END
73#define K_SCROLL VK_SCROLL
74#define K_MENU VK_APPS
75#define K_PAGEUP VK_PRIOR
76#define K_PAGEDOWN VK_NEXT
77#define KEY_A ((uint16_t) 'A') // select all in GUI-editbox
78#define KEY_C ((uint16_t) 'C') // copy in GUI-editbox
79#define KEY_F ((uint16_t) 'F') // search in ScenSelDlg
80#define KEY_I ((uint16_t) 'I') // console mode control key
81#define KEY_M ((uint16_t) 'M') // console mode control key
82#define KEY_T ((uint16_t) 'T') // console mode control key
83#define KEY_V ((uint16_t) 'V') // paste in GUI-editbox
84#define KEY_W ((uint16_t) 'W') // console mode control key
85#define KEY_X ((uint16_t) 'X') // cut from GUI-editbox
86#elif defined(USE_X11)
87#include <X11/keysym.h>
88#include <sys/time.h>
89#define K_F1 XK_F1
90#define K_F2 XK_F2
91#define K_F3 XK_F3
92#define K_F4 XK_F4
93#define K_F5 XK_F5
94#define K_F6 XK_F6
95#define K_F7 XK_F7
96#define K_F8 XK_F8
97#define K_F9 XK_F9
98#define K_F10 XK_F10
99#define K_F11 XK_F11
100#define K_F12 XK_F12
101#define K_ADD XK_KP_Add
102#define K_SUBTRACT XK_KP_Subtract
103#define K_MULTIPLY XK_KP_Multiply
104#define K_ESCAPE XK_Escape
105#define K_PAUSE XK_Pause
106#define K_TAB XK_Tab
107#define K_RETURN XK_Return
108#define K_DELETE XK_Delete
109#define K_INSERT XK_Insert
110#define K_BACK XK_BackSpace
111#define K_SPACE XK_space
112#define K_UP XK_Up
113#define K_DOWN XK_Down
114#define K_LEFT XK_Left
115#define K_RIGHT XK_Right
116#define K_HOME XK_Home
117#define K_END XK_End
118#define K_SCROLL XK_Scroll_Lock
119#define K_MENU XK_Menu
120#define K_PAGEUP XK_Page_Up
121#define K_PAGEDOWN XK_Page_Down
122#define KEY_A XK_a // select all in GUI-editbox
123#define KEY_C XK_c // copy in GUI-editbox
124#define KEY_F XK_f // search in ScenSelDlg
125#define KEY_I XK_i // console mode control key
126#define KEY_M XK_m // console mode control key
127#define KEY_T XK_t // console mode control key
128#define KEY_V XK_v // paste in GUI-editbox
129#define KEY_W XK_w // console mode control key
130#define KEY_X XK_x // cut from GUI-editbox
131// from X.h:
132//#define ShiftMask (1 << 0)
133//#define ControlMask (1 << 2)
134#define MK_ALT (1 << 3)
135#define MK_CONTROL (1 << 2)
136#define MK_SHIFT (1 << 0)
137#elif defined(USE_SDL_MAINLOOP)
138#include <StdSdlSubSystem.h>
139#include <SDL.h>
140#include <optional>
141#define K_F1 SDL_SCANCODE_F1
142#define K_F2 SDL_SCANCODE_F2
143#define K_F3 SDL_SCANCODE_F3
144#define K_F4 SDL_SCANCODE_F4
145#define K_F5 SDL_SCANCODE_F5
146#define K_F6 SDL_SCANCODE_F6
147#define K_F7 SDL_SCANCODE_F7
148#define K_F8 SDL_SCANCODE_F8
149#define K_F9 SDL_SCANCODE_F9
150#define K_F10 SDL_SCANCODE_F10
151#define K_F11 SDL_SCANCODE_F11
152#define K_F12 SDL_SCANCODE_F12
153#define K_ADD SDL_SCANCODE_KP_PLUS
154#define K_SUBTRACT SDL_SCANCODE_MINUS
155#define K_MULTIPLY SDL_SCANCODE_KP_MULTIPLY
156#define K_ESCAPE SDL_SCANCODE_ESCAPE
157#define K_PAUSE SDL_SCANCODE_PAUSE
158#define K_TAB SDL_SCANCODE_TAB
159#define K_RETURN SDL_SCANCODE_RETURN
160#define K_DELETE SDL_SCANCODE_DELETE
161#define K_INSERT SDL_SCANCODE_INSERT
162#define K_BACK SDL_SCANCODE_BACKSPACE
163#define K_SPACE SDL_SCANCODE_SPACE
164#define K_UP SDL_SCANCODE_UP
165#define K_DOWN SDL_SCANCODE_DOWN
166#define K_LEFT SDL_SCANCODE_LEFT
167#define K_RIGHT SDL_SCANCODE_RIGHT
168#define K_HOME SDL_SCANCODE_HOME
169#define K_END SDL_SCANCODE_END
170#define K_SCROLL SDL_SCANCODE_SCROLLOCK
171#define K_MENU SDL_SCANCODE_MENU
172#define K_PAGEUP SDL_SCANCODE_PAGEUP
173#define K_PAGEDOWN SDL_SCANCODE_PAGEDOWN
174#define KEY_M SDL_SCANCODE_M
175#define KEY_T SDL_SCANCODE_T
176#define KEY_W SDL_SCANCODE_W
177#define KEY_I SDL_SCANCODE_I
178#define KEY_C SDL_SCANCODE_C
179#define KEY_V SDL_SCANCODE_V
180#define KEY_X SDL_SCANCODE_X
181#define KEY_A SDL_SCANCODE_A
182#define KEY_F SDL_SCANCODE_F
183#define MK_ALT KMOD_ALT
184#define MK_CONTROL KMOD_CTRL
185#define MK_SHIFT KMOD_SHIFT
186#elif defined(USE_CONSOLE)
187#define K_F1 0
188#define K_F2 0
189#define K_F3 0
190#define K_F4 0
191#define K_F5 0
192#define K_F6 0
193#define K_F7 0
194#define K_F8 0
195#define K_F9 0
196#define K_F10 0
197#define K_F11 0
198#define K_F12 0
199#define K_ADD 0
200#define K_SUBTRACT 0
201#define K_MULTIPLY 0
202#define K_ESCAPE 0
203#define K_PAUSE 0
204#define K_TAB 0
205#define K_RETURN 0
206#define K_DELETE 0
207#define K_INSERT 0
208#define K_BACK 0
209#define K_SPACE 0
210#define K_UP 0
211#define K_DOWN 0
212#define K_LEFT 0
213#define K_RIGHT 0
214#define K_HOME 0
215#define K_END 0
216#define K_SCROLL 0
217#define K_MENU 0
218#define K_PAGEUP 0
219#define K_PAGEDOWN 0
220#define KEY_M 0
221#define KEY_T 0
222#define KEY_W 0
223#define KEY_I 0
224#define KEY_C 0
225#define KEY_V 0
226#define KEY_X 0
227#define KEY_F 0
228#define KEY_A 0
229#define MK_ALT 0
230#define MK_SHIFT 0
231#define MK_CONTROL 0
232#else
233#error need window system
234#endif
235
236enum C4AppHandleResult
237{
238 HR_Timeout,
239 HR_Message, // handled a message
240 HR_Timer, // got timer event
241 HR_Failure, // error, or quit message received
242};
243
244class CStdApp
245{
246public:
247 class StartupException : public std::runtime_error
248 {
249 public:
250 using runtime_error::runtime_error;
251 };
252
253public:
254 CStdApp();
255 virtual ~CStdApp();
256
257 bool Active{false};
258
259 virtual void Clear();
260 virtual void Execute();
261 void Run();
262 virtual void Quit();
263 virtual int32_t &ScreenWidth() = 0;
264 virtual int32_t &ScreenHeight() = 0;
265 virtual float GetScale() = 0;
266 C4AppHandleResult HandleMessage(unsigned int iTimeout = StdSync::Infinite, bool fCheckTimer = true);
267 void SetDisplayMode(DisplayMode mode) { pWindow->SetDisplayMode(mode); }
268 void ResetTimer(unsigned int uDelay);
269 CStdWindow *pWindow;
270 bool fQuitMsgReceived{false}; // if true, a quit message has been received and the application should terminate
271 const char *GetCommandLine() { return szCmdLine; }
272
273 // Copy the text to the clipboard or the primary selection
274 bool Copy(std::string_view text, bool fClipboard = true);
275 // Paste the text from the clipboard or the primary selection
276 std::string Paste(bool fClipboard = true);
277 // Is there something in the clipboard?
278 bool IsClipboardFull(bool fClipboard = true);
279 // a command from stdin
280 virtual void OnCommand(const char *szCmd) = 0; // callback
281
282 // notify user to get back to the program
283 void NotifyUserIfInactive()
284 {
285#ifndef USE_CONSOLE
286 if (pWindow) pWindow->FlashWindow();
287#endif
288 }
289
290 bool IsMainThread()
291 {
292 return mainThread == std::this_thread::get_id();
293 }
294
295 bool AssertMainThread()
296 {
297 if (!IsMainThread())
298 {
299 throw std::runtime_error{"Not in main thread"};
300 }
301
302 return true;
303 }
304
305#ifdef _WIN32
306 HINSTANCE hInstance;
307 int iLastExecute, iTimerOffset;
308 CStdEvent TimerEvent{CStdEvent::AutoReset()}; // set periodically by critical timer (C4Engine)
309 CStdEvent NetworkEvent{CStdEvent::AutoReset()}; // set if a network event occured
310 void Init(HINSTANCE hInst, int nCmdShow, char *szCmdLine);
311 void NextTick(bool fYield) { TimerEvent.Set(); if (fYield) Sleep(0); }
312 bool IsShiftDown() { return GetKeyState(VK_SHIFT) < 0; }
313 bool IsControlDown() { return GetKeyState(VK_CONTROL) < 0; }
314 bool IsAltDown() { return GetKeyState(VK_MENU) < 0; }
315 HWND GetWindowHandle() { return pWindow ? pWindow->hWindow : nullptr; }
316
317protected:
318 bool SetCriticalTimer();
319 void CloseCriticalTimer();
320 bool fTimePeriod;
321 UINT uCriticalTimerDelay, uCriticalTimerResolution;
322 UINT idCriticalTimer;
323 UINT GetDelay() { return uCriticalTimerDelay; }
324#else
325#ifdef USE_X11
326 Display *dpy{nullptr};
327 int xf86vmode_major_version, xf86vmode_minor_version;
328#endif
329 const char *Location{""};
330 void Init(int argc, char *argv[]);
331 bool DoNotDelay{false};
332 void NextTick(bool fYield);
333 bool IsShiftDown() { return KeyMask & MK_SHIFT; }
334 bool IsControlDown() { return KeyMask & MK_CONTROL; }
335 bool IsAltDown() { return KeyMask & MK_ALT; }
336 unsigned int GetModifiers() const { return KeyMask; }
337 bool SignalNetworkEvent();
338
339 // These must be public to be callable from callback functions from
340 // the glib main loop that are in an anonymous namespace in
341 // StdXApp.cpp.
342 void OnXInput();
343 void OnPipeInput();
344 void OnStdInInput();
345#endif
346
347protected:
348#ifndef _WIN32
349 unsigned int Delay{27777}; // 36 FPS
350 timeval LastExecute;
351 int argc;
352 char **argv;
353 int Pipe[2];
354 unsigned int KeyMask{0};
355
356#ifdef WITH_GLIB
357 virtual std::shared_ptr<spdlog::logger> CreateGLibLogger() = 0;
358
359 std::shared_ptr<spdlog::logger> glibLogger;
360 unsigned int glibLogHandlerId{};
361 _GMainLoop *loop{nullptr};
362#ifdef USE_X11
363 _GIOChannel *xChannel{nullptr};
364#endif
365 _GIOChannel *pipeChannel{nullptr};
366#endif
367
368#ifdef USE_X11
369 int detectable_autorepeat_supported;
370 _XIM *inputMethod{nullptr};
371 _XIC *inputContext{nullptr};
372 std::unordered_map<unsigned long, CStdWindow *> windows;
373 unsigned long LastEventTime{0};
374 std::string primarySelection;
375 std::string clipboardSelection;
376#elif defined(USE_SDL_MAINLOOP)
377 int nextWidth, nextHeight, nextBPP;
378 std::optional<StdSdlSubSystem> sdlVideoSubSys;
379#endif
380
381#ifdef USE_X11
382 void NewWindow(CStdWindow *window);
383 void HandleXMessage();
384#elif defined(USE_SDL_MAINLOOP)
385 void HandleSDLEvent(SDL_Event &event);
386#endif
387#endif
388
389 const char *szCmdLine;
390 std::thread::id mainThread{};
391 bool InitTimer();
392 virtual void DoInit() = 0;
393 virtual void OnNetworkEvents() = 0;
394
395 // commands from stdin (console only)
396 StdStrBuf CmdBuf;
397 bool ReadStdInCommand();
398
399 friend class CStdGL;
400 friend class CStdWindow;
401 friend class CStdGtkWindow;
402};
403
404#ifdef WITH_GLIB
405template<>
406struct C4LoggerConfig::Name<_GMainLoop>
407{
408 static constexpr auto Value = "GLib";
409};
410#endif
411