frontend: Use system locale with UTF-8 instead of 'C' - #12624
Conversation
|
Scouted the web and the codebase for potential issues. Here's some observations... General stuff about setlocale() on Windows
multibyte <-> utf8 <-> wcharIn platform.h there are various string conversion functions. From these only the multibyte functions with The The utf8 <-> wchar functions (ie Streams and file operationsC++ streams, like C-style
When to setlocale() ?On principle locale should be one of the first things to set, since many things inherit it and it's cumbersome to retroactively reset it's state to existing things. However, since Qt overwrites locale during construction of |
9d20b0c to
c0dbe23
Compare
3849250 to
92f93f4
Compare
Highlighting this because this is a severe change, even though I think it's correct in principle. Changing an application's display language should not change the regional settings (which encompass sorting rules as well as decimal point character, etc.), and at least that's how it works on macOS. @Warchamp7 @Fenrirthviti would be good to hear if you'd be fine with this change conceptually as well. |
|
My main concern here, as someone who only uses the English/USA locale/region, is that I'm unsure what the expectation for a Windows application is. The current motivation seems to be "Unix does it this way" and that to me, is not sufficient. Do we have examples and recommendations from Microsoft, or other prominent Windows applications on how they handle this kind of setting for apps that use translations? |
|
Microsoft general guidelines for globalization suggests:
My own take is that OS regional settings + app translations is the desired output with no additional settings in UI, good likelyhood being fine by default, but allows configuration when needed. The obvious drawback is that to change the regional settings one needs to change OS settings, which could be an issue when using a shared device, but OS should provide options for it. Many Microsoft apps understandably just follow the OS region settings. That includes the file explorer. Apps like Office do offer a separate in app setting for regional settings as well, but that's because they specialize in that sort of thing. For apps in general many have translation + maybe time formatting options and don't follow a specific region per se. The sorting order is a gamble. Steam seems to sort games and friends using the app display language. Spotify sorts playlists by Windows display language (not region settings, nor Spotify display language, I don't recommend this). Whatsapp uses OS regional settings. In any case, any locale is still better than the current situation with non-changeable "C" locale. |
|
Thanks for the additional context here. My, admittedly mostly uninformed opinion based on the discussion here, is that this seems fine. Without lack of a clear "best practice" on Windows, moving things in-line with our cross-platform implementation seems like the best option. @PatTheMav or @jcm93 Does macOS follow a similar approach to what is being proposed here? |
On macOS language and regional settings are also separate things and it's expected that your app's language is actually changed from the OS' language settings (rather than within the application itself) which in gendered languages also includes choice of preferred pronoun: The language setting indeed only changes the display language, decimal format, sorting, et. al. still follow the regional setting (at least in "native" apps). |
|
In the current state the PR works as designed, but I'm thinking of adding the following lines to obs.manifest to set active code page to utf-8 for Win32 APIs (the A versions of functions). While OBS uses those relatively little, with hard coded strings mostly, it could ease 3rd party code adaptation, plugins etc. It also allows utf-8 encoding of commandline arguments for loading a specific collection, scene or profile by their name. Lines to add to obs.manifest Doing this would mean the locale setup routine would need to be done much earlier in the program than the |
dd143ca to
29aa8fc
Compare
|
The obs.manifest changes has been added and locale setup routine moved to beginning of the app, with unix re-running it after OBSApp has been created. Here's some info about the manifest if you wish to read. https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page If there's any issues with locale setup routine being the first thing to do please let me know. |
|
The main "problem" this PR has to address is that OBS on the whole is conceptually not built to be aware (much less so capable of handling) of locales (and locale differences), similarly to how it pretends that any set of bytes in memory is "UTF-8" but then happily treats it as "just ASCII". For better or worse all that OBS can handle without issue is US-American text and formats, anything beyond that is a "happy accident". Introducing these capabilities has to go far beyond just slapping on some
That's already a big pile of work to get through and figure out where/how/if to change OBS (and
Mind you, I'm not saying that we shouldn't adopt changes like these, but that these changes require a great deal of thought and need to address many architectural design flaws in OBS as it is right now. Simply adding it in bits and pieces runs the risk of fixing symptoms but not fixing the core issue(s), particularly as the entire app has not been designed to properly handle locale's or anything beyond ASCII text and making it handle text "right" in one area might collapse a whole house of cards of assumptions about character data in another. For that (and a few other reasons) it might take same time (and might even require splitting the whole endeavour up into separate "units") to get it over the finish line. |
|
The input is much appreciated. I aknowledge the uncertainty and I'm fine with whatever approach is taken, but I have some counterpoints too.
I don't believe this. The fact that locales, the region and utf-8 enforcement, have been in use on non-Windows OSs for all of OBS existence while it has been using Qt is a point for that. Some degree of good design choices is needed for that.
This is not true. The threads on Windows have the locale, as long as it is set before spawning threads. References below.
Some only care about the encoding, others about the regional things. Others follow other ways of setting locale, like the manifest. Some really don't care, but considering their limited usability, it might not matter to us either. If it does it's limited job fine it's ok, if not then another approach is in order. Regarding OBS, Probably biggest culprit we have is the I also expected this to be a hurdle. So I did search the codebase for all prominent standard C and C++ functions that care about the locale and addressed them in this PR if there was a need. That includes tracking down their input origins and where they go to see if UTF-8 and localization is acceptable. I have looked if they care about the region or the encoding, and by set by what (setlocale, active codepage manifest, std::locale::global, or something else). These are not a random list of magic switches in the pr, it's all very much mappable, and have reason to be here. My experience about utf-8 readiness is that it is almost there. Kind of like you said, many things pretend it already is utf-8 while we have been playing around with ASCII. Other things don't really need to become locale or encoding aware, just as long as their limitation is recognized and used in contexts where you don't expect anything special. I'm weirdly enough expecting this to fix more things by collateral than break, especially those cases where the encoding is coincidentally in ANSI because there was nothing to tell the source we'd like utf-8. The unicode handling in OBS is otherwise... ok. We have a few ways and can keep using those, nothing wrong with that, they are built correctly for the purpose and work fine after too. This doesn't touch Qt, -W Apis (or defaults selected by UNICODE build flag), utf-16 or ucs2. It is not as if we need to adapt or handle more things, quite the opposite. It's one of the things that coercing utf-8-ness is about. The ANSI pages being one of those things exactly to prevent. The -A apis currently return ANSI coded strings, which simply do not function in OBS if they happen to contain anything non-ASCII. std::string and char are byte containers with programmer hints about using it for text. They support utf-8 as well as any other encoding with multibyte bytes. Things like strlen will tell the size in bytes, which is usually good for example allocating arrays and when you iterate over them you are often times looking for something that is in ASCII anyway (that is compatible with utf-8 codepoints). Some after thoughts The need for this started from trying to get locale-awarness to C, for sorting specifically. Locale availability on C++ or Qt could probably be leveraged with some externs to C, true. Or a signaling mechanism in case module encapsulation paradigms would fight against direct usage, though it is a round about way for something relatively low level. For commandline arguments I don't see a nice way to fix them without the also -A API changing manifest. I suppose we could use MultibyteToWideChar(), but with CP_ACP (Current code page, OS default if not changed) instead of CP_UTF8 flag and then follow with WideCharToMultibyte with the UTF-8 flag. Should work, but I don't think that type thing should become any sort of standard. I might look into this a bit more from broader, design point of view too later. Any decision is fine though. |
|
Continuing a bit here.
I do not share the sentiment about the the kind of architectural flaws present here. Reasoning being that this is already being done on non-Windows systems and the research done as described to introduce this PR. The parts of code that supposedly have a problem with non-ASCII data have that now and will, without this update, still be fed non-ASCII data if they are used as such in what ever encoding. In short, parts that don't work after this, never have, and this will not make fixing them more difficult. We treat all non utf-16/32 text as if it were utf-8 encoded already and use proper conversion functions to convert between that and target. Any other encoding text might be in is not supported by our conversion functions, so we should do everything we can to coerce external strings, and internal functions to work with the assumption the text is indeed what we believe, utf-8. While we can and have played around with ASCII, the actual data has always been what it is, it has never actually been limited to codepoints representable in ASCII. |
|
Some more details about the standard string apis, that don't fully work with utf-8, but why we shouldn't care. The standard string/ctype/std:.string functions take locale (LC_CTYPE) and try to accommodate it, but are limited to single-byte characters, which utf-8 is not. This means function like This is the the state of things now and after. Nothing will break in this regard because we changed locale or encoding. They have always done this and the results have been the same. Now for future, if one was to improve this, they should search the codebase for ctype, std::string, and the I'm only singling out only these, because frankly anything higher, QString, utf8<->wide is already done correctly by my observation and that is reflected in the code commits in this PR. |
|
Thank you for your comments, but the point I tried to make (a goal I possibly failed to achieve) is that as maintainers I'd prefer us to take multiple steps back and understand a few things first before diving head-first into changes:
The changes to And that immediately leads to the issue that OBS should just use ISO formats here, which would allow the use of The changes to Because we cannot know the robustness of any given implementation, casting to an unsigned type is indeed the safer approach (because "is value between 48 and 57" is not how the check seems to be commonly implemented). |
|
I appreciate the conversation here, and I too may misjudge what was intended, but let's get cracking. The Qt locale setup seems to default to For commandline, yes GetCommandLineW would work and also be better than the first workaround that came to my mind about acp->wide->utf8 conversion. I still think the manifest approach is somewhat preferable as an environment configuration rather than introducing more platform specific code. I also see changing of the -A apis by the active code page as thing that would help adoption of 3rd party code. If they used -A apis they either didn't know better or maybe they maintained some existing code later called About MultibyteToWideChar() in OBS, we always use it with CP_UTF8 flag, which implies all non-wide chars should be utf-8. If it's not and it is outside ASCII range, it will be corrupted by the call. For time things, yes we should use a standardized format and use the tools that most approriate in context (Qt). It is somewhat out of the scope of this, so I settled with preserving current results/expectations as much as possible. This can be changed to do ISO formatting though. |
So, without having looked at Qt's source code (on purpose in this case), my naive assumption as an application developer would be that if I implement a Qt application the regional settings set up in the operating system are followed. So if my OS is set to French language and European regional settings (using Celsius, metric units, week starts on Monday, floating comma instead of floating point, etc.) I'd expect Qt to adapt the same regional settings automatically for all Qt-specific functionality (e.g.
The problem is that any existing use of the ANSI APIs without accompanying conversion functions would be wrong conceptually as any such implementation would have always been just one non-ASCII character away from breaking in unpredictable ways (that's precisely one of the "happy accidents" I mentioned above). And rather than trying to retroactively "fix" code that is broken by design (and would not even pass code review if it were submitted as a PR these days), I'd want to see a correct implementation that is actually aware of character encodings, code pages, and the appropriate Windows APIs. If we change code, we might as well change it into what it should've been from the get-go. Case in point: The code should have understood that due to it using
It's probably fine here, even though I wouldn't mind a more holistic refactoring (that looks at an issue at a conceptual level first and is happy to discard existing code for something better), because we'd want to make that change sooner rather than later anyway. But I wouldn't hoist that requirement onto this PR. |
For Qt APIs it is. Default instance of QLocale is that of systems. It doesn't adapt the codepage though. Qt manages encoding by itself and once initialized only interfaces in utf-8 or itself by default. We do that interfacing correctly. Qt doesn't change locale/encoding of C/C++ runtime on Windows, but does on unix. It's only the C/C++ we are trying to match. But as said, we could revert and trust the locale setup routines here and in Qt are compatible.
Isn't this a win for manifest, because it would make them valid. It relaxes the technical expertise to successfully interface. We also don't control all 3rd party code to deny them, like plugins loaded in as dlls (which inherit OBS C\C++ locale, encoding and active code page).
Just to clarify, the codepage declaration in the manifest is used to encode argv, despite terminals or Windows default encoding. Since we want argv in utf-8 internally, the manifest would be the least complex way. wmain is Windows only, so we would need multiple mains in ifdefs (Does it work like that?). If we don't use the manifest declaration, I recommend GetCommandLineW for Windows as replacement too. Finally I want you know I will update the PR and adapt if anything just doesn't sit right despite my arguments. |
PatTheMav
left a comment
There was a problem hiding this comment.
Explicit casts to unsigned char should be fine IMO, using the locale-independent time format strings is also more correct, though we could use the same corresponding fixed format for the date as well (as commented).
The other changes are still pending discussion.
We actually have to distinguish between Windows, macOS, and "the rest", because only on *nix does Qt use low level C APIs for regional formatting due to lack of a universal high-level platform API. On Windows the appropriate modern Windows APIs are used and on macOS both Cocoa and Core Foundation are used, neither of which is affected by Thus from Qt's own perspective there is no need to call And if a lower-level C++ API call is necessary (and C API calls should be avoided entirely), then a corresponding facet should be set up based on the current
The UTF-8 code page is still considered "Beta" even in Windows 11 (because some ANSI methods are still incompatible with it) and its existence is mostly just a kludge. C API methods that accept But OBS is already aware of Unicode APIs and has implemented them thoroughly and it's only the command line handling code that is the outlier. So I rather have us fix the outlier to do the right thing than retroactively reward an "ignorant" implementation, particularly if we can avoid superfluous encoding/decoding (that will happen anyway) by making Windows pass us the command line in UTF-16 directly (either by using Trying to "help" 3rd party code that exhibits the same ignorance is a non-goal. If we encounter such a broken implementation we should obviously report it to its author(s), but it's not OBS' place to change its app behaviour to "fix" their bugs. IMO it would make more sense to refactor our start up code and move as much of it out of "low level" functions into "high level" application layer code that can use Qt functions and thus side-step those issues entirely. Such a refactor is planned anyway, and the command-line encoding issue is just another nail in the coffin for the current code. |
|
I forgot to mention in my previous comment that I am still undecided whether to still use the UTF-8 codepage as a "stopgap" until we properly fix the command line parsing code though. It's still a kludge, but as there are plans to clean up that code anyway, it would be a temporary kludge we can live with. |
|
Alright, slept on it, here's my suggestion: Could you please package all changes of this PR except for the changes related to Off the top of my head that should be:
I'll probably want to have a comment in I can then have someone else review those changes as well and we can discuss those in isolation from the larger issue of how/where to do locale-specific stuff in the app. |
It's here #13097 I'll rebase this branch on to that soon. In the mean time about how/where to do locale-specific stuff in the app.
If the facet is set up like it is in obs-text The resulting localization would be no different from what is achieved with setlocale. Both are based on language_region string that is effectively the same in QLocale got from system or one acquired from setlocale() (the example is using translations locale, but anyway) Setting up locale this way only applies language_region defaults per category while QLocale also applies customized localization rules. ( I was wrong about the capability in a code comment) Like if I customized decimal point in OS settings without changing language_region. Not sure about the rest of the things here, as it is more about architecture I still might be missing some things: If we wanted more granular localization and broader utf-8 support for the low level char apis identical to what is available in Qt in C++, some work would be needed:
Either way avoiding C API calls sounds like we should move to or extern c++ a bit more, I'm not yet sure how would providing locale to 3rd party plugins go either unless it's just the language_region string(s). On OBS own plugins there's still atleast vlc and slideshow that's in C that could use locale aware sorting for the playlist/slides. Though that could also be bound to the platform libraries that do the directory listing to sort, which suggests libobs should have access. If not rewritten / externed to C++, an api like the procedure/signal would be needed, which is starting to get murky for something as standard as locale-aware operations. |
|
As I mentioned earlier, the core issue is that nothing in OBS' design is built to be aware of locale-specific changes to C APIs and indeed not to the finer points of UTF-8 encoded data:
Indeed any first-party as well as third-party code is currently on its own when it wants to do "the right thing" (see examples like I also haven't checked whether all C library functions whose behaviour is influenced by the current locale have a corresponding variant that allows passing in a different locale. Otherwise, and because OBS is a multithreaded program, the "current locale" becomes a global state variable that will need to be "locked" similarly to the graphics context. And while that will allow functions like For better (and in this case for worse), OBS provides a bit of a "free-for-all" when it comes to plugins and 3rd party code, providing almost unlimited access to internal functionality. But "with great power comes great responsibility", which means that plugins have to gain all that knowledge about locales and UTF-8 specificities themselves. OBS cannot provide any form of assistance. (And before OBS would be able to do so, it would need to get its own house in order first, e.g. properly differentiating between "display language" and "locale" among other things). |
358acb4 to
7ed811c
Compare
We still do not need everything to be locale or encoding aware. We can use them for what we have and just understand that they have that limitation about them. We only need them to not crash and be deterministic. It would still be good if there was something available to use when we do need the awareness.
Standard C doesn't support passing locales, it (std::locale and facets) are a C++ feature for which the overcoming the earlier process/thread-basedness was one of the core things to address. It would work without locks. For C there is the non-standard, platform specific
A bit beside the point, but interestingly these functions currently and after do nothing different from standard
Indeed using tolower/toupper (or strmpi for case insensitive comparison) is not sufficient for anything locale-aware. If locale awareness is needed, in standard C (otherwise we could use Qt or some other locale library) they should be done with wide char functions for the reasons mentioned, like wcscoll. Tolower/upper also being kind of means to end, it should rather be considered whats the end goal, if it's locale aware case-insesitive comparison we could use wcscmpi_l if we adopt the non-standard C platform specifics. We also need to think whether we actually need true localized case sensitivity or is the current ascii limited/byte-wise comparison desired. For example checking for existing profile or source name, because then you could change locale and affect the case sensitivity comparison to match/not match an existing profile / source name. This has given me some thoughts on what to look for and address though, so I will be looking for those, though that may take some time. |
|
For reference, I just introduced some new locale functionality for libsobs/util/platform in another pr here #12577 It follows the idea of using locale only where needed instead of setting a global one. So if this approach doesn't bear fruit, or is too much of a lengthy process, maybe that one could for now. |
|
If you don't mind coarse language you might be interested in reading the commit "stream_libarchive: workaround various types of locale braindeath" on the |
It's great, and I agree. We have touched many of the same points. But here we are weighting options how to best apply locale awareness to stuff written in C. If not global locale or the per call |
Cast ctype function char parameters to unsigned char to ensure they are in correct range (0 to 255 vs -128 to 127) when used with utf-8 encoding (or extended ascii). Fixes dstr astrcmp* functions when used with utf-8 (or extended ascii) characters, so now they are treated greater than the base ascii and thus sorted after them, not before.
Switch locale-aware timestamping for logging / crash handling to %H:%M:%S Update frontend/OBSApp.cpp Co-authored-by: Patrick Heyer <PatTheMav@users.noreply.github.com> Update libobs/obs-win-crash-handler.c Co-authored-by: Patrick Heyer <PatTheMav@users.noreply.github.com>
Declaring Utf-8 as active code page in manifest makes Win32 API use utf-8 instead of ANSI codepages when using the "A" versions of functions. Manifest declaration also encodes command line arguments as utf8. This allows for example --profile <name> to load profiles with special characters.
Sets runtime locale to system locale with UTF-8 codepage. This is already default behavior on unix, but Windows defaults to minimal 'C' locale. Use CRT locale for C++ std::locale default OBS Studio language settings no longer change QLocale default locale, instead system locale is used for conformity. It is likely this is what user wants as well. Ie. sorting and formatting functions should follow OS locale instead of OBS Studio language (which also lacks country information).

Description
The changes here are based on PR #13097 that was split from this issue and does some preliminary work to support changes introduced here.
Sets C runtime locale to system locale with UTF-8 codepage on Windows.
This has always been default behavior on unix, but Windows defaults to minimal 'C' locale.
LC_NUMERICis still set to"C". Now on all platforms instead of just unix.This is so decimal point is a dot (not a comma) for string <-> float conversions.
The configured CRT locale is copied to be the default
std::localefor C++.All platforms have been using minimal
"C"until now. This change affects newfacetandios_baseinstances without a specified locale orimbue()call.OBS Studio language setting no longer changes
QLocale's default locale and instead always uses system locale.This gives conformity with non Qt functions, but most importantly is likely what user wants as well. Ie. sorting and formatting functions should follow OS locale rules instead of OBS Studio translations language. (Reverts c4840dd)
obs_get_locale()still returns OBS language locale, which is used for Python and LUA apis, GDI+ text widget transformations, and HTTP accepted languages header.Motivation and Context
Locale-aware operations like sorting and time formatting in C are not available on Windows, but are on unix, as pointed out in PR #12577.
Fixes #11133, fixes #12953
The C++ locale and QLocale changes make the locale-aware functions of all layers work in similiar fashion.
For example: On unix currently the used locales are: OS locale for CRT, minimal
"C"for C++ and OBS language for QLocale.A weekday name can be in three different languages depending if you used
strftime(),std::time_getfacet orQLocale.This makes string transformations between C, C++ and Qt very tricky.
How Has This Been Tested?
An important point is that the CRT locale settings introduced here have always been this way for unix, which suggests there aren't any insurmountable problems with the new locales. Windows specific functions should be tested for CRT locale. Changes for C++ and QLocale defaults affect all platforms.
Searched the codebase for affected areas and addressed as necessary:
strftime()formatting with%placeholdersscanf()andprintf()formatting with%placeholdersFILEoperationsfstreamoperationsfacetlocale usageQStringlocale-aware methodsSome general testing with Japanese characters
I'm on Windows 11 English US version, but with Finnish locale settings (fi_FI). OBS language is English.
Types of changes
_mbs_conversion functions directly or via file io operations have to input text in utf-8 and expect utf-8 output (except forwchar/_wcs_which is OS defined).Checklist: