screen-selector 0.1.1

A reimplementation of Unity’s ScreenSelector.so plugin, using GTK 4
Documentation
// 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
}