1 /** \file uimedia.c
2 * \brief Media recording dialog
3 *
4 * \author Bas Wassink <b.wassink@ziggo.nl>
5 */
6
7 /*
8 * $VICERES SoundRecordDeviceArg all
9 * $VICERES SoundRecordDeviceName all
10 * $VICERES OCPOversizeHandling -vsid
11 * $VICERES OCPUndersizeHandling -vsid
12 * $VICERES OCPMultiColorHandling -vsid
13 * $VICERES OCPTEDLumHandling -vsid
14 * $VICERES KoalaOversizeHandling -vsid
15 * $VICERES KoalaUndersizeHandling -vsid
16 * $VICERES KoalaTEDLumHandling -vsid
17 */
18
19 /*
20 * This file is part of VICE, the Versatile Commodore Emulator.
21 * See README for copyright notice.
22 *
23 * This program is free software; you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation; either version 2 of the License, or
26 * (at your option) any later version.
27 *
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
32 *
33 * You should have received a copy of the GNU General Public License
34 * along with this program; if not, write to the Free Software
35 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
36 * 02111-1307 USA.
37 */
38
39 #include "vice.h"
40 #include <gtk/gtk.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <stdint.h>
44
45 #include "gfxoutput.h"
46 #include "lib.h"
47 #include "machine.h"
48 #include "mainlock.h"
49 #include "openfiledialog.h"
50 #include "resources.h"
51 #include "savefiledialog.h"
52 #include "screenshot.h"
53 #include "selectdirectorydialog.h"
54 #include "sound.h"
55 #include "statusbarrecordingwidget.h"
56 #include "ui.h"
57 #include "uiapi.h"
58 #include "uistatusbar.h"
59 #include "vice_gtk3.h"
60
61 #ifdef HAVE_FFMPEG
62 #include "ffmpegwidget.h"
63 #endif
64
65 #include "uimedia.h"
66
67
68 /** \brief Custom response IDs for the dialog
69 */
70 enum {
71 RESPONSE_SAVE = 1 /**< Save button clicked */
72 };
73
74
75 /** \brief Name of the stack child for screenshots
76 */
77 #define CHILD_SCREENSHOT "Screenshot"
78
79 /** \brief Name of the stack child for sound recording
80 */
81 #define CHILD_SOUND "Sound"
82
83 /** \brief Name of the stack child for video recording
84 */
85 #define CHILD_VIDEO "Video"
86
87
88 /** \brief Struct to hold information on available video recording drivers
89 */
90 typedef struct video_driver_info_s {
91 const char *display; /**< display string (used in the UI) */
92 const char *name; /**< driver name */
93 const char *ext; /**< default file extension */
94 } video_driver_info_t;
95
96
97 /** \brief Struct to hold information on available audio recording drivers
98 */
99 typedef struct audio_driver_info_s {
100 const char *display; /**< display name (used in the UI) */
101 const char *name; /**< driver name */
102 const char *ext; /**< default file extension */
103 } audio_driver_info_t;
104
105
106 /** \brief List of available video drivers, gotten during runtime
107 */
108 static video_driver_info_t *video_driver_list = NULL;
109
110
111 /** \brief Number of available video drivers
112 */
113 static int video_driver_count = 0;
114
115
116 /** \brief Index of currently selected screenshot driver
117 *
118 * Initially set to -1 to select PNG as default in the screenshot driver list.
119 * Set to last selected driver on subsequent uses of the dialog.
120 */
121 static int screenshot_driver_index = -1;
122
123
124 #ifdef HAVE_FFMPEG
125 /** \brief Index of currently selected video driver
126 *
127 */
128 static int video_driver_index = 0;
129 #endif /* HAVE_FFMPEG */
130
131
132 /** \brief Index of currently selected audio driver
133 *
134 * Initially set to -1 to select WAV as default in the audio driver list.
135 * Set to last selected driver on subsequent uses of the dialog.
136 */
137 static int audio_driver_index = -1;
138
139
140 /** \brief List of available audio recording drivers
141 *
142 * This list is dependent on compile-time options
143 */
144 static audio_driver_info_t audio_driver_list[] = {
145 { "WAV", "wav", "wav" },
146 { "AIFF", "aiff", "aif" },
147 { "VOC", "voc", "voc" },
148 { "IFF", "iff", "iff" },
149 #ifdef USE_LAMEMP3
150 { "MP3", "mp3", "mp3" },
151 #endif
152 #ifdef USE_FLAC
153 { "FLAC", "flac", "flac" },
154 #endif
155 #ifdef USE_VORBIS
156 { "Ogg/Vorbis", "ogg", "ogg" },
157 #endif
158 { NULL, NULL, NULL }
159 };
160
161
162 /** \brief List of 'oversize modes' for some screenshot output drivers
163 */
164 static vice_gtk3_combo_entry_int_t oversize_modes[] = {
165 { "scale down", 0 },
166 { "crop left top", 1, },
167 { "crop center top", 2 },
168 { "crop right top", 3 },
169 { "crop left center", 4 },
170 { "crop center", 5 },
171 { "crop right center", 6 },
172 { "crop left bottom", 7 },
173 { "crop center bottom", 8 },
174 { "crop right bottom", 9 },
175 { NULL, -1 }
176 };
177
178
179 /** \brief List of 'undersize modes' for some screenshot output drivers
180 */
181 static vice_gtk3_combo_entry_int_t undersize_modes[] = {
182 { "scale up", 0 },
183 { "border size", 1 },
184 { NULL, -1 }
185 };
186
187
188 /** \brief List of multi color modes for some screenshot output drivers
189 */
190 static const vice_gtk3_combo_entry_int_t multicolor_modes[] = {
191 { "B&W", 0 },
192 { "2 colors", 1 },
193 { "4 colors", 2 },
194 { "Grey scale", 3 },
195 { "Best cell colors", 4 },
196 { NULL, -1 }
197 };
198
199
200 /** \brief TED output driver Luma modes
201 */
202 static const vice_gtk3_combo_entry_int_t ted_luma_modes[] = {
203 { "ignore", 0 },
204 { "Best cell colors", 1 },
205 { NULL, -1 },
206 };
207
208
209 /* forward declarations of helper functions */
210 static GtkWidget *create_screenshot_param_widget(const char *prefix);
211 static void save_screenshot_handler(GtkWidget *parent);
212 static void save_audio_recording_handler(GtkWidget *parent);
213 static void save_video_recording_handler(GtkWidget *parent);
214
215
216 /** \brief Reference to the GtkStack containing the media types
217 *
218 * Used in the dialog response callback to determine recording mode and params
219 */
220 static GtkWidget *stack;
221
222
223 /** \brief Pause state when activating the dialog
224 */
225 static int old_pause_state = 0;
226
227
228 /* references to widgets, used from various event handlers */
229
230 /** \brief Screenshot options grid reference */
231 static GtkWidget *screenshot_options_grid = NULL;
232 /** \brief Screenshot "Oversize" widget reference */
233 static GtkWidget *oversize_widget = NULL;
234 /** \brief Screenshot "Undersize" widget reference */
235 static GtkWidget *undersize_widget = NULL;
236 /** \brief Screenshot "Multicolor mode" widget reference */
237 static GtkWidget *multicolor_widget = NULL;
238 /** \brief Screenshot "TED luma" widget reference */
239 static GtkWidget *ted_luma_widget = NULL;
240 #ifdef HAVE_FFMPEG
241 /** \brief FFMPEG video driver options grid reference */
242 static GtkWidget *video_driver_options_grid = NULL;
243 #endif
244
245
246 /*****************************************************************************
247 * Event handlers *
248 ****************************************************************************/
249
250 /** \brief Handler for the "destroy" event of the dialog
251 *
252 * \param[in] widget dialog
253 * \param[in] data extra event data (unused)
254 */
on_dialog_destroy(GtkWidget * widget,gpointer data)255 static void on_dialog_destroy(GtkWidget *widget, gpointer data)
256 {
257 if (machine_class != VICE_MACHINE_VSID) {
258 lib_free(video_driver_list);
259 }
260
261 /* only unpause when the emu wasn't paused when activating the dialog */
262 if (!old_pause_state) {
263 ui_pause_disable();
264 }
265 }
266
267
268 /** \brief Set new widget for the screenshot options, replacing the old one
269 *
270 * Destroys any widget present before setting the \a new widget.
271 *
272 * \param[in] new new widget
273 */
update_screenshot_options_grid(GtkWidget * new)274 static void update_screenshot_options_grid(GtkWidget *new)
275 {
276 GtkWidget *old;
277
278 if (new != NULL) {
279 old = gtk_grid_get_child_at(GTK_GRID(screenshot_options_grid), 0, 1);
280 if (old != NULL) {
281 gtk_widget_destroy(old);
282 }
283 gtk_grid_attach(GTK_GRID(screenshot_options_grid), new, 0, 1, 1, 1);
284 }
285 }
286
287
288 /** \brief Handler for the "response" event of the \a dialog
289 *
290 * \param[in,out] dialog dialog triggering the event
291 * \param[in] response_id response ID
292 * \param[in] data parent dialog
293 */
on_response(GtkDialog * dialog,gint response_id,gpointer data)294 static void on_response(GtkDialog *dialog, gint response_id, gpointer data)
295 {
296 const gchar *child_name;
297
298 switch (response_id) {
299 case GTK_RESPONSE_DELETE_EVENT:
300 mainlock_release();
301 gtk_widget_destroy(GTK_WIDGET(dialog));
302 mainlock_obtain();
303 break;
304
305 case RESPONSE_SAVE:
306
307 if (machine_class != VICE_MACHINE_VSID) {
308 /* stack child name determines what to do next */
309 child_name = gtk_stack_get_visible_child_name(GTK_STACK(stack));
310
311 if (strcmp(child_name, CHILD_SCREENSHOT) == 0) {
312 save_screenshot_handler(data);
313 } else if (strcmp(child_name, CHILD_SOUND) == 0) {
314 save_audio_recording_handler(data);
315 ui_display_recording(1);
316 } else if (strcmp(child_name, CHILD_VIDEO) == 0) {
317 save_video_recording_handler(data);
318 ui_display_recording(1);
319 }
320 } else {
321 save_audio_recording_handler(data);
322 ui_display_recording(1);
323 }
324 #if 0
325 mainlockk_release();
326 gtk_widget_destroy(GTK_WIDGET(dialog));
327 mainlock_obtain();
328 #endif
329 break;
330
331 default:
332 break;
333 }
334 }
335
336
337 /** \brief Handler for the "toggled" event of the screenshot driver radios
338 *
339 * \param[in] widget radio button riggering the event
340 * \param[in] data extra event data (unused)
341 */
on_screenshot_driver_toggled(GtkWidget * widget,gpointer data)342 static void on_screenshot_driver_toggled(GtkWidget *widget, gpointer data)
343 {
344 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
345 int index = GPOINTER_TO_INT(data);
346 screenshot_driver_index = index;
347 update_screenshot_options_grid(
348 create_screenshot_param_widget(video_driver_list[index].name));
349 }
350 }
351
352
353 /** \brief Handler for the "toggled" event of the audio driver radios
354 *
355 * \param[in] widget radio button riggering the event
356 * \param[in] data extra event data (unused)
357 */
on_audio_driver_toggled(GtkWidget * widget,gpointer data)358 static void on_audio_driver_toggled(GtkWidget *widget, gpointer data)
359 {
360 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
361 int index = GPOINTER_TO_INT(data);
362 audio_driver_index = index;
363 }
364 }
365
366
367
368 /*****************************************************************************
369 * Helpers functions *
370 ****************************************************************************/
371
372 /** \brief Create a string in the format 'yyyymmddHHMMssffffff' of the current time
373 *
374 * \return string owned by GLib, free with g_free()
375 */
create_datetime_string(void)376 static gchar *create_datetime_string(void)
377 {
378 GDateTime *d;
379 gint m;
380 gchar *s;
381 gchar *t;
382
383 d = g_date_time_new_now_local();
384 m = g_date_time_get_microsecond(d);
385 s = g_date_time_format(d, "%Y%m%d%H%M%S");
386 g_date_time_unref(d);
387 t = g_strdup_printf("%s%06d", s, m);
388 g_free(s);
389 return t;
390 }
391
392
393 /** \brief Create a filename based on the current datetime and \a ext
394 *
395 * \param[in] ext file extension (without the dot)
396 *
397 * \return heap-allocated string, owned by VICE, free with lib_free()
398 */
create_proposed_screenshot_name(const char * ext)399 static char *create_proposed_screenshot_name(const char *ext)
400 {
401 char *date;
402 char *filename;
403
404 date = create_datetime_string();
405 filename = lib_msprintf("vice-screen-%s.%s", date, ext);
406 g_free(date);
407 return filename;
408 }
409
410
411
412 /** \brief Create a filename based on the current datetime and \a ext
413 *
414 * \param[in] ext file extension (without the dot)
415 *
416 * \return heap-allocated string, owned by VICE, free with lib_free()
417 */
create_proposed_video_recording_name(const char * ext)418 static char *create_proposed_video_recording_name(const char *ext)
419 {
420 gchar *date;
421 char *filename;
422
423 date = create_datetime_string();
424 filename = lib_msprintf("vice-video-%s.%s", date, ext);
425 g_free(date);
426 return filename;
427 }
428
429
430
431 /** \brief Create a filename based on the current datetime and \a ext
432 *
433 * \param[in] ext file extension (without the dot)
434 *
435 * \return heap-allocated string, owned by VICE, free with lib_free()
436 */
create_proposed_audio_recording_name(const char * ext)437 static char *create_proposed_audio_recording_name(const char *ext)
438 {
439 gchar *date;
440 char *filename;
441
442 date = create_datetime_string();
443 filename = lib_msprintf("vice-audio-%s.%s", date, ext);
444 g_free(date);
445 return filename;
446 }
447
448
449 /** \brief Callback for the save-screenshot dialog
450 *
451 * \param[in,out] dialog dialog
452 * \param[in,out] filename screenshot filename
453 * \param[in] data extra data (unused)
454 */
on_save_screenshot_filename(GtkDialog * dialog,gchar * filename,gpointer data)455 static void on_save_screenshot_filename(GtkDialog *dialog,
456 gchar *filename,
457 gpointer data)
458 {
459 const char *name;
460
461 name = video_driver_list[screenshot_driver_index].name;
462
463 if (filename != NULL) {
464
465 gchar *filename_locale = file_chooser_convert_to_locale(filename);
466
467 /* TODO: add extension if not present? */
468 if (screenshot_save(name, filename_locale, ui_get_active_canvas()) < 0) {
469 vice_gtk3_message_error("VICE Error",
470 "Failed to write screenshot file '%s'", filename);
471 }
472 g_free(filename);
473 g_free(filename_locale);
474 }
475 mainlock_release();
476 gtk_widget_destroy(GTK_WIDGET(dialog));
477 mainlock_obtain();
478 }
479
480
481
482 /** \brief Save a screenshot
483 *
484 * Pops up a save-file dialog with a proposed filename (ie
485 * 'screenshot-197411151210.png'.
486 *
487 * \note Destroys \a parent along with the dialog
488 *
489 * \param[in,out] parent parent dialog
490 */
save_screenshot_handler(GtkWidget * parent)491 static void save_screenshot_handler(GtkWidget *parent)
492 {
493 GtkWidget *dialog;
494 const char *display;
495 char *title;
496 char *proposed;
497 const char *ext;
498
499 ext = video_driver_list[screenshot_driver_index].ext;
500 display = video_driver_list[screenshot_driver_index].display;
501 title = lib_msprintf("Save %s file", display);
502 proposed = create_proposed_screenshot_name(ext);
503
504 dialog = vice_gtk3_save_file_dialog(
505 title, proposed, TRUE, NULL,
506 on_save_screenshot_filename,
507 NULL);
508 /* destroy parent dialog when the dialog is destroyed */
509 g_signal_connect_swapped(
510 dialog,
511 "destroy",
512 G_CALLBACK(gtk_widget_destroy),
513 parent);
514
515 lib_free(proposed);
516 lib_free(title);
517 }
518
519
520 /** \brief Callback for the save-audio dialog
521 *
522 * \param[in,out] dialog dialog
523 * \param[in,out] filename audio recording filename
524 * \param[in] data extra data (unused)
525 */
on_save_audio_filename(GtkDialog * dialog,gchar * filename,gpointer data)526 static void on_save_audio_filename(GtkDialog *dialog,
527 gchar *filename,
528 gpointer data)
529 {
530 gchar *filename_locale;
531
532 if (filename != NULL) {
533 filename_locale = file_chooser_convert_to_locale(filename);
534 const char *name = audio_driver_list[audio_driver_index].name;
535 /* XXX: setting resources doesn't exactly help with catching errors */
536 resources_set_string("SoundRecordDeviceArg", filename_locale);
537 resources_set_string("SoundRecordDeviceName", name);
538 g_free(filename);
539 g_free(filename_locale);
540 }
541 mainlock_release();
542 gtk_widget_destroy(GTK_WIDGET(dialog));
543 mainlock_obtain();
544 }
545
546
547
548 /** \brief Start an audio recording
549 *
550 * Pops up a save-file dialog with a proposed filename (ie
551 * 'audio-recording-197411151210.png'.
552 *
553 * \note Destroys \a parent along with the dialog
554 *
555 * \param[in,out] parent dialog
556 */
save_audio_recording_handler(GtkWidget * parent)557 static void save_audio_recording_handler(GtkWidget *parent)
558 {
559 GtkWidget *dialog;
560 const char *display;
561 const char *ext;
562 char *title;
563 char *proposed;
564
565 ext = audio_driver_list[audio_driver_index].ext;
566 display = audio_driver_list[audio_driver_index].display;
567 title = lib_msprintf("Save %s file", display);
568 proposed = create_proposed_audio_recording_name(ext);
569
570 dialog = vice_gtk3_save_file_dialog(
571 title, proposed, TRUE, NULL,
572 on_save_audio_filename,
573 NULL);
574
575 /* destroy parent dialog when the dialog is destroyed */
576 g_signal_connect_swapped(
577 dialog,
578 "destroy",
579 G_CALLBACK(gtk_widget_destroy),
580 parent);
581
582 lib_free(title);
583 lib_free(proposed);
584 }
585
586
587
588 /** \brief Callback for the save-video dialog
589 *
590 * \param[in,out] dialog dialog
591 * \param[in,out] filename video recording filename
592 * \param[in] data extra data (unused)
593 */
on_save_video_filename(GtkDialog * dialog,gchar * filename,gpointer data)594 static void on_save_video_filename(GtkDialog *dialog,
595 gchar *filename,
596 gpointer data)
597 {
598 if (filename != NULL) {
599
600 const char *driver;
601 int ac;
602 int vc;
603 int ab;
604 int vb;
605 gchar *filename_locale;
606
607 resources_get_string("FFMPEGFormat", &driver);
608 resources_get_int("FFMPEGVideoCodec", &vc);
609 resources_get_int("FFMPEGVideoBitrate", &vb);
610 resources_get_int("FFMPEGAudioCodec", &ac);
611 resources_get_int("FFMPEGAudioBitrate", &ab);
612
613 debug_gtk3("Format = '%s'.", driver);
614 debug_gtk3("Video = %d, bitrate %d.", vc, vb);
615 debug_gtk3("Audio = %d, bitrate %d.", ac, ab);
616
617 ui_pause_disable();
618
619 filename_locale = file_chooser_convert_to_locale(filename);
620
621 /* TODO: add extension if not present? */
622 if (screenshot_save("FFMPEG", filename_locale, ui_get_active_canvas()) < 0) {
623 vice_gtk3_message_error("VICE Error",
624 "Failed to write video file '%s'", filename);
625 }
626 g_free(filename);
627 g_free(filename_locale);
628 }
629 mainlock_release();
630 gtk_widget_destroy(GTK_WIDGET(dialog));
631 mainlock_obtain();
632 }
633
634
635 /** \brief Start recording a video
636 *
637 * Pops up a save-file dialog with a proposed filename (ie
638 * 'video-recording-197411151210.png'.
639 *
640 * \note Destroys \a parent along with the dialog
641 *
642 * \param[in,out] parent parent dialog
643 */
save_video_recording_handler(GtkWidget * parent)644 static void save_video_recording_handler(GtkWidget *parent)
645 {
646 GtkWidget *dialog;
647 const char *ext;
648 char *title;
649 char *proposed;
650
651 #if 0
652 display = video_driver_list[video_driver_index].display;
653 name = video_driver_list[video_driver_index].name;
654 #endif
655 /* we don't have a format->extension mapping, so the format name itself is
656 better than `video_driver_list[video_driver_index].ext' */
657 resources_get_string("FFMPEGFormat", &ext);
658
659 title = lib_msprintf("Save %s file", "FFMPEG");
660 proposed = create_proposed_video_recording_name(ext);
661
662 dialog = vice_gtk3_save_file_dialog(
663 title, proposed, TRUE, NULL,
664 on_save_video_filename, NULL);
665
666 /* destroy parent dialog when the dialog is destroyed */
667 g_signal_connect_swapped(
668 dialog,
669 "destroy",
670 G_CALLBACK(gtk_widget_destroy),
671 parent);
672
673 lib_free(proposed);
674 lib_free(title);
675 }
676
677
678 /** \brief Create heap-allocated list of available video drivers
679 *
680 * Queries the gfxoutputdrv subsystem and builds a list of currently available
681 * drivers and stores it in `driver_list`. Must be freed with lib_free() after
682 * use.
683 */
create_video_driver_list(void)684 static void create_video_driver_list(void)
685 {
686 gfxoutputdrv_t *driver;
687 int index;
688
689 video_driver_count = gfxoutput_num_drivers();
690 video_driver_list = lib_malloc((size_t)(video_driver_count + 1) *
691 sizeof *video_driver_list);
692
693 index = 0;
694
695 if (video_driver_count > 0) {
696 driver = gfxoutput_drivers_iter_init();
697 while (driver != NULL) {
698 video_driver_list[index].display = driver->displayname;
699 video_driver_list[index].name = driver->name;
700 video_driver_list[index].ext = driver->default_extension;
701 index++;
702 driver = gfxoutput_drivers_iter_next();
703 }
704 }
705 video_driver_list[index].display = NULL;
706 video_driver_list[index].name = NULL;
707 video_driver_list[index].ext = NULL;
708 }
709
710
711 /** \brief Determine if driver \a name is a video driver
712 *
713 * \param[in] name driver name
714 *
715 * \return bool
716 *
717 * \todo There has to be a better, more reliable way than this
718 */
driver_is_video(const char * name)719 static int driver_is_video(const char *name)
720 {
721 int result;
722
723 result = strcmp(name, "FFMPEG") == 0;
724 return result;
725 }
726
727
728 /** \brief Create widget to control screenshot parameters
729 *
730 * This widget exposes controls to alter screenshot parameters, depending on
731 * machine class and output file format
732 *
733 * \param[in] prefix resource prefix (either "OCP", "Koala", or ""/NULL)
734 *
735 * \return GtkGrid
736 */
create_screenshot_param_widget(const char * prefix)737 static GtkWidget *create_screenshot_param_widget(const char *prefix)
738 {
739 GtkWidget *grid;
740 GtkWidget *label;
741 int row;
742 int artstudio = 0;
743 int koala = 0;
744
745 grid = gtk_grid_new();
746 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
747 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
748
749 /* according to the standard, doing a strcmp() with one or more `NULL`
750 * arguments is implementation-defined, so better safe than sorry */
751 if (prefix == NULL) {
752 debug_gtk3("some idiot passed NULL.");
753 return grid;
754 }
755
756 if (strcmp(prefix, "ARTSTUDIO") == 0) {
757 prefix = "OCP"; /* XXX: not strictly required since resource names
758 seem to be case insensitive, but better
759 safe than sorry */
760 artstudio = 1;
761 } else if (strcmp(prefix, "KOALA") == 0) {
762 prefix = "Koala";
763 koala = 1;
764 }
765
766 if (!koala && !artstudio) {
767 label = gtk_label_new("No parameters required");
768 g_object_set(label, "margin-left", 16, NULL);
769 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
770 gtk_widget_show_all(grid);
771 return grid;
772 }
773
774 /* ${FORMAT}OversizeHandling */
775 label = gtk_label_new("Oversize handling");
776 g_object_set(label, "margin-left", 16, NULL);
777 gtk_widget_set_halign(label, GTK_ALIGN_START);
778 oversize_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
779 "%sOversizeHandling", oversize_modes, prefix);
780 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
781 gtk_grid_attach(GTK_GRID(grid), oversize_widget, 1, 0, 1, 1);
782
783 /* ${FORMAT}UndersizeHandling */
784 label = gtk_label_new("Undersize handling");
785 g_object_set(label, "margin-left", 16, NULL);
786 gtk_widget_set_halign(label, GTK_ALIGN_START);
787 undersize_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
788 "%sUndersizeHandling", undersize_modes, prefix);
789 gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
790 gtk_grid_attach(GTK_GRID(grid), undersize_widget, 1, 1, 1, 1);
791
792 /* ${FORMAT}MultiColorHandling */
793
794 row = 2; /* from now on, the widgets depend on machine and image type */
795
796 /* OCPMultiColorHandling */
797 if (artstudio) {
798 label = gtk_label_new("Multi color handling");
799 g_object_set(label, "margin-left", 16, NULL);
800 gtk_widget_set_halign(label, GTK_ALIGN_START);
801 multicolor_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
802 "%sMultiColorHandling", multicolor_modes, prefix);
803 gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
804 gtk_grid_attach(GTK_GRID(grid), multicolor_widget, 1, row, 1, 1);
805 row++;
806 }
807
808 /* ${FORMAT}TEDLumaHandling */
809 if (machine_class == VICE_MACHINE_PLUS4) {
810 label = gtk_label_new("TED luma handling");
811 g_object_set(label, "margin-left", 16, NULL);
812 gtk_widget_set_halign(label, GTK_ALIGN_START);
813 ted_luma_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
814 "%sTEDLumHandling", ted_luma_modes, prefix);
815 gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
816 gtk_grid_attach(GTK_GRID(grid), ted_luma_widget, 1, row, 1, 1);
817 row++;
818 }
819
820 gtk_widget_show_all(grid);
821 return grid;
822 }
823
824
825
826
827 /** \brief Create the main 'screenshot' widget
828 *
829 * \return GtkGrid
830 */
create_screenshot_widget(void)831 static GtkWidget *create_screenshot_widget(void)
832 {
833 GtkWidget *grid;
834 GtkWidget *drv_grid;
835 GtkWidget *label;
836 GtkWidget *radio;
837 GtkWidget *last;
838 GSList *group = NULL;
839 int index;
840 int grid_index;
841
842 grid = gtk_grid_new();
843 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
844 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
845
846 /* grid without extra row spacing */
847 drv_grid = vice_gtk3_grid_new_spaced_with_label(-1, 0, "Driver", 1);
848 g_object_set(drv_grid, "margin-top", 8, "margin-left", 16, NULL);
849 /* add some padding to the label */
850 label = gtk_grid_get_child_at(GTK_GRID(drv_grid), 0, 0);
851 g_object_set(label, "margin-bottom", 8, NULL);
852
853 /* add drivers */
854 grid_index = 1;
855 last = NULL;
856 for (index = 0; video_driver_list[index].name != NULL; index++) {
857 const char *display = video_driver_list[index].display;
858 const char *name = video_driver_list[index].name;
859
860 if (!driver_is_video(name)) {
861 radio = gtk_radio_button_new_with_label(group, display);
862 g_object_set(radio, "margin-left", 8, NULL);
863 gtk_radio_button_join_group(GTK_RADIO_BUTTON(radio),
864 GTK_RADIO_BUTTON(last));
865 gtk_grid_attach(GTK_GRID(drv_grid), radio, 0, grid_index, 1, 1);
866
867 /* Make PNG default (doesn't look we have any numeric define to
868 * indicate PNG, so a strcmp() will have to do)
869 * Also trigger the event handler to set the driver index (by
870 * connecting it before this call), so I don't have to manually
871 * set it here, though all this text could have been used to set it.
872 *
873 * Only set PNG first time the dialog is created, on second time
874 * it should use whatever was used before
875 */
876 if (screenshot_driver_index < 0) {
877 if (strcmp(name, "PNG") == 0) {
878 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
879 screenshot_driver_index = index; /* set this driver as
880 currently selected one */
881 }
882 } else {
883 if (screenshot_driver_index == index) {
884 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
885 }
886 }
887
888 /* connect signal *after* setting a radio button's state, to avoid
889 * triggering the handler before the UI is properly set up. */
890 g_signal_connect(radio, "toggled",
891 G_CALLBACK(on_screenshot_driver_toggled),
892 GINT_TO_POINTER(index));
893
894 last = radio;
895 grid_index++;
896 }
897 }
898
899 /* this is where the various options go per screenshot driver (for example
900 * Koala or Artstudio) */
901 screenshot_options_grid = vice_gtk3_grid_new_spaced_with_label(
902 -1, -1, "Driver options", 1);
903 g_object_set(screenshot_options_grid,
904 "margin-top", 8, "margin-left", 16, NULL);
905 gtk_grid_attach(GTK_GRID(grid), drv_grid, 0, 0, 1, 1);
906 gtk_grid_attach(GTK_GRID(grid), screenshot_options_grid, 1, 0, 1, 1);
907
908 update_screenshot_options_grid(create_screenshot_param_widget(""));
909
910 gtk_widget_show_all(grid);
911 return grid;
912 }
913
914
915 /** \brief Create the main 'sound recording' widget
916 *
917 * \return GtkGrid
918 */
create_sound_widget(void)919 static GtkWidget *create_sound_widget(void)
920 {
921 GtkWidget *grid;
922 GtkWidget *drv_grid;
923 GtkWidget *label;
924 GtkWidget *radio;
925 GtkWidget *last;
926 GSList *group = NULL;
927 int index;
928
929 grid = gtk_grid_new();
930 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
931 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
932
933 drv_grid = vice_gtk3_grid_new_spaced_with_label(-1, 0, "Driver", 1);
934 label = gtk_grid_get_child_at(GTK_GRID(drv_grid), 0, 0);
935 g_object_set(label, "margin-bottom", 8, NULL);
936 g_object_set(drv_grid, "margin-top", 8, "margin-left", 16, NULL);
937
938 last = NULL;
939 for (index = 0; audio_driver_list[index].name != NULL; index++) {
940 const char *display = audio_driver_list[index].display;
941
942 radio = gtk_radio_button_new_with_label(group, display);
943 g_object_set(radio, "margin-left", 8, NULL);
944 gtk_radio_button_join_group(GTK_RADIO_BUTTON(radio),
945 GTK_RADIO_BUTTON(last));
946 gtk_grid_attach(GTK_GRID(drv_grid), radio, 0, index + 1, 1, 1);
947
948 /*
949 * Set default audio driver, or restore previously selected audio
950 * driver.
951 */
952 if (audio_driver_index < 0) {
953 if (index == 0) { /* 0 == WAV */
954 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
955 audio_driver_index = index;
956 }
957 } else {
958 if (index == audio_driver_index) {
959 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
960 }
961 }
962
963 g_signal_connect(radio, "toggled",
964 G_CALLBACK(on_audio_driver_toggled),
965 GINT_TO_POINTER(index));
966
967 last = radio;
968 }
969
970 gtk_grid_attach(GTK_GRID(grid), drv_grid, 0, 0, 1, 1);
971
972 gtk_widget_show_all(grid);
973 return grid;
974 }
975
976
977 /** \brief Create the main 'video recording' widget
978 *
979 * \return GtkGrid
980 */
create_video_widget(void)981 static GtkWidget *create_video_widget(void)
982 {
983 GtkWidget *grid;
984 GtkWidget *label;
985 #ifdef HAVE_FFMPEG
986 GtkWidget *combo;
987 int index;
988 GtkWidget *selection_grid;
989 GtkWidget *options_grid;
990 #endif
991 grid = vice_gtk3_grid_new_spaced(16, 8);
992
993
994 #ifdef HAVE_FFMPEG
995 label = gtk_label_new("Video driver");
996 g_object_set(label, "margin-left", 16, NULL);
997
998 combo = gtk_combo_box_text_new();
999 for (index = 0; video_driver_list[index].name != NULL; index++) {
1000 const char *display = video_driver_list[index].display;
1001 const char *name = video_driver_list[index].name;
1002
1003 if (driver_is_video(name)) {
1004 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo), name, display);
1005 }
1006
1007 if (video_driver_index < 0) {
1008 video_driver_index = 0;
1009 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), index);
1010 } else {
1011 if (video_driver_index == index) {
1012 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), index);
1013 }
1014 }
1015
1016 }
1017 gtk_widget_set_hexpand(combo, TRUE);
1018 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1019
1020 selection_grid = vice_gtk3_grid_new_spaced_with_label
1021 (-1, -1, "Driver selection", 2);
1022 g_object_set(selection_grid, "margin-top", 8, "margin-left", 16, NULL);
1023 gtk_grid_set_column_spacing(GTK_GRID(selection_grid), 16);
1024 gtk_grid_set_row_spacing(GTK_GRID(selection_grid), 8);
1025 gtk_grid_attach(GTK_GRID(selection_grid), label, 0, 1, 1, 1);
1026 gtk_grid_attach(GTK_GRID(selection_grid), combo, 1, 1, 1, 1);
1027 gtk_widget_show_all(selection_grid);
1028
1029 gtk_grid_attach(GTK_GRID(grid), selection_grid, 0, 0, 1, 1);
1030
1031 /* grid around ffmpeg */
1032 options_grid = vice_gtk3_grid_new_spaced_with_label(
1033 -1, -1, "Driver options", 1);
1034 g_object_set(options_grid, "margin-top", 8, "margin-left", 16, NULL);
1035 gtk_grid_set_column_spacing(GTK_GRID(options_grid), 16);
1036 gtk_grid_set_row_spacing(GTK_GRID(options_grid), 8);
1037
1038 gtk_grid_attach(GTK_GRID(options_grid), ffmpeg_widget_create(), 0, 1, 1,1);
1039 video_driver_options_grid = options_grid;
1040
1041 gtk_grid_attach(GTK_GRID(grid), options_grid, 0, 1, 1, 1);
1042 #else
1043 label = gtk_label_new(NULL);
1044 gtk_label_set_line_wrap_mode(GTK_LABEL(label), PANGO_WRAP_WORD);
1045 g_object_set(G_OBJECT(label),
1046 "margin-left", 16,
1047 "margin-right", 16,
1048 "margin-top", 16, NULL);
1049 gtk_label_set_markup(GTK_LABEL(label),
1050 "Video recording is unavailable due to VICE having being compiled"
1051 " without FFMPEG support.\nPlease recompile with either"
1052 " <tt>--enable-static-ffmpeg</tt> or"
1053 " <tt>--enable-external-ffmpeg</tt>.\n\n"
1054 "If you didn't compile VICE yourself, ask your provider.");
1055 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
1056
1057 #endif
1058 gtk_widget_show_all(grid);
1059 return grid;
1060 }
1061
1062
1063 /** \brief Create the content widget for the dialog
1064 *
1065 * Contains a GtkStack and GtkStackSwitcher to switch between 'screenshot',
1066 * 'sound' and 'video' sub widgets.
1067 *
1068 * \return GtkGrid
1069 */
create_content_widget(void)1070 static GtkWidget *create_content_widget(void)
1071 {
1072 GtkWidget *grid;
1073 GtkWidget *switcher;
1074
1075 grid = gtk_grid_new();
1076 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
1077 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
1078
1079 stack = gtk_stack_new();
1080 gtk_stack_add_titled(GTK_STACK(stack), create_screenshot_widget(),
1081 CHILD_SCREENSHOT, "Screenshot");
1082 gtk_stack_add_titled(GTK_STACK(stack), create_sound_widget(),
1083 CHILD_SOUND, "Sound recording");
1084 gtk_stack_add_titled(GTK_STACK(stack), create_video_widget(),
1085 CHILD_VIDEO, "Video recording");
1086 gtk_stack_set_transition_type(GTK_STACK(stack),
1087 GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
1088 gtk_stack_set_transition_duration(GTK_STACK(stack), 1000);
1089 /* avoid resizing the dialog when switching */
1090 gtk_stack_set_homogeneous(GTK_STACK(stack), TRUE);
1091
1092 switcher = gtk_stack_switcher_new();
1093 gtk_widget_set_halign(switcher, GTK_ALIGN_CENTER);
1094 gtk_widget_set_hexpand(switcher, TRUE);
1095 gtk_stack_switcher_set_stack(GTK_STACK_SWITCHER(switcher), GTK_STACK(stack));
1096 /* make all titles of the switcher the same size */
1097 gtk_box_set_homogeneous(GTK_BOX(switcher), TRUE);
1098
1099 gtk_widget_show_all(stack);
1100 gtk_widget_show_all(switcher);
1101
1102 gtk_grid_attach(GTK_GRID(grid), switcher, 0, 0, 1, 1);
1103 gtk_grid_attach(GTK_GRID(grid), stack, 0, 1, 1, 1);
1104
1105 gtk_widget_show_all(grid);
1106 return grid;
1107 }
1108
1109
1110
1111 /** \brief Show dialog to save screenshot, record sound and/or video
1112 *
1113 * \param[in] parent parent widget (unused)
1114 * \param[in] data extra data (unused)
1115 *
1116 * \return TRUE
1117 */
ui_media_dialog_show(GtkWidget * parent,gpointer data)1118 gboolean ui_media_dialog_show(GtkWidget *parent, gpointer data)
1119 {
1120 GtkWidget *dialog;
1121 GtkWidget *content;
1122
1123 /*
1124 * Pause emulation
1125 */
1126
1127 /* remember pause state before entering the widget */
1128 old_pause_state = ui_pause_active();
1129
1130 /* pause emulation */
1131 ui_pause_enable();
1132
1133 /* create driver list */
1134 if (machine_class != VICE_MACHINE_VSID) {
1135 create_video_driver_list();
1136 }
1137
1138 dialog = gtk_dialog_new_with_buttons(
1139 "Record media file",
1140 ui_get_active_window(),
1141 GTK_DIALOG_MODAL,
1142 "Save", RESPONSE_SAVE,
1143 "Close", GTK_RESPONSE_DELETE_EVENT,
1144 NULL);
1145
1146 /* add content widget */
1147 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1148 if (machine_class != VICE_MACHINE_VSID) {
1149 gtk_container_add(GTK_CONTAINER(content), create_content_widget());
1150 } else {
1151 gtk_container_add(GTK_CONTAINER(content), create_sound_widget());
1152 }
1153
1154 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1155 g_signal_connect(dialog, "response", G_CALLBACK(on_response), (gpointer)dialog);
1156 g_signal_connect_unlocked(dialog, "destroy", G_CALLBACK(on_dialog_destroy), NULL);
1157
1158 gtk_widget_show_all(dialog);
1159 return TRUE;
1160 }
1161
1162
1163 /** \brief Stop audio or video recording, if active
1164 *
1165 * \param[in] parent parent widget (unused)
1166 * \param[in] data extra event data (unused)
1167 *
1168 * \return TRUE, so the emulated machine doesn't get the shortcut key
1169 */
ui_media_stop_recording(GtkWidget * parent,gpointer data)1170 gboolean ui_media_stop_recording(GtkWidget *parent, gpointer data)
1171 {
1172 /* stop sound recording, if active */
1173 if (sound_is_recording()) {
1174 sound_stop_recording();
1175 }
1176 /* stop video recording */
1177 if (screenshot_is_recording()) {
1178 screenshot_stop_recording();
1179 }
1180
1181 ui_display_recording(0);
1182 statusbar_recording_widget_hide_all(ui_statusbar_get_recording_widget(), 10);
1183
1184 return TRUE;
1185 }
1186
1187
1188 /** \brief Create screenshot with autogenerated filename
1189 *
1190 * Creates a PNG screenshot with an autogenerated filename with an ISO-8601-ish
1191 * timestamp:
1192 * "vice-screenshot-<year><month><day><hour><month><seconds><sec-frac>.png"
1193 */
ui_media_auto_screenshot(void)1194 void ui_media_auto_screenshot(void)
1195 {
1196 char *filename;
1197
1198 /* remember pause state before entering the widget */
1199 old_pause_state = ui_pause_active();
1200
1201 /* pause emulation */
1202 ui_pause_enable();
1203
1204 /* no need for locale bullshit */
1205 filename = create_proposed_screenshot_name("png");
1206 if (screenshot_save("PNG", filename, ui_get_active_canvas()) < 0) {
1207 debug_gtk3("OOPS");
1208 }
1209
1210 if (!old_pause_state) {
1211 ui_pause_disable();
1212 }
1213 }
1214