import typing
from collections import UserDict
import IPython
from pygments import formatters, highlight, lexers
from rpcclient.allocated import Allocated
from rpcclient.exceptions import RpcClientException
SHELL_USAGE = """
# Welcome to SCPreference plist interactive editor!
# Please use the `d` global in order to make changes to the current plist.
# Use: `d.commit()` to save your changes when done.
#
# For example, consider the following:
# view current plist
print(d)
# modify an item
d['item1'] = 5
# update
d.commit()
# That's it! hope you had a pleasant ride! 👋
"""
[docs]
class SCPreference(Allocated):
def __init__(self, client, preferences_id: str, ref):
super().__init__()
self._client = client
self._ref = ref
self._preferences_id = preferences_id
@property
def keys(self) -> typing.List[str]:
""" wrapper for SCPreferencesCopyKeyList """
return self._client.symbols.SCPreferencesCopyKeyList(self._ref).py()
def _set(self, key: str, value):
""" wrapper for SCPreferencesSetValue """
if not self._client.symbols.SCPreferencesSetValue(self._ref, self._client.cf(key), self._client.cf(value)):
raise RpcClientException(f'SCPreferencesSetValue failed to set: {key}')
[docs]
def set(self, key: str, value):
""" set key:value and commit the change """
self._set(key, value)
self._commit()
[docs]
def set_dict(self, d: typing.Mapping):
""" set the entire preference dictionary (clear if already exists) and commit the change """
self._clear()
self._update_dict(d)
self._commit()
def _update_dict(self, d: typing.Mapping):
""" update preference dictionary """
for k, v in d.items():
self._set(k, v)
[docs]
def update_dict(self, d: typing.Mapping):
""" update preference dictionary and commit """
self._update_dict(d)
self._commit()
def _remove(self, key: str):
""" wrapper for SCPreferencesRemoveValue """
if not self._client.symbols.SCPreferencesRemoveValue(self._ref, self._client.cf(key)):
raise RpcClientException(f'SCPreferencesRemoveValue failed to remove: {key}')
[docs]
def remove(self, key: str):
""" remove given key and commit """
self._remove(key)
self._commit()
[docs]
def get(self, key: str):
""" wrapper for SCPreferencesGetValue """
return self._client.symbols.SCPreferencesGetValue(self._ref, self._client.cf(key)).py()
[docs]
def get_dict(self) -> typing.Mapping:
""" get a dictionary representation """
result = {}
for k in self.keys:
result[k] = self.get(k)
return result
[docs]
def interactive(self):
""" open an interactive IPython shell for viewing and editing """
plist = Plist(self)
IPython.embed(
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.TerminalTrueColorFormatter(style='native')),
user_ns={
'd': plist,
})
def _clear(self):
""" clear dictionary """
for k in self.keys:
self._remove(k)
[docs]
def clear(self):
""" clear dictionary and commit """
self._clear()
self._commit()
def _deallocate(self):
""" free the preference object """
self._client.symbols.CFRelease(self._ref)
def _commit(self):
""" commit all changes """
if not self._client.symbols.SCPreferencesCommitChanges(self._ref):
raise RpcClientException('SCPreferencesCommitChanges failed')
self._client.symbols.SCPreferencesSynchronize(self._ref)
def __repr__(self):
return f'<{self.__class__.__name__} NAME:{self._preferences_id}>'
[docs]
class Plist(UserDict):
def __init__(self, preference: SCPreference):
super().__init__(preference.get_dict())
self._preference = preference
[docs]
def commit(self):
self._preference.set_dict(self)
[docs]
class SCPreferences:
"""
API to the SCPreferences* functions - preferences managed by SystemConfiguration framework.
https://developer.apple.com/documentation/systemconfiguration/scpreferences?language=objc
"""
def __init__(self, client):
"""
:param rpcclient.darwin.client.DarwinClient client:
"""
self._client = client
[docs]
def open(self, preferences_id: str) -> SCPreference:
""" get an SCPreference from a given preferences_id """
ref = self._client.symbols.SCPreferencesCreate(0, self._client.cf('rpcserver'), self._client.cf(preferences_id))
if not ref:
raise RpcClientException(f'SCPreferencesCreate failed for: {preferences_id}')
return SCPreference(self._client, preferences_id, ref)
[docs]
def get_keys(self, preferences_id: str) -> typing.List[str]:
""" get all keys from given preferences_id """
with self.open(preferences_id) as o:
return o.keys
[docs]
def get_dict(self, preferences_id: str):
""" get dict from given preferences_id """
with self.open(preferences_id) as o:
return o.get_dict()