1// LAF OS Library
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2012-2017 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#ifndef OS_SYSTEM_H_INCLUDED
9#define OS_SYSTEM_H_INCLUDED
10#pragma once
11
12#include "gfx/fwd.h"
13#include "os/app_mode.h"
14#include "os/capabilities.h"
15#include "os/color_space.h"
16#include "os/keys.h"
17#include "os/ref.h"
18#include "os/screen.h"
19#include "os/window.h"
20#include "os/window_spec.h"
21
22#include <functional>
23#include <memory>
24#include <stdexcept>
25#include <string>
26
27namespace os {
28
29 class ColorSpaceConversion;
30 class EventQueue;
31 class Font;
32 class FontManager;
33 class Logger;
34 class Menus;
35 class NativeDialogs;
36 class Surface;
37 class System;
38
39 using SystemRef = Ref<System>;
40
41 // TODO why we just don't return nullptr if the window creation fails?
42 // maybe an error handler function?
43 class WindowCreationException : public std::runtime_error {
44 public:
45 WindowCreationException(const char* msg) throw()
46 : std::runtime_error(msg) { }
47 };
48
49 // Windows-specific details: API to use to get tablet input information.
50 enum class TabletAPI {
51 // Default tablet API to use in the system (Windows Ink on
52 // Windows; only valid value on other systems).
53 Default = 0,
54
55 // Use Windows 8/10 pointer messages (Windows Ink).
56 WindowsPointerInput = 0,
57
58 // Use the Wintab API to get pressure information from packets but
59 // mouse movement from Windows system messages
60 // (WM_MOUSEMOVE).
61 Wintab = 1,
62
63 // Use the Wintab API processing packets directly (pressure and
64 // stylus movement information). With this we might get more
65 // precision from the device (but still work-in-progress, some
66 // messages might be mixed up).
67 WintabPackets = 2,
68 };
69
70 class System : public RefCount {
71 protected:
72 virtual ~System() { }
73 public:
74
75 // Windows-specific: The app name at the moment is used to receive
76 // DDE messages (WM_DDE_INITIATE) and convert WM_DDE_EXECUTE
77 // messages into Event::DropFiles. This allows to the user
78 // double-click files in the File Explorer and open the file in a
79 // running instance of your app.
80 //
81 // To receive DDE messages you have to configure the registry in
82 // this way (HKCR=HKEY_CLASSES_ROOT):
83 //
84 // HKCR\.appfile (Default)="AppFile"
85 // HKCR\AppFile (Default)="App File"
86 // HKCR\AppFile\shell\open\command (Default)="C:\\...\\AppName.EXE"
87 // HKCR\AppFile\shell\open\ddeexec (Default)="[open(\"%1\")]"
88 // HKCR\AppFile\shell\open\ddeexec\application (Default)="AppName"
89 // HKCR\AppFile\shell\open\ddeexec\topic (Default)="system"
90 //
91 // The default value of "HKCR\AppFile\shell\open\ddeexec\application"
92 // must match the "appName" given in this function.
93 virtual void setAppName(const std::string& appName) = 0;
94
95 // We can use this function to create an application that can run
96 // in CLI and GUI mode depending on the given arguments, and in
97 // this way avoid to showing the app in the macOS dock bar if we
98 // are running in CLI only.
99 virtual void setAppMode(AppMode appMode) = 0;
100
101 // Marks a specific file as a file that was processed in the CLI.
102 // Only useful on macOS to avoid generating DropFiles events for
103 // files that were processed from the CLI arguments directly.
104 virtual void markCliFileAsProcessed(const std::string& cliFile) = 0;
105
106 // On macOS it calls [NSApplication finishLaunching] that will
107 // produce some extra events like [NSApplicationDelegate
108 // application:openFiles:] which generates os::Event::DropFiles
109 // events for each file specified in the command line.
110 //
111 // You can ignore those DropFiles events if you've already
112 // processed through the CLI arguments (app_main(argc, argv)) or
113 // you can use markCliFileAsProcessed() before calling this
114 // function.
115 virtual void finishLaunching() = 0;
116
117 // We might need to call this function when the app is launched
118 // from Steam. It appears that there is a bug on macOS Steam
119 // client where the app is launched, activated, and then the Steam
120 // client is activated again.
121 virtual void activateApp() = 0;
122
123 virtual Capabilities capabilities() const = 0;
124 bool hasCapability(Capabilities c) const {
125 return (int(capabilities()) & int(c)) == int(c);
126 }
127
128 // Sets the specific API to use to process tablet/stylus/pen
129 // messages.
130 //
131 // It can be used to avoid loading wintab32.dll too (sometimes a
132 // program can be locked when we load the wintab32.dll, so we need
133 // a way to opt-out loading this library.)
134 virtual void setTabletAPI(TabletAPI api) = 0;
135 virtual TabletAPI tabletAPI() const = 0;
136
137 // Sub-interfaces
138 virtual Logger* logger() = 0;
139 virtual Menus* menus() = 0;
140 virtual NativeDialogs* nativeDialogs() = 0;
141 virtual EventQueue* eventQueue() = 0;
142
143 virtual bool gpuAcceleration() const = 0;
144 virtual void setGpuAcceleration(bool state) = 0;
145
146 // Returns the main screen
147 virtual ScreenRef mainScreen() = 0;
148
149 // Returns a list of screens attached to the computer.
150 virtual void listScreens(ScreenList& screens) = 0;
151
152 virtual Window* defaultWindow() = 0;
153
154 // Creates a new window in the operating system with the given
155 // specs (width, height, etc.).
156 virtual WindowRef makeWindow(const WindowSpec& spec) = 0;
157
158 WindowRef makeWindow(const int contentWidth,
159 const int contentHeight,
160 const int scale = 1) {
161 return makeWindow(WindowSpec(contentWidth, contentHeight, scale));
162 }
163
164 virtual Ref<Surface> makeSurface(int width, int height, const os::ColorSpaceRef& colorSpace = nullptr) = 0;
165 virtual Ref<Surface> makeRgbaSurface(int width, int height, const os::ColorSpaceRef& colorSpace = nullptr) = 0;
166 virtual Ref<Surface> loadSurface(const char* filename) = 0;
167 virtual Ref<Surface> loadRgbaSurface(const char* filename) = 0;
168
169 // Creates a new cursor with the given surface.
170 //
171 // Warning: On Windows there is a limit of 10,000 GDI objects per
172 // process and creating a cursor needs 3 GDI objects (a HCURSOR
173 // and two HBITMAPs).
174 virtual Ref<Cursor> makeCursor(const Surface* surface,
175 const gfx::Point& focus,
176 const int scale) = 0;
177
178 // New font manager
179 virtual FontManager* fontManager() = 0;
180
181 // Old font functions (to be removed)
182 virtual Ref<Font> loadSpriteSheetFont(const char* filename, int scale = 1) = 0;
183 virtual Ref<Font> loadTrueTypeFont(const char* filename, int height) = 0;
184
185 // Returns true if the the given scancode key is pressed/actived.
186 virtual bool isKeyPressed(KeyScancode scancode) = 0;
187
188 // Returns the active pressed modifiers.
189 virtual KeyModifiers keyModifiers() = 0;
190
191 // Returns the latest unicode character that activated the given
192 // scancode.
193 virtual int getUnicodeFromScancode(KeyScancode scancode) = 0;
194
195 // Indicates if you want to use dead keys or not. By default it's
196 // false, which behaves as regular shortcuts. You should set this
197 // to true when you're inside a text field in your app.
198 //
199 // TODO Improve this API using different input modes,
200 // e.g. GameLike, TextInput, TextInputWithDeadKeys
201 virtual void setTranslateDeadKeys(bool state) = 0;
202
203 // Returns the mouse position in the screen. Try to avoid using
204 // this and prefer the Event mouse position.
205 virtual gfx::Point mousePosition() const = 0;
206
207 // Sets the mouse position to a specific point in the screen.
208 virtual void setMousePosition(const gfx::Point& screenPosition) = 0;
209
210 // Gets a color from the desktop in given screen position.
211 //
212 // WARNING for macOS: This function will ask the user for
213 // permissions to record the screen. If the app is not in a signed
214 // bundle, the color will be from the wallpaper, but if the
215 // function is used in a signed bundled app, the color will be
216 // from any opened window.
217 virtual gfx::Color getColorFromScreen(const gfx::Point& screenPosition) const = 0;
218
219 // Color management
220 virtual void listColorSpaces(
221 std::vector<os::ColorSpaceRef>& list) = 0;
222 virtual os::ColorSpaceRef makeColorSpace(
223 const gfx::ColorSpaceRef& colorSpace) = 0;
224 virtual Ref<ColorSpaceConversion> convertBetweenColorSpace(
225 const os::ColorSpaceRef& src,
226 const os::ColorSpaceRef& dst) = 0;
227
228 // Set a default color profile for all windows (nullptr to use the
229 // active monitor color profile and change it dynamically when the
230 // window changes to another monitor).
231 virtual void setWindowsColorSpace(const os::ColorSpaceRef& cs) = 0;
232 virtual os::ColorSpaceRef windowsColorSpace() = 0;
233
234 // Function called to handle a "live resize"/resizing loop of a
235 // native window. If this is nullptr, an Event::ResizeWindow is
236 // generated when the resizing is finished.
237 //
238 // TODO I think we should have a SystemDelegate or something
239 // similar instead of a public property.
240 std::function<void(os::Window*)> handleWindowResize = nullptr;
241
242#if LAF_WINDOWS
243 // Only useful on Windows, the delegate must be a pointer to a
244 // WintabAPI::Delegate, and it must be deleted by the user
245 // manually (it's not owned by the os::System impl).
246 //
247 // This can be used to get information about the wintab32.dll
248 // vendor (company name, etc.)
249 virtual void setWintabDelegate(void* delegate) { }
250#endif
251 };
252
253 SystemRef make_system();
254 System* instance();
255 void set_instance(System* system);
256
257} // namespace os
258
259#endif
260