Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config file support #1344

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config/display.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Configuration files are installed in /etc/gamescope.d/*.conf
{
"display_configuration": {
"VLV": {
"0x3003" : {
"refresh_rates": [ 45, 47, 48, 49, 50, 51, 53, 55, 56, 59,
60, 62, 64, 65, 66, 68, 72, 73, 76, 77,
78, 80, 81, 82, 84, 85, 86, 87, 88, 90 ]
},
"0x3004" : {
"refresh_rates": [ 45, 47, 48, 49, 50, 51, 53, 55, 56, 59,
60, 62, 64, 65, 66, 68, 72, 73, 76, 77,
78, 80, 81, 82, 84, 85, 86, 87, 88, 90 ]
}
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add an example_config.json to the install output? Sort of as a config documentation with every field commented out.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is installing it as config.json.sample so it would be ignored by default because of the extension (that's how scripts in .git/hooks work).

191 changes: 191 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#include "config.hpp"

#include <algorithm>
#include <cinttypes>
#include <filesystem>
#include <fstream>
#include <json/json.h>
#include <unistd.h>

namespace gamescope {

static std::map<std::pair<std::string, uint16_t>, std::vector<uint32_t>> refresh_rates;
static const char config_dir_name[] = "/etc/gamescope.d";

// Parse the refresh_rates array
static int parse_refresh_rates(const Json::Value& rates, const std::string& vendor_id, uint16_t product_id)
{
if (!rates.isArray())
{
fprintf(stderr, "Unexpected value: 'display_configuration.%s.%" PRIu16 ".refresh_rates' sound be an array\n",
vendor_id.c_str(), product_id);
return -1;
}

std::vector<uint32_t> v;
v.reserve(rates.size());
for (const auto& iter : rates)
{
if (!iter.isUInt() || iter.asUInt() > 0xffff)
{
fprintf(stderr, "Invalid value in 'display_configuration.%s.%" PRIu16 ".refresh_rates': '%s'\n",
vendor_id.c_str(), product_id, iter.asString().c_str());
return -1;
}
v.push_back(iter.asUInt());
}
// Overwrite any existing values (from e.g. a previous config file)
refresh_rates.insert_or_assign(std::make_pair(vendor_id, product_id), std::move(v));

return 0;
}

// Parse the 'display_configuration' setting
static int parse_display_config(const Json::Value& display_cfg)
{
if (!display_cfg.isObject())
{
fprintf(stderr, "Unexpected value: 'display_configuration' should be an object\n");
return -1;
}

for (const auto& vendor_id : display_cfg.getMemberNames())
{
const auto& vendor = display_cfg[vendor_id];
if (!vendor.isObject())
{
fprintf(stderr, "Unexpected value: 'display_configuration.%s' should be an object\n",
vendor_id.c_str());
return -1;
}

for (const auto& product_id_str : vendor.getMemberNames())
{
char* endptr;
unsigned long product_id = strtoul(product_id_str.c_str(), &endptr, 0);
if (product_id_str.empty() || *endptr != '\0' || product_id > 0xffff)
{
fprintf(stderr, "Unexpected value: product ID '%s' in 'display_configuration.%s' should be a 16-bit integer\n",
product_id_str.c_str(), vendor_id.c_str());
return -1;
}

const auto& product = vendor[product_id_str];
if (!product.isObject())
{
fprintf(stderr, "Unexpected value: 'display_configuration.%s.%s' should be an object\n",
vendor_id.c_str(), product_id_str.c_str());
return -1;
}

for (const auto& name : product.getMemberNames())
{
int ret = 0;
if (name == "refresh_rates")
{
ret = parse_refresh_rates(product[name], vendor_id, product_id);
}
else
{
fprintf(stderr, "Unknown configuration setting 'display_configuration.%s.%s.%s'\n",
vendor_id.c_str(), product_id_str.c_str(), name.c_str());
}
if (ret < 0)
return ret;
}
}
}

return 0;
}

// Read a single configuration file
static int config_read_file(const std::string& file_name)
{
std::ifstream file(file_name);
if (!file.is_open())
{
fprintf(stderr, "Error opening config file '%s'\n", file_name.c_str());
return -1;
}

Json::Value root;
Json::CharReaderBuilder builder;
std::string errors;
if (!parseFromStream(builder, file, &root, &errors))
{
fprintf(stderr, "Error parsing config file '%s': %s\n",
file_name.c_str(), errors.c_str());
return -1;
}

if (!root.isObject())
{
fprintf(stderr, "Unexpected format in config file '%s'\n", file_name.c_str());
return -1;
}

for (const auto& name : root.getMemberNames())
{
int ret = 0;
if (name == "display_configuration")
{
ret = parse_display_config(root[name]);
}
else
{
fprintf(stderr, "Unknown configuration setting in '%s': '%s'\n",
file_name.c_str(), name.c_str());
}
if (ret < 0)
return ret;
}

return 0;
}

// Read all configuration files
int config_read()
{
std::filesystem::path config_dir = config_dir_name;
if (!std::filesystem::is_directory(config_dir))
return 0;

if (access(config_dir_name, R_OK | X_OK) != 0)
{
fprintf(stderr, "Cannot open directory '%s'\n", config_dir_name);
return -1;
}

std::vector<std::string> files;
for (const auto& iter : std::filesystem::directory_iterator(config_dir))
{
const auto& path = iter.path();
if (std::filesystem::is_regular_file(iter.status()) && path.extension() == ".conf")
Copy link

@lostgoat lostgoat May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm on the fence about using a .conf extensions here. Since the contents are json that makes me want to lean towards the extension being .json instead.

Overall, not a big deal, will go with whatever you or @Joshua-Ashton think is best

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually using .json initially 🙂 but I really don't mind, both options are fine with me.

{
files.push_back(path);
}
}

std::sort(files.begin(), files.end());
for (const auto& path : files)
{
int ret = config_read_file(path);
if (ret < 0)
return ret;
}

return 0;
}

// Get the list of valid refresh rates for the specified display
std::span<const uint32_t> config_get_refresh_rates(const char *vendor_id, uint16_t product_id)
{
const auto& rates = refresh_rates.find(std::make_pair(vendor_id, product_id));
if (rates == refresh_rates.end())
return {};
else
return rates->second;
}

} // namespace gamescope
14 changes: 14 additions & 0 deletions src/config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include <span>
#include <stdint.h>

namespace gamescope
{
// Read all configurations files
int config_read();

// Get the list of valid refresh rates for the specified display,
// or an empty std::span if they are not found.
std::span<const uint32_t> config_get_refresh_rates(const char *vendor_id, uint16_t product_id);
}
14 changes: 11 additions & 3 deletions src/drm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "backend.h"
#include "color_helpers.h"
#include "config.hpp"
#include "defer.hpp"
#include "drm_include.h"
#include "edid.h"
Expand Down Expand Up @@ -2099,6 +2100,10 @@ namespace gamescope

drm_log.infof("Connector %s -> %s - %s", m_Mutable.szName, m_Mutable.szMakePNP, m_Mutable.szModel );

m_Mutable.ValidDynamicRefreshRates = config_get_refresh_rates( m_Mutable.szMakePNP, pProduct->product );
if ( !m_Mutable.ValidDynamicRefreshRates.empty() )
drm_log.infof("Got refresh rates from the configuration file");

const bool bSteamDeckDisplay =
( m_Mutable.szMakePNP == "WLC"sv && m_Mutable.szModel == "ANX7530 U"sv ) ||
( m_Mutable.szMakePNP == "ANX"sv && m_Mutable.szModel == "ANX7530 U"sv ) ||
Expand All @@ -2114,17 +2119,20 @@ namespace gamescope
if ( pProduct->product == kPIDGalileoSDC )
{
m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC;
m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates );
if ( m_Mutable.ValidDynamicRefreshRates.empty() )
m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates );
}
else if ( pProduct->product == kPIDGalileoBOE )
{
m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE;
m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates );
if ( m_Mutable.ValidDynamicRefreshRates.empty() )
m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates );
}
else
{
m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD;
m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckLCDRates );
if ( m_Mutable.ValidDynamicRefreshRates.empty() )
m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckLCDRates );
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <float.h>

#include "main.hpp"
#include "config.hpp"
#include "steamcompmgr.hpp"
#include "rendervulkan.hpp"
#include "wlserver.hpp"
Expand Down Expand Up @@ -709,6 +710,9 @@ int main(int argc, char **argv)
}
}

if ( gamescope::config_read() != 0 )
return 1;

#if defined(__linux__) && HAVE_LIBCAP
cap_t caps = cap_get_proc();
if ( caps != nullptr )
Expand Down
4 changes: 3 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ thread_dep = dependency('threads')
cap_dep = dependency('libcap', required: get_option('rt_cap'))
epoll_dep = dependency('epoll-shim', required: false)
glm_dep = dependency('glm')
jsoncpp_dep = dependency('jsoncpp')
sdl_dep = dependency('SDL2', required: get_option('sdl2_backend'))
stb_dep = dependency('stb')
avif_dep = dependency('libavif', version: '>=1.0.0', required: get_option('avif_screenshots'))
Expand Down Expand Up @@ -94,6 +95,7 @@ required_wlroots_features = ['xwayland']

src = [
'steamcompmgr.cpp',
'config.cpp',
'convar.cpp',
'commit.cpp',
'color_helpers.cpp',
Expand Down Expand Up @@ -161,7 +163,7 @@ endforeach
include_directories : [reshade_include],
dependencies: [
dep_wayland, dep_x11, dep_xdamage, dep_xcomposite, dep_xrender, dep_xext, dep_xfixes,
dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server,
dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, jsoncpp_dep,
xkbcommon, thread_dep, sdl_dep, wlroots_dep,
vulkan_dep, liftoff_dep, dep_xtst, dep_xmu, cap_dep, epoll_dep, pipewire_dep, librt_dep,
stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi,
Expand Down