Skip to content

Instantly share code, notes, and snippets.

@nikolay-n
Last active October 29, 2022 14:26
Show Gist options
  • Save nikolay-n/359474898f61c3479e0be79e3b9957d3 to your computer and use it in GitHub Desktop.
Save nikolay-n/359474898f61c3479e0be79e3b9957d3 to your computer and use it in GitHub Desktop.
Defaults Monitor - tool to sniff defaults keys and values using unified log
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
'''
Defaults Monitor - tool to sniff defaults keys and values using unified log
to launch use standard python 2.7, eg python2.7 ./defsmon.py
'''
import os
import argparse
import sys
import tempfile as tmp
import re
import base64
import subprocess
import AppKit
import WebKit
import ctypes
import ctypes.util
import threading
import json
from PyObjCTools import AppHelper
from objc import _objc, nil, super, pyobjc_unicode, registerMetaDataForSelector
import Foundation
from Foundation import ( YES, NSBundle, NSLocale, NSLocalizedStringFromTableInBundle,
NSAutoreleasePool, NSURL)
################################################
# DEFINE TYPES, CONST, FUNCS AND LOAD LIBRARIES
################################################
# ApplicationServices
################################################
class ProcessSerialNumber(ctypes.Structure):
_fields_ = [
('highLongOfPSN', ctypes.c_uint32),
('lowLongOfPSN', ctypes.c_uint32),
]
kNoProcess = 0
kSystemProcess = 1
kCurrentProcess = 2
kProcessTransformToForegroundApplication = 1
kProcessTransformToBackgroundApplication = 2
kProcessTransformToUIElementAppication = 4
appSrvc = ctypes.CDLL('/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices')
appSrvc.TransformProcessType.argtypes = [ctypes.POINTER(ProcessSerialNumber), ctypes.c_uint32]
appSrvc.SetFrontProcess.argtypes = [ctypes.POINTER(ProcessSerialNumber)]
psn = ProcessSerialNumber(0, kCurrentProcess)
appSrvc.TransformProcessType(psn, kProcessTransformToForegroundApplication)
appSrvc.SetFrontProcess(psn)
################################################
# WebKit stuff
################################################
_eval_js_metadata = { 'arguments': { 3: { 'callable': { 'retval': { 'type': b'v' },
'arguments': { 0: { 'type': b'^v' }, 1: { 'type': b'@' }, 2: { 'type': b'@' }}}}}}
################################################
# Other constants
################################################
APP_NAME = "Defaults Monitor"
PROFILES_BIN = "/usr/bin/profiles"
# Following profile enables private data logging that allows us to get defaults values, without it there will be "<private>" values
# more info: https://georgegarside.com/blog/macos/sierra-console-private/
PROFILE_ID = "com.georgegarside.profile.logprivate"
PROFILE_FILE_NAME = "enable-unified-log-private-data.mobileconfig"
# Profile signed and contains binary characters -> base64
PROFILE_DATA = base64.b64decode("MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCAJIAEggUKPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+UGF5bG9hZENvbnRlbnQ8L2tleT4KCTxhcnJheT4KCQk8ZGljdD4KCQkJPGtleT5QYXlsb2FkRGlzcGxheU5hbWU8L2tleT4KCQkJPHN0cmluZz5NYW5hZ2VkQ2xpZW50IGxvZ2dpbmc8L3N0cmluZz4KCQkJPGtleT5QYXlsb2FkRW5hYmxlZDwva2V5PgoJCQk8dHJ1ZS8+CgkJCTxrZXk+UGF5bG9hZElkZW50aWZpZXI8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUubG9nZ2luZy5NYW5hZ2VkQ2xpZW50LjE8L3N0cmluZz4KCQkJPGtleT5QYXlsb2FkVHlwZTwva2V5PgoJCQk8c3RyaW5nPmNvbS5hcHBsZS5zeXN0ZW0ubG9nZ2luZzwvc3RyaW5nPgoJCQk8a2V5PlBheWxvYWRVVUlEPC9rZXk+CgkJCTxzdHJpbmc+RUQ1REUzMDctQTVGQy00MzRGLUFEODgtMTg3Njc3RjAyMjIyPC9zdHJpbmc+CgkJCTxrZXk+UGF5bG9hZFZlcnNpb248L2tleT4KCQkJPGludGVnZXI+MTwvaW50ZWdlcj4KCQkJPGtleT5TeXN0ZW08L2tleT4KCQkJPGRpY3Q+CgkJCQk8a2V5PkVuYWJsZS1Qcml2YXRlLURhdGE8L2tleT4KCQkJCTx0cnVlLz4KCQkJPC9kaWN0PgoJCTwvZGljdD4KCTwvYXJyYXk+Cgk8a2V5PlBheWxvYWREZXNjcmlwdGlvbjwva2V5PgoJPHN0cmluZz5TaG93ICZsdDtwcml2YXRlJmd0OyBsb2dzPC9zdHJpbmc+Cgk8a2V5PlBheWxvYWREaXNwbGF5TmFtZTwva2V5PgoJPHN0cmlu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")
LOG_BIN = "/usr/bin/log"
# The UI is web based because ¯\_(ツ)_/¯
HTML_UI = '''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Prefs Monitor</title>
<style type="text/css">
* {
font-family: "PT Sans";
font-size: 12pt;
}
html, body {
padding:0px;
margin:0px;
}
body {
width:100%;
height:100%;
overflow-y: auto;
background: #323232;
color:#fff;
}
#navbar {
position: fixed;
top:0px;
left: 0px;
width: 100%;
height: 40px;
background: #2d2d2d;
border-bottom: #111 solid 1px;
white-space: nowrap;
overflow: hidden;
-webkit-user-select: none;
user-select: none;
cursor: default;
}
#navbar ul {
padding: 0px;
margin: 0px;
list-style-type: none;
}
#navbar ul li{
padding: 0px 5px 0px;
margin: 0px;
display: block;
float: left;
line-height: 40px;
}
#navbar ul li input{
width:90px;
transition: width .2s ease-in-out;
}
#navbar ul li input.wide{
width:180px;
}
#navbar ul li.title{
font-weight: bold;
padding: 0px 15px 0px;
color: orange;
}
#navbar ul li.sep{
width: 15px;
}
#navbar ul li.clear{
float: right;
padding-right: 10px;
}
#clear{
text-decoration: none;
cursor: pointer;
display: inline-block;
width:20px;
height:20px;
background: #ffffff01;
}
#prefview {
padding-top:50px;
padding-left: 10px;
}
.proc-title{
color: #1bc0e5;
font-weight: bold;
}
.domain-title {
color: #e3c700;
font-weight: bold;
}
.pcont {
padding-left: 20px;
}
.has-value {
color: #56d523;
}
.no-value {
color: #d05428;
}
a.expand{
text-decoration: none;
color: #fff;
display: inline-block;
width:8px;
}
.pref-key {
display: inline-block;
background: transparent;
/*min-width: 30%;*/
}
.fade{
animation: fade 1s normal forwards;
}
@keyframes fade {
from {background-color: blue;}
to {background-color: #ffffff00;}
}
.pref-value{
color: #999;
}
.collapse .domain-container{
display: none;
}
.domain-container.collapse .pref-container{
display: none;
}
.proc-container, .domain-container{
padding-bottom: 5px;
padding-top: 5px;
border-bottom: #3a3a3a solid 1px;
}
.proc-container .domain-container{
border-bottom: none;
}
.domain-container {
padding-left: 0px;
padding-bottom: 10px;
}
.proc-container .domain-container{
padding-left: 20px;
padding-bottom: 5px;
padding-top: 5px;
}
.pref-container{
padding-top: 5px;
}
.hidden{
display: none;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', (event) => main());
function main(){
var filter = new Filter();
var navBar = new NavBar(document.querySelector("#navbar"), filter);
var prefDataView = new PrefDataView(document.querySelector("#prefview"), filter);
navBar.render();
var dq = window.DQ = new DataQueue(function(data){
prefDataView.render(data);
});
dq.state = true;
setTimeout(()=> postMsg("loaded"), 100);
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function postMsg(message){
window.webkit.messageHandlers.browserDelegate.postMessage(message);
}
class DataQueue{
constructor(cb){
this.queue = [];
this.cb = cb;
this.state = false;
this.interval = setInterval(this._queueLoop.bind(this), 0);
}
_queueLoop(){
if(!this.state)
return;
if(this.queue.length){
this.cb(this.queue.shift())
}
}
get state(){
return this._state;
}
set state(val){
this._state = val;
}
enqueue(data){
this.queue.push(data)
}
}
class Filter {
constructor() {
this.cbs = []
}
get group(){
return this._group
}
set group(val){
this._group = val;
this._onChange("group");
}
set clear(val){
this._onChange("clear");
}
get key(){
return this._key
}
set key(val){
this._key = val;
this._onChange("key");
}
get process(){
return this._process;
}
set process(val){
this._process = val;
this._onChange("process");
}
get domain(){
return this._domain;
}
set domain(val){
this._domain = val;
this._onChange("domain");
}
get values(){
return this._values;
}
set values(val){
this._values = val;
this._onChange("values");
}
_onChange(field){
this.cbs.forEach( cb => setTimeout(() => cb(field),0));
}
registerCallback(cb){
this.cbs.push(cb)
}
unregisterCallback(cb){
this.cbs = this.cbs.filter( c => c != cb)
}
}
class NavBar {
constructor(el, filter) {
this.el = el;
this.filter = filter;
this.groupSelectEl = el.querySelector("#group")
this.procFilterEl = el.querySelector("#procname")
this.domainFilterEl = el.querySelector("#domain")
this.keyFilterEl = el.querySelector("#key")
this.valueFilterEl = el.querySelector("#valvis")
this.clearEl = el.querySelector("#clear")
}
render(data){
self = this
this.groupSelectEl.addEventListener("change", function () {
self.filter.group = this.value;
if(this.value == "process"){
self.procFilterEl.classList.remove("hidden");
}else{
self.procFilterEl.classList.add("hidden");
}
});
this.groupSelectEl.dispatchEvent(new Event("change"));
this.procFilterEl.addEventListener("input", function () {
self.filter.process = this.value;
});
this.domainFilterEl.addEventListener("input", function () {
self.filter.domain = this.value;
});
this.keyFilterEl.addEventListener("input", function () {
self.filter.key = this.value;
});
this.valueFilterEl.addEventListener("change", function () {
self.filter.values = this.value;
});
this.clearEl.addEventListener("click", function () {
self.filter.clear = true;
});
[this.procFilterEl, this.domainFilterEl, this.keyFilterEl].forEach(input => {
input.setAttribute('autocomplete', 'off')
input.setAttribute('autocorrect', 'off')
input.setAttribute('autocapitalize', 'off')
input.setAttribute('spellcheck', false)
input.addEventListener("focus", () => input.classList.add("wide"));
input.addEventListener("blur", () => input.classList.remove("wide"));
})
}
}
class PrefDataView {
constructor(el, filter) {
this.el = el;
this.procGroupEl = document.createElement("div");
this.el.appendChild(this.procGroupEl);
this.domGroupEl = document.createElement("div");
this.domGroupEl.classList.add("hidden");
this.el.appendChild(this.domGroupEl);
this.filter = filter;
this.filter.registerCallback(this.render.bind(this));
this.cache = [];
this.tplCache = {};
this.procs = {};
this.domains = {};
}
renderTpl(tpl, data){
var tplContent = this.tplCache[tpl];
if(!tplContent){
tplContent = document.querySelector("#"+tpl).innerHTML;
this.tplCache[tpl] = tplContent;
}
for (var k in data){
if (data.hasOwnProperty(k)) {
tplContent = tplContent.replace(new RegExp(`{%${k}%}`,"gi"), data[k]);
}
}
var elem = document.createElement("div");
elem.innerHTML = tplContent.trim();
return elem.firstChild;
}
setupExpand(el, expand){
var expEl = el.querySelector(".expand");
expEl.addEventListener("click", function(){
var clsLs = expEl.parentNode.parentNode.classList;
var sym = clsLs.contains("collapse")? "-": "+";
clsLs.toggle("collapse");
expEl.innerHTML = sym;
});
}
_insertBeforePref(data, prefs, el, parentEl){
var keys = Object.keys(prefs);
keys.push(data.key);
keys.sort();
var idx = keys.indexOf(data.key);
if(keys.length == 1 || (idx + 1) == keys.length){
parentEl.appendChild(el);
}else{
var afterEl = prefs[keys[idx+1]]["el"];
parentEl.insertBefore(el, afterEl);
}
}
render(data) {
var self = this;
var dataType= typeof(data);
if(dataType == "object") {
var proc = this.procs[data.process];
if (!proc){
var procEl = this.renderTpl("tpl-proc", {"proc": data.process, "proc_path": data.processPath});
this.procGroupEl.appendChild(procEl);
this.setupExpand(procEl);
proc = {
"el": procEl,
"expand": true,
"domains": {}
};
this.procs[data.process] = proc;
}
var domains = [[proc["el"], proc["domains"]], [this.domGroupEl, this.domains]];
domains.forEach(function (el) {
var parentEl = el[0];
var domains = el[1];
var domain = domains[data.domain];
if (!domain){
var domainEl = self.renderTpl("tpl-domain", {"domain": data.domain});
var prefsContEl = domainEl.querySelector(".pref-container")
parentEl.appendChild(domainEl);
self.setupExpand(domainEl);
var domain_data = {
"el": domainEl,
"prefsEl": prefsContEl,
"expand": true,
"prefs": {}
};
domains[data.domain] = domain_data;
}
});
[proc["domains"][data.domain], this.domains[data.domain]].forEach(function (domain) {
var pref = domain["prefs"][data.key];
if (!pref){
var prefEl = self.renderTpl("tpl-kv", {
"key": data.key,
"value": data.value == null ? "&lt;no value&gt;": escapeHtml(data.value),
"key_cls": data.value == null ? "no-value": "has-value"
});
pref = {
"el": prefEl,
"value": data.value
};
self._insertBeforePref(data, domain["prefs"], prefEl, domain["prefsEl"])
domain["prefs"][data.key] = pref;
prefEl.querySelector(".pref-key").addEventListener('animationend', function() {
this.classList.toggle("fade");
});
}
})
self.render("");
} else if (dataType == "string"){
if (data == "clear"){
this.procs = {};
this.domains = {};
this.procGroupEl.innerHTML = "";
this.domGroupEl.innerHTML = "";
// filter event occurred
}else if(data == "group"){
var el = [this.procGroupEl, this.domGroupEl];
if (this.filter.group == "domain"){
el = el.reverse();
}
el[0].classList.remove("hidden");
el[1].classList.add("hidden");
} else {
var procs = Object.keys(this.procs);
const domProc= '_domain_proc_';
procs.push(domProc);
for (var p of procs.values()){
var isDomainProc = p == domProc? true:false;
if (this.procs.hasOwnProperty(p) || isDomainProc) {
var domains = isDomainProc ? this.domains: this.procs[p]["domains"];
if(!isDomainProc){
var rx = new RegExp("_no_match_");
try {
rx = new RegExp(this.filter.process,"i");
} catch (error) {
//console.error(error);
}
if(rx.test(p)){
this.procs[p]["el"].classList.remove("hidden");
}else{
this.procs[p]["el"].classList.add("hidden");
continue;
}
}
var visibleDomains = 0;
for (var d in domains){
if (domains.hasOwnProperty(d)) {
var rx = new RegExp("_no_match_");
try {
rx = new RegExp(this.filter.domain,"i");
} catch (error) {
//console.error(error);
}
if(rx.test(d)){
domains[d]["el"].classList.remove("hidden");
visibleDomains++;
}else{
domains[d]["el"].classList.add("hidden");
continue;
}
var prefs = domains[d]["prefs"];
var visiblePrefs = 0;
for (var pref in prefs){
if (prefs.hasOwnProperty(pref)) {
var rx = new RegExp("_no_match_");
try {
rx = new RegExp(this.filter.key,"i");
} catch (error) {
//console.error(error);
}
var hideByValue = false;
if (this.filter.values == "value")
hideByValue = prefs[pref]["value"] == null? true:false;
else if (this.filter.values == "null")
hideByValue = prefs[pref]["value"] == null? false:true;
if(rx.test(pref) && !hideByValue){
prefs[pref]["el"].classList.remove("hidden");
visiblePrefs++;
}else{
prefs[pref]["el"].classList.add("hidden");
continue;
}
}
}
if(!visiblePrefs){
domains[d]["el"].classList.add("hidden");
visibleDomains--;
}
}
}
if(!isDomainProc){
if(visibleDomains){
this.procs[p]["el"].classList.remove("hidden");
}else{
this.procs[p]["el"].classList.add("hidden");
}
}
}
}
}
}
}
}
</script>
</head>
<body>
<div id="navbar">
<ul>
<li class="title">&#9881; Defaults Monitor</li>
<li class="sep"></li>
<li>group by:</li>
<li>
<select id="group">
<option value="process">process</option>
<option value="domain">domain</option>
</select>
</li>
<li>filter by:</li>
<li><input placeholder="Process: Finder" type="search" id="procname"></li>
<li><input placeholder="Domain: com.apple.Finder" type="search" id="domain"></li>
<li><input placeholder="Key: ShowSidebar" type="search" id="key"></li>
<li>values:</li>
<li>
<select id="valvis">
<option value="all">All</option>
<option value="value">Has value</option>
<option value="null">No value</option>
</select>
</li>
<li class="clear"><a id="clear" title="Clear All" href="javascript:void(0)">&#128465;</a></li>
</ul>
</div>
<div id="prefview"></div>
<script id="tpl-proc" type="text/x-template">
<div class="proc-container">
<div class="proc-title" title="{%PROC_PATH%}"><a href="javascript:void(0)" class="expand">-</a>&nbsp;{%PROC%}</div>
</div>
</script>
<script id="tpl-domain" type="text/x-template">
<div class="domain-container pcont">
<div class="domain-title"><a href="javascript:void(0)" class="expand">-</a>&nbsp;{%DOMAIN%}</div>
<div class="pref-container pcont"></div>
</div>
</script>
<script id="tpl-kv" type="text/x-template">
<div class="pref-kv"><span class="pref-key fade {%KEY_CLS%}">{%KEY%}</span> = <span class="pref-value">{%VALUE%}</span></div>
</script>
</body>
</html>'''
################################################
# Main app stuff
################################################
class LogStreamReader:
def __init__(self, cb):
self.cb = cb
def _thread(self):
args = [LOG_BIN, "stream", "--style", "json","--predicate", 'category=="User Defaults"', "--info", "--debug"]
f = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
event_json = ""
while True:
line = f.stdout.readline()
if line.startswith("[{"):
event_json = "{\n";
elif line.startswith("},{"):
event_json += "}"
self._process_event(event_json)
event_json = "{\n";
else:
event_json += line
def _process_event(self, event_json):
try:
event_raw = json.loads(event_json)
event = dict()
matches = re.findall(r"looked up value (.+?) for key (.+?) in .*\(Domain: (.*?),", event_raw["eventMessage"], flags=re.DOTALL)
if matches:
event["value"] = matches[0][0]
event["key"] = matches[0][1]
event["domain"] = matches[0][2]
else:
matches = re.findall(r"found no value for key (.+?) in .*\(Domain: (.*?),", event_raw["eventMessage"], flags=re.DOTALL)
if matches:
event["value"] = None
event["key"] = matches[0][0]
event["domain"] = matches[0][1]
if "domain" not in event:
return
event["process"] = os.path.basename(event_raw["processImagePath"])
event["processPath"] = event_raw["processImagePath"]
event["library"] = os.path.basename(event_raw["senderImagePath"])
event["libraryPath"] = event_raw["senderImagePath"]
self.cb(event)
except Exception as e:
pass
def start(self):
self.thread = threading.Thread(target=self._thread)
self.thread.start()
# self._thread()
class DefaultsMonitorApp(AppKit.NSApplication):
sharedApp = None
bundle = None
def finishLaunching(self):
super(DefaultsMonitorApp, self).finishLaunching()
self._window = None
self._webkit = None
self._logStream = LogStreamReader(self._onLogEvent)
self._checkProfileInstalled( lambda: self._createWebkitWindow())
def sendEvent_(self, event):
super(DefaultsMonitorApp, self).sendEvent_(event)
def _checkProfileInstalled(self, cb):
output = subprocess.check_output([PROFILES_BIN, "show", PROFILE_ID]).decode()
if "There are no configuration profiles installed" in output:
if self._confirm("To display configuration values need to enable PRIVATE DATA, this can be achieved by installing configuration profile. \n\nSystem Preferences app will be launched. After installation close System Preferences to continue. \n\nDo you want to install profile?"):
def _install():
tmp_path = tmp.mktemp(suffix=".mobileconfig")
with open(tmp_path, "w+b") as t:
t.write(PROFILE_DATA)
print("!!! Close System Preferences to continue")
subprocess.check_call(["open","-W", tmp_path])
os.unlink(tmp_path)
cb()
AppHelper.callAfter(_install);
return
cb()
def _alert(self, message):
AppKit.NSRunningApplication.currentApplication().activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps)
alert = AppKit.NSAlert.alloc().init()
alert.setInformativeText_(message)
alert.runModal()
def _confirm(self, message, modal=True):
AppKit.NSApplication.sharedApplication()
AppKit.NSRunningApplication.currentApplication().activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps)
alert = AppKit.NSAlert.alloc().init()
alert.addButtonWithTitle_("Yes")
alert.addButtonWithTitle_("No")
alert.setMessageText_(message)
alert.setAlertStyle_(AppKit.NSWarningAlertStyle)
if alert.runModal() == AppKit.NSAlertFirstButtonReturn:
return True
else:
return False
def _createWebkitWindow(self):
app = self
class MainWindow(AppKit.NSWindow):
def canBecomeKeyWindow(self):
return True
def canBecomeMainWindow(self):
return True
class WindowDelegate(AppKit.NSObject):
#def windowShouldClose_(self, window):
# return Foundation.YES
def windowWillClose_(self, notification):
app._terminate()
rect = AppKit.NSMakeRect(0.0, 0.0, 1010, 500)
window_mask = AppKit.NSTitledWindowMask | AppKit.NSClosableWindowMask | AppKit.NSMiniaturizableWindowMask | AppKit.NSResizableWindowMask
window = MainWindow.alloc().\
initWithContentRect_styleMask_backing_defer_(rect, window_mask, AppKit.NSBackingStoreBuffered, False).retain()
window.setTitle_(APP_NAME)
window.setMinSize_(AppKit.NSSize(1010, 200))
# window.setAnimationBehavior_(AppKit.NSWindowAnimationBehaviorNone)
frame = window.frame()
frame.size.width = rect.size.width
frame.size.height = rect.size.height
class WebKitHost(WebKit.WKWebView):
def performKeyEquivalent_(self, theEvent):
return True
webkit = WebKitHost.alloc().initWithFrame_(rect).retain()
# webkit.setCustomUserAgent_(user_agent)
window.setOpaque_(True)
window.setHasShadow_(True)
# window.setBackgroundColor_(AppKit.NSColor.colorWithSRGBRed_green_blue_alpha_(100, 100, 100, 0))
# webkit.setValue_forKey_(True, 'drawsTransparentBackground')
window.setAlphaValue_(0.0)
# window.setLevel_(AppKit.NSStatusWindowLevel)
try:
webkit.evaluateJavaScript_completionHandler_('', lambda a, b: None)
except TypeError:
registerMetaDataForSelector(b'WKWebView', b'evaluateJavaScript:completionHandler:', _eval_js_metadata)
config = webkit.configuration()
try:
config.preferences().setValue_forKey_(Foundation.NO, 'backspaceKeyNavigationEnabled')
config.preferences().setValue_forKey_(Foundation.YES, 'developerExtrasEnabled')
except Exception as e:
pass
url = NSURL.URLWithString_("")
webkit.loadHTMLString_baseURL_(HTML_UI, url)
window.setContentView_(webkit)
class BrowserDelegate(AppKit.NSObject):
def userContentController_didReceiveScriptMessage_(self, controller, message):
action = message.body()
if action == "loaded":
window.makeFirstResponder_(webkit)
window.makeKeyAndOrderFront_(window)
app.currentApp.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps)
window.setAlphaValue_(1.0)
window.center()
app._logStream.start()
browserDelegate = BrowserDelegate.alloc().init().retain()
config.userContentController().addScriptMessageHandler_name_(browserDelegate, 'browserDelegate')
windowDelegate = WindowDelegate.alloc().init().retain()
window.setDelegate_(windowDelegate)
self._window = window
self._webkit = webkit
def _onLogEvent(self, event):
if self._webkit:
def _event():
json_str = json.dumps(event)
self._webkit.evaluateJavaScript_completionHandler_('window.DQ.enqueue({})'.format(json_str), lambda a,b: None)
AppHelper.callAfter(_event)
def _terminate(self):
AppHelper.stopEventLoop()
@classmethod
def run(cls, args):
cls.bundle = AppKit.NSBundle.mainBundle()
info = cls.bundle.localizedInfoDictionary() or cls.bundle.infoDictionary()
info['CFBundleName'] = APP_NAME
info['NSAppTransportSecurity'] = {'NSAllowsArbitraryLoads': Foundation.YES}
info['NSRequiresAquaSystemAppearance'] = Foundation.NO
cls.sharedApp = DefaultsMonitorApp.sharedApplication()
cls.currentApp = AppKit.NSRunningApplication.currentApplication()
# hide frod Dock
# cls.sharedApp.setActivationPolicy_(AppKit.NSApplicationActivationPolicyAccessory)
AppHelper.runEventLoop()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
DefaultsMonitorApp.run(parser.parse_args())
@kaunteya
Copy link

kaunteya commented Dec 8, 2020

Shows error

Traceback (most recent call last):
  File "./defaults.py", line 17, in <module>
    import AppKit
ImportError: No module named AppKit

@nikolay-n
Copy link
Author

nikolay-n commented Dec 8, 2020

Shows error

Traceback (most recent call last):
  File "./defaults.py", line 17, in <module>
    import AppKit
ImportError: No module named AppKit

@kaunteya Check previous comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment