Last active
October 29, 2022 14:26
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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, "&") | |
.replace(/</g, "<") | |
.replace(/>/g, ">") | |
.replace(/"/g, """) | |
.replace(/'/g, "'"); | |
} | |
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 ? "<no value>": 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">⚙ 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)">🗑</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> {%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> {%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()) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@kaunteya Check previous comment