1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// SPDX-License-Identifier: GPL-3.0-only

//! A Unity plugin replacing the proprietary ScreenSelector.so.
//!
//! It is built using GTK 4, and released under the GNU GPL 3.0 only license.
//!
//! Once built, you should rename the shared object to ScreenSelector.so, and either drop it in
//! your game’s plugins directory, or install it system-wide and symlink it in each game you want
//! to use it in.

use core::ffi::c_void;
use once_cell::sync::OnceCell;
use std::ffi::CStr;

mod callbacks;
mod cpp_vector;

#[cfg(not(feature = "gui"))]
mod cli;
#[cfg(feature = "gui")]
mod gtk;

use cpp_vector::CppVector;

type GetAxisDescription = extern "C" fn(i32, *mut i8, *mut i8, *mut i8) -> i32;
type ConfigureAxis = extern "C" fn(i32, i32);
type GetResolutions = extern "C" fn(u32) -> *mut CppVector<(u32, u32)>;
type GetSelectedResolution = extern "C" fn(*mut u32, *mut u32, *mut bool);
type SetSelectedResolution = extern "C" fn(u32, u32, bool);
type GetQualityLevels = extern "C" fn() -> *mut CppVector<*const i8>;
type GetSelectedQualityLevel = extern "C" fn() -> i32;
type SetSelectedQualityLevel = extern "C" fn(i32);
type GetDisplays = extern "C" fn() -> *mut CppVector<*const i8>;
type GetSelectedDisplay = extern "C" fn() -> u32;
type SetSelectedDisplay = extern "C" fn(u32);

static GET_AXIS_DESCRIPTION: OnceCell<GetAxisDescription> = OnceCell::new();
static CONFIGURE_AXIS: OnceCell<ConfigureAxis> = OnceCell::new();
static GET_RESOLUTIONS: OnceCell<GetResolutions> = OnceCell::new();
static GET_SELECTED_RESOLUTION: OnceCell<GetSelectedResolution> = OnceCell::new();
static SET_SELECTED_RESOLUTION: OnceCell<SetSelectedResolution> = OnceCell::new();
static GET_QUALITY_LEVELS: OnceCell<GetQualityLevels> = OnceCell::new();
static GET_SELECTED_QUALITY_LEVEL: OnceCell<GetSelectedQualityLevel> = OnceCell::new();
static SET_SELECTED_QUALITY_LEVEL: OnceCell<SetSelectedQualityLevel> = OnceCell::new();
static GET_DISPLAYS: OnceCell<GetDisplays> = OnceCell::new();
static GET_SELECTED_DISPLAY: OnceCell<GetSelectedDisplay> = OnceCell::new();
static SET_SELECTED_DISPLAY: OnceCell<SetSelectedDisplay> = OnceCell::new();

/// Sets the callbacks from Unity.
#[no_mangle]
pub extern "C" fn SetupUnityCallbacks(
    get_axis_description: GetAxisDescription,
    configure_axis: ConfigureAxis,
    get_resolutions: GetResolutions,
    get_selected_resolution: GetSelectedResolution,
    set_selected_resolution: SetSelectedResolution,
    get_quality_levels: GetQualityLevels,
    get_selected_quality_level: GetSelectedQualityLevel,
    set_selected_quality_level: SetSelectedQualityLevel,
    get_displays: GetDisplays,
    get_selected_display: GetSelectedDisplay,
    set_selected_display: SetSelectedDisplay,
) {
    println!(
        "SetupUnityCallbacks({:p}, {:p}, {:p}, {:p}, {:p}, {:p}, {:p}, {:p}, {:p}, {:p}, {:p})",
        get_axis_description,
        configure_axis,
        get_resolutions,
        get_selected_resolution,
        set_selected_resolution,
        get_quality_levels,
        get_selected_quality_level,
        set_selected_quality_level,
        get_displays,
        get_selected_display,
        set_selected_display
    );
    GET_AXIS_DESCRIPTION.set(get_axis_description).unwrap();
    CONFIGURE_AXIS.set(configure_axis).unwrap();
    GET_RESOLUTIONS.set(get_resolutions).unwrap();
    GET_SELECTED_RESOLUTION
        .set(get_selected_resolution)
        .unwrap();
    SET_SELECTED_RESOLUTION
        .set(set_selected_resolution)
        .unwrap();
    GET_QUALITY_LEVELS.set(get_quality_levels).unwrap();
    GET_SELECTED_QUALITY_LEVEL
        .set(get_selected_quality_level)
        .unwrap();
    SET_SELECTED_QUALITY_LEVEL
        .set(set_selected_quality_level)
        .unwrap();
    GET_DISPLAYS.set(get_displays).unwrap();
    GET_SELECTED_DISPLAY.set(get_selected_display).unwrap();
    SET_SELECTED_DISPLAY.set(set_selected_display).unwrap();
}

/// Main entrypoint of this plugin.
#[no_mangle]
pub extern "C" fn LoadScreenSelectorWindow(
    _module: *const c_void,
    title: *const i8,
    icon: *const i8,
    pixbuf_path: *const i8,
) -> i32 {
    let title = unsafe { CStr::from_ptr(title) }
        .to_string_lossy()
        .to_string();
    let icon = unsafe { CStr::from_ptr(icon) }
        .to_string_lossy()
        .to_string();
    let pixbuf_path = unsafe { CStr::from_ptr(pixbuf_path) }
        .to_string_lossy()
        .to_string();

    #[cfg(not(feature = "gui"))]
    let ret = cli::load_screen_selector_window(title, icon, pixbuf_path);
    #[cfg(feature = "gui")]
    let ret = gtk::load_screen_selector_window(title, icon, pixbuf_path);
    ret
}