01.16
I’m currently designing a minimalistic user interface in FPC (Freepascal), and I wanted it to blend in with the existing Windows desktop. So naturally, I started looking for how to obtain the user’s chosen accent color (I’m deliberately alternating American and British spelling of colour/color to help anyone that needs this to be able to google it successfully!)
Knowing Microsoft, I knew obtaining this probably wasn’t going to be as easy as back in the old Win32 days, where there was a simple API like GetSysColor(COLOR_ACTIVECAPTION), but I still had hopes that it wasn’t going to be too difficult.
As is often the case, the best reference I could find on how to do this was in Raymond Chen’s blog. Helpfully, he even demonstrates how to obtain this value in “raw C++”, which is the closest equivalent we can expect to doing so in FPC. Here’s the sample:
namespace abi_vm = ABI::Windows::UI::ViewManagement; namespace wrl = Microsoft::WRL; namespace wf = Windows::Foundation; void GetAccentColor() { wrl::ComPtr<abi_vm::IUISettings> settings; wf::ActivateInstance(wrl::Wrappers::HStringReference( RuntimeClass_Windows_UI_ViewManagement_UISettings).Get(), &settings); ABI::Windows::UI::Color color; settings->GetColorValue(abi_vm::UIColorType::Accent, &color); }
OK; it looks manageable, except in FPC, we don’t have access to any of those namespace compiler definitions. Which are leveraged for everything. So this isn’t going to be straightforward. Someone else must have solved this problem though, right? If not for FPC, then for Delphi? After an hour of Googling… no, I don’t think the source code exists for this in Pascal anywhere on the internet.
Breaking it down
Let’s look at what this code actually does, then we can hopefully rewrite it in FPC, step-by-step:
- Define a COM interface to UISettings
- Figure out what that HStringReference wrapper does
- Call a function called ActivateInstance()
- Call UISettings.GetColorValue() to obtain colour
- Tidying up
Allright. I’ve used COM before. So let’s get started! I’ll go into a lot of detail, because there’s a good chance there’s someone out there that wants to take this code and port it to yet another language that has no code sample! For those that just want the results, download the source code at the end.
1. Define a COM interface to UISettings
First problem with this: I don’t know what the UISettings interface looks like, and I’m going to need to write a complete definition of the interface (as well as the GUID) in order to use it correctly. There’s no real way to ‘discover’ this, either. You just have to search for an existing definition in any language, and translate it. Fortunately, there’s a handy definition available on Github. From there, it was relatively straightforward to translate it into the Pascal dialect. But first! IUISettings derives from IInspectable, which seems to be common to all of the WinRT APIs. So we have to translate that first:
type IInspectable = interface (IUnknown) ['{AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90}'] function GetIIDs (out iidCount: Cardinal; out IIDs: PGUID): HRESULT; stdcall; function GetRuntimeClassName (out ClassName: HSTRING): HRESULT; stdcall; function GetTrustLevel (out Trust: TrustLevel): HRESULT; stdcall; end;
And you can see from there, we also need definitions for HSTRING, and for an enum called TrustLevel (while we’re here, I’ll add other types we need later too):
type HSTRING = type THandle; TrustLevel = (BaseTrust, PartialTrust, FullTrust); PCNZWCH = PWideChar; RO_INIT_TYPE = (RO_INIT_SINGLETHREADED, RO_INIT_MULTITHREADED); TUIColorType = (Background=0,Foreground=1,AccentDark3=2,AccentDark2=3,AccentDark1=4,Accent=5,AccentLight1=6,AccentLight2=7,AccentLight3=8,Complement=9,Force_Dword=$7fffffff);
Here, for the first time, I actually got lucky. The IUISettings interface contains a whole bunch of functions and new types (you can see them here on Github, starting at line 699), and normally to define the interface you need to translate all of them. However, Microsoft added extra functionality in newer versions of the interface, IUISettings2, IUISettings3 and IUISettings4, and it so happens that the only function we need GetColorValue, is in IUISettings3 – and it’s the only function in that interface! This makes translation trivial, and we now have all the definitions we need.
type IUISettings3 = interface (IInspectable) ['{03021BE4-5254-4781-8194-5168F7D06D7B}'] function GetColorValue (desiredColor: TUIColorType; out Value: UINT32): HRESULT; stdcall; end;
2. Figure out what that HStringReference wrapper does
Well, obviously it’s a wrapper for an HSTRING. What’s an HSTRING? It’s how the Windows Runtime manages strings (because there weren’t enough string types already). For FPC’s purposes, they’re equivalent to Handles. The function we need to call in step 3, ActivateInstance(), takes an HSTRING as it’s first parameter.
It turns out we can manipulate HSTRINGs using the WindowsCreateString() and WindowsDeleteString() functions in the Windows Runtime. Of course, FPC has no idea about HSTRINGs or about the Windows Runtime in general. Additionally, we don’t want to make our program dependent on the Windows Runtime (Windows 8+) unnecessarily – not being able to acquire the user’s accent color is a really poor reason to prevent an application from executing on earlier Windows versions. So we need to use dynamic linking. The key code you need to dynamically link against the Windows Runtime (for our purposes) is as follows:
const WINRTSTRING_DLL = 'api-ms-win-core-winrt-string-l1-1-0.dll'; type TWindowsCreateString = function (sourceString: PCNZWCH; Length: UINT32; out Str: HSTRING): HRESULT; stdcall; TWindowsDeleteString = function (Str: HSTRING): HRESULT; stdcall;
For space, I’ll leave out the usual calls to LoadLibrary and GetProcAddress; they’re included in the full downloadable source code at the end of this article. Now we have some functions we can use to obtain HSTRINGs from PWideChar strings (which are FPC-native), what do we actually populate the HSTRING with? What’s this – RuntimeClass_Windows_UI_ViewManagement_UISettings ?
OK, that one’s easy. It’s just a constant string:
const RunTimeClass_UISettings = WideString('Windows.UI.ViewManagement.UISettings')
3. Call a function called ActivateInstance()
Getting closer to the action… now we’ve got the HSTRING, and we’ve got the interfaces we want to populate. But what is this wf::ActivateInstance() call?
This function is defined in the Windows Runtime too, and it’s how we actually instantiate the UISettings interface. But before we do so, and similarly to how normal COM works, we need to initialize (and eventually uninitialize) the runtime itself before we can instantiate interfaces from it. As before, here are the types you need for dynamic linking:
const WINRTCORE_DLL = 'api-ms-win-core-winrt-l1-1-0.dll'; type TRoInitialize = function (InitType: RO_INIT_TYPE): HRESULT; stdcall; TRoUninitialize = procedure; stdcall; TRoActivateInstance = function (activatableClassId: HSTRING; out Instance: IInspectable): HRESULT; stdcall;
And finally, here is how we put it all together to finally obtain a usable IUISettings3 interface instance:
// ALL ERROR checking omitted - refer to source code at end of article! var Hstr : HSTRING; Inspectable : IInspectable; UISettings : IUISettings3; begin RoInitialize(RO_INIT_MULTITHREADED); WindowsCreateString(RunTimeClass_UISettings ,Length(RunTimeClass_UISettings),hstr); RoActivateInstance(hstr,Inspectable); UISettings := Inspectable as IUISettings3; // ..use our UISettings object! UISettings := nil; Inspectable := nil; WindowsDeleteString(hstr); RoUninitialize; // more on this below end;
4. Call UISettings.GetColorValue() to obtain colour
After all the setup work to get to this point, this part is trivially easy:
var color : dword; begin UISettings.GetColorValue(Accent,color); end;
Accent is the TUIColorType enum we defined earlier; if you’re lazy, you can replace this with the magic integer 5. Color is the same type as Windows’ GDI Color: a dword, in BRGA format.
5. Tidying up
If you’ve been following along, you should be able to build a working sample from the above code, but if you run it, it won’t be quite perfect. It’ll work, and you’ll get an actual color value, BUT your program will exception out with an Access Violation when your IUISettings interface variable leaves scope.
This is an unfortunate consequence of FPC not having a native understanding of the types we’re working with; even though we explicitly set UISettings and Inspectable to nil, the compiler still believes we’re holding a reference, and finalizes it when the variables leave scope. Unfortunately, if that’s *after* we’ve called RoUninitialize, we’ll take an access violation. I’ve tried various methods to try and figure out where FPC thinks the extra reference is, or alternatively to try and tell it *not* to be smart about finalization, but I’ve only found three methods that work reliably:
- Just don’t call RoUninitialize
- Make sure the variable’s scope ends before calling RoUninitialize
- Move RoInitialize and RoUninitialize to unit initialization/finalization seconds
Of the three, the second is my preferred option – and the way you’ll find it laid out in the attached source code. FPC doesn’t quite have the fine-grained scope control of C++, but you can simply wrap a nested procedure or function to do the same job. In pseudocode:
begin RoInitialize(RO_INIT_MULTITHREADED); DoActualWork; RoUninitialize; end. procedure DoActualWork; var UISettings: IUISettings3; begin // ... etc end;
Option 3 (using an FPC Unit’s initialization/finalization sections) works just fine too – and I’d probably use that if I was going to statically link to the WinRT DLLs; but with Dynamic Linking, I chose to keep everything self-contained within a function instead.
Side note: If you have a smarter way of trying to prevent FPC from generating the spurious finalization call, I’d love to hear it!
Source Code and demo
Because I couldn’t find an FPC version of this code anywhere else on the internet, I’ve made the smallest possible working code sample available too (tested with FPC 3.0.4). Here’s the source code and a compiled demo download. (18.6KB).