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