1 /*
2     This file is part of darktable,
3     copyright (c) 2019--2020 Diederik ter Rahe.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "common/darktable.h"
20 #include "common/ratings.h"
21 #include "common/colorlabels.h"
22 #include "bauhaus/bauhaus.h"
23 #include "control/conf.h"
24 #include "gui/gtk.h"
25 #include "gui/accelerators.h"
26 #include "common/file_location.h"
27 #include <fcntl.h>
28 
29 DT_MODULE(1)
30 
31 #ifdef HAVE_SDL
32 
33 #include <SDL.h>
34 
name(dt_lib_module_t * self)35 const char *name(dt_lib_module_t *self)
36 {
37   return _("gamepad");
38 }
39 
views(dt_lib_module_t * self)40 const char **views(dt_lib_module_t *self)
41 {
42   static const char *v[] = {"*", NULL};
43   return v;
44 }
45 
container(dt_lib_module_t * self)46 uint32_t container(dt_lib_module_t *self)
47 {
48   return DT_UI_CONTAINER_PANEL_TOP_CENTER;
49 }
50 
expandable(dt_lib_module_t * self)51 int expandable(dt_lib_module_t *self)
52 {
53   return 0;
54 }
55 
position()56 int position()
57 {
58   return 1;
59 }
60 
61 typedef struct gamepad_device
62 {
63   dt_input_device_t id;
64   SDL_GameController *controller;
65   Uint32 timestamp;
66   int value[SDL_CONTROLLER_AXIS_MAX];
67   int location[SDL_CONTROLLER_AXIS_MAX];
68 } gamepad_device;
69 
70 const char *button_names[]
71   = { N_("button a"), N_("button b"), N_("button x"), N_("button y"),
72       N_("button back"), N_("button guide"), N_("button start"),
73       N_("left stick"), N_("right stick"), N_("left shoulder"), N_("right shoulder"),
74       N_("dpad up"), N_("dpad down"), N_("dpad left"), N_("dpad right"),
75       N_("button misc1"), N_("paddle1"), N_("paddle2"), N_("paddle3"), N_("paddle4"), N_("touchpad"),
76       NULL };
77 
key_to_string(const guint key,const gboolean display)78 gchar *key_to_string(const guint key, const gboolean display)
79 {
80   const gchar *name = key < SDL_CONTROLLER_BUTTON_MAX ? button_names[key] : N_("invalid gamepad button");
81   return g_strdup(display ? _(name) : name);
82 }
83 
string_to_key(const gchar * string,guint * key)84 gboolean string_to_key(const gchar *string, guint *key)
85 {
86   *key = 0;
87   while(button_names[*key])
88     if(!strcmp(button_names[*key], string))
89       return TRUE;
90     else
91       (*key)++;
92 
93   return FALSE;
94 }
95 
96 const char *move_names[]
97   = { N_("left x"), N_("left y"), N_("right x"), N_("right y"),
98       N_("left trigger"), N_("right trigger"),
99       N_("left diagonal"), N_("left skew"), N_("right diagonal"), N_("right skew"),
100       NULL };
101 
move_to_string(const guint move,const gboolean display)102 gchar *move_to_string(const guint move, const gboolean display)
103 {
104   const gchar *name = move < SDL_CONTROLLER_AXIS_MAX + 4 /* diagonals */ ? move_names[move] : N_("invalid gamepad axis");
105   return g_strdup(display ? _(name) : name);
106 }
107 
string_to_move(const gchar * string,guint * move)108 gboolean string_to_move(const gchar *string, guint *move)
109 {
110   *move = 0;
111   while(move_names[*move])
112     if(!strcmp(move_names[*move], string))
113       return TRUE;
114     else
115       (*move)++;
116 
117   return FALSE;
118 }
119 
120 const dt_input_driver_definition_t driver_definition
121   = { "game", key_to_string, string_to_key, move_to_string, string_to_move };
122 
pump_events(gpointer user_data)123 static gboolean pump_events(gpointer user_data)
124 {
125   SDL_PumpEvents();
126 
127   return G_SOURCE_CONTINUE;
128 }
129 
process_axis_timestep(gamepad_device * gamepad,Uint32 timestamp)130 void process_axis_timestep(gamepad_device *gamepad, Uint32 timestamp)
131 {
132   if(timestamp > gamepad->timestamp)
133   {
134     Uint32 time = timestamp - gamepad->timestamp;
135     for(SDL_GameControllerAxis axis = SDL_CONTROLLER_AXIS_LEFTX; axis <= SDL_CONTROLLER_AXIS_RIGHTY; axis++)
136     {
137       if(abs(gamepad->value[axis]) > 4000)
138         gamepad->location[axis] += time * gamepad->value[axis];
139     }
140   }
141 
142   gamepad->timestamp = timestamp;
143 }
144 
process_axis_and_send(gamepad_device * gamepad,Uint32 timestamp)145 void process_axis_and_send(gamepad_device *gamepad, Uint32 timestamp)
146 {
147   process_axis_timestep(gamepad, timestamp);
148 
149   const gdouble step_size = 32768 * 1000 / 5; // FIXME configurable, x & y separately
150 
151   for(int side = 0; side < 2; side++)
152   {
153     int stick = SDL_CONTROLLER_AXIS_LEFTX + 2 * side;
154 
155     gdouble angle = gamepad->location[stick] / (0.001 + gamepad->location[stick + 1]);
156 
157     gdouble size = trunc(gamepad->location[stick] / step_size);
158 
159     if(size != 0 && fabs(angle) >= 2)
160     {
161       gamepad->location[stick] -= size * step_size;
162       gamepad->location[stick + 1] = 0;
163       dt_shortcut_move(gamepad->id, timestamp, stick, size);
164     }
165     else
166     {
167       size = - trunc(gamepad->location[stick + 1] / step_size);
168       if(size != 0)
169       {
170         gamepad->location[stick + 1] += size * step_size;
171         if(fabs(angle) < .5)
172         {
173           gamepad->location[stick] = 0;
174           dt_shortcut_move(gamepad->id, timestamp, stick + 1, size);
175         }
176         else
177         {
178           gamepad->location[stick]  += size * step_size * angle;
179           dt_shortcut_move(gamepad->id, timestamp, stick + ((angle < 0) ? 7 : 6), size);
180         }
181       }
182     }
183 
184 /*
185     int trigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT + side;
186     if(gamepad->value[trigger]) dt_shortcut_move(gamepad->id, timestamp, trigger, gamepad->value[trigger] / 32000);
187 */
188   }
189 }
190 
poll_gamepad_devices(gpointer user_data)191 static gboolean poll_gamepad_devices(gpointer user_data)
192 {
193 //  dt_input_device_t id = GPOINTER_TO_INT(user_data);
194   dt_lib_module_t *self = user_data;
195 
196   SDL_Event event;
197   int num_events = 0;
198 
199   gamepad_device *gamepad = NULL;
200   SDL_JoystickID prev_which = -1;
201 
202   while(SDL_PollEvent(&event) > 0 )
203   {
204     num_events++;
205 
206     if(event.cbutton.which != prev_which)
207     {
208       prev_which = event.cbutton.which;
209       SDL_GameController *controller = SDL_GameControllerFromInstanceID(prev_which);
210       gamepad = NULL;
211       for(GSList *gamepads = self->data; gamepads; gamepads = gamepads->next)
212         if(((gamepad_device *)(gamepads->data))->controller == controller)
213         {
214           gamepad = gamepads->data;
215           break;
216         }
217       if(!gamepad) return G_SOURCE_REMOVE;
218     }
219 
220     switch(event.type)
221     {
222     case SDL_CONTROLLERBUTTONDOWN:
223       dt_print(DT_DEBUG_INPUT, "SDL button down event time %d id %d button %hhd state %hhd\n", event.cbutton.timestamp, event.cbutton.which, event.cbutton.button, event.cbutton.state);
224       process_axis_and_send(gamepad, event.cbutton.timestamp);
225       dt_shortcut_key_press(gamepad->id + event.cbutton.which, event.cbutton.timestamp, event.cbutton.button);
226 
227       break;
228     case SDL_CONTROLLERBUTTONUP:
229       dt_print(DT_DEBUG_INPUT, "SDL button up event time %d id %d button %hhd state %hhd\n", event.cbutton.timestamp, event.cbutton.which, event.cbutton.button, event.cbutton.state);
230       process_axis_and_send(gamepad, event.cbutton.timestamp);
231       dt_shortcut_key_release(gamepad->id + event.cbutton.which, event.cbutton.timestamp, event.cbutton.button);
232       break;
233     case SDL_CONTROLLERAXISMOTION:
234       dt_print(DT_DEBUG_INPUT, "SDL axis event type %d time %d id %d axis %hhd value %hd\n", event.caxis.type, event.caxis.timestamp, event.caxis.which, event.caxis.axis, event.caxis.value);
235       process_axis_timestep(gamepad, event.caxis.timestamp);
236       gamepad->value[event.caxis.axis] = event.caxis.value;
237       break;
238     case SDL_CONTROLLERDEVICEADDED:
239       break;
240     }
241   }
242 
243   for(GSList *gamepads = self->data; gamepads; gamepads = gamepads->next) process_axis_and_send(gamepads->data, SDL_GetTicks());
244 
245   if(num_events) dt_print(DT_DEBUG_INPUT, "sdl num_events: %d time: %u\n", num_events, SDL_GetTicks());
246   return G_SOURCE_CONTINUE;
247 }
248 
gamepad_open_devices(dt_lib_module_t * self)249 void gamepad_open_devices(dt_lib_module_t *self)
250 {
251   if(SDL_Init(SDL_INIT_GAMECONTROLLER))
252   {
253     fprintf(stderr, "[gamepad_open_devices] ERROR initialising SDL\n");
254     return;
255   }
256   else
257     dt_print(DT_DEBUG_INPUT, "[gamepad_open_devices] SDL initialized\n");
258 
259   dt_input_device_t id = dt_register_input_driver(self, &driver_definition);
260 
261   for(int i = 0; i < SDL_NumJoysticks() && i < 10; i++)
262   {
263     if(SDL_IsGameController(i))
264     {
265       SDL_GameController *controller = SDL_GameControllerOpen(i);
266 
267       if(!controller)
268       {
269         fprintf(stderr, "[gamepad_open_devices] ERROR opening game controller '%s'\n", SDL_GameControllerNameForIndex(i));
270         continue;
271       }
272       else
273       {
274         fprintf(stderr, "[gamepad_open_devices] opened game controller '%s'\n", SDL_GameControllerNameForIndex(i));
275       }
276 
277       gamepad_device *gamepad = (gamepad_device *)g_malloc0(sizeof(gamepad_device));
278 
279       gamepad->controller = controller;
280       gamepad->id = id++;
281 
282       self->data = g_slist_append(self->data, gamepad);
283     }
284   }
285   if(self->data)
286   {
287     g_timeout_add(10, poll_gamepad_devices, self);
288     g_timeout_add_full(G_PRIORITY_HIGH, 5, pump_events, self, NULL);
289   }
290 }
291 
gamepad_device_free(gamepad_device * gamepad)292 void gamepad_device_free(gamepad_device *gamepad)
293 {
294   SDL_GameControllerClose(gamepad->controller);
295 
296   g_free(gamepad);
297 }
298 
gamepad_close_devices(dt_lib_module_t * self)299 void gamepad_close_devices(dt_lib_module_t *self)
300 {
301   g_source_remove_by_user_data(self); // poll_gamepad_devices
302   g_source_remove_by_user_data(self); // pump_events
303 
304   g_slist_free_full(self->data, (void (*)(void *))gamepad_device_free);
305   self->data = NULL;
306 
307   SDL_Quit();
308 }
309 
gui_init(dt_lib_module_t * self)310 void gui_init(dt_lib_module_t *self)
311 {
312   if(!self->widget)
313   {
314     self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
315     gtk_widget_set_no_show_all(self->widget, TRUE);
316   }
317   self->data = NULL;
318 
319   gamepad_open_devices(self);
320 }
321 
gui_cleanup(dt_lib_module_t * self)322 void gui_cleanup(dt_lib_module_t *self)
323 {
324   gamepad_close_devices(self);
325 }
326 
327 #endif // HAVE_SDL
328 
329 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
330 // vim: shiftwidth=2 expandtab tabstop=2 cindent
331 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
332