1#include "os.h"
2
3#include "dbg.h"
4#include "fs.h"
5#include "mem.h"
6#include "str.h"
7#include "windows.h"
8
9#include <cstdlib>
10#include <iterator> // std::size
11
12#if defined(CONF_FAMILY_UNIX)
13#include "io.h"
14
15#include <sys/utsname.h> // uname, utsname
16#include <unistd.h> // execlp, fork
17
18#if defined(CONF_PLATFORM_MACOS)
19#include <CoreFoundation/CoreFoundation.h>
20#endif
21#elif defined(CONF_FAMILY_WINDOWS)
22#include "mem.h"
23
24#include <objbase.h> // required for shellapi.h
25#include <shellapi.h> // ShellExecuteExW
26#include <windows.h>
27
28#include <cfenv> // fesetenv
29#include <cstring> // std::wstring
30#else
31#error NOT IMPLEMENTED
32#endif
33
34void cmdline_fix(int *argc, const char ***argv)
35{
36#if defined(CONF_FAMILY_WINDOWS)
37 int wide_argc = 0;
38 WCHAR **wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc);
39 dbg_assert(wide_argv != NULL, "CommandLineToArgvW failure");
40 dbg_assert(wide_argc > 0, "Invalid argc value");
41
42 int total_size = 0;
43
44 for(int i = 0; i < wide_argc; i++)
45 {
46 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, nullptr, 0, nullptr, nullptr);
47 dbg_assert(size != 0, "WideCharToMultiByte failure");
48 total_size += size;
49 }
50
51 char **new_argv = (char **)malloc((wide_argc + 1) * sizeof(*new_argv));
52 new_argv[0] = (char *)malloc(total_size);
53 mem_zero(new_argv[0], total_size);
54
55 int remaining_size = total_size;
56 for(int i = 0; i < wide_argc; i++)
57 {
58 int size = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i], -1, new_argv[i], remaining_size, nullptr, nullptr);
59 dbg_assert(size != 0, "WideCharToMultiByte failure");
60
61 remaining_size -= size;
62 new_argv[i + 1] = new_argv[i] + size;
63 }
64
65 LocalFree(wide_argv);
66 new_argv[wide_argc] = nullptr;
67 *argc = wide_argc;
68 *argv = (const char **)new_argv;
69#endif
70}
71
72void cmdline_free(int argc, const char **argv)
73{
74#if defined(CONF_FAMILY_WINDOWS)
75 free((void *)*argv);
76 free((char **)argv);
77#endif
78}
79
80#if !defined(CONF_PLATFORM_ANDROID)
81int os_open_link(const char *link)
82{
83#if defined(CONF_FAMILY_WINDOWS)
84 const std::wstring wide_link = windows_utf8_to_wide(link);
85
86 SHELLEXECUTEINFOW info;
87 mem_zero(&info, sizeof(SHELLEXECUTEINFOW));
88 info.cbSize = sizeof(SHELLEXECUTEINFOW);
89 info.lpVerb = nullptr; // NULL to use the default verb, as "open" may not be available
90 info.lpFile = wide_link.c_str();
91 info.nShow = SW_SHOWNORMAL;
92 // The SEE_MASK_NOASYNC flag ensures that the ShellExecuteEx function
93 // finishes its DDE conversation before it returns, so it's not necessary
94 // to pump messages in the calling thread.
95 // The SEE_MASK_FLAG_NO_UI flag suppresses error messages that would pop up
96 // when the link cannot be opened, e.g. when a folder does not exist.
97 // The SEE_MASK_ASYNCOK flag is not used. It would allow the call to
98 // ShellExecuteEx to return earlier, but it also prevents us from doing
99 // our own error handling, as the function would always return TRUE.
100 info.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
101 // Save and restore the FPU control word because ShellExecute might change it
102 fenv_t floating_point_environment;
103 int fegetenv_result = fegetenv(&floating_point_environment);
104 BOOL success = ShellExecuteExW(&info);
105 if(fegetenv_result == 0)
106 fesetenv(&floating_point_environment);
107 return success;
108#elif defined(CONF_PLATFORM_LINUX)
109 const int pid = fork();
110 if(pid == 0)
111 execlp(file: "xdg-open", arg: "xdg-open", link, nullptr);
112 return pid > 0;
113#elif defined(CONF_FAMILY_UNIX)
114 const int pid = fork();
115 if(pid == 0)
116 execlp("open", "open", link, nullptr);
117 return pid > 0;
118#endif
119}
120
121int os_open_file(const char *path)
122{
123#if defined(CONF_PLATFORM_MACOS)
124 return os_open_link(path);
125#else
126 // Create a file link so the path can contain forward and
127 // backward slashes. But the file link must be absolute.
128 char buf[512];
129 char workingDir[IO_MAX_PATH_LENGTH];
130 if(fs_is_relative_path(path))
131 {
132 if(!fs_getcwd(buffer: workingDir, buffer_size: sizeof(workingDir)))
133 return 0;
134 str_append(dst&: workingDir, src: "/");
135 }
136 else
137 workingDir[0] = '\0';
138 str_format(buffer: buf, buffer_size: sizeof(buf), format: "file://%s%s", workingDir, path);
139 return os_open_link(link: buf);
140#endif
141}
142#endif // !defined(CONF_PLATFORM_ANDROID)
143
144bool os_version_str(char *version, size_t length)
145{
146#if defined(CONF_FAMILY_WINDOWS)
147 const WCHAR *module_path = L"kernel32.dll";
148 DWORD handle;
149 DWORD size = GetFileVersionInfoSizeW(module_path, &handle);
150 if(!size)
151 {
152 return false;
153 }
154 void *data = malloc(size);
155 if(!GetFileVersionInfoW(module_path, handle, size, data))
156 {
157 free(data);
158 return false;
159 }
160 VS_FIXEDFILEINFO *fileinfo;
161 UINT unused;
162 if(!VerQueryValueW(data, L"\\", (void **)&fileinfo, &unused))
163 {
164 free(data);
165 return false;
166 }
167 str_format(version, length, "Windows %hu.%hu.%hu.%hu",
168 HIWORD(fileinfo->dwProductVersionMS),
169 LOWORD(fileinfo->dwProductVersionMS),
170 HIWORD(fileinfo->dwProductVersionLS),
171 LOWORD(fileinfo->dwProductVersionLS));
172 free(data);
173 return true;
174#else
175 struct utsname u;
176 if(uname(name: &u))
177 {
178 return false;
179 }
180 char extra[128];
181 extra[0] = 0;
182
183 do
184 {
185 IOHANDLE os_release = io_open(filename: "/etc/os-release", flags: IOFLAG_READ);
186 char buf[4096];
187 int read;
188 int offset;
189 char *newline;
190 if(!os_release)
191 {
192 break;
193 }
194 read = io_read(io: os_release, buffer: buf, size: sizeof(buf) - 1);
195 io_close(io: os_release);
196 buf[read] = 0;
197 if(str_startswith(str: buf, prefix: "PRETTY_NAME="))
198 {
199 offset = 0;
200 }
201 else
202 {
203 const char *found = str_find(haystack: buf, needle: "\nPRETTY_NAME=");
204 if(!found)
205 {
206 break;
207 }
208 offset = found - buf + 1;
209 }
210 newline = (char *)str_find(haystack: buf + offset, needle: "\n");
211 if(newline)
212 {
213 *newline = 0;
214 }
215 str_format(buffer: extra, buffer_size: sizeof(extra), format: "; %s", buf + offset + 12);
216 } while(false);
217
218 str_format(buffer: version, buffer_size: length, format: "%s %s (%s, %s)%s", u.sysname, u.release, u.machine, u.version, extra);
219 return true;
220#endif
221}
222
223void os_locale_str(char *locale, size_t length)
224{
225#if defined(CONF_FAMILY_WINDOWS)
226 wchar_t wide_buffer[LOCALE_NAME_MAX_LENGTH];
227 dbg_assert(GetUserDefaultLocaleName(wide_buffer, std::size(wide_buffer)) > 0, "GetUserDefaultLocaleName failure");
228
229 const std::optional<std::string> buffer = windows_wide_to_utf8(wide_buffer);
230 dbg_assert(buffer.has_value(), "GetUserDefaultLocaleName returned invalid UTF-16");
231 str_copy(locale, buffer.value().c_str(), length);
232#elif defined(CONF_PLATFORM_MACOS)
233 CFLocaleRef locale_ref = CFLocaleCopyCurrent();
234 CFStringRef locale_identifier_ref = static_cast<CFStringRef>(CFLocaleGetValue(locale_ref, kCFLocaleIdentifier));
235
236 // Count number of UTF16 codepoints, +1 for zero-termination.
237 // Assume maximum possible length for encoding as UTF-8.
238 CFIndex locale_identifier_size = (UTF8_BYTE_LENGTH * CFStringGetLength(locale_identifier_ref) + 1) * sizeof(char);
239 char *locale_identifier = (char *)malloc(locale_identifier_size);
240 dbg_assert(CFStringGetCString(locale_identifier_ref, locale_identifier, locale_identifier_size, kCFStringEncodingUTF8), "CFStringGetCString failure");
241
242 str_copy(locale, locale_identifier, length);
243
244 free(locale_identifier);
245 CFRelease(locale_ref);
246#else
247 static const char *ENV_VARIABLES[] = {
248 "LC_ALL",
249 "LC_MESSAGES",
250 "LANG",
251 };
252
253 locale[0] = '\0';
254 for(const char *env_variable : ENV_VARIABLES)
255 {
256 const char *env_value = getenv(name: env_variable);
257 if(env_value)
258 {
259 str_copy(dst: locale, src: env_value, dst_size: length);
260 break;
261 }
262 }
263#endif
264
265 // Ensure RFC 3066 format:
266 // - use hyphens instead of underscores
267 // - truncate locale string after first non-standard letter
268 for(int i = 0; i < str_length(str: locale); ++i)
269 {
270 if(locale[i] == '_')
271 {
272 locale[i] = '-';
273 }
274 else if(locale[i] != '-' && !(locale[i] >= 'a' && locale[i] <= 'z') && !(locale[i] >= 'A' && locale[i] <= 'Z') && !(str_isnum(c: locale[i])))
275 {
276 locale[i] = '\0';
277 break;
278 }
279 }
280
281 // Use default if we could not determine the locale,
282 // i.e. if only the C or POSIX locale is available.
283 if(locale[0] == '\0' || str_comp(a: locale, b: "C") == 0 || str_comp(a: locale, b: "POSIX") == 0)
284 str_copy(dst: locale, src: "en-US", dst_size: length);
285}
286