diff --git a/VisualC/tests/testautomation/testautomation.vcxproj b/VisualC/tests/testautomation/testautomation.vcxproj
index ff09adef5..b781dcac9 100644
--- a/VisualC/tests/testautomation/testautomation.vcxproj
+++ b/VisualC/tests/testautomation/testautomation.vcxproj
@@ -210,6 +210,7 @@
+
diff --git a/WhatsNew.txt b/WhatsNew.txt
index fffa9d2e7..90d94243d 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -9,6 +9,7 @@ General:
* Added support for 2 bits-per-pixel indexed surface formats
* Added the function SDL_GameControllerGetSteamHandle() to get the Steam API handle for a controller, if available
* Added the event SDL_CONTROLLERSTEAMHANDLEUPDATED which is sent when the Steam API handle for a controller changes. This could also change the name, VID, and PID of the controller.
+* Added the environment variable SDL_LOGGING to control default log output
macOS:
* Added the hint SDL_HINT_JOYSTICK_IOKIT to control whether the IOKit controller driver should be used
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index 7356bf4db..e775a6509 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1282,6 +1282,22 @@ extern "C" {
*/
#define SDL_HINT_LINUX_JOYSTICK_DEADZONES "SDL_LINUX_JOYSTICK_DEADZONES"
+/**
+ * \brief A variable controlling the default SDL log levels.
+ *
+ * This variable is a comma separated set of category=level tokens that define the default logging levels for SDL applications.
+ *
+ * The category can be a numeric category, one of "app", "error", "assert", "system", "audio", "video", "render", "input", "test", or `*` for any unspecified category.
+ *
+ * The level can be a numeric level, one of "verbose", "debug", "info", "warn", "error", "critical", or "quiet" to disable that category.
+ *
+ * You can omit the category if you want to set the logging level for all categories.
+ *
+ * If this hint isn't set, the default log levels are equivalent to:
+ * "app=info,assert=warn,test=verbose,*=error"
+ */
+#define SDL_HINT_LOGGING "SDL_LOGGING"
+
/**
* \brief When set don't force the SDL app to become a foreground process
*
diff --git a/src/SDL_log.c b/src/SDL_log.c
index 8cf07fccd..9e324e3dd 100644
--- a/src/SDL_log.c
+++ b/src/SDL_log.c
@@ -28,6 +28,7 @@
#include "SDL_error.h"
#include "SDL_log.h"
+#include "SDL_hints.h"
#include "SDL_mutex.h"
#include "SDL_log_c.h"
@@ -44,6 +45,8 @@
/* The size of the stack buffer to use for rendering log messages. */
#define SDL_MAX_LOG_MESSAGE_STACK 256
+#define DEFAULT_CATEGORY -1
+
typedef struct SDL_LogLevel
{
int category;
@@ -61,7 +64,8 @@ static SDL_LogOutputFunction SDL_log_function = SDL_LogOutput;
static void *SDL_log_userdata = NULL;
static SDL_mutex *log_function_mutex = NULL;
-static const char *SDL_priority_prefixes[SDL_NUM_LOG_PRIORITIES] = {
+/* If this list changes, update the documentation for SDL_HINT_LOGGING */
+static const char *SDL_priority_prefixes[] = {
NULL,
"VERBOSE",
"DEBUG",
@@ -70,8 +74,9 @@ static const char *SDL_priority_prefixes[SDL_NUM_LOG_PRIORITIES] = {
"ERROR",
"CRITICAL"
};
+SDL_COMPILE_TIME_ASSERT(priority_prefixes, SDL_arraysize(SDL_priority_prefixes) == SDL_NUM_LOG_PRIORITIES);
-#ifdef __ANDROID__
+/* If this list changes, update the documentation for SDL_HINT_LOGGING */
static const char *SDL_category_prefixes[] = {
"APP",
"ERROR",
@@ -83,9 +88,9 @@ static const char *SDL_category_prefixes[] = {
"INPUT",
"TEST"
};
+SDL_COMPILE_TIME_ASSERT(category_prefixes, SDL_arraysize(SDL_category_prefixes) == SDL_LOG_CATEGORY_RESERVED1);
-SDL_COMPILE_TIME_ASSERT(category_prefixes_enum, SDL_TABLESIZE(SDL_category_prefixes) == SDL_LOG_CATEGORY_RESERVED1);
-
+#ifdef __ANDROID__
static int SDL_android_priority[SDL_NUM_LOG_PRIORITIES] = {
ANDROID_LOG_UNKNOWN,
ANDROID_LOG_VERBOSE,
@@ -147,6 +152,122 @@ void SDL_LogSetPriority(int category, SDL_LogPriority priority)
}
}
+static SDL_bool SDL_ParseLogCategory(const char *string, size_t length, int *category)
+{
+ int i;
+
+ if (SDL_isdigit(*string)) {
+ *category = SDL_atoi(string);
+ return SDL_TRUE;
+ }
+
+ if (*string == '*') {
+ *category = DEFAULT_CATEGORY;
+ return SDL_TRUE;
+ }
+
+ for (i = 0; i < SDL_arraysize(SDL_category_prefixes); ++i) {
+ if (SDL_strncasecmp(string, SDL_category_prefixes[i], length) == 0) {
+ *category = i;
+ return SDL_TRUE;
+ }
+ }
+ return SDL_FALSE;
+}
+
+static SDL_bool SDL_ParseLogPriority(const char *string, size_t length, SDL_LogPriority *priority)
+{
+ int i;
+
+ if (SDL_isdigit(*string)) {
+ i = SDL_atoi(string);
+ if (i == 0) {
+ /* 0 has a special meaning of "disable this category" */
+ *priority = SDL_NUM_LOG_PRIORITIES;
+ return SDL_TRUE;
+ }
+ if (i >= SDL_LOG_PRIORITY_VERBOSE && i < SDL_NUM_LOG_PRIORITIES) {
+ *priority = (SDL_LogPriority)i;
+ return SDL_TRUE;
+ }
+ return SDL_FALSE;
+ }
+
+ if (SDL_strncasecmp(string, "quiet", length) == 0) {
+ *priority = SDL_NUM_LOG_PRIORITIES;
+ return SDL_TRUE;
+ }
+
+ for (i = SDL_LOG_PRIORITY_VERBOSE; i < SDL_NUM_LOG_PRIORITIES; ++i) {
+ if (SDL_strncasecmp(string, SDL_priority_prefixes[i], length) == 0) {
+ *priority = (SDL_LogPriority)i;
+ return SDL_TRUE;
+ }
+ }
+ return SDL_FALSE;
+}
+
+static SDL_bool SDL_ParseLogCategoryPriority(const char *hint, int category, SDL_LogPriority *priority)
+{
+ const char *name, *next;
+ int current_category;
+
+ if (category == DEFAULT_CATEGORY && SDL_strchr(hint, '=') == NULL) {
+ return SDL_ParseLogPriority(hint, SDL_strlen(hint), priority);
+ }
+
+ for (name = hint; name; name = next) {
+ const char *sep = SDL_strchr(name, '=');
+ if (!sep) {
+ break;
+ }
+ next = SDL_strchr(sep, ',');
+ if (next) {
+ ++next;
+ }
+
+ if (SDL_ParseLogCategory(name, (sep - name), ¤t_category)) {
+ if (current_category == category) {
+ const char *value = sep + 1;
+ size_t len;
+ if (next) {
+ len = (next - value - 1);
+ } else {
+ len = SDL_strlen(value);
+ }
+ return SDL_ParseLogPriority(value, len, priority);
+ }
+ }
+ }
+ return SDL_FALSE;
+}
+
+static SDL_LogPriority SDL_GetDefaultLogPriority(int category)
+{
+ const char *hint = SDL_GetHint(SDL_HINT_LOGGING);
+ if (hint) {
+ SDL_LogPriority priority;
+
+ if (SDL_ParseLogCategoryPriority(hint, category, &priority)) {
+ return priority;
+ }
+ if (SDL_ParseLogCategoryPriority(hint, DEFAULT_CATEGORY, &priority)) {
+ return priority;
+ }
+ }
+
+ switch (category) {
+ case SDL_LOG_CATEGORY_APPLICATION:
+ return SDL_LOG_PRIORITY_INFO;
+ case SDL_LOG_CATEGORY_ASSERT:
+ return SDL_LOG_PRIORITY_WARN;
+ case SDL_LOG_CATEGORY_TEST:
+ return SDL_LOG_PRIORITY_VERBOSE;
+ default:
+ return SDL_LOG_PRIORITY_ERROR;
+ }
+}
+
SDL_LogPriority SDL_LogGetPriority(int category)
{
SDL_LogLevel *entry;
@@ -161,16 +282,7 @@ SDL_LogPriority SDL_LogGetPriority(int category)
return SDL_forced_priority_level;
}
- switch (category) {
- case SDL_LOG_CATEGORY_APPLICATION:
- return SDL_LOG_PRIORITY_INFO;
- case SDL_LOG_CATEGORY_ASSERT:
- return SDL_LOG_PRIORITY_WARN;
- case SDL_LOG_CATEGORY_TEST:
- return SDL_LOG_PRIORITY_VERBOSE;
- default:
- return SDL_LOG_PRIORITY_ERROR;
- }
+ return SDL_GetDefaultLogPriority(category);
}
void SDL_LogResetPriorities(void)
diff --git a/test/testautomation_log.c b/test/testautomation_log.c
new file mode 100644
index 000000000..c5d34b9f4
--- /dev/null
+++ b/test/testautomation_log.c
@@ -0,0 +1,209 @@
+/**
+ * Log test suite
+ */
+#include "SDL.h"
+#include "SDL_test.h"
+
+
+static SDL_LogOutputFunction original_function;
+static void *original_userdata;
+
+static void TestLogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message)
+{
+ int *message_count = (int *)userdata;
+ ++(*message_count);
+}
+
+static void EnableTestLog(int *message_count)
+{
+ *message_count = 0;
+ SDL_LogGetOutputFunction(&original_function, &original_userdata);
+ SDL_LogSetOutputFunction(TestLogOutput, message_count);
+}
+
+static void DisableTestLog()
+{
+ SDL_LogSetOutputFunction(original_function, original_userdata);
+}
+
+/* Fixture */
+
+/* Test case functions */
+
+/**
+ * Check SDL_HINT_LOGGING functionality
+ */
+static int log_testHint(void *arg)
+{
+ int count;
+
+ SDL_SetHint(SDL_HINT_LOGGING, NULL);
+ SDLTest_AssertPass("SDL_SetHint(SDL_HINT_LOGGING, NULL)");
+ {
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+ }
+
+ SDL_SetHint(SDL_HINT_LOGGING, "debug");
+ SDLTest_AssertPass("SDL_SetHint(SDL_HINT_LOGGING, \"debug\")");
+ {
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+ }
+
+ SDL_SetHint(SDL_HINT_LOGGING, "system=debug");
+ SDLTest_AssertPass("SDL_SetHint(SDL_HINT_LOGGING, \"system=debug\")");
+ {
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_VERBOSE, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_VERBOSE, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+ }
+
+ SDL_SetHint(SDL_HINT_LOGGING, "app=warn,system=debug,assert=quiet,*=info");
+ SDLTest_AssertPass("SDL_SetHint(SDL_HINT_LOGGING, \"app=warn,system=debug,assert=quiet,*=info\")");
+ {
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_VERBOSE, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_VERBOSE, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_CRITICAL, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_CRITICAL, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_INFO, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_INFO, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ }
+
+ SDL_SetHint(SDL_HINT_LOGGING, "0=4,3=2,2=0,*=3");
+ SDLTest_AssertPass("SDL_SetHint(SDL_HINT_LOGGING, \"0=4,3=2,2=0,*=3\")");
+ {
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_VERBOSE, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_VERBOSE, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_CRITICAL, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_CRITICAL, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_INFO, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_INFO, \"test\")");
+ SDLTest_AssertCheck(count == 1, "Check result value, expected: 1, got: %d", count);
+
+ EnableTestLog(&count);
+ SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_DEBUG, "test");
+ DisableTestLog();
+ SDLTest_AssertPass("SDL_LogMessage(SDL_LOG_CATEGORY_CUSTOM, SDL_LOG_PRIORITY_DEBUG, \"test\")");
+ SDLTest_AssertCheck(count == 0, "Check result value, expected: 0, got: %d", count);
+
+ }
+
+ return TEST_COMPLETED;
+}
+
+/* ================= Test References ================== */
+
+/* Log test cases */
+static const SDLTest_TestCaseReference logTestHint = {
+ (SDLTest_TestCaseFp)log_testHint, "log_testHint", "Check SDL_HINT_LOGGING functionality", TEST_ENABLED
+};
+
+/* Sequence of Log test cases */
+static const SDLTest_TestCaseReference *logTests[] = {
+ &logTestHint, NULL
+};
+
+/* Timer test suite (global) */
+SDLTest_TestSuiteReference logTestSuite = {
+ "Log",
+ NULL,
+ logTests,
+ NULL
+};
diff --git a/test/testautomation_suites.h b/test/testautomation_suites.h
index 24909db70..95528f271 100644
--- a/test/testautomation_suites.h
+++ b/test/testautomation_suites.h
@@ -16,6 +16,7 @@ extern SDLTest_TestSuiteReference guidTestSuite;
extern SDLTest_TestSuiteReference hintsTestSuite;
extern SDLTest_TestSuiteReference joystickTestSuite;
extern SDLTest_TestSuiteReference keyboardTestSuite;
+extern SDLTest_TestSuiteReference logTestSuite;
extern SDLTest_TestSuiteReference mainTestSuite;
extern SDLTest_TestSuiteReference mathTestSuite;
extern SDLTest_TestSuiteReference mouseTestSuite;
@@ -41,6 +42,7 @@ SDLTest_TestSuiteReference *testSuites[] = {
&hintsTestSuite,
&joystickTestSuite,
&keyboardTestSuite,
+ &logTestSuite,
&mainTestSuite,
&mathTestSuite,
&mouseTestSuite,