1 // Copyright (c) 2015 Sergio Gonzalez. All rights reserved.
2 // License: https://github.com/serge-rgb/milton#license
3
4 #include <SDL_image.h>
5
6 #include "platform.h"
7
8 #include "common.h"
9 #include "memory.h"
10
11 #define IMPL_MISSING mlt_assert(!"IMPLEMENT")
12
13
14 extern "C" void* glXGetProcAddressARB(const GLubyte * procName);
15
16 float
perf_count_to_sec(u64 counter)17 perf_count_to_sec(u64 counter)
18 {
19 // Input as nanoseconds
20 return (float)counter * 1e-9;
21 }
22
23 u64
perf_counter()24 perf_counter()
25 {
26 // http://stackoverflow.com/a/2660610/4717805
27 timespec tp;
28 int res = clock_gettime(CLOCK_REALTIME, &tp);
29
30 // TODO: Check errno and provide more information
31 if ( res ) {
32 milton_log("Something went wrong with clock_gettime\n");
33 }
34
35 return tp.tv_sec * 1000 * 1000 * 1000 + tp.tv_nsec;
36 }
37
38 void
platform_init(PlatformState * platform,SDL_SysWMinfo * sysinfo)39 platform_init(PlatformState* platform, SDL_SysWMinfo* sysinfo)
40 {
41 mlt_assert(sysinfo->subsystem == SDL_SYSWM_X11);
42 gtk_init(NULL, NULL);
43 EasyTab_Load(sysinfo->info.x11.display, sysinfo->info.x11.window);
44 }
45
46 EasyTabResult
platform_handle_sysevent(PlatformState * platform,SDL_SysWMEvent * sysevent)47 platform_handle_sysevent(PlatformState* platform, SDL_SysWMEvent* sysevent)
48 {
49 mlt_assert(sysevent->msg->subsystem == SDL_SYSWM_X11);
50 EasyTabResult res = EasyTab_HandleEvent(&sysevent->msg->msg.x11.event);
51 return res;
52 }
53
54 void
platform_event_tick()55 platform_event_tick()
56 {
57 gtk_main_iteration_do(FALSE);
58 }
59
60 void
platform_point_to_pixel(PlatformState * ps,v2l * inout)61 platform_point_to_pixel(PlatformState* ps, v2l* inout)
62 {
63
64 }
65
66 void
platform_point_to_pixel_i(PlatformState * ps,v2i * inout)67 platform_point_to_pixel_i(PlatformState* ps, v2i* inout)
68 {
69
70 }
71
72 float
platform_ui_scale(PlatformState * p)73 platform_ui_scale(PlatformState* p)
74 {
75 return 1.0f;
76 }
77
78 void
platform_pixel_to_point(PlatformState * ps,v2l * inout)79 platform_pixel_to_point(PlatformState* ps, v2l* inout)
80 {
81 }
82
83 b32
platform_delete_file_at_config(PATH_CHAR * fname,int error_tolerance)84 platform_delete_file_at_config(PATH_CHAR* fname, int error_tolerance)
85 {
86 char fname_at_config[MAX_PATH];
87 strncpy(fname_at_config, fname, MAX_PATH);
88 platform_fname_at_config(fname_at_config, MAX_PATH*sizeof(char));
89 int res = remove(fname_at_config);
90 b32 result = true;
91 if ( res != 0 ) {
92 result = false;
93 // Delete failed for some reason.
94 if ( (error_tolerance == DeleteErrorTolerance_OK_NOT_EXIST) &&
95 (errno == EEXIST || errno == ENOENT) ) {
96 result = true;
97 }
98 }
99
100 return result;
101 }
102
103 void
linux_set_GTK_filter(GtkFileChooser * chooser,GtkFileFilter * filter,FileKind kind)104 linux_set_GTK_filter(GtkFileChooser* chooser, GtkFileFilter* filter, FileKind kind)
105 {
106 switch ( kind ) {
107 case FileKind_IMAGE: {
108 gtk_file_filter_set_name(filter, "jpeg images (.jpg, .jpeg)");
109 gtk_file_filter_add_pattern(filter, "*.[jJ][pP][gG]");
110 gtk_file_filter_add_pattern(filter, "*.[jJ][pP][eE][gG]");
111 gtk_file_chooser_add_filter(chooser, filter);
112 } break;
113 case FileKind_MILTON_CANVAS: {
114 gtk_file_filter_set_name(filter, "milton canvases (.mlt)");
115 gtk_file_filter_add_pattern(filter, "*.[mM][lL][tT]");
116 gtk_file_chooser_add_filter(chooser, filter);
117 } break;
118 default: {
119 INVALID_CODE_PATH;
120 }
121 }
122 }
123
124 void
platform_dialog(char * info,char * title)125 platform_dialog(char* info, char* title)
126 {
127 platform_cursor_show();
128 GtkWidget *dialog = gtk_message_dialog_new(
129 NULL,
130 (GtkDialogFlags)0,
131 GTK_MESSAGE_INFO,
132 GTK_BUTTONS_OK,
133 "%s",
134 info
135 );
136 gtk_window_set_title(GTK_WINDOW(dialog), title);
137 gtk_dialog_run(GTK_DIALOG(dialog));
138 gtk_widget_destroy(dialog);
139 }
140
141 b32
platform_dialog_yesno(char * info,char * title)142 platform_dialog_yesno(char* info, char* title)
143 {
144 platform_cursor_show();
145 GtkWidget *dialog = gtk_message_dialog_new(
146 NULL,
147 (GtkDialogFlags)0,
148 GTK_MESSAGE_QUESTION,
149 GTK_BUTTONS_YES_NO,
150 "%s",
151 info
152 );
153 gtk_window_set_title(GTK_WINDOW(dialog), title);
154 if ( gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_YES ) {
155 gtk_widget_destroy(dialog);
156 return false;
157 }
158 gtk_widget_destroy(dialog);
159 return true;
160 }
161
162 YesNoCancelAnswer
platform_dialog_yesnocancel(char * info,char * title)163 platform_dialog_yesnocancel(char* info, char* title)
164 {
165 // NOTE: As of 2019-09-23, this function hasn't been tested on Linux.
166
167 platform_cursor_show();
168 GtkWidget *dialog = gtk_message_dialog_new(
169 NULL,
170 (GtkDialogFlags)0,
171 GTK_MESSAGE_QUESTION,
172 GTK_BUTTONS_OK_CANCEL,
173 "%s",
174 info
175 );
176 gtk_window_set_title(GTK_WINDOW(dialog), title);
177 gint answer = gtk_dialog_run(GTK_DIALOG(dialog));
178 gtk_widget_destroy(dialog);
179 if ( answer == GTK_RESPONSE_YES )
180 return YesNoCancelAnswer::YES_;
181 if ( answer == GTK_RESPONSE_NO )
182 return YesNoCancelAnswer::NO_;
183 return YesNoCancelAnswer::CANCEL_;
184 }
185
186 void
platform_fname_at_config(PATH_CHAR * fname,size_t len)187 platform_fname_at_config(PATH_CHAR* fname, size_t len)
188 {
189 // we'll copy fname to file_name so that we can edit fname to our liking then later append fname to it.
190 char *string_copy = (char*)mlt_calloc(1, len, "Strings");
191 size_t copy_length = strlen(fname);
192
193 if ( string_copy ) {
194 strncpy(string_copy, fname, len);
195 char *folder;
196 char *home = getenv("XDG_DATA_HOME");
197 if ( !home ) {
198 home = getenv("HOME");
199 folder = ".milton";
200 } else {
201 folder = "milton";
202 }
203 size_t final_length = strlen(home) + strlen(folder) + copy_length + 3; // +2 for the separators /, +1 for extra null terminator
204 if( final_length > len ) {
205 // building a path will lead to overflow. return string_copy as it can be used as a relative path
206 strncpy(fname, string_copy, len);
207 } else {
208 snprintf(fname, len, "%s/%s", home, folder);
209 mkdir(fname, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
210 strcat(fname, "/");
211 strcat(fname, string_copy);
212 }
213
214 mlt_free(string_copy, "Strings");
215 }
216 }
217
218 void
platform_fname_at_exe(PATH_CHAR * fname,size_t len)219 platform_fname_at_exe(PATH_CHAR* fname, size_t len)
220 {
221 // TODO: Fix this
222 #if 0
223 u32 bufsize = (u32)len;
224 char buffer[MAX_PATH] = {};
225 strncpy(buffer, fname, MAX_PATH);
226 strncpy(fname, program_invocation_name, bufsize);
227 { // Remove the executable name
228 PATH_CHAR* last_slash = fname;
229 for( PATH_CHAR* iter = fname;
230 *iter != '\0';
231 ++iter ) {
232 if ( *iter == '/' ) {
233 last_slash = iter;
234 }
235 }
236 *(last_slash+1) = '\0';
237 }
238 strncat(fname, "/", len);
239 strncat(fname, buffer, len);
240 #endif
241 return;
242 }
243
244 FILE*
platform_fopen(const PATH_CHAR * fname,const PATH_CHAR * mode)245 platform_fopen(const PATH_CHAR* fname, const PATH_CHAR* mode)
246 {
247 FILE *fd = fopen(fname, mode);
248 return fd;
249 }
250
251 b32
platform_move_file(PATH_CHAR * src,PATH_CHAR * dest)252 platform_move_file(PATH_CHAR* src, PATH_CHAR* dest)
253 {
254 int res = rename(src, dest);
255
256 return res == 0;
257 }
258
259 PATH_CHAR*
platform_open_dialog(FileKind kind)260 platform_open_dialog(FileKind kind)
261 {
262 platform_cursor_show();
263 GtkWidget *dialog = gtk_file_chooser_dialog_new(
264 "Open File",
265 NULL,
266 GTK_FILE_CHOOSER_ACTION_OPEN,
267 "Cancel", GTK_RESPONSE_CANCEL,
268 "Open", GTK_RESPONSE_ACCEPT,
269 NULL
270 );
271 GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
272 GtkFileFilter *filter = gtk_file_filter_new();
273 linux_set_GTK_filter(chooser, filter, kind);
274 gtk_file_chooser_set_current_folder(chooser, getenv("HOME"));
275 if ( gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT ) {
276 gtk_widget_destroy(dialog);
277 return NULL;
278 }
279 gchar *gtk_filename = gtk_file_chooser_get_filename(chooser);
280 PATH_CHAR* open_filename = (PATH_CHAR*)mlt_calloc(MAX_PATH, sizeof(PATH_CHAR), "Strings");
281
282 PATH_STRNCPY(open_filename, gtk_filename, MAX_PATH);
283 g_free(gtk_filename);
284 gtk_widget_destroy(dialog);
285 return open_filename;
286 }
287
288 void
platform_open_link(char * link)289 platform_open_link(char* link)
290 {
291 // This variant isn't safe.
292 char browser[strlen(link) + 12]; // 2 quotes + 1 space + 8 'xdg-open' + 1 end
293 strcpy(browser, "xdg-open '");
294 strcat(browser, link);
295 strcat(browser, "'");
296 system(browser);
297 return;
298 }
299
300 PATH_CHAR*
platform_save_dialog(FileKind kind)301 platform_save_dialog(FileKind kind)
302 {
303 platform_cursor_show();
304 GtkWidget *dialog = gtk_file_chooser_dialog_new(
305 "Save File",
306 NULL,
307 GTK_FILE_CHOOSER_ACTION_SAVE,
308 "Cancel", GTK_RESPONSE_CANCEL,
309 "Save", GTK_RESPONSE_ACCEPT,
310 NULL
311 );
312 GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
313 GtkFileFilter *filter = gtk_file_filter_new();
314 linux_set_GTK_filter(chooser, filter, kind);
315 gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE);
316 gtk_file_chooser_set_current_folder(chooser, getenv("HOME"));
317 gtk_file_chooser_set_current_name(chooser, kind == FileKind_IMAGE ? "untitled.jpg" : "untitled.mlt");
318 if ( gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT ) {
319 gtk_widget_destroy(dialog);
320 return NULL;
321 }
322 gchar *gtk_filename = gtk_file_chooser_get_filename(chooser);
323 PATH_CHAR* save_filename = (PATH_CHAR*)mlt_calloc(1, MAX_PATH*sizeof(PATH_CHAR), "Strings");
324
325 PATH_STRNCPY(save_filename, gtk_filename, MAX_PATH);
326 g_free(gtk_filename);
327 gtk_widget_destroy(dialog);
328 return save_filename;
329 }
330 // ====
331
332 WallTime
platform_get_walltime()333 platform_get_walltime()
334 {
335 WallTime wt = {};
336 struct timeval tv;
337 gettimeofday(&tv, NULL);
338 struct tm *time;
339 time = localtime(&tv.tv_sec);
340 wt.h = time->tm_hour;
341 wt.m = time->tm_min;
342 wt.s = time->tm_sec;
343 wt.ms = tv.tv_usec / 1000;
344 return wt;
345 }
346
347 void*
platform_get_gl_proc(char * name)348 platform_get_gl_proc(char* name)
349 {
350 return glXGetProcAddressARB((GLubyte*)name);
351 }
352
353 void
platform_deinit(PlatformState * platform)354 platform_deinit(PlatformState* platform)
355 {
356
357 }
358
359 void
platform_setup_cursor(Arena * arena,PlatformState * platform)360 platform_setup_cursor(Arena* arena, PlatformState* platform)
361 {
362 SDL_Surface *surface;
363
364 surface = IMG_Load("/usr/local/share/icons/hicolor/256x256/apps/milton.png");
365 if (surface == NULL)
366 return;
367
368 SDL_SetWindowIcon(platform->window, surface);
369 SDL_FreeSurface(surface);
370 }
371
372 v2i
platform_cursor_get_position(PlatformState * platform)373 platform_cursor_get_position(PlatformState* platform)
374 {
375 v2i pos;
376
377 SDL_GetMouseState(&pos.x, &pos.y);
378 return pos;
379 }
380
381 void
platform_cursor_set_position(PlatformState * platform,v2i pos)382 platform_cursor_set_position(PlatformState* platform, v2i pos)
383 {
384 SDL_WarpMouseInWindow(platform->window, pos.x, pos.y);
385 // Pending mouse move events will have the cursor close
386 // to where it was before we set it.
387 SDL_FlushEvent(SDL_MOUSEMOTION);
388 SDL_FlushEvent(SDL_SYSWMEVENT);
389 }
390