diff --git a/src/config.h b/src/config.h index 3556ce1f..328e3f0a 100644 --- a/src/config.h +++ b/src/config.h @@ -92,6 +92,7 @@ CONFIGDEF_INT (FULLSCREEN_DESKTOP, "fullscreen_desktop_mode", 1) \ CONFIGDEF_INT (VID_WIDTH, "vid_width", RESX) \ CONFIGDEF_INT (VID_HEIGHT, "vid_height", RESY) \ + CONFIGDEF_INT (VID_DISPLAY, "vid_display", 0) \ CONFIGDEF_INT (VID_RESIZABLE, "vid_resizable", 0) \ CONFIGDEF_INT (VID_FRAMESKIP, "vid_frameskip", 1) \ CONFIGDEF_INT (VSYNC, "vsync", CONFIG_VSYNC_DEFAULT) \ diff --git a/src/menu/options.c b/src/menu/options.c index b6a28fcf..24254a2a 100644 --- a/src/menu/options.c +++ b/src/menu/options.c @@ -30,6 +30,7 @@ typedef enum BindingType { BT_GamepadKeyBinding, BT_GamepadAxisBinding, BT_GamepadDevice, + BT_VideoDisplay, } BindingType; typedef struct OptionBinding { @@ -321,6 +322,24 @@ static int bind_common_onoffplus_set(OptionBinding *b, int v) { #define bind_common_int_get bind_common_onoff_inverted_get #define bind_common_int_set bind_common_onoff_inverted_set +// BT_VideoDisplay: fullscreen display number +static OptionBinding* bind_video_display(int cfgentry) { + OptionBinding *bind = bind_new(); + + bind->configentry = cfgentry; + bind->type = BT_VideoDisplay; + + bind->getter = bind_common_int_get; + bind->setter = bind_common_int_set; + + bind->valrange_min = 0; + bind->valrange_max = 0; // updated later + + bind->selected = video_current_display(); + + return bind; +} + static int bind_common_intplus1_get(OptionBinding *b) { return config_get_int(b->configentry) - 1; } @@ -374,6 +393,8 @@ typedef struct OptionsMenuContext { } OptionsMenuContext; static void destroy_options_menu(MenuData *m) { + bool change_vidmode = false; + for(int i = 0; i < m->ecount; ++i) { OptionBinding *bind = bind_get(m, i); @@ -384,14 +405,9 @@ static void destroy_options_menu(MenuData *m) { if(bind->type == BT_Resolution && video_query_capability(VIDEO_CAP_CHANGE_RESOLUTION) == VIDEO_AVAILABLE) { if(bind->selected != -1) { VideoMode *mode = video.modes + bind->selected; - - video_set_mode(mode->width, mode->height, - config_get_int(CONFIG_FULLSCREEN), - config_get_int(CONFIG_VID_RESIZABLE) - ); - - config_set_int(CONFIG_VID_WIDTH, video.intended.width); - config_set_int(CONFIG_VID_HEIGHT, video.intended.height); + config_set_int(CONFIG_VID_WIDTH, mode->width); + config_set_int(CONFIG_VID_HEIGHT, mode->height); + change_vidmode = true; } } @@ -399,6 +415,16 @@ static void destroy_options_menu(MenuData *m) { free(bind); } + if(change_vidmode) { + video_set_mode( + config_get_int(CONFIG_VID_DISPLAY), + config_get_int(CONFIG_VID_WIDTH), + config_get_int(CONFIG_VID_HEIGHT), + config_get_int(CONFIG_FULLSCREEN), + config_get_int(CONFIG_VID_RESIZABLE) + ); + } + if(m->context) { OptionsMenuContext *ctx = m->context; free(ctx->data); @@ -455,11 +481,16 @@ static MenuData* create_options_menu_video(MenuData *parent) { MenuData *m = create_options_menu_base("Video Options"); OptionBinding *b; + add_menu_entry(m, "Fullscreen", do_nothing, b = bind_option(CONFIG_FULLSCREEN, bind_common_onoff_get, bind_common_onoff_set) ); bind_onoff(b); b->dependence = bind_fullscreen_dependence; + add_menu_entry(m, "Display", do_nothing, + b = bind_video_display(CONFIG_VID_DISPLAY) + ); + add_menu_entry(m, "Window size", do_nothing, b = bind_resolution() ); b->setter = bind_resolution_set; @@ -470,12 +501,12 @@ static MenuData* create_options_menu_video(MenuData *parent) { ); bind_onoff(b); b->dependence = bind_resizable_dependence; - add_menu_entry(m, "Pause the game when it's not focused", do_nothing, + add_menu_separator(m); + + add_menu_entry(m, "Pause the game when not focused", do_nothing, b = bind_option(CONFIG_FOCUS_LOSS_PAUSE, bind_common_onoff_get, bind_common_onoff_set) ); bind_onoff(b); - add_menu_separator(m); - add_menu_entry(m, "Vertical synchronization", do_nothing, b = bind_option(CONFIG_VSYNC, bind_common_onoffplus_get, bind_common_onoffplus_set) ); bind_addvalue(b, "on"); @@ -972,7 +1003,7 @@ static void draw_options_menu(MenuData *menu) { case BT_GamepadDevice: { if(bind_isactive(bind)) { // XXX: I'm not exactly a huge fan of fixing up state in drawing code, but it seems the way to go for now... - bind->valrange_max = gamepad_device_count(); + bind->valrange_max = gamepad_device_count() - 1; if(bind->selected < 0 || bind->selected > bind->valrange_max) { bind->selected = gamepad_current_device_num(); @@ -1003,6 +1034,29 @@ static void draw_options_menu(MenuData *menu) { break; } + case BT_VideoDisplay: { + // XXX: see the BT_GamepadDevice case... + bind->valrange_max = video_num_displays() - 1; + + if(bind->selected < 0 || bind->selected > bind->valrange_max) { + bind->selected = video_current_display(); + + if(bind->selected < 0) { + bind->selected = 0; + } + } + + char buf[64]; + snprintf(buf, sizeof(buf), "#%i: %s", bind->selected + 1, video_display_name(bind->selected)); + text_draw(buf, &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + .color = &clr, + .max_width = (SCREEN_W - 220) / 2, + }); + break; + } + case BT_GamepadKeyBinding: case BT_GamepadAxisBinding: { bool is_axis = (bind->type == BT_GamepadAxisBinding); @@ -1378,6 +1432,7 @@ static bool options_input_handler(SDL_Event *event, void *arg) { case BT_GamepadDevice: case BT_IntValue: case BT_Resolution: + case BT_VideoDisplay: (next ? bind_setnext : bind_setprev)(bind); break; diff --git a/src/video.c b/src/video.c index 1770c332..92ee4e43 100644 --- a/src/video.c +++ b/src/video.c @@ -185,7 +185,7 @@ static const char* modeflagsstr(uint32_t flags) { } } -static void video_new_window_internal(int w, int h, uint32_t flags, bool fallback) { +static void video_new_window_internal(uint display, uint w, uint h, uint32_t flags, bool fallback) { if(video.window) { SDL_DestroyWindow(video.window); video.window = NULL; @@ -193,7 +193,12 @@ static void video_new_window_internal(int w, int h, uint32_t flags, bool fallbac char title[sizeof(WINDOW_TITLE) + strlen(TAISEI_VERSION) + 2]; snprintf(title, sizeof(title), "%s v%s", WINDOW_TITLE, TAISEI_VERSION); - video.window = r_create_window(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, flags); + video.window = r_create_window( + title, + SDL_WINDOWPOS_CENTERED_DISPLAY(display), + SDL_WINDOWPOS_CENTERED_DISPLAY(display), + w, h, flags + ); if(video.window) { SDL_SetWindowMinimumSize(video.window, SCREEN_W / 4, SCREEN_H / 4); @@ -206,10 +211,10 @@ static void video_new_window_internal(int w, int h, uint32_t flags, bool fallbac return; } - video_new_window_internal(RESX, RESY, flags & ~SDL_WINDOW_FULLSCREEN_DESKTOP, true); + video_new_window_internal(display, RESX, RESY, flags & ~SDL_WINDOW_FULLSCREEN_DESKTOP, true); } -static void video_new_window(int w, int h, bool fs, bool resizable) { +static void video_new_window(uint display, uint w, uint h, bool fs, bool resizable) { uint32_t flags = 0; if(fs) { @@ -218,21 +223,23 @@ static void video_new_window(int w, int h, bool fs, bool resizable) { flags |= SDL_WINDOW_RESIZABLE; } - video_new_window_internal(w, h, flags, false); + video_new_window_internal(display, w, h, flags, false); + display = video_current_display(); - log_info("Created a new window: %ix%i (%s)", + log_info("Created a new window: %ix%i (%s), on display #%i %s", video.current.width, video.current.height, - modeflagsstr(SDL_GetWindowFlags(video.window)) + modeflagsstr(SDL_GetWindowFlags(video.window)), + display, + video_display_name(display) ); events_pause_keyrepeat(); SDL_RaiseWindow(video.window); } -static bool video_set_display_mode(int w, int h) { +static bool video_set_display_mode(uint display, uint w, uint h) { SDL_DisplayMode closest, target = { .w = w, .h = h }; - int display = SDL_GetWindowDisplayIndex(video.window); if(!SDL_GetClosestDisplayMode(display, &target, &closest)) { log_error("No available display modes for %ix%i on display %i", w, h, display); @@ -262,18 +269,32 @@ static void video_set_fullscreen_internal(bool fullscreen) { SDL_RaiseWindow(video.window); } -void video_set_mode(int w, int h, bool fs, bool resizable) { +void video_set_mode(uint display, uint w, uint h, bool fs, bool resizable) { video.intended.width = w; video.intended.height = h; + if(display >= video_num_displays()) { + log_warn("Display index %u is invalid, falling back to 0 (%s)", display, video_display_name(0)); + display = 0; + } + if(!video.window) { - video_new_window(w, h, fs, resizable); + video_new_window(display, w, h, fs, resizable); return; } - if(w != video.current.width || h != video.current.height) { + uint old_display = video_current_display(); + bool display_changed = display != old_display; + bool size_changed = w != video.current.width || h != video.current.height; + + if(display_changed) { + video_new_window(display, w, h, fs, resizable); + return; + } + + if(size_changed) { if(fs && !config_get_int(CONFIG_FULLSCREEN_DESKTOP)) { - video_set_display_mode(w, h); + video_set_display_mode(display, w, h); video_set_fullscreen_internal(fs); video_update_mode_settings(); } else if(video.backend == VIDEO_BACKEND_X11) { @@ -282,11 +303,11 @@ void video_set_mode(int w, int h, bool fs, bool resizable) { // and we'd never know. SDL_GL_GetDrawableSize/SDL_GetWindowSize aren't helping as of SDL 2.0.5. // // There's not much to be done about it. We're at mercy of SDL here and SDL is at mercy of the WM. - video_new_window(w, h, fs, resizable); + video_new_window(display, w, h, fs, resizable); return; } else if(video.backend == VIDEO_BACKEND_EMSCRIPTEN && !fs) { // Needed to work around various SDL bugs and HTML/DOM quirks... - video_new_window(w, h, fs, resizable); + video_new_window(display, w, h, fs, resizable); return; } else { SDL_SetWindowSize(video.window, w, h); @@ -299,7 +320,23 @@ void video_set_mode(int w, int h, bool fs, bool resizable) { } void video_set_fullscreen(bool fullscreen) { - video_set_mode(video.intended.width, video.intended.height, fullscreen, config_get_int(CONFIG_VID_RESIZABLE)); + video_set_mode( + SDL_GetWindowDisplayIndex(video.window), + video.intended.width, + video.intended.height, + fullscreen, + config_get_int(CONFIG_VID_RESIZABLE) + ); +} + +void video_set_display(uint idx) { + video_set_mode( + idx, + video.intended.width, + video.intended.height, + config_get_int(CONFIG_FULLSCREEN), + config_get_int(CONFIG_VID_RESIZABLE) + ); } static void* video_screenshot_task(void *arg) { @@ -486,7 +523,7 @@ static void video_handle_resize(int w, int h) { log_warn("Bad resize: %ix%i is too small!", w, h); // FIXME: the video_new_window is actually a workaround for Wayland. // I'm not sure if it's necessary for anything else. - video_new_window(video.intended.width, video.intended.height, false, video_is_resizable()); + video_new_window(video_current_display(), video.intended.width, video.intended.height, false, video_is_resizable()); return; } @@ -521,6 +558,10 @@ static bool video_handle_config_event(SDL_Event *evt, void *arg) { video_set_fullscreen(val->i); break; + case CONFIG_VID_DISPLAY: + video_set_display(val->i); + break; + case CONFIG_VID_RESIZABLE: SDL_SetWindowResizable(video.window, val->i); break; @@ -533,6 +574,46 @@ static bool video_handle_config_event(SDL_Event *evt, void *arg) { return false; } +uint video_num_displays(void) { + int displays = SDL_GetNumVideoDisplays(); + + if(displays < 1) { + if(displays == 0) { + log_warn("SDL_GetNumVideoDisplays() returned 0, this shouldn't happen"); + } else { + log_sdl_error(LOG_WARN, "SDL_GetNumVideoDisplays"); + } + + displays = 1; + } + + return displays; +} + +const char *video_display_name(uint id) { + assert(id < video_num_displays()); + + const char *name = SDL_GetDisplayName(id); + + if(name == NULL) { + log_sdl_error(LOG_WARN, "SDL_GetDisplayName"); + name = "Unknown"; + } + + return name; +} + +uint video_current_display(void) { + int display = SDL_GetWindowDisplayIndex(video.window); + + if(display < 0) { + log_sdl_error(LOG_WARN, "SDL_GetWindowDisplayIndex"); + display = 1; + } + + return display; +} + void video_init(void) { bool fullscreen_available = false; @@ -566,7 +647,8 @@ void video_init(void) { // Register all resolutions that are available in fullscreen - for(int s = 0; s < SDL_GetNumVideoDisplays(); ++s) { + for(int s = 0; s < video_num_displays(); ++s) { + log_info("Found display #%i: %s", s, video_display_name(s)); for(int i = 0; i < SDL_GetNumDisplayModes(s); ++i) { SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; @@ -609,6 +691,7 @@ void video_init(void) { qsort(video.modes, video.mcount, sizeof(VideoMode), video_compare_modes); video_set_mode( + config_get_int(CONFIG_VID_DISPLAY), config_get_int(CONFIG_VID_WIDTH), config_get_int(CONFIG_VID_HEIGHT), config_get_int(CONFIG_FULLSCREEN), diff --git a/src/video.h b/src/video.h index 76cf58f8..b5eed596 100644 --- a/src/video.h +++ b/src/video.h @@ -46,7 +46,7 @@ typedef enum VideoBackend { typedef struct { VideoMode *modes; SDL_Window *window; - int mcount; + uint mcount; VideoMode intended; VideoMode current; VideoBackend backend; @@ -70,7 +70,7 @@ extern Video video; void video_init(void); void video_shutdown(void); -void video_set_mode(int w, int h, bool fs, bool resizable); +void video_set_mode(uint display, uint w, uint h, bool fs, bool resizable); void video_set_fullscreen(bool fullscreen); void video_get_viewport(FloatRect *vp); void video_get_viewport_size(float *width, float *height); @@ -79,5 +79,9 @@ bool video_is_resizable(void); extern VideoCapabilityState (*video_query_capability)(VideoCapability cap); void video_take_screenshot(void); void video_swap_buffers(void); +uint video_num_displays(void); +uint video_current_display(void); +void video_set_display(uint idx); +const char *video_display_name(uint id) attr_returns_nonnull; #endif // IGUARD_video_h