Read Preference values using CoreFoundation + Low Level API

Mac OSX specific forum
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Read Preference values using CoreFoundation + Low Level API

Post by Shardik »

Airr has posted a nice example for reading and writing Application
preferences using CoreFoundation API calls:
http://www.purebasic.fr/english/viewtop ... 19&t=37245

But I had to delve still a little bit deeper into the Preferences API
because a member of the German PureBasic Forum had asked me
via PM how to detect which keyboard layout is currently enabled.
On MacOS X you have the option to activate several keyboard
layouts by selecting "System Preferences/Language & Text" and
selecting the tab "Input sources". But how do you determine which
layouts a user has selected?

To solve this problem the first step was to locate where this information
is stored. It is stored in

/Users/YourUserName/Library/Preferences/ByHost/com.apple.HIToolbox.xxx.plist

The xxx denotes a cryptic code of your user name. It is not possible
to utilize the High Level API in Airr's code example because you can't
simply specify an application but you have to use Low Level API calls
and specify the constants for user name and host name and let the OS
find your specified key. For clarity's sake I have taken this snapshot of
my current keyboard layout plist:

Image

As you can see I have activated two keyboard layouts: "German" and
"USInternational PC" and I want to read out these values from the plist.
I had to program these steps:
1. Read out the key "AppleEnabledInputSources" (a CF array) and find out
how many array items (keyboard layouts) are defined.
2. Read out each defined item (as a CF dictionary object).
3. Read the key "KeyboardLayout Name" (a CF string) from each dictionary
and obtain the keyboard layout string.
4. Display the results.

One difficulty was to find out how the constants kCFPreferencesCurrentUser
and kCFPreferencesCurrentHost are defined. In CFPreferences.h they are
exported but I wasn't able to find out where and how they are defined until
I found the solution in the Apple Mailing lists:
Pete Gontier wrote:kCFPreferencesCurrentUser has a string value. I passed it to CFShow and it
turns out to be "kCFPreferencesCurrentUser".

Very tricky: the content of the constant is its own name... :lol:

Code: Select all

EnableExplicit

ImportC ""
  CFArrayGetCount(CFArrayRef.L)
  CFArrayGetTypeID()
  CFArrayGetValueAtIndex(CFArrayRef.L, Index.L)
  CFDictionaryGetTypeID()
  CFDictionaryGetValue(CFDictionaryRef.L, *KeyToFind)
  CFStringGetTypeID()
  CFGetTypeID(CFTypeRef.L)
  CFPreferencesCopyValue(CFKeyRef.L, CFApplicationIDRef, UserName.L, HostName.L)
  CFRelease(CFTypeRef.L)
  CFStringCreateWithCString(CFAllocatorRef.L, CString, CFStringEncoding.L)
  CFStringGetCString(CFStringRef.L, *StringBuffer, BufferSize.L, CFStringEncoding.L)
  KLGetCurrentKeyboardLayout(*KeyboardLayoutRef)
  KLGetKeyboardLayoutProperty(KeyboardLayoutRef.L, PropertyTag.L, *PropertyValue)
EndImport

#kKLName = 5

Procedure.S ConvertCFStringIntoString(CFStringRef.L)
  Protected String.S = Space(256)

  CFStringGetCString(CFStringRef, @String, Len(String), 0)

  ProcedureReturn Trim(String)
EndProcedure

Define AppIDRef.L
Define ArrayRef.L
Define DictionaryRef.L
Define HostNameRef.L
Define i.L
Define Info.S
Define ItemCount.L
Define KeyboardLayoutRef.L
Define KeyRef.L
Define LanguageNameRef.L
Define LayoutCount.L
Define LayoutList.S
Define StringRef.L
Define UserNameRef.L

; ----- Get current keyboard layout

If KLGetCurrentKeyboardLayout(@KeyboardLayoutRef) = 0
  If KLGetKeyboardLayoutProperty(KeyboardLayoutRef, #kKLName, @LanguageNameRef) = 0
    Info = "Current keyboard layout: " + ConvertCFStringIntoString(LanguageNameRef) + #CR$ + #CR$
  EndIf
EndIf

; ----- Get number of enabled keyboard layouts

KeyRef = CFStringCreateWithCString(0, @"AppleEnabledInputSources", 0)
AppIDRef = CFStringCreateWithCString(0, @"com.apple.HIToolbox", 0)
UserNameRef = CFStringCreateWithCString(0, @"kCFPreferencesCurrentUser", 0)
HostNameRef = CFStringCreateWithCString(0, @"kCFPreferencesCurrentHost", 0)
ArrayRef = CFPreferencesCopyValue(KeyRef, AppIDRef, UserNameRef, HostNameRef)

If ArrayRef
  If CFGetTypeID(ArrayRef) = CFArrayGetTypeID()
    ItemCount = CFArrayGetCount(ArrayRef)
  EndIf

  CFRelease(KeyRef)
  CFRelease(AppIDRef)
  CFRelease(UserNameRef)
  CFRelease(HostNameRef)
  CFRelease(ArrayRef)
EndIf

; ----- Get the name of each enabled keyboard layout

If ItemCount > 1
  For i = 0 To ItemCount - 1
    DictionaryRef = CFArrayGetValueAtIndex(ArrayRef, i)

    If CFGetTypeID(DictionaryRef) = CFDictionaryGetTypeID()
      KeyRef = CFStringCreateWithCString(0, @"KeyboardLayout Name", 0)
      StringRef = CFDictionaryGetValue(DictionaryRef, KeyRef)

      If StringRef
        If CFGetTypeID(StringRef) = CFStringGetTypeID()
          LayoutCount + 1
          LayoutList + Str(LayoutCount) + ". " + ConvertCFStringIntoString(StringRef) + #CR$
        EndIf

        CFRelease(StringRef)
      EndIf

      CFRelease(KeyRef)
    EndIf

    CFRelease(DictionaryRef)
  Next i
EndIf

Info = Info + "Enabled keyboard layouts: " + Str(LayoutCount) + #CR$ + LayoutList

MessageRequester("Preferences-Info", Info)
Update: I had to modify my code because I detected that not every array item
of "AppleEnabledInputSources" must have a key "KeyboardLayout Name"!