As you may or may not know, Windows has two ways of scaling up UI elements:
- DPI scaling
- Text scaling
Take Windows 10 as an example, the following two entries are about DPI scaling:
and this one, is the text only scaling
Problems
The text only scaling is not well-documented and not widely supported by applications. Your application is probably one of them. But, if your application uses CEF to display anything, you may notice that it does support text scaling which contradicts with your application and mess up your interface.
And what's worse is that there is no switch or command line flag to turn this off as it is hardcoded into the framework itself.
Locate the culprit
The source code responsible for text factor awareness can be inspected at
https://source.chromium.org/chromium/chromium/src/+/main:ui/display/win/uwp_text_scale_factor.cc;l=137?q=TextScaleFactor
which looks something like:
float GetTextScaleFactor() const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
double result = 1.0;
// This is just a null check, so if we don't have access to the text
// scaling / service for any reason we'll just use 1x.
if (ui_settings_com_object_) {
HRESULT hr = ui_settings_com_object_->get_TextScaleFactor(&result);
if (FAILED(hr)) {
VLOG(2) << "IUISettings2::TextScaleFactor failed: "
<< logging::SystemErrorCodeToString(hr);
// COM calls overwrite their out-params, typically by zeroing them out.
// Since we can't rely on this being a valid value on failure, we'll
// reset it.
result = 1.0;
}
}
// Windows documents this property to always have a value greater than or
// equal to 1. Let's make sure that's the case - if we don't, we could get
// bizarre behavior and divide-by-zeros later on.
DCHECK_GE(result, 1.0);
return static_cast<float>(result);
}
As you can see, it has no switch or whatsoever. Since we don't be given an official way to disable it, we do it hard.
The hard way
By examing the code, we can see that this functionality needs the existence of ui_settings_com_object_
which is first introduced in Windows 8. That's why it is dynamically created.
To create ui_settings_com_object_
, there is a dedicate function called CreateUiSettingsComObject
which can be found at https://source.chromium.org/chromium/chromium/src/+/main:ui/display/win/uwp_text_scale_factor.cc?q=RuntimeClass_Windows_UI_ViewManagement_UISettings
// Constructs the UWP UI Settings COM object, or fails with as useful of a log
// message as possible given Windows error reporting.
//
// Lots of things could potentially go wrong so we want to be able to bail out
// when creating the UWP UI Settings object, so we've moved the initialization
// to a separate function.
bool CreateUiSettingsComObject(ComPtr<IUISettings2>& ptr) {
DCHECK(!ptr);
// Create the COM object.
auto hstring = base::win::ScopedHString::Create(
RuntimeClass_Windows_UI_ViewManagement_UISettings);
if (!hstring.is_valid()) {
return false;
}
ComPtr<IInspectable> inspectable;
HRESULT hr = base::win::RoActivateInstance(hstring.get(), &inspectable);
if (FAILED(hr)) {
VLOG(2) << "RoActivateInstance failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
// Verify that it supports the correct interface.
hr = inspectable.As(&ptr);
if (FAILED(hr)) {
VLOG(2) << "As IUISettings2 failed: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
return true;
}
There are multiple checks of what's created. If any of them fails, the whole creation process fails.
The first call to base::win::ScopedHString::Create
will in turn call a Windows API WindowsCreateString
. That's a great check to cut into the function and see what we can do to it.
Pull out your ollydbg and load the cefclient.exe
. bp WindowsCreateString
and F9
When you see the following in the stack window, that's the location we're looking for:
047AEC50 12BBC059 return to libcef.12BBC059
047AEC54 17E749EC UNICODE "Windows.UI.ViewManagement.UISettings"
047AEC58 00000024
Ctrl+F9
twice to return to libcef and Alt+F9
once to return to the caller. It should look something like
13CBF4FE 6A 24 PUSH 0x24
13CBF500 68 EC49E717 PUSH libcef.17E749EC ; UNICODE "Windows.UI.ViewManagement.UISettings"
13CBF505 56 PUSH ESI
13CBF506 E8 F5CAEFFE CALL libcef.12BBC000
13CBF50B 83C4 0C ADD ESP,0xC ;<-----we are here
13CBF50E 8B46 04 MOV EAX,DWORD PTR DS:[ESI+0x4]
13CBF511 85C0 TEST EAX,EAX
13CBF513 0F84 A9000000 JE libcef.13CBF5C2
13CBF519 8D4D E0 LEA ECX,DWORD PTR SS:[EBP-0x20]
13CBF51C C701 00000000 MOV DWORD PTR DS:[ECX],0x0
13CBF522 51 PUSH ECX
13CBF523 50 PUSH EAX
Now
if (!hstring.is_valid()) {
return false;
}
is the function we're going to hijack, and clearly 13CBF513 0F84 A9000000 JE libcef.13CBF5C2
is that if check.
Simply modify it to JMP libcef.13CBF5C2
would do the trick.
Copy to executable and save to the disk.
Result
Launch the cefclient.exe again and change text scale factor to see the result. Now CEF should not respond to the change any more.
0 comment