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