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