Skip to content

Commit

Permalink
Include “Remote” plugin in InjectionIII (johnno1962#218)
Browse files Browse the repository at this point in the history
* Update of SwiftTrace for Swift 5.2 runtime including tracing with values.

* Values for tracing of Objective-C methods.

* Remote control?

* Restore video recording (If QTKit is available)

* Tracing speed improvements

* SwiftTrace ready, looking at Remote

* Complete rework of Remote

* Remote good to go.

* A few final tweaks for testing.

* As available in 2.0 release candidate.

* Spinner while reading from network
  • Loading branch information
johnno1962 authored Apr 30, 2020
1 parent ad89bec commit afb27f8
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "SwiftTrace"]
path = SwiftTrace
url = https://github.com/johnno1962/SwiftTrace
[submodule "Remote"]
path = Remote
url = https://github.com/johnno1962/Remote.git
13 changes: 3 additions & 10 deletions InjectionBundle/InjectionClient.mm
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,11 @@ - (void)runInBackground {
case InjectionSigned:
[writer writeString:[self readString]];
break;
case InjectionTrace: {
void *handle = dlopen(NULL, RTLD_NOW);
void *main = dlsym(handle ?: RTLD_DEFAULT, "main");
Dl_info info;
if (main && dladdr(main, &info) && info.dli_fname) {
[SwiftTrace traceWithBundlePath:(int8_t *)info.dli_fname];
printf("💉 Tracing class' methods in: %s\n", info.dli_fname);
}
case InjectionTrace:
[SwiftTrace traceMainBundleWithSubLevels:0];
break;
}
case InjectionUntrace:
[SwiftTrace removeAllPatches];
[SwiftTrace removeAllTraces];
break;
case InjectionIdeProcPath: {
[SwiftEval sharedInstance].lastIdeProcPath = [self readString];
Expand Down
2 changes: 1 addition & 1 deletion InjectionBundle/SwiftEval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public class SwiftEval: NSObject {
let tmpfile = "\(tmpDir)/eval\(injectionNumber)"
let logfile = "\(tmpfile).log"

guard var (compileCommand, sourceFile) = try SwiftEval.compileByClass[classNameOrFile] ??
guard let (compileCommand, sourceFile) = try SwiftEval.compileByClass[classNameOrFile] ??
findCompileCommand(logsDir: logsDir, classNameOrFile: classNameOrFile, tmpfile: tmpfile) ??
SwiftEval.longTermCache[classNameOrFile].flatMap({ ($0 as! String, classNameOrFile) }) else {
throw evalError("""
Expand Down
68 changes: 66 additions & 2 deletions InjectionIII.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,28 @@
objects = {

/* Begin PBXBuildFile section */
BB34C3FD244FAAB400D520A9 /* RMWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB34C3FC244FAAB400D520A9 /* RMWindowController.xib */; };
BB34C3FF244FAD3C00D520A9 /* log.html in Resources */ = {isa = PBXBuildFile; fileRef = BB34C3FE244FAD3C00D520A9 /* log.html */; };
BB34C401244FAE3000D520A9 /* iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = BB34C400244FAE2F00D520A9 /* iphone.png */; };
BB439B801FABA64300B4F50B /* SwiftEvalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB439B7F1FABA64300B4F50B /* SwiftEvalTests.swift */; };
BB4EC3FB244FA3C40079E244 /* RMMacroManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BB4EC3F7244FA3C30079E244 /* RMMacroManager.m */; };
BB4EC3FC244FA3C40079E244 /* RMDeviceController.m in Sources */ = {isa = PBXBuildFile; fileRef = BB4EC3F8244FA3C30079E244 /* RMDeviceController.m */; };
BB4EC3FD244FA3C40079E244 /* RMImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = BB4EC3F9244FA3C40079E244 /* RMImageView.m */; };
BB4EC3FE244FA3C40079E244 /* RMWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = BB4EC3FA244FA3C40079E244 /* RMWindowController.m */; };
BB53B642244FC08F00F006E3 /* RemoteCapture.h in Resources */ = {isa = PBXBuildFile; fileRef = BB4EC3FF244FA42A0079E244 /* RemoteCapture.h */; };
BB53B648244FC32C00F006E3 /* RemoteHeaders.h in Resources */ = {isa = PBXBuildFile; fileRef = BB53B647244FC2F800F006E3 /* RemoteHeaders.h */; };
BB56393C1FD5C25A002FFCEF /* SignerService.m in Sources */ = {isa = PBXBuildFile; fileRef = BB67DBB61FB0D0F2000EAC8A /* SignerService.m */; };
BB5D67AD244DA00C003AA7F4 /* SwiftSwizzle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D67A7244DA00B003AA7F4 /* SwiftSwizzle.swift */; };
BB5D67AE244DA00C003AA7F4 /* SwiftArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D67A8244DA00B003AA7F4 /* SwiftArgs.swift */; };
BB5D67AF244DA00C003AA7F4 /* SwiftAspects.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D67A9244DA00B003AA7F4 /* SwiftAspects.swift */; };
BB5D67B0244DA00C003AA7F4 /* SwiftMeta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D67AA244DA00B003AA7F4 /* SwiftMeta.swift */; };
BB5D67B1244DA00C003AA7F4 /* SwiftInvoke.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D67AB244DA00C003AA7F4 /* SwiftInvoke.swift */; };
BB5D67B2244DA00C003AA7F4 /* SwiftStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D67AC244DA00C003AA7F4 /* SwiftStack.swift */; };
BB6306FE1FCD1A410021D30C /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = BB6306FD1FCD1A410021D30C /* Credits.rtf */; };
BB76EA0822929E1100E454F0 /* SwiftTrace.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB76EA0522929E1100E454F0 /* SwiftTrace.mm */; };
BB76EA0922929E1100E454F0 /* SwiftTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB76EA0622929E1100E454F0 /* SwiftTrace.swift */; };
BB76EA0A22929E1100E454F0 /* xt_forwarding_trampoline_x64.s in Sources */ = {isa = PBXBuildFile; fileRef = BB76EA0722929E1100E454F0 /* xt_forwarding_trampoline_x64.s */; };
BB85A7412452CD7600E94762 /* SwiftTrace.h in Resources */ = {isa = PBXBuildFile; fileRef = BB76EA0422929E0E00E454F0 /* SwiftTrace.h */; };
BB877CE42186EB6E0070EEC7 /* DDHotKeyCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = BB6306F51FCD16600021D30C /* DDHotKeyCenter.m */; };
BB877CE52186EB6E0070EEC7 /* DDHotKeyUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = BB6306F61FCD16600021D30C /* DDHotKeyUtilities.m */; };
BBB040641FB17A6C007DDD0A /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB040631FB1798A007DDD0A /* ScriptingBridge.framework */; };
Expand Down Expand Up @@ -86,6 +102,9 @@
BB1DEA9C1FD6A74900AF509F /* Xprobe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Xprobe.h; path = XprobePlugin/Classes/Xprobe.h; sourceTree = "<group>"; };
BB1DEA9D1FD6A74900AF509F /* XprobeConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XprobeConsole.h; path = XprobePlugin/Classes/XprobeConsole.h; sourceTree = "<group>"; };
BB1DEA9E1FD6A74900AF509F /* XprobePluginMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XprobePluginMenuController.h; path = XprobePlugin/Classes/XprobePluginMenuController.h; sourceTree = "<group>"; };
BB34C3FC244FAAB400D520A9 /* RMWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = RMWindowController.xib; path = Remote/Classes/RMWindowController.xib; sourceTree = SOURCE_ROOT; };
BB34C3FE244FAD3C00D520A9 /* log.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = log.html; path = Remote/log.html; sourceTree = SOURCE_ROOT; };
BB34C400244FAE2F00D520A9 /* iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = iphone.png; path = Remote/iphone.png; sourceTree = SOURCE_ROOT; };
BB439B681FABA64300B4F50B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
BB439B6A1FABA64300B4F50B /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = "<group>"; };
BB439B6C1FABA64300B4F50B /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
Expand All @@ -99,8 +118,21 @@
BB439B8A1FABA65D00B4F50B /* SwiftEval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftEval.swift; sourceTree = "<group>"; };
BB439BAC1FAC030E00B4F50B /* SwiftEval.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftEval.entitlements; sourceTree = "<group>"; };
BB439BB31FAC185200B4F50B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
BB4EC3F7244FA3C30079E244 /* RMMacroManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RMMacroManager.m; path = Remote/Classes/RMMacroManager.m; sourceTree = SOURCE_ROOT; };
BB4EC3F8244FA3C30079E244 /* RMDeviceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RMDeviceController.m; path = Remote/Classes/RMDeviceController.m; sourceTree = SOURCE_ROOT; };
BB4EC3F9244FA3C40079E244 /* RMImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RMImageView.m; path = Remote/Classes/RMImageView.m; sourceTree = SOURCE_ROOT; };
BB4EC3FA244FA3C40079E244 /* RMWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RMWindowController.m; path = Remote/Classes/RMWindowController.m; sourceTree = SOURCE_ROOT; };
BB4EC3FF244FA42A0079E244 /* RemoteCapture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RemoteCapture.h; path = Remote/Classes/RemoteCapture.h; sourceTree = SOURCE_ROOT; };
BB4EC400244FA4420079E244 /* RMWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RMWindowController.h; path = Remote/Classes/RMWindowController.h; sourceTree = SOURCE_ROOT; };
BB53B647244FC2F800F006E3 /* RemoteHeaders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RemoteHeaders.h; path = Remote/Classes/RemoteHeaders.h; sourceTree = SOURCE_ROOT; };
BB56393F1FD5D535002FFCEF /* Xprobe+Service.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "Xprobe+Service.mm"; path = "XprobePlugin/Classes/Xprobe+Service.mm"; sourceTree = "<group>"; };
BB5639401FD5D535002FFCEF /* Xtrace.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = Xtrace.mm; path = XprobePlugin/Classes/Xtrace.mm; sourceTree = "<group>"; };
BB5D67A7244DA00B003AA7F4 /* SwiftSwizzle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftSwizzle.swift; path = SwiftTrace/SwiftTrace/SwiftSwizzle.swift; sourceTree = SOURCE_ROOT; };
BB5D67A8244DA00B003AA7F4 /* SwiftArgs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftArgs.swift; path = SwiftTrace/SwiftTrace/SwiftArgs.swift; sourceTree = SOURCE_ROOT; };
BB5D67A9244DA00B003AA7F4 /* SwiftAspects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftAspects.swift; path = SwiftTrace/SwiftTrace/SwiftAspects.swift; sourceTree = SOURCE_ROOT; };
BB5D67AA244DA00B003AA7F4 /* SwiftMeta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftMeta.swift; path = SwiftTrace/SwiftTrace/SwiftMeta.swift; sourceTree = SOURCE_ROOT; };
BB5D67AB244DA00C003AA7F4 /* SwiftInvoke.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftInvoke.swift; path = SwiftTrace/SwiftTrace/SwiftInvoke.swift; sourceTree = SOURCE_ROOT; };
BB5D67AC244DA00C003AA7F4 /* SwiftStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftStack.swift; path = SwiftTrace/SwiftTrace/SwiftStack.swift; sourceTree = SOURCE_ROOT; };
BB6224411FC5B0E300AD7A3A /* HelperProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelperProxy.m; sourceTree = "<group>"; };
BB6224421FC5B0E500AD7A3A /* HelperProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelperProxy.h; sourceTree = "<group>"; };
BB6224431FC5B0E500AD7A3A /* HelperInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelperInstaller.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -367,6 +399,16 @@
BBCA020B1FB0F10400E45F0F /* InjectionIII.entitlements */,
BBA7BE0223473D660067124D /* build_bundles.sh */,
BBDF943C2349277A00334E08 /* InjectionIII-Bridging-Header.h */,
BB4EC3FF244FA42A0079E244 /* RemoteCapture.h */,
BB53B647244FC2F800F006E3 /* RemoteHeaders.h */,
BB4EC400244FA4420079E244 /* RMWindowController.h */,
BB4EC3FA244FA3C40079E244 /* RMWindowController.m */,
BB34C3FC244FAAB400D520A9 /* RMWindowController.xib */,
BB4EC3F8244FA3C30079E244 /* RMDeviceController.m */,
BB4EC3F7244FA3C30079E244 /* RMMacroManager.m */,
BB4EC3F9244FA3C40079E244 /* RMImageView.m */,
BB34C400244FAE2F00D520A9 /* iphone.png */,
BB34C3FE244FAD3C00D520A9 /* log.html */,
);
path = InjectionIII;
sourceTree = "<group>";
Expand All @@ -384,6 +426,12 @@
BB76EA0422929E0E00E454F0 /* SwiftTrace.h */,
BB76EA0522929E1100E454F0 /* SwiftTrace.mm */,
BB76EA0622929E1100E454F0 /* SwiftTrace.swift */,
BB5D67A7244DA00B003AA7F4 /* SwiftSwizzle.swift */,
BB5D67A9244DA00B003AA7F4 /* SwiftAspects.swift */,
BB5D67AB244DA00C003AA7F4 /* SwiftInvoke.swift */,
BB5D67A8244DA00B003AA7F4 /* SwiftArgs.swift */,
BB5D67AA244DA00B003AA7F4 /* SwiftMeta.swift */,
BB5D67AC244DA00C003AA7F4 /* SwiftStack.swift */,
BB76EA0722929E1100E454F0 /* xt_forwarding_trampoline_x64.s */,
);
path = InjectionBundle;
Expand Down Expand Up @@ -538,6 +586,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BB85A7412452CD7600E94762 /* SwiftTrace.h in Resources */,
BB53B648244FC32C00F006E3 /* RemoteHeaders.h in Resources */,
BB53B642244FC08F00F006E3 /* RemoteCapture.h in Resources */,
BBB64FDE1FD5744A0020BE47 /* XprobeConsole.xib in Resources */,
BBB64FDF1FD5744A0020BE47 /* XprobePluginMenuController.xib in Resources */,
BBB64DDC1FD570220020BE47 /* graph-xdot.gv in Resources */,
Expand All @@ -555,7 +606,10 @@
BB6306FE1FCD1A410021D30C /* Credits.rtf in Resources */,
BBCA02611FB122C300E45F0F /* InjectionOK.tif in Resources */,
BBCA02041FB0F10300E45F0F /* Assets.xcassets in Resources */,
BB34C3FF244FAD3C00D520A9 /* log.html in Resources */,
BBCA02071FB0F10300E45F0F /* MainMenu.xib in Resources */,
BB34C3FD244FAAB400D520A9 /* RMWindowController.xib in Resources */,
BB34C401244FAE3000D520A9 /* iphone.png in Resources */,
BBE490DB1FB2C643003D41BB /* InjectionBusy.tif in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -615,15 +669,19 @@
files = (
BB877CE42186EB6E0070EEC7 /* DDHotKeyCenter.m in Sources */,
BB877CE52186EB6E0070EEC7 /* DDHotKeyUtilities.m in Sources */,
BB4EC3FD244FA3C40079E244 /* RMImageView.m in Sources */,
BBB64FEA1FD583B60020BE47 /* Xprobe.mm in Sources */,
BBB64DD31FD56F570020BE47 /* XprobePluginMenuController.m in Sources */,
BBB64DD41FD56F570020BE47 /* XprobeConsole.m in Sources */,
BBCA022A1FB0F64800E45F0F /* SimpleSocket.mm in Sources */,
BB56393C1FD5C25A002FFCEF /* SignerService.m in Sources */,
BBEB704C1FD28C6F00127711 /* XcodeHash.m in Sources */,
BBCA02021FB0F10300E45F0F /* AppDelegate.swift in Sources */,
BB4EC3FE244FA3C40079E244 /* RMWindowController.m in Sources */,
BBE490D01FB2368A003D41BB /* FileWatcher.swift in Sources */,
BB4EC3FC244FA3C40079E244 /* RMDeviceController.m in Sources */,
BBCA02561FB1099500E45F0F /* InjectionServer.swift in Sources */,
BB4EC3FB244FA3C40079E244 /* RMMacroManager.m in Sources */,
BBBFD2D01FCFEE300057D899 /* SwiftEval.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -636,11 +694,17 @@
BB76EA0822929E1100E454F0 /* SwiftTrace.mm in Sources */,
BD35949E21A6C5DE0020EB94 /* Vaccine.swift in Sources */,
BBCA02511FB107AF00E45F0F /* SwiftInjection.swift in Sources */,
BB5D67B1244DA00C003AA7F4 /* SwiftInvoke.swift in Sources */,
BB5D67AF244DA00C003AA7F4 /* SwiftAspects.swift in Sources */,
BB5D67AE244DA00C003AA7F4 /* SwiftArgs.swift in Sources */,
BB76EA0A22929E1100E454F0 /* xt_forwarding_trampoline_x64.s in Sources */,
BBB64FE11FD575260020BE47 /* XprobeSwift.swift in Sources */,
BB5D67AD244DA00C003AA7F4 /* SwiftSwizzle.swift in Sources */,
BBB64FE61FD577EB0020BE47 /* SwiftSwizzler.swift in Sources */,
BBCA02621FB1312A00E45F0F /* SimpleSocket.mm in Sources */,
BB76EA0922929E1100E454F0 /* SwiftTrace.swift in Sources */,
BB5D67B0244DA00C003AA7F4 /* SwiftMeta.swift in Sources */,
BB5D67B2244DA00C003AA7F4 /* SwiftStack.swift in Sources */,
BBCA025D1FB114FF00E45F0F /* InjectionClient.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -863,7 +927,7 @@
INFOPLIST_FILE = InjectionIII/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 1.8;
MARKETING_VERSION = 2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionIII;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -886,7 +950,7 @@
INFOPLIST_FILE = InjectionIII/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 1.8;
MARKETING_VERSION = 2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionIII;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down

This file was deleted.

5 changes: 5 additions & 0 deletions InjectionIII/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ class AppDelegate : NSObject, NSApplicationDelegate {
self.lastConnection?.sendCommand(.vaccineSettingChanged, with:vaccineConfiguration())
}

@IBAction func startRemote(_ sender: NSMenuItem) {
RMWindowController.startServer(sender)
sender.state = .on
}

@IBAction func traceApp(_ sender: NSMenuItem) {
toggleState(sender)
self.lastConnection?.sendCommand(sender.state == NSControl.StateValue.on ?
Expand Down
Loading

0 comments on commit afb27f8

Please sign in to comment.