├── .gitignore ├── LICENSE ├── LaunchRocket.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── LaunchRocket.xcscheme ├── LaunchRocket ├── FlippedView.h ├── FlippedView.m ├── HorizontalDivider.h ├── HorizontalDivider.m ├── LaunchRocket-Info.plist ├── LaunchRocket-Prefix.pch ├── LaunchRocket.h ├── LaunchRocket.m ├── LaunchRocket.tiff ├── OpenSans-Light.ttf ├── OpenSansFont.h ├── OpenSansFont.m ├── Process.h ├── Process.m ├── Service.h ├── Service.m ├── ServiceController.h ├── ServiceController.m ├── ServiceManager.h ├── ServiceManager.m ├── en.lproj │ ├── InfoPlist.strings │ └── LaunchRocket.xib ├── gray.png ├── green.png ├── red.png ├── rocket.png ├── special-plists.plist ├── sudo.app │ └── Contents │ │ ├── Info.plist │ │ ├── MacOS │ │ └── applet │ │ ├── PkgInfo │ │ └── Resources │ │ ├── Scripts │ │ └── main.scpt │ │ ├── applet.icns │ │ ├── applet.rsrc │ │ └── description.rtfd │ │ └── TXT.rtf └── yellow.png ├── README.md ├── applescript └── sudo.scpt └── screenshots └── LaunchRocket.png /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | 3 | # Exclude temp nibs and swap files 4 | *~.nib 5 | *.swp 6 | 7 | # Exclude OS X folder attributes 8 | .DS_Store 9 | 10 | # Exclude user-specific XCode 3 and 4 files 11 | *.mode1 12 | *.mode1v3 13 | *.mode2v3 14 | *.perspective 15 | *.perspectivev3 16 | *.pbxuser 17 | *.xcworkspace 18 | xcuserdata 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) <2014> Josh Butts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /LaunchRocket.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 03233D5F17054544009727CB /* ServiceController.h in Headers */ = {isa = PBXBuildFile; fileRef = 03233D5D17054544009727CB /* ServiceController.h */; }; 11 | 03233D6017054544009727CB /* ServiceController.m in Sources */ = {isa = PBXBuildFile; fileRef = 03233D5E17054544009727CB /* ServiceController.m */; }; 12 | 0351F8731705543600CE9495 /* ServiceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 0351F8711705543600CE9495 /* ServiceManager.h */; }; 13 | 0351F8741705543600CE9495 /* ServiceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0351F8721705543600CE9495 /* ServiceManager.m */; }; 14 | 039EEF8A189A1E1E00B5F169 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 039EEF89189A1E1E00B5F169 /* ServiceManagement.framework */; }; 15 | 039F63D918920EF2004C6696 /* OpenSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 039F63D818920EF2004C6696 /* OpenSans-Light.ttf */; }; 16 | 039F63DB18921F19004C6696 /* rocket.png in Resources */ = {isa = PBXBuildFile; fileRef = 039F63DA18921F19004C6696 /* rocket.png */; }; 17 | 039F63E51892220F004C6696 /* red.png in Resources */ = {isa = PBXBuildFile; fileRef = 039F63E21892220F004C6696 /* red.png */; }; 18 | 039F63E61892220F004C6696 /* green.png in Resources */ = {isa = PBXBuildFile; fileRef = 039F63E31892220F004C6696 /* green.png */; }; 19 | 039F63E71892220F004C6696 /* yellow.png in Resources */ = {isa = PBXBuildFile; fileRef = 039F63E41892220F004C6696 /* yellow.png */; }; 20 | 039F63EA18922D1F004C6696 /* OpenSansFont.h in Headers */ = {isa = PBXBuildFile; fileRef = 039F63E818922D1F004C6696 /* OpenSansFont.h */; }; 21 | 039F63EB18922D1F004C6696 /* OpenSansFont.m in Sources */ = {isa = PBXBuildFile; fileRef = 039F63E918922D1F004C6696 /* OpenSansFont.m */; }; 22 | 039F63EE18923821004C6696 /* HorizontalDivider.h in Headers */ = {isa = PBXBuildFile; fileRef = 039F63EC18923821004C6696 /* HorizontalDivider.h */; }; 23 | 039F63EF18923821004C6696 /* HorizontalDivider.m in Sources */ = {isa = PBXBuildFile; fileRef = 039F63ED18923821004C6696 /* HorizontalDivider.m */; }; 24 | 039F63F118923AA8004C6696 /* gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 039F63F018923AA8004C6696 /* gray.png */; }; 25 | 03A8692B189327D900ED639C /* Process.h in Headers */ = {isa = PBXBuildFile; fileRef = 03A86929189327D900ED639C /* Process.h */; }; 26 | 03A8692C189327D900ED639C /* Process.m in Sources */ = {isa = PBXBuildFile; fileRef = 03A8692A189327D900ED639C /* Process.m */; }; 27 | 03C5C20618AAC02B00FB25EF /* special-plists.plist in Resources */ = {isa = PBXBuildFile; fileRef = 03C5C20518AAC02B00FB25EF /* special-plists.plist */; }; 28 | 03D25AA318A35FEF007B253F /* sudo.app in Resources */ = {isa = PBXBuildFile; fileRef = 03D25AA218A35FEF007B253F /* sudo.app */; }; 29 | 03EE9DA216FFD39900E09197 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03EE9DA116FFD39900E09197 /* Cocoa.framework */; }; 30 | 03EE9DA416FFD39900E09197 /* PreferencePanes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03EE9DA316FFD39900E09197 /* PreferencePanes.framework */; }; 31 | 03EE9DAE16FFD39900E09197 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 03EE9DAC16FFD39900E09197 /* InfoPlist.strings */; }; 32 | 03EE9DB216FFD39900E09197 /* LaunchRocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 03EE9DB116FFD39900E09197 /* LaunchRocket.m */; }; 33 | 03EE9DB716FFD39900E09197 /* LaunchRocket.xib in Resources */ = {isa = PBXBuildFile; fileRef = 03EE9DB516FFD39900E09197 /* LaunchRocket.xib */; }; 34 | 03EE9DBE16FFD40200E09197 /* LaunchRocket.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 03EE9DBD16FFD40200E09197 /* LaunchRocket.tiff */; }; 35 | 03F2EBBA1702997E000077DE /* Service.h in Headers */ = {isa = PBXBuildFile; fileRef = 03F2EBB81702997D000077DE /* Service.h */; }; 36 | 03F2EBBB1702997E000077DE /* Service.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F2EBB91702997E000077DE /* Service.m */; }; 37 | 03F2EBC61702A55C000077DE /* FlippedView.h in Headers */ = {isa = PBXBuildFile; fileRef = 03F2EBC41702A55C000077DE /* FlippedView.h */; }; 38 | 03F2EBC71702A55C000077DE /* FlippedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F2EBC51702A55C000077DE /* FlippedView.m */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 03233D5D17054544009727CB /* ServiceController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceController.h; sourceTree = ""; }; 43 | 03233D5E17054544009727CB /* ServiceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceController.m; sourceTree = ""; }; 44 | 0351F8711705543600CE9495 /* ServiceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceManager.h; sourceTree = ""; }; 45 | 0351F8721705543600CE9495 /* ServiceManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceManager.m; sourceTree = ""; }; 46 | 039EEF89189A1E1E00B5F169 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 47 | 039F63D818920EF2004C6696 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "OpenSans-Light.ttf"; sourceTree = ""; }; 48 | 039F63DA18921F19004C6696 /* rocket.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rocket.png; sourceTree = ""; }; 49 | 039F63E21892220F004C6696 /* red.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = red.png; sourceTree = ""; }; 50 | 039F63E31892220F004C6696 /* green.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = green.png; sourceTree = ""; }; 51 | 039F63E41892220F004C6696 /* yellow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = yellow.png; sourceTree = ""; }; 52 | 039F63E818922D1F004C6696 /* OpenSansFont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenSansFont.h; sourceTree = ""; }; 53 | 039F63E918922D1F004C6696 /* OpenSansFont.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpenSansFont.m; sourceTree = ""; }; 54 | 039F63EC18923821004C6696 /* HorizontalDivider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HorizontalDivider.h; sourceTree = ""; }; 55 | 039F63ED18923821004C6696 /* HorizontalDivider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HorizontalDivider.m; sourceTree = ""; }; 56 | 039F63F018923AA8004C6696 /* gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gray.png; sourceTree = ""; }; 57 | 03A86929189327D900ED639C /* Process.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Process.h; sourceTree = ""; }; 58 | 03A8692A189327D900ED639C /* Process.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Process.m; sourceTree = ""; }; 59 | 03C5C20518AAC02B00FB25EF /* special-plists.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "special-plists.plist"; sourceTree = ""; }; 60 | 03D25AA218A35FEF007B253F /* sudo.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; name = sudo.app; path = LaunchRocket/sudo.app; sourceTree = ""; }; 61 | 03EE9D9E16FFD39900E09197 /* LaunchRocket.prefPane */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LaunchRocket.prefPane; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 03EE9DA116FFD39900E09197 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 63 | 03EE9DA316FFD39900E09197 /* PreferencePanes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PreferencePanes.framework; path = System/Library/Frameworks/PreferencePanes.framework; sourceTree = SDKROOT; }; 64 | 03EE9DA616FFD39900E09197 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 65 | 03EE9DA716FFD39900E09197 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 66 | 03EE9DA816FFD39900E09197 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 67 | 03EE9DAB16FFD39900E09197 /* LaunchRocket-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "LaunchRocket-Info.plist"; sourceTree = ""; }; 68 | 03EE9DAD16FFD39900E09197 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 69 | 03EE9DAF16FFD39900E09197 /* LaunchRocket-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LaunchRocket-Prefix.pch"; sourceTree = ""; }; 70 | 03EE9DB016FFD39900E09197 /* LaunchRocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchRocket.h; sourceTree = ""; }; 71 | 03EE9DB116FFD39900E09197 /* LaunchRocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchRocket.m; sourceTree = ""; }; 72 | 03EE9DB616FFD39900E09197 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/LaunchRocket.xib; sourceTree = ""; }; 73 | 03EE9DBD16FFD40200E09197 /* LaunchRocket.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = LaunchRocket.tiff; sourceTree = ""; }; 74 | 03F2EBB81702997D000077DE /* Service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Service.h; sourceTree = ""; }; 75 | 03F2EBB91702997E000077DE /* Service.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Service.m; sourceTree = ""; }; 76 | 03F2EBC41702A55C000077DE /* FlippedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlippedView.h; sourceTree = ""; }; 77 | 03F2EBC51702A55C000077DE /* FlippedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlippedView.m; sourceTree = ""; }; 78 | /* End PBXFileReference section */ 79 | 80 | /* Begin PBXFrameworksBuildPhase section */ 81 | 03EE9D9916FFD39900E09197 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 039EEF8A189A1E1E00B5F169 /* ServiceManagement.framework in Frameworks */, 86 | 03EE9DA216FFD39900E09197 /* Cocoa.framework in Frameworks */, 87 | 03EE9DA416FFD39900E09197 /* PreferencePanes.framework in Frameworks */, 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 039F63D718920EDC004C6696 /* Fonts */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 039F63D818920EF2004C6696 /* OpenSans-Light.ttf */, 98 | ); 99 | name = Fonts; 100 | sourceTree = ""; 101 | }; 102 | 03EE9D9316FFD39900E09197 = { 103 | isa = PBXGroup; 104 | children = ( 105 | 03D25AA218A35FEF007B253F /* sudo.app */, 106 | 03EE9DA916FFD39900E09197 /* LaunchRocket */, 107 | 03EE9DA016FFD39900E09197 /* Frameworks */, 108 | 03EE9D9F16FFD39900E09197 /* Products */, 109 | ); 110 | sourceTree = ""; 111 | }; 112 | 03EE9D9F16FFD39900E09197 /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 03EE9D9E16FFD39900E09197 /* LaunchRocket.prefPane */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | 03EE9DA016FFD39900E09197 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 039EEF89189A1E1E00B5F169 /* ServiceManagement.framework */, 124 | 03EE9DA116FFD39900E09197 /* Cocoa.framework */, 125 | 03EE9DA316FFD39900E09197 /* PreferencePanes.framework */, 126 | 03EE9DA516FFD39900E09197 /* Other Frameworks */, 127 | ); 128 | name = Frameworks; 129 | sourceTree = ""; 130 | }; 131 | 03EE9DA516FFD39900E09197 /* Other Frameworks */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 03EE9DA616FFD39900E09197 /* AppKit.framework */, 135 | 03EE9DA716FFD39900E09197 /* CoreData.framework */, 136 | 03EE9DA816FFD39900E09197 /* Foundation.framework */, 137 | ); 138 | name = "Other Frameworks"; 139 | sourceTree = ""; 140 | }; 141 | 03EE9DA916FFD39900E09197 /* LaunchRocket */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 039F63E818922D1F004C6696 /* OpenSansFont.h */, 145 | 039F63E918922D1F004C6696 /* OpenSansFont.m */, 146 | 039F63D718920EDC004C6696 /* Fonts */, 147 | 03EE9DB016FFD39900E09197 /* LaunchRocket.h */, 148 | 03EE9DB116FFD39900E09197 /* LaunchRocket.m */, 149 | 03EE9DBD16FFD40200E09197 /* LaunchRocket.tiff */, 150 | 03EE9DB516FFD39900E09197 /* LaunchRocket.xib */, 151 | 03EE9DAA16FFD39900E09197 /* Supporting Files */, 152 | 03F2EBB81702997D000077DE /* Service.h */, 153 | 03F2EBB91702997E000077DE /* Service.m */, 154 | 03F2EBC41702A55C000077DE /* FlippedView.h */, 155 | 03F2EBC51702A55C000077DE /* FlippedView.m */, 156 | 03233D5D17054544009727CB /* ServiceController.h */, 157 | 03233D5E17054544009727CB /* ServiceController.m */, 158 | 0351F8711705543600CE9495 /* ServiceManager.h */, 159 | 0351F8721705543600CE9495 /* ServiceManager.m */, 160 | 039F63EC18923821004C6696 /* HorizontalDivider.h */, 161 | 039F63ED18923821004C6696 /* HorizontalDivider.m */, 162 | 03A86929189327D900ED639C /* Process.h */, 163 | 03A8692A189327D900ED639C /* Process.m */, 164 | ); 165 | path = LaunchRocket; 166 | sourceTree = ""; 167 | }; 168 | 03EE9DAA16FFD39900E09197 /* Supporting Files */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 039F63E21892220F004C6696 /* red.png */, 172 | 039F63F018923AA8004C6696 /* gray.png */, 173 | 039F63E31892220F004C6696 /* green.png */, 174 | 039F63E41892220F004C6696 /* yellow.png */, 175 | 039F63DA18921F19004C6696 /* rocket.png */, 176 | 03EE9DAB16FFD39900E09197 /* LaunchRocket-Info.plist */, 177 | 03EE9DAC16FFD39900E09197 /* InfoPlist.strings */, 178 | 03EE9DAF16FFD39900E09197 /* LaunchRocket-Prefix.pch */, 179 | 03C5C20518AAC02B00FB25EF /* special-plists.plist */, 180 | ); 181 | name = "Supporting Files"; 182 | sourceTree = ""; 183 | }; 184 | /* End PBXGroup section */ 185 | 186 | /* Begin PBXHeadersBuildPhase section */ 187 | 03EE9D9A16FFD39900E09197 /* Headers */ = { 188 | isa = PBXHeadersBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 03F2EBBA1702997E000077DE /* Service.h in Headers */, 192 | 03F2EBC61702A55C000077DE /* FlippedView.h in Headers */, 193 | 039F63EE18923821004C6696 /* HorizontalDivider.h in Headers */, 194 | 039F63EA18922D1F004C6696 /* OpenSansFont.h in Headers */, 195 | 03A8692B189327D900ED639C /* Process.h in Headers */, 196 | 03233D5F17054544009727CB /* ServiceController.h in Headers */, 197 | 0351F8731705543600CE9495 /* ServiceManager.h in Headers */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXHeadersBuildPhase section */ 202 | 203 | /* Begin PBXNativeTarget section */ 204 | 03EE9D9D16FFD39900E09197 /* LaunchRocket */ = { 205 | isa = PBXNativeTarget; 206 | buildConfigurationList = 03EE9DBA16FFD39900E09197 /* Build configuration list for PBXNativeTarget "LaunchRocket" */; 207 | buildPhases = ( 208 | 03EE9D9816FFD39900E09197 /* Sources */, 209 | 03EE9D9916FFD39900E09197 /* Frameworks */, 210 | 03EE9D9A16FFD39900E09197 /* Headers */, 211 | 03EE9D9B16FFD39900E09197 /* Resources */, 212 | 03EE9D9C16FFD39900E09197 /* Rez */, 213 | ); 214 | buildRules = ( 215 | ); 216 | dependencies = ( 217 | ); 218 | name = LaunchRocket; 219 | productName = LaunchRocket; 220 | productReference = 03EE9D9E16FFD39900E09197 /* LaunchRocket.prefPane */; 221 | productType = "com.apple.product-type.bundle"; 222 | }; 223 | /* End PBXNativeTarget section */ 224 | 225 | /* Begin PBXProject section */ 226 | 03EE9D9416FFD39900E09197 /* Project object */ = { 227 | isa = PBXProject; 228 | attributes = { 229 | LastUpgradeCheck = 0930; 230 | ORGANIZATIONNAME = "Josh Butts"; 231 | }; 232 | buildConfigurationList = 03EE9D9716FFD39900E09197 /* Build configuration list for PBXProject "LaunchRocket" */; 233 | compatibilityVersion = "Xcode 9.3"; 234 | developmentRegion = English; 235 | hasScannedForEncodings = 0; 236 | knownRegions = ( 237 | en, 238 | ); 239 | mainGroup = 03EE9D9316FFD39900E09197; 240 | productRefGroup = 03EE9D9F16FFD39900E09197 /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | 03EE9D9D16FFD39900E09197 /* LaunchRocket */, 245 | ); 246 | }; 247 | /* End PBXProject section */ 248 | 249 | /* Begin PBXResourcesBuildPhase section */ 250 | 03EE9D9B16FFD39900E09197 /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | 039F63E61892220F004C6696 /* green.png in Resources */, 255 | 039F63E71892220F004C6696 /* yellow.png in Resources */, 256 | 03D25AA318A35FEF007B253F /* sudo.app in Resources */, 257 | 039F63F118923AA8004C6696 /* gray.png in Resources */, 258 | 03EE9DAE16FFD39900E09197 /* InfoPlist.strings in Resources */, 259 | 039F63DB18921F19004C6696 /* rocket.png in Resources */, 260 | 03EE9DB716FFD39900E09197 /* LaunchRocket.xib in Resources */, 261 | 039F63E51892220F004C6696 /* red.png in Resources */, 262 | 03EE9DBE16FFD40200E09197 /* LaunchRocket.tiff in Resources */, 263 | 039F63D918920EF2004C6696 /* OpenSans-Light.ttf in Resources */, 264 | 03C5C20618AAC02B00FB25EF /* special-plists.plist in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXRezBuildPhase section */ 271 | 03EE9D9C16FFD39900E09197 /* Rez */ = { 272 | isa = PBXRezBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | /* End PBXRezBuildPhase section */ 279 | 280 | /* Begin PBXSourcesBuildPhase section */ 281 | 03EE9D9816FFD39900E09197 /* Sources */ = { 282 | isa = PBXSourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | 03EE9DB216FFD39900E09197 /* LaunchRocket.m in Sources */, 286 | 03F2EBBB1702997E000077DE /* Service.m in Sources */, 287 | 03F2EBC71702A55C000077DE /* FlippedView.m in Sources */, 288 | 03A8692C189327D900ED639C /* Process.m in Sources */, 289 | 03233D6017054544009727CB /* ServiceController.m in Sources */, 290 | 0351F8741705543600CE9495 /* ServiceManager.m in Sources */, 291 | 039F63EF18923821004C6696 /* HorizontalDivider.m in Sources */, 292 | 039F63EB18922D1F004C6696 /* OpenSansFont.m in Sources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXSourcesBuildPhase section */ 297 | 298 | /* Begin PBXVariantGroup section */ 299 | 03EE9DAC16FFD39900E09197 /* InfoPlist.strings */ = { 300 | isa = PBXVariantGroup; 301 | children = ( 302 | 03EE9DAD16FFD39900E09197 /* en */, 303 | ); 304 | name = InfoPlist.strings; 305 | sourceTree = ""; 306 | }; 307 | 03EE9DB516FFD39900E09197 /* LaunchRocket.xib */ = { 308 | isa = PBXVariantGroup; 309 | children = ( 310 | 03EE9DB616FFD39900E09197 /* en */, 311 | ); 312 | name = LaunchRocket.xib; 313 | sourceTree = ""; 314 | }; 315 | /* End PBXVariantGroup section */ 316 | 317 | /* Begin XCBuildConfiguration section */ 318 | 03EE9DB816FFD39900E09197 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_COMMA = YES; 328 | CLANG_WARN_CONSTANT_CONVERSION = YES; 329 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 330 | CLANG_WARN_EMPTY_BODY = YES; 331 | CLANG_WARN_ENUM_CONVERSION = YES; 332 | CLANG_WARN_INFINITE_RECURSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 335 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 336 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 337 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 338 | CLANG_WARN_STRICT_PROTOTYPES = YES; 339 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 340 | CLANG_WARN_UNREACHABLE_CODE = YES; 341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 342 | COPY_PHASE_STRIP = NO; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | ENABLE_TESTABILITY = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu99; 346 | GCC_DYNAMIC_NO_PIC = NO; 347 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_OPTIMIZATION_LEVEL = 0; 350 | GCC_PREPROCESSOR_DEFINITIONS = ( 351 | "DEBUG=1", 352 | "$(inherited)", 353 | ); 354 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | MACOSX_DEPLOYMENT_TARGET = 10.10; 362 | ONLY_ACTIVE_ARCH = YES; 363 | SDKROOT = macosx; 364 | }; 365 | name = Debug; 366 | }; 367 | 03EE9DB916FFD39900E09197 /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 372 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 373 | CLANG_CXX_LIBRARY = "libc++"; 374 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 375 | CLANG_WARN_BOOL_CONVERSION = YES; 376 | CLANG_WARN_COMMA = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INFINITE_RECURSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 386 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 387 | CLANG_WARN_STRICT_PROTOTYPES = YES; 388 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 389 | CLANG_WARN_UNREACHABLE_CODE = YES; 390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 391 | COPY_PHASE_STRIP = YES; 392 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 393 | ENABLE_STRICT_OBJC_MSGSEND = YES; 394 | GCC_C_LANGUAGE_STANDARD = gnu99; 395 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 396 | GCC_NO_COMMON_BLOCKS = YES; 397 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 398 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 399 | GCC_WARN_UNDECLARED_SELECTOR = YES; 400 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 401 | GCC_WARN_UNUSED_FUNCTION = YES; 402 | GCC_WARN_UNUSED_VARIABLE = YES; 403 | MACOSX_DEPLOYMENT_TARGET = 10.10; 404 | SDKROOT = macosx; 405 | }; 406 | name = Release; 407 | }; 408 | 03EE9DBB16FFD39900E09197 /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | CLANG_ENABLE_OBJC_WEAK = YES; 412 | COMBINE_HIDPI_IMAGES = YES; 413 | DEPLOYMENT_LOCATION = YES; 414 | DSTROOT = /; 415 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 416 | GCC_PREFIX_HEADER = "LaunchRocket/LaunchRocket-Prefix.pch"; 417 | INFOPLIST_FILE = "LaunchRocket/LaunchRocket-Info.plist"; 418 | INSTALL_PATH = "$(USER_LIBRARY_DIR)/PreferencePanes"; 419 | MACOSX_DEPLOYMENT_TARGET = 10.10; 420 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshbutts.${PRODUCT_NAME:rfc1034identifier}"; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | WRAPPER_EXTENSION = prefPane; 423 | }; 424 | name = Debug; 425 | }; 426 | 03EE9DBC16FFD39900E09197 /* Release */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | CLANG_ENABLE_OBJC_WEAK = YES; 430 | COMBINE_HIDPI_IMAGES = YES; 431 | DEPLOYMENT_LOCATION = YES; 432 | DSTROOT = /; 433 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 434 | GCC_PREFIX_HEADER = "LaunchRocket/LaunchRocket-Prefix.pch"; 435 | INFOPLIST_FILE = "LaunchRocket/LaunchRocket-Info.plist"; 436 | INSTALL_PATH = "$(USER_LIBRARY_DIR)/PreferencePanes"; 437 | MACOSX_DEPLOYMENT_TARGET = 10.10; 438 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshbutts.${PRODUCT_NAME:rfc1034identifier}"; 439 | PRODUCT_NAME = "$(TARGET_NAME)"; 440 | WRAPPER_EXTENSION = prefPane; 441 | }; 442 | name = Release; 443 | }; 444 | /* End XCBuildConfiguration section */ 445 | 446 | /* Begin XCConfigurationList section */ 447 | 03EE9D9716FFD39900E09197 /* Build configuration list for PBXProject "LaunchRocket" */ = { 448 | isa = XCConfigurationList; 449 | buildConfigurations = ( 450 | 03EE9DB816FFD39900E09197 /* Debug */, 451 | 03EE9DB916FFD39900E09197 /* Release */, 452 | ); 453 | defaultConfigurationIsVisible = 0; 454 | defaultConfigurationName = Release; 455 | }; 456 | 03EE9DBA16FFD39900E09197 /* Build configuration list for PBXNativeTarget "LaunchRocket" */ = { 457 | isa = XCConfigurationList; 458 | buildConfigurations = ( 459 | 03EE9DBB16FFD39900E09197 /* Debug */, 460 | 03EE9DBC16FFD39900E09197 /* Release */, 461 | ); 462 | defaultConfigurationIsVisible = 0; 463 | defaultConfigurationName = Release; 464 | }; 465 | /* End XCConfigurationList section */ 466 | }; 467 | rootObject = 03EE9D9416FFD39900E09197 /* Project object */; 468 | } 469 | -------------------------------------------------------------------------------- /LaunchRocket.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LaunchRocket.xcodeproj/xcshareddata/xcschemes/LaunchRocket.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 52 | 53 | 63 | 67 | 68 | 69 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 100 | 102 | 103 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /LaunchRocket/FlippedView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlippedView.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/26/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FlippedView : NSView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LaunchRocket/FlippedView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlippedView.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/26/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "FlippedView.h" 10 | 11 | @implementation FlippedView 12 | 13 | -(BOOL) isFlipped { 14 | return YES; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LaunchRocket/HorizontalDivider.h: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalDivider.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 1/23/14. 6 | // Copyright (c) 2014 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HorizontalDivider : NSView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LaunchRocket/HorizontalDivider.m: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalDivider.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 1/23/14. 6 | // Copyright (c) 2014 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "HorizontalDivider.h" 10 | 11 | @implementation HorizontalDivider 12 | 13 | - (void)drawRect:(NSRect)dirtyRect { 14 | // set any NSColor for filling, say white: 15 | [[NSColor grayColor] setFill]; 16 | NSRectFill(dirtyRect); 17 | [super drawRect:dirtyRect]; 18 | } 19 | 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /LaunchRocket/LaunchRocket-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ATSApplicationFontsPath 6 | Fonts 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | BNDL 21 | CFBundleShortVersionString 22 | 0.7.1 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 0.7.1 27 | LSApplicationCategoryType 28 | 29 | NSHumanReadableCopyright 30 | 31 | NSMainNibFile 32 | LaunchRocket 33 | NSPrefPaneIconFile 34 | LaunchRocket.tiff 35 | NSPrefPaneIconLabel 36 | LaunchRocket 37 | NSPrincipalClass 38 | LaunchRocket 39 | PreferenceFileVersion 40 | 2 41 | 42 | 43 | -------------------------------------------------------------------------------- /LaunchRocket/LaunchRocket-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'LaunchRocket' target in the 'LaunchRocket' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /LaunchRocket/LaunchRocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchRocket.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/24/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LaunchRocket : NSPreferencePane 12 | 13 | @property (assign) IBOutlet NSScrollView *serviceParent; 14 | @property (assign) IBOutlet NSButton *homebrewScan; 15 | @property (assign) IBOutlet NSButton *addPlist; 16 | @property (assign) IBOutlet NSTextField *launchRocketLabel; 17 | @property (assign) IBOutlet NSTextField *versionNumber; 18 | 19 | 20 | - (void)mainViewDidLoad; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /LaunchRocket/LaunchRocket.m: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchRocket.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/24/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "LaunchRocket.h" 10 | #import "ServiceManager.h" 11 | #import "OpenSansFont.h" 12 | #import "Process.h" 13 | 14 | 15 | @implementation LaunchRocket 16 | 17 | @synthesize serviceParent; 18 | @synthesize homebrewScan; 19 | @synthesize launchRocketLabel; 20 | @synthesize versionNumber; 21 | @synthesize addPlist; 22 | 23 | - (void)mainViewDidLoad 24 | { 25 | NSLog(@"Initialzing LaunchRocket"); 26 | ServiceManager *sm = [[ServiceManager alloc] initWithView:self.serviceParent]; 27 | [sm cleanServices]; 28 | [self.homebrewScan setTarget:sm]; 29 | [self.homebrewScan setAction:@selector(handleHomebrewScanClick:)]; 30 | [self.addPlist setTarget:sm]; 31 | [self.addPlist setAction:@selector(handleAddPlistClick:)]; 32 | 33 | NSLog(@"Setting fonts"); 34 | self.launchRocketLabel.font = [OpenSansFont getFontWithSize:16]; 35 | self.versionNumber.font = [OpenSansFont getFontWithSize:13]; 36 | [sm renderList]; 37 | 38 | [self.versionNumber setStringValue: [NSString stringWithFormat:@"Version %@", [[[self bundle] infoDictionary] valueForKey:@"CFBundleVersion"]]]; 39 | 40 | } 41 | 42 | - (void)didUnselect { 43 | [Process killSudoHelper]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /LaunchRocket/LaunchRocket.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/LaunchRocket.tiff -------------------------------------------------------------------------------- /LaunchRocket/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/OpenSans-Light.ttf -------------------------------------------------------------------------------- /LaunchRocket/OpenSansFont.h: -------------------------------------------------------------------------------- 1 | // 2 | // OpenSansFont.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 1/23/14. 6 | // Copyright (c) 2014 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface OpenSansFont : NSObject 12 | 13 | +(NSFont *) getFontWithSize:(int) size; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LaunchRocket/OpenSansFont.m: -------------------------------------------------------------------------------- 1 | // 2 | // OpenSansFont.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 1/23/14. 6 | // Copyright (c) 2014 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "OpenSansFont.h" 10 | 11 | @implementation OpenSansFont 12 | 13 | +(NSFont *) getFontWithSize:(int)size { 14 | NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 15 | NSString *openSansFontPath = [NSString stringWithFormat:@"%@%@", [bundle resourcePath], @"/OpenSans-Light.ttf", Nil]; 16 | CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef) openSansFontPath, kCFURLPOSIXPathStyle, false); 17 | CGDataProviderRef dataProvider = CGDataProviderCreateWithURL(url); 18 | CGFontRef theCGFont = CGFontCreateWithDataProvider(dataProvider); 19 | CTFontRef theCTFont = CTFontCreateWithGraphicsFont(theCGFont, size, nil, nil); 20 | NSFont *openSans = (__bridge NSFont *) theCTFont; 21 | CFRelease(theCGFont); 22 | CFRelease(dataProvider); 23 | CFRelease(url); 24 | return openSans; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /LaunchRocket/Process.h: -------------------------------------------------------------------------------- 1 | // 2 | // Process.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 1/24/14. 6 | // Copyright (c) 2014 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Process : NSObject 12 | 13 | @property AuthorizationRef authref; 14 | 15 | 16 | -(NSString *) execute:(NSString *)command; 17 | -(NSString *) executeSudo:(NSString *)command; 18 | +(void) killSudoHelper; 19 | @end 20 | -------------------------------------------------------------------------------- /LaunchRocket/Process.m: -------------------------------------------------------------------------------- 1 | // 2 | // Process.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 1/24/14. 6 | // Copyright (c) 2014 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "Process.h" 10 | 11 | @implementation Process 12 | 13 | -(NSString *) execute:(NSString *)command { 14 | NSLog(@"%@%@", @"Executing command: ", command); 15 | 16 | NSString *sudoHelperPath = [NSString stringWithFormat:@"%@%@", [[NSBundle bundleForClass:[self class]] resourcePath], @"/sudo.app"]; 17 | NSMutableString *scriptSource = [NSMutableString stringWithFormat:@"tell application \"%@\"\n exec(\"%@\")\n end tell\n", sudoHelperPath, command]; 18 | NSAppleScript *script = [[NSAppleScript alloc] initWithSource:scriptSource]; 19 | NSDictionary *error; 20 | NSString *output = [[script executeAndReturnError:&error] stringValue]; 21 | NSLog(@"Result of `%@` was %@", command, output); 22 | return output; 23 | } 24 | 25 | -(NSString *) executeSudo:(NSString *)command { 26 | NSLog(@"%@%@", @"Executing command with sudo: ", command); 27 | 28 | NSString *sudoHelperPath = [NSString stringWithFormat:@"%@%@", [[NSBundle bundleForClass:[self class]] resourcePath], @"/sudo.app"]; 29 | NSMutableString *scriptSource = [NSMutableString stringWithFormat:@"tell application \"%@\"\n execsudo(\"%@\")\n end tell\n", sudoHelperPath, command]; 30 | NSAppleScript *script = [[NSAppleScript alloc] initWithSource:scriptSource]; 31 | NSDictionary *error; 32 | NSString *output = [[script executeAndReturnError:&error] stringValue]; 33 | NSLog(@"Result of `sudo %@` was %@", command, output); 34 | return output; 35 | } 36 | 37 | +(void) killSudoHelper { 38 | NSLog(@"Killing helper"); 39 | NSString *sudoHelperPath = [NSString stringWithFormat:@"%@%@", [[NSBundle bundleForClass:[self class]] resourcePath], @"/sudo.app"]; 40 | NSString *scriptSource = [NSString stringWithFormat:@"tell application \"%@\"\n stopscript()\n end tell\n", sudoHelperPath]; 41 | NSAppleScript *script = [[NSAppleScript new] initWithSource:scriptSource]; 42 | [script executeAndReturnError:nil]; 43 | } 44 | 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /LaunchRocket/Service.h: -------------------------------------------------------------------------------- 1 | // 2 | // Service.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/26/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Service : NSObject 12 | 13 | @property (retain) NSString* plist; 14 | @property (retain) NSString* identifier; 15 | @property (retain) NSString* name; 16 | @property bool useSudo; 17 | @property bool runAtLogin; 18 | 19 | 20 | - (id) initWithOptions:(NSDictionary*) options; 21 | - (NSMutableDictionary *) getPlistData; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /LaunchRocket/Service.m: -------------------------------------------------------------------------------- 1 | // 2 | // Service.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/26/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "Service.h" 10 | 11 | @implementation Service 12 | 13 | @synthesize plist; 14 | @synthesize identifier; 15 | @synthesize name; 16 | @synthesize useSudo; 17 | @synthesize runAtLogin; 18 | 19 | - (id) initWithOptions:(NSDictionary *)options { 20 | self = [super init]; 21 | self.plist = [options objectForKey:@"plist"]; 22 | 23 | NSDictionary *plistData = [[NSDictionary alloc] initWithContentsOfFile:self.plist]; 24 | self.identifier = [plistData objectForKey:@"Label"]; 25 | self.name = [options objectForKey:@"name"]; 26 | 27 | NSNumber *shouldUseSudo = [options objectForKey:@"useSudo"]; 28 | if (shouldUseSudo == nil) { 29 | self.useSudo = NO; 30 | } else { 31 | self.useSudo = [shouldUseSudo boolValue]; 32 | } 33 | 34 | NSNumber *shouldRunAtLogin = [options objectForKey:@"runAtLogin"]; 35 | if (shouldRunAtLogin == nil) { 36 | self.runAtLogin = NO; 37 | } else { 38 | self.runAtLogin = [shouldRunAtLogin boolValue]; 39 | } 40 | 41 | 42 | return self; 43 | } 44 | 45 | -(NSMutableDictionary *) getPlistData { 46 | NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; 47 | [data setObject:self.name forKey:@"name"]; 48 | [data setObject:self.plist forKey:@"plist"]; 49 | [data setObject:[NSNumber numberWithBool:self.useSudo] forKey:@"useSudo"]; 50 | [data setObject:[NSNumber numberWithBool:self.runAtLogin] forKey:@"runAtLogin"]; 51 | return data; 52 | } 53 | 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /LaunchRocket/ServiceController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceController.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/28/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Service.h" 11 | #import "ServiceManager.h" 12 | 13 | @interface ServiceController : NSObject 14 | 15 | @property (strong) Service *service; 16 | @property (strong) NSFileManager *fm; 17 | @property (strong) NSImageView *statusIndicator; 18 | @property (strong) NSButton *startStop; 19 | @property (strong) NSButton *useSudo; 20 | @property (strong) NSButton *runAtLogin; 21 | @property (strong) ServiceManager *serviceManager; 22 | 23 | @property int status; 24 | 25 | -(id) initWithService:(Service *) theService; 26 | -(BOOL) isStarted; 27 | -(void) start; 28 | -(void) stop; 29 | -(void) updateStatusIndicator; 30 | -(void) handleStartStopClick:(id)sender; 31 | -(void) handleSudoClick:(id)sender; 32 | -(void) updateStartStopStatus; 33 | -(void) handleRunAtLoginClick:(id)sender; 34 | -(void) handleRemoveClick:(id)sender; 35 | 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /LaunchRocket/ServiceController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceController.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/28/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "ServiceController.h" 10 | #import "Process.h" 11 | #import "Service.h" 12 | 13 | @implementation ServiceController 14 | 15 | @synthesize service; 16 | @synthesize statusIndicator; 17 | @synthesize useSudo; 18 | @synthesize startStop; 19 | @synthesize status; 20 | @synthesize runAtLogin; 21 | @synthesize serviceManager; 22 | 23 | -(id) initWithService:(Service *)theService { 24 | 25 | NSLog(@"%@%@", @"Initializing service ", theService.identifier); 26 | 27 | self = [super init]; 28 | 29 | self.service = theService; 30 | if ([self isStarted]) { 31 | self.status = 2; 32 | } 33 | return self; 34 | } 35 | 36 | -(BOOL) isStarted { 37 | 38 | Process *p = [[Process alloc] init]; 39 | 40 | NSString *output; 41 | NSMutableString *launchCtlCommand = [[NSMutableString alloc] initWithString:@"/bin/launchctl list | grep "]; 42 | [launchCtlCommand appendString:self.service.identifier]; 43 | [launchCtlCommand appendString:@"$"]; 44 | 45 | if (self.service.useSudo) { 46 | output = [p executeSudo:launchCtlCommand]; 47 | } else { 48 | output = [p execute:launchCtlCommand]; 49 | } 50 | 51 | if ([output length] > 0) { 52 | self.status = 2; 53 | return YES; 54 | } 55 | self.status = 0; 56 | return NO; 57 | 58 | } 59 | 60 | -(void) stop { 61 | self.status = 1; 62 | [self updateStatusIndicator]; 63 | 64 | 65 | Process *p = [[Process alloc] init]; 66 | if (self.service.useSudo) { 67 | NSString *tmpPlist = [NSString stringWithFormat:@"/tmp/%@.plist", self.service.identifier]; 68 | NSString *copyCommand = [NSString stringWithFormat:@"cp %@ %@", self.service.plist, tmpPlist]; 69 | NSString *runCommand = [NSString stringWithFormat:@"/bin/launchctl unload %@", tmpPlist]; 70 | NSString *cleanupCommand = [NSString stringWithFormat:@"rm %@", tmpPlist]; 71 | [p executeSudo:copyCommand]; 72 | [p executeSudo:runCommand]; 73 | [p executeSudo:cleanupCommand]; 74 | } else { 75 | NSString *command = [NSString stringWithFormat:@"/bin/launchctl unload %@", self.service.plist]; 76 | [p execute:command]; 77 | } 78 | 79 | [self isStarted]; 80 | [self updateStatusIndicator]; 81 | [self updateStartStopStatus]; 82 | 83 | } 84 | 85 | -(void) start { 86 | self.status = 1; 87 | [self updateStatusIndicator]; 88 | 89 | Process *p = [[Process alloc] init]; 90 | if (self.service.useSudo) { 91 | NSString *tmpPlist = [NSString stringWithFormat:@"/tmp/%@.plist", self.service.identifier]; 92 | NSString *copyCommand = [NSString stringWithFormat:@"cp %@ %@", self.service.plist, tmpPlist]; 93 | NSString *runCommand = [NSString stringWithFormat:@"/bin/launchctl load %@", tmpPlist]; 94 | NSString *cleanupCommand = [NSString stringWithFormat:@"rm %@", tmpPlist]; 95 | [p executeSudo:copyCommand]; 96 | [p executeSudo:runCommand]; 97 | [p executeSudo:cleanupCommand]; 98 | } else { 99 | NSString *command = [NSString stringWithFormat:@"/bin/launchctl load %@", self.service.plist]; 100 | [p execute:command]; 101 | } 102 | 103 | [self isStarted]; 104 | [self updateStatusIndicator]; 105 | [self updateStartStopStatus]; 106 | } 107 | 108 | 109 | -(void) updateStatusIndicator { 110 | NSString *statusImageName; 111 | NSString *statusImageAccessibilityDescription; 112 | switch (self.status) { 113 | case 0: 114 | statusImageName = @"red"; 115 | statusImageAccessibilityDescription = NSLocalizedString(@"Not running", nil); 116 | break; 117 | case 1: 118 | statusImageName = @"yellow"; 119 | statusImageAccessibilityDescription = NSLocalizedString(@"Starting or stopping", nil); 120 | break; 121 | case 2: 122 | statusImageName = @"green"; 123 | statusImageAccessibilityDescription = NSLocalizedString(@"Running", nil); 124 | break; 125 | } 126 | NSImage *image = [[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:statusImageName ofType:@"png"]]; 127 | [self.statusIndicator setImage:image]; 128 | self.statusIndicator.cell.accessibilityRoleDescription = statusImageAccessibilityDescription; 129 | [self.statusIndicator setNeedsDisplay:YES]; 130 | } 131 | 132 | -(void) updateStartStopStatus { 133 | if (self.status == 0) { 134 | [self.startStop setTitle:@"Start"]; 135 | } else { 136 | [self.startStop setTitle:@"Stop"]; 137 | } 138 | [self.startStop setNeedsDisplay:YES]; 139 | } 140 | 141 | -(void) handleStartStopClick:(id)sender { 142 | if (self.status == 0) { 143 | [self start]; 144 | } else { 145 | [self stop]; 146 | } 147 | } 148 | 149 | -(void) handleSudoClick:(id)sender { 150 | [self stop]; 151 | NSButton *b = (NSButton *)sender; 152 | if (b.state == NSOnState) { 153 | self.service.useSudo = YES; 154 | } else { 155 | self.service.useSudo = NO; 156 | } 157 | [self.serviceManager saveService:self.service]; 158 | [self start]; 159 | [self isStarted]; 160 | [self updateStartStopStatus]; 161 | [self updateStatusIndicator]; 162 | } 163 | 164 | -(void) handleRunAtLoginClick:(id)sender { 165 | NSButton *b = (NSButton *)sender; 166 | if (b.state == NSOnState) { 167 | self.service.runAtLogin = YES; 168 | } else { 169 | self.service.runAtLogin = NO; 170 | } 171 | [self.serviceManager saveService:self.service]; 172 | 173 | if (self.service.runAtLogin) { 174 | NSString *newPlist; 175 | if (self.service.useSudo) { 176 | newPlist = [NSString stringWithFormat:@"/Library/LaunchDaemons/%@.plist", self.service.identifier]; 177 | } else { 178 | newPlist = [NSString stringWithFormat:@"%@/Library/LaunchAgents/%@.plist", NSHomeDirectory(), self.service.identifier]; 179 | } 180 | NSString *copyCommand = [NSString stringWithFormat:@"cp %@ %@", self.service.plist, newPlist]; 181 | NSLog(@"Executing for at-login run: %@", copyCommand); 182 | Process *p = [[Process alloc] init]; 183 | if (self.service.useSudo) { 184 | [p executeSudo:copyCommand]; 185 | } else { 186 | [p execute:copyCommand]; 187 | } 188 | } else { 189 | NSString *plistToDelete; 190 | if (self.service.useSudo) { 191 | plistToDelete = [NSString stringWithFormat:@"/Library/LaunchDaemons/%@.plist", self.service.identifier]; 192 | } else { 193 | plistToDelete = [NSString stringWithFormat:@"%@/Library/LaunchAgents/%@.plist", NSHomeDirectory(), self.service.identifier]; 194 | } 195 | NSString *deleteCommand = [NSString stringWithFormat:@"rm -f %@", plistToDelete]; 196 | NSLog(@"Cleaning up: %@", deleteCommand); 197 | Process *p = [[Process alloc] init]; 198 | if (self.service.useSudo) { 199 | [p executeSudo:deleteCommand]; 200 | } else { 201 | [p execute:deleteCommand]; 202 | } 203 | } 204 | } 205 | 206 | -(void) handleRemoveClick:(id)sender { 207 | [self stop]; 208 | [self.serviceManager removeService:self.service]; 209 | } 210 | 211 | @end 212 | -------------------------------------------------------------------------------- /LaunchRocket/ServiceManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceManager.h 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/28/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Service.h" 11 | 12 | @interface ServiceManager : NSObject 13 | 14 | @property (strong) NSMutableArray *serviceControllers; 15 | @property (strong) NSBundle *bundle; 16 | @property (strong) NSScrollView* serviceParent; 17 | @property (strong) NSString *preferencesFile; 18 | @property (strong) NSMutableDictionary *preferences; 19 | 20 | -(id) initWithView:(NSScrollView *)sv; 21 | -(void) createPreferencesFile; 22 | -(void) cleanServices; 23 | -(void) renderList; 24 | -(void) loadServices; 25 | -(void) addService: (NSString *)plistFile; 26 | -(void) removeService: (Service *)service; 27 | -(void) saveService: (Service *)service; 28 | -(void) writePreferences; 29 | -(void) loadPreferences; 30 | 31 | 32 | -(IBAction) handleHomebrewScanClick:(id) sender; 33 | -(IBAction) handleAddPlistClick:(id)sender; 34 | 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /LaunchRocket/ServiceManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceManager.m 3 | // LaunchRocket 4 | // 5 | // Created by Josh Butts on 3/28/13. 6 | // Copyright (c) 2013 Josh Butts. All rights reserved. 7 | // 8 | 9 | #import "ServiceManager.h" 10 | #import "Service.h" 11 | #import "ServiceController.h" 12 | #import "FlippedView.h" 13 | #import "OpenSansFont.h" 14 | #import "HorizontalDivider.h" 15 | #import "Process.h" 16 | 17 | @implementation ServiceManager 18 | 19 | @synthesize serviceControllers; 20 | @synthesize bundle; 21 | @synthesize serviceParent; 22 | @synthesize preferencesFile; 23 | @synthesize preferences; 24 | 25 | 26 | -(id) initWithView:(NSScrollView *)sv { 27 | 28 | NSLog(@"Initializing service mananger"); 29 | self = [super init]; 30 | self.serviceControllers = [[NSMutableArray alloc] init]; 31 | self.bundle = [NSBundle bundleForClass:[self class]]; 32 | self.serviceParent = sv; 33 | self.preferencesFile = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(), @"/Library/Preferences/com.joshbutts.launchrocket.plist"]; 34 | [self loadPreferences]; 35 | [self loadServices]; 36 | return self; 37 | } 38 | 39 | -(void) createPreferencesFile { 40 | NSFileManager *fm = [[NSFileManager alloc] init]; 41 | if (![fm fileExistsAtPath:self.preferencesFile]) { 42 | NSLog(@"Creating preferences file"); 43 | NSMutableDictionary *prefs = [[NSMutableDictionary alloc] init]; 44 | NSString *version =[[[self bundle] infoDictionary] valueForKey:@"PreferenceFileVersion"]; 45 | [prefs setObject:version forKey:@"version"]; 46 | [prefs setObject:[[NSDictionary alloc] init] forKey:@"services"]; 47 | [prefs writeToFile:self.preferencesFile atomically:YES]; 48 | } 49 | } 50 | 51 | -(void) writePreferences { 52 | [self.preferences writeToFile:self.preferencesFile atomically:YES]; 53 | NSLog(@"Wrote preferences file"); 54 | } 55 | 56 | -(void) loadPreferences { 57 | NSFileManager *fm = [[NSFileManager alloc] init]; 58 | 59 | if ([fm fileExistsAtPath:self.preferencesFile]) { 60 | NSMutableDictionary *prefs = [NSMutableDictionary dictionaryWithContentsOfFile:self.preferencesFile]; 61 | NSString *version = [[[self bundle] infoDictionary] valueForKey:@"PreferenceFileVersion"]; 62 | if (![version isEqualToString:[prefs objectForKey:@"version"]]) { 63 | [fm removeItemAtPath:self.preferencesFile error:nil]; 64 | NSLog(@"Killing preferences file, versions did not match"); 65 | [self createPreferencesFile]; 66 | [self loadPreferences]; 67 | } else { 68 | self.preferences = prefs; 69 | } 70 | } else { 71 | [self createPreferencesFile]; 72 | [self loadPreferences]; 73 | } 74 | } 75 | 76 | -(void) cleanServices { 77 | 78 | NSLog(@"Removing non-existent services from preferences"); 79 | NSFileManager *fm = [[NSFileManager alloc] init]; 80 | NSMutableArray *servicesToRemove = [[NSMutableArray alloc] init]; 81 | for (NSString *key in [self.preferences objectForKey:@"services"]) { 82 | NSDictionary *data = [[self.preferences objectForKey:@"services"] objectForKey:key]; 83 | if (![fm fileExistsAtPath:[data objectForKey:@"plist"]]) { 84 | [servicesToRemove addObject:key]; 85 | } 86 | } 87 | for (NSString *key in servicesToRemove) { 88 | [[self.preferences objectForKey:@"services"] removeObjectForKey:key]; 89 | } 90 | [self writePreferences]; 91 | } 92 | 93 | -(IBAction) handleHomebrewScanClick:(id)sender { 94 | 95 | NSFileManager *fm = [[NSFileManager alloc] init]; 96 | 97 | NSString *homebrewPrefix = nil; 98 | if ([fm fileExistsAtPath:@"/usr/local/bin/brew"]) { 99 | NSLog(@"Found 'brew' at /usr/local/bin/brew, assuming that's the homebrew prefix"); 100 | homebrewPrefix = @"/usr/local"; 101 | } 102 | 103 | if (homebrewPrefix == nil) { 104 | homebrewPrefix = [self.preferences objectForKey:@"homebrewPrefix"]; 105 | } 106 | 107 | 108 | if (homebrewPrefix == nil) { 109 | NSLog(@"Prompting user to select homebrew prefix"); 110 | 111 | NSAlert *alert = [[NSAlert alloc] init]; 112 | [alert addButtonWithTitle:@"OK"]; 113 | [alert addButtonWithTitle:@"Cancel"]; 114 | [alert setMessageText:@"You appear to have a non-standard Hombrew installation. Please browse to your homebrew prefix. (brew --prefix)"]; 115 | NSInteger response = [alert runModal]; 116 | 117 | if (response == 1001) { //@TODO: find a better way to do this 118 | return; 119 | } 120 | 121 | NSLog(@"Waiting for path selection"); 122 | NSOpenPanel *brewPicker = [NSOpenPanel openPanel]; 123 | [brewPicker setCanChooseDirectories:YES]; 124 | [brewPicker setCanChooseFiles:NO]; 125 | [brewPicker setAllowsMultipleSelection:NO]; 126 | [brewPicker setDirectoryURL:[NSURL URLWithString:@"/usr"]]; 127 | 128 | NSInteger clicked = [brewPicker runModal]; 129 | if (clicked == NSFileHandlingPanelOKButton) { 130 | NSString *selectedHomebrewPrefix = [[brewPicker URL] path]; 131 | NSLog(@"User selected %@", selectedHomebrewPrefix); 132 | if ([fm fileExistsAtPath:[NSString stringWithFormat:@"%@/bin/brew", selectedHomebrewPrefix]]) { 133 | homebrewPrefix = selectedHomebrewPrefix; 134 | [self.preferences setObject:homebrewPrefix forKey:@"homebrewPrefix"]; 135 | [self writePreferences]; 136 | NSLog(@"Wrote homebrew prefix to preferences"); 137 | } 138 | } 139 | } 140 | 141 | if (homebrewPrefix == nil) { 142 | NSAlert *alert = [[NSAlert alloc] init]; 143 | [alert addButtonWithTitle:@"OK"]; 144 | [alert setMessageText:@"We couldn't find your homebrew prefix at the path you selected"]; 145 | [alert setAlertStyle:NSWarningAlertStyle]; 146 | [alert beginSheetModalForWindow:[self.serviceParent window] completionHandler:nil]; 147 | NSLog(@"Unable to get Hombrew prefix"); 148 | return; 149 | 150 | } 151 | 152 | NSLog(@"Scanning homebrew opt/"); 153 | NSString *optPath = [NSString stringWithFormat:@"%@/opt/", homebrewPrefix]; 154 | NSDirectoryEnumerator *de = [fm enumeratorAtPath:optPath]; 155 | for (NSString *item in de) { 156 | NSString *servicePlist = [NSString stringWithFormat:@"%@%@%@%@%@", optPath, item, @"/homebrew.mxcl.", item, @".plist"]; 157 | if ([fm fileExistsAtPath:servicePlist]) { 158 | [self addService:servicePlist]; 159 | } 160 | } 161 | 162 | NSLog(@"Scanning for special plists"); 163 | NSString *specialPlistPath = [[self bundle] pathForResource:@"special-plists" ofType:@"plist"]; 164 | NSArray *additionalPlists = [NSArray arrayWithContentsOfFile:specialPlistPath]; 165 | for (NSString *plist in additionalPlists) { 166 | NSString *servicePlist = [NSString stringWithFormat:@"%@%@", optPath, plist]; 167 | if ([fm fileExistsAtPath:servicePlist]) { 168 | [self addService:servicePlist]; 169 | } 170 | } 171 | 172 | NSLog(@"Scanning for homebrew etc/launchrocket"); 173 | // scan etc/launchrocket 174 | NSString *launchrocketPlistsPath = [NSString stringWithFormat:@"%@/etc/launchrocket", homebrewPrefix]; 175 | if ([fm fileExistsAtPath:launchrocketPlistsPath]) { 176 | de = [fm enumeratorAtPath:launchrocketPlistsPath]; 177 | for (NSString *plist in de) { 178 | NSString *servicePlist = [NSString stringWithFormat:@"%@/%@", launchrocketPlistsPath, plist]; 179 | if ([fm fileExistsAtPath:servicePlist]) { 180 | [self addService:servicePlist]; 181 | } 182 | } 183 | } 184 | 185 | [self cleanServices]; 186 | [self loadServices]; 187 | [self renderList]; 188 | } 189 | 190 | -(IBAction) handleAddPlistClick:(id)sender { 191 | NSOpenPanel *filePicker = [NSOpenPanel openPanel]; 192 | [filePicker setCanChooseDirectories:NO]; 193 | [filePicker setCanChooseFiles:YES]; 194 | [filePicker setAllowsMultipleSelection:NO]; 195 | 196 | NSInteger clicked = [filePicker runModal]; 197 | if (clicked == NSFileHandlingPanelOKButton) { 198 | NSString *plistFile = [[filePicker URL] path]; 199 | [self addService:plistFile]; 200 | [self cleanServices]; 201 | [self loadServices]; 202 | [self renderList]; 203 | } 204 | 205 | } 206 | 207 | -(void) addService:(NSString *)plistFile { 208 | NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; 209 | NSArray *pathComponents = [plistFile componentsSeparatedByString:@"/"]; 210 | NSArray *filenameComponents = [[pathComponents lastObject] componentsSeparatedByString:@"."]; 211 | NSString *identifier = [[filenameComponents subarrayWithRange:NSMakeRange(0, [filenameComponents count] - 1)] componentsJoinedByString:@"."]; 212 | NSString *serviceName = [[[filenameComponents subarrayWithRange:NSMakeRange(2, [filenameComponents count] - 3)] componentsJoinedByString:@"."] capitalizedString]; 213 | [dict setObject:plistFile forKey:@"plist"]; 214 | [dict setObject:serviceName forKey:@"name"]; 215 | 216 | NSMutableDictionary *current = [[[self.preferences objectForKey:@"services"] objectForKey:identifier] mutableCopy]; 217 | if (current == nil) { 218 | NSLog(@"Adding %@", plistFile); 219 | [[self.preferences objectForKey:@"services"] setObject:dict forKey:identifier]; 220 | } else { 221 | [current addEntriesFromDictionary: dict]; 222 | [[self.preferences objectForKey:@"services"] setObject:current.copy forKey:identifier]; 223 | NSLog(@"%@ already exists -- updating", plistFile); 224 | } 225 | [self writePreferences]; 226 | } 227 | 228 | -(void) saveService:(Service *)service { 229 | [[self.preferences objectForKey:@"services"] setObject:[service getPlistData] forKey:service.identifier]; 230 | [self writePreferences]; 231 | } 232 | 233 | -(void) removeService:(Service *)service { 234 | [[self.preferences objectForKey:@"services"] removeObjectForKey:service.identifier]; 235 | [self writePreferences]; 236 | [self loadServices]; 237 | [self renderList]; 238 | } 239 | 240 | 241 | -(void) loadServices { 242 | NSLog(@"Attempting to load services from plist"); 243 | [self.serviceControllers release]; 244 | self.serviceControllers = [[NSMutableArray alloc] init]; 245 | NSDictionary *plistData = [self.preferences objectForKey:@"services"]; 246 | NSFileManager *fm = [[NSFileManager alloc] init]; 247 | 248 | NSMutableArray *staleServices = [[NSMutableArray alloc] init]; 249 | for (NSString *key in plistData) { 250 | NSMutableDictionary *serviceData = [plistData objectForKey:key]; 251 | Service *service = [[Service alloc] initWithOptions:serviceData]; 252 | 253 | if (![fm fileExistsAtPath:service.plist]) { 254 | NSLog(@"%@%@", service.plist, @" was not found. Will be removed from list."); 255 | service.identifier = key; 256 | [staleServices addObject:service]; 257 | continue; 258 | } 259 | 260 | ServiceController *sc = [[ServiceController alloc] initWithService:service]; 261 | sc.serviceManager = self; 262 | sc.service = service; 263 | [self.serviceControllers addObject:sc]; 264 | } 265 | NSLog(@"Successfully loaded services from plist"); 266 | 267 | for (Service *s in staleServices) { 268 | [self removeService:s]; 269 | } 270 | 271 | } 272 | 273 | 274 | -(void) renderList { 275 | int serviceListHeight = (int) (50 * [self.serviceControllers count]); 276 | FlippedView *serviceList = [[FlippedView alloc] initWithFrame:NSMakeRect(0, 0, 500, serviceListHeight)]; 277 | 278 | int listOffsetPixels = 0; 279 | for (ServiceController *sc in self.serviceControllers) { 280 | 281 | NSImageView *statusIndicator = [[NSImageView alloc] initWithFrame:NSMakeRect(0, listOffsetPixels, 30, 30)]; 282 | [statusIndicator setImageScaling:NSImageScaleAxesIndependently]; 283 | [serviceList addSubview:statusIndicator]; 284 | sc.statusIndicator = statusIndicator; 285 | [sc updateStatusIndicator]; 286 | 287 | 288 | NSTextField *name = [[NSTextField alloc] initWithFrame:NSMakeRect(40, listOffsetPixels, 120, 30)]; 289 | [name setStringValue:sc.service.name]; 290 | [name setBezeled:NO]; 291 | [name setDrawsBackground:NO]; 292 | [name setEditable:NO]; 293 | [name setSelectable:NO]; 294 | name.font = [OpenSansFont getFontWithSize:16]; 295 | [serviceList addSubview:name]; 296 | 297 | NSButton *startStop = [[NSButton alloc] initWithFrame:NSMakeRect(190, listOffsetPixels, 50, 30)]; 298 | [startStop setBezelStyle:NSTexturedRoundedBezelStyle]; 299 | [startStop setTarget:sc]; 300 | [startStop setAction:@selector(handleStartStopClick:)]; 301 | sc.startStop = startStop; 302 | [sc updateStartStopStatus]; 303 | [serviceList addSubview:startStop]; 304 | 305 | NSButton *sudo = [[NSButton alloc] initWithFrame:NSMakeRect(260, listOffsetPixels - 1, 80, 30)]; 306 | [sudo setButtonType:NSSwitchButton]; 307 | [sudo setTitle:@"As Root"]; 308 | [sudo setTarget:sc]; 309 | [sudo setAction:@selector(handleSudoClick:)]; 310 | if (sc.service.useSudo) { 311 | [sudo setState:NSOnState]; 312 | } 313 | [serviceList addSubview:sudo]; 314 | 315 | NSButton *runAtLogin = [[NSButton alloc] initWithFrame:NSMakeRect(330, listOffsetPixels - 1, 80, 30)]; 316 | [runAtLogin setButtonType:NSSwitchButton]; 317 | [runAtLogin setTitle:@"At Login"]; 318 | [runAtLogin setTarget:sc]; 319 | [runAtLogin setAction:@selector(handleRunAtLoginClick:)]; 320 | if (sc.service.runAtLogin) { 321 | [runAtLogin setState:NSOnState]; 322 | } 323 | [serviceList addSubview:runAtLogin]; 324 | 325 | NSButton *remove = [[NSButton alloc] initWithFrame:NSMakeRect(410, listOffsetPixels, 70, 30)]; 326 | [remove setBezelStyle:NSTexturedRoundedBezelStyle]; 327 | [remove setTitle:@"Remove"]; 328 | [remove setTarget:sc]; 329 | [remove setAction:@selector(handleRemoveClick:)]; 330 | [serviceList addSubview:remove]; 331 | 332 | 333 | 334 | listOffsetPixels += 37; 335 | 336 | HorizontalDivider *horizontalLine = [[HorizontalDivider alloc] initWithFrame:CGRectMake(0 , listOffsetPixels, 480, 1)]; 337 | [serviceList addSubview:horizontalLine]; 338 | 339 | listOffsetPixels += 10; 340 | 341 | 342 | } 343 | 344 | [self.serviceParent setDocumentView:serviceList]; 345 | 346 | } 347 | 348 | 349 | 350 | @end 351 | -------------------------------------------------------------------------------- /LaunchRocket/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LaunchRocket/en.lproj/LaunchRocket.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 76 | 80 | 81 | 82 | 90 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /LaunchRocket/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/gray.png -------------------------------------------------------------------------------- /LaunchRocket/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/green.png -------------------------------------------------------------------------------- /LaunchRocket/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/red.png -------------------------------------------------------------------------------- /LaunchRocket/rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/rocket.png -------------------------------------------------------------------------------- /LaunchRocket/special-plists.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LaunchRocket/sudo.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleAllowMixedLocalizations 6 | 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | applet 11 | CFBundleIconFile 12 | applet 13 | CFBundleIdentifier 14 | com.apple.ScriptEditor.id.sudo 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | sudo 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | aplt 25 | LSMinimumSystemVersionByArchitecture 26 | 27 | x86_64 28 | 10.6 29 | 30 | LSRequiresCarbon 31 | 32 | LSBackgroundOnly 33 | 34 | WindowState 35 | 36 | dividerCollapsed 37 | 38 | eventLogLevel 39 | -1 40 | name 41 | ScriptWindowState 42 | positionOfDivider 43 | 333 44 | savedFrame 45 | 1012 288 602 597 0 0 1920 1058 46 | selectedTabView 47 | result 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /LaunchRocket/sudo.app/Contents/MacOS/applet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/sudo.app/Contents/MacOS/applet -------------------------------------------------------------------------------- /LaunchRocket/sudo.app/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | APPLaplt -------------------------------------------------------------------------------- /LaunchRocket/sudo.app/Contents/Resources/Scripts/main.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/sudo.app/Contents/Resources/Scripts/main.scpt -------------------------------------------------------------------------------- /LaunchRocket/sudo.app/Contents/Resources/applet.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/sudo.app/Contents/Resources/applet.icns -------------------------------------------------------------------------------- /LaunchRocket/sudo.app/Contents/Resources/applet.rsrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/sudo.app/Contents/Resources/applet.rsrc -------------------------------------------------------------------------------- /LaunchRocket/sudo.app/Contents/Resources/description.rtfd/TXT.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1265 2 | {\fonttbl} 3 | {\colortbl;\red255\green255\blue255;} 4 | } -------------------------------------------------------------------------------- /LaunchRocket/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/LaunchRocket/yellow.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LaunchRocket 2 | ============ 3 | 4 | A Mac PreferencePane for managing services with `launchd`. For an introduction on `launchd`, see [launchd.info](http://launchd.info/). LaunchRocket was primarily created for managing various services installed by Homebrew, though it should work with most launchd-compatible plists. 5 | 6 | Of particular note is the ability to run some services as root. With Homebrew, most services can simply run as the current user, and this is the desired behavior for a development environment. However, some services (dnsmasq) require being bound to privileged ports, and others (nginx, apache) don't require it, but might in some circumstances. For example, many times it's just easier to run Apache on port 80 and 443 than to deal with code that might not like port numbers in the URLs. I recommend you only run things as root if you absolutely must, to get them working properly. 7 | 8 | ![LaunchRocke Screenshot](/screenshots/LaunchRocket.png) 9 | 10 | Features 11 | -------- 12 | * **Green/Yellow/Red status indicators** 13 | * Green - launchctl reports process is running 14 | * Yellow - LaunchRocket is current executing or waiting on a start/stop command 15 | * Red - launchctl reports process is not running 16 | * **Start/Stop buttons** 17 | * Dynamically updates state based on running state of service 18 | * **As Root option** 19 | * Checking this will cause launchrocket to use root privileges to access launchctl 20 | * Checking this will NOT restart a running service automatically as root 21 | * LaunchRocket will prompt you to authenticate and elevate privileges 22 | * We ask for credentials as seldom as possible, however when launchrocket loads, it checks the current status of every service it's tracking. This means that if you have services running as root, it will prompt you for credentials immediately on load, as an unprivileged account cannot even list launchd services run by a privileged one 23 | * **Preferences file** 24 | * LaunchRocket stores a preferences file in ~/Library/Preferences/com.joshbutts.launchrocket.plist 25 | 26 | Installing 27 | ------------ 28 | 29 | **Requirements** 30 | 31 | * OS X 10.7 or above 32 | * Gatekeeper must be set to allow all apps to run (See issues [#3][] and [#25][]) 33 | 34 | [#3]: https://github.com/jimbojsb/launchrocket/issues/3 35 | [#25]: https://github.com/jimbojsb/launchrocket/issues/25 36 | 37 | The binary release should work on Mac OSX 10.7.0 and above, and might even work on older versions, though it has not been tested on those. The latest builds are compiled on 10.9.1 and targeted at 10.7 in Xcode. 38 | 39 | **Direct Download** 40 | 41 | Download and unzip the binary release from the [release page](https://github.com/jimbojsb/launchrocket/releases) 42 | 43 | *Note: This zip was created with Finder's "Compress" utility. I've heard that there are issues unzipping it with other tools. I will try to provide a more compatible zipfile on the releases page. ([related info here](http://stackoverflow.com/questions/107903/how-to-create-a-zip-file-in-the-same-format-as-the-finders-compress-menu-item))* 44 | 45 | **Homebrew** 46 | 47 | Launchrocket is available as a Cask for [Homebrew Cask](https://github.com/phinze/homebrew-cask). As of 3/3/2014 it is no longer maintained in my personal tap, and is now available in the main Homebrew Cask repo. 48 | 49 | brew cask install launchrocket 50 | 51 | 52 | Further information about running as root 53 | ----------------------------------------- 54 | LaunchRocket uses an AppleScript helper to obtain root privileges. This is a hacky way of accomplishing it, but it avoids having to actually install a privileged helper outside of the prefpane bundle with SMJobBless and is WAY simpler. This is not the most secure approach and does not use code signing. As such, it is possible that if LaunchRocket's AppleScript helper has cached your privileged authorization, another application maliciously address that helper and request it execute other commands with root level privileges. I considered this a reasonable tradeoff between security, usability and code simplicity. Closing System Preferences or simply switching back to the main view will terminate the helper and drop privileges. If you don't have anything running as root, no programs can request that the helper execute code with privilieges without triggering a credential prompt. 55 | 56 | Contributing 57 | ------------ 58 | Found a bug? File an issue and I'll take a look. Pull requests are welcome. If you'd like to chat about a feature or issue, I can sometimes be found in #launchrocket on Freenode. 59 | -------------------------------------------------------------------------------- /applescript/sudo.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/applescript/sudo.scpt -------------------------------------------------------------------------------- /screenshots/LaunchRocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbojsb/launchrocket/64456ba8ccacff29016bdee2d1389c563b1a4105/screenshots/LaunchRocket.png --------------------------------------------------------------------------------