add display option for multi-monitor systems

This commit is contained in:
Andrei Alexeyev 2019-04-18 22:56:16 +03:00
parent 7f8b72d864
commit 58b964baba
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
4 changed files with 175 additions and 32 deletions

View file

@ -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) \

View file

@ -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;

View file

@ -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),

View file

@ -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