├── .gitignore ├── WebShell.xcodeproj └── project.pbxproj ├── WebShell ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── fileicon-1024.png │ │ └── fileicon-514.png │ ├── Contents.json │ ├── MenuIcon.imageset │ │ ├── -webpage-1.png │ │ ├── -webpage.png │ │ └── Contents.json │ └── Udemy.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png ├── Base.lproj │ └── Main.storyboard ├── Core │ ├── WSAppDelegate.swift │ ├── WSApplication.swift │ ├── WSBaseSettings.swift │ ├── WSCore.swift │ ├── WSCustomInject.swift │ ├── WSDebug.swift │ ├── WSDownloadManager.swift │ ├── WSEventMonitor.swift │ ├── WSFileHandler.swift │ ├── WSInjector.swift │ ├── WSPageActions.swift │ ├── WSPasswordManager.swift │ ├── WSStringExtension.swift │ ├── WSTrackpadGestures.swift │ ├── WSViewController.swift │ ├── WSWebViewFunctions.swift │ └── WSWindow.swift ├── Credits.rtf ├── Settings.swift ├── Sites │ ├── Udemy │ │ └── Settings.swift │ └── WebShell │ │ └── Settings.swift ├── Support │ ├── Battery.swift │ ├── Notifications.swift │ └── navigator_geolocation_getCurrentPosition.swift ├── Udemy-Info.plist ├── WSCore.swift ├── WSDebug.swift ├── WSDownloadManager.swift ├── WSFileHandler.swift ├── WSInjector.swift ├── WSPageActions.swift ├── WSStringExtension.swift ├── WSTrackpadGestures.swift ├── WebShell-Info.plist └── nl.lproj │ └── Main.strings ├── contributing.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | ### Swift ### 2 | # Xcode 3 | # 4 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData/ 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata/ 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xccheckout 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | *.dSYM.zip 30 | *.dSYM 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | # Packages/ 40 | # Package.pins 41 | .build/ 42 | 43 | # CocoaPods - Refactored to standalone file 44 | 45 | # Carthage - Refactored to standalone file 46 | 47 | # fastlane 48 | # 49 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 50 | # screenshots whenever they are needed. 51 | # For more information about the recommended setup visit: 52 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 53 | 54 | fastlane/report.xml 55 | fastlane/Preview.html 56 | fastlane/screenshots 57 | fastlane/test_output 58 | 59 | ### Xcode ### 60 | # Xcode 61 | # 62 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 63 | 64 | ## Build generated 65 | 66 | ## Various settings 67 | 68 | ## Other 69 | 70 | ### Xcode Patch ### 71 | *.xcodeproj/* 72 | !*.xcodeproj/project.pbxproj 73 | !*.xcodeproj/xcshareddata/ 74 | !*.xcworkspace/contents.xcworkspacedata 75 | /*.gcno -------------------------------------------------------------------------------- /WebShell.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 25B2225F1C25328E00F848B7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 25B2225E1C25328E00F848B7 /* Assets.xcassets */; }; 11 | 25B222621C25328E00F848B7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 25B222601C25328E00F848B7 /* Main.storyboard */; }; 12 | 6E2DD7DE1CC56209000FA61A /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 6E2DD7DD1CC56209000FA61A /* Credits.rtf */; }; 13 | 6EE64A961CCBEAA000E9DA9B /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE64A951CCBEAA000E9DA9B /* Settings.swift */; }; 14 | DD0D711A1FDCF595002F50D1 /* WSStringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71081FDCF595002F50D1 /* WSStringExtension.swift */; }; 15 | DD0D711B1FDCF595002F50D1 /* WSDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71091FDCF595002F50D1 /* WSDownloadManager.swift */; }; 16 | DD0D711C1FDCF595002F50D1 /* WSInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710A1FDCF595002F50D1 /* WSInjector.swift */; }; 17 | DD0D711D1FDCF595002F50D1 /* WSWebViewFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710B1FDCF595002F50D1 /* WSWebViewFunctions.swift */; }; 18 | DD0D711E1FDCF595002F50D1 /* WSPasswordManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710C1FDCF595002F50D1 /* WSPasswordManager.swift */; }; 19 | DD0D711F1FDCF595002F50D1 /* WSEventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710D1FDCF595002F50D1 /* WSEventMonitor.swift */; }; 20 | DD0D71211FDCF595002F50D1 /* WSDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710F1FDCF595002F50D1 /* WSDebug.swift */; }; 21 | DD0D71221FDCF595002F50D1 /* WSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71101FDCF595002F50D1 /* WSViewController.swift */; }; 22 | DD0D71231FDCF595002F50D1 /* WSCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71111FDCF595002F50D1 /* WSCore.swift */; }; 23 | DD0D71241FDCF595002F50D1 /* WSCustomInject.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71121FDCF595002F50D1 /* WSCustomInject.swift */; }; 24 | DD0D71251FDCF595002F50D1 /* WSBaseSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71131FDCF595002F50D1 /* WSBaseSettings.swift */; }; 25 | DD0D71271FDCF595002F50D1 /* WSPageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71151FDCF595002F50D1 /* WSPageActions.swift */; }; 26 | DD0D71281FDCF595002F50D1 /* WSTrackpadGestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71161FDCF595002F50D1 /* WSTrackpadGestures.swift */; }; 27 | DD0D71291FDCF595002F50D1 /* WSApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71171FDCF595002F50D1 /* WSApplication.swift */; }; 28 | DD0D712A1FDCF595002F50D1 /* WSFileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71181FDCF595002F50D1 /* WSFileHandler.swift */; }; 29 | DD0D712B1FDCF595002F50D1 /* WSAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71191FDCF595002F50D1 /* WSAppDelegate.swift */; }; 30 | DD0D712C1FDCF5B6002F50D1 /* WSAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71191FDCF595002F50D1 /* WSAppDelegate.swift */; }; 31 | DD0D712D1FDCF5B6002F50D1 /* WSApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71171FDCF595002F50D1 /* WSApplication.swift */; }; 32 | DD0D712E1FDCF5B6002F50D1 /* WSBaseSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71131FDCF595002F50D1 /* WSBaseSettings.swift */; }; 33 | DD0D712F1FDCF5B6002F50D1 /* WSCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71111FDCF595002F50D1 /* WSCore.swift */; }; 34 | DD0D71301FDCF5B6002F50D1 /* WSCustomInject.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71121FDCF595002F50D1 /* WSCustomInject.swift */; }; 35 | DD0D71311FDCF5B6002F50D1 /* WSDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710F1FDCF595002F50D1 /* WSDebug.swift */; }; 36 | DD0D71321FDCF5B6002F50D1 /* WSDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71091FDCF595002F50D1 /* WSDownloadManager.swift */; }; 37 | DD0D71331FDCF5B6002F50D1 /* WSEventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710D1FDCF595002F50D1 /* WSEventMonitor.swift */; }; 38 | DD0D71341FDCF5B6002F50D1 /* WSFileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71181FDCF595002F50D1 /* WSFileHandler.swift */; }; 39 | DD0D71351FDCF5B6002F50D1 /* WSInjector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710A1FDCF595002F50D1 /* WSInjector.swift */; }; 40 | DD0D71361FDCF5B6002F50D1 /* WSPageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71151FDCF595002F50D1 /* WSPageActions.swift */; }; 41 | DD0D71371FDCF5B6002F50D1 /* WSPasswordManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710C1FDCF595002F50D1 /* WSPasswordManager.swift */; }; 42 | DD0D71391FDCF5B6002F50D1 /* WSStringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71081FDCF595002F50D1 /* WSStringExtension.swift */; }; 43 | DD0D713A1FDCF5B6002F50D1 /* WSTrackpadGestures.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71161FDCF595002F50D1 /* WSTrackpadGestures.swift */; }; 44 | DD0D713B1FDCF5B6002F50D1 /* WSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71101FDCF595002F50D1 /* WSViewController.swift */; }; 45 | DD0D713D1FDCF5B6002F50D1 /* WSWebViewFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D710B1FDCF595002F50D1 /* WSWebViewFunctions.swift */; }; 46 | DD0D71421FDCF5FD002F50D1 /* navigator_geolocation_getCurrentPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D713F1FDCF5FD002F50D1 /* navigator_geolocation_getCurrentPosition.swift */; }; 47 | DD0D71431FDCF5FD002F50D1 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71401FDCF5FD002F50D1 /* Notifications.swift */; }; 48 | DD0D71441FDCF5FD002F50D1 /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71411FDCF5FD002F50D1 /* Battery.swift */; }; 49 | DD0D71451FDCF610002F50D1 /* navigator_geolocation_getCurrentPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D713F1FDCF5FD002F50D1 /* navigator_geolocation_getCurrentPosition.swift */; }; 50 | DD0D71461FDCF610002F50D1 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71401FDCF5FD002F50D1 /* Notifications.swift */; }; 51 | DD0D71471FDCF610002F50D1 /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0D71411FDCF5FD002F50D1 /* Battery.swift */; }; 52 | DD593BC61FDB7F4200C15D57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 25B2225E1C25328E00F848B7 /* Assets.xcassets */; }; 53 | DD593BC71FDB7F4200C15D57 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 6E2DD7DD1CC56209000FA61A /* Credits.rtf */; }; 54 | DD593BC81FDB7F4200C15D57 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 25B222601C25328E00F848B7 /* Main.storyboard */; }; 55 | DD593BD91FDB80DC00C15D57 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD593BD51FDB80CB00C15D57 /* Settings.swift */; }; 56 | DEC374881FFEBE4000A8B04E /* WSWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC374871FFEBE4000A8B04E /* WSWindow.swift */; }; 57 | DEC374891FFEBE4000A8B04E /* WSWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC374871FFEBE4000A8B04E /* WSWindow.swift */; }; 58 | /* End PBXBuildFile section */ 59 | 60 | /* Begin PBXFileReference section */ 61 | 25B222571C25328E00F848B7 /* WebShell.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WebShell.app; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 25B2225E1C25328E00F848B7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63 | 25B222611C25328E00F848B7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 64 | 6E2DD7DD1CC56209000FA61A /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 65 | 6E3733301C2890A600CE0058 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Main.strings; sourceTree = ""; }; 66 | 6EE64A951CCBEAA000E9DA9B /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 67 | DD0D71081FDCF595002F50D1 /* WSStringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSStringExtension.swift; sourceTree = ""; }; 68 | DD0D71091FDCF595002F50D1 /* WSDownloadManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSDownloadManager.swift; sourceTree = ""; }; 69 | DD0D710A1FDCF595002F50D1 /* WSInjector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSInjector.swift; sourceTree = ""; }; 70 | DD0D710B1FDCF595002F50D1 /* WSWebViewFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSWebViewFunctions.swift; sourceTree = ""; }; 71 | DD0D710C1FDCF595002F50D1 /* WSPasswordManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSPasswordManager.swift; sourceTree = ""; }; 72 | DD0D710D1FDCF595002F50D1 /* WSEventMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSEventMonitor.swift; sourceTree = ""; }; 73 | DD0D710F1FDCF595002F50D1 /* WSDebug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSDebug.swift; sourceTree = ""; }; 74 | DD0D71101FDCF595002F50D1 /* WSViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSViewController.swift; sourceTree = ""; }; 75 | DD0D71111FDCF595002F50D1 /* WSCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSCore.swift; sourceTree = ""; }; 76 | DD0D71121FDCF595002F50D1 /* WSCustomInject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSCustomInject.swift; sourceTree = ""; }; 77 | DD0D71131FDCF595002F50D1 /* WSBaseSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSBaseSettings.swift; sourceTree = ""; }; 78 | DD0D71151FDCF595002F50D1 /* WSPageActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSPageActions.swift; sourceTree = ""; }; 79 | DD0D71161FDCF595002F50D1 /* WSTrackpadGestures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSTrackpadGestures.swift; sourceTree = ""; }; 80 | DD0D71171FDCF595002F50D1 /* WSApplication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSApplication.swift; sourceTree = ""; }; 81 | DD0D71181FDCF595002F50D1 /* WSFileHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSFileHandler.swift; sourceTree = ""; }; 82 | DD0D71191FDCF595002F50D1 /* WSAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSAppDelegate.swift; sourceTree = ""; }; 83 | DD0D713F1FDCF5FD002F50D1 /* navigator_geolocation_getCurrentPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = navigator_geolocation_getCurrentPosition.swift; sourceTree = ""; }; 84 | DD0D71401FDCF5FD002F50D1 /* Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; 85 | DD0D71411FDCF5FD002F50D1 /* Battery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; }; 86 | DD0D716A1FDCF7C1002F50D1 /* Udemy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Udemy-Info.plist"; sourceTree = ""; }; 87 | DD0D716B1FDCF7C1002F50D1 /* WebShell-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "WebShell-Info.plist"; sourceTree = ""; }; 88 | DD593BCD1FDB7F4200C15D57 /* Udemy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Udemy.app; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | DD593BD51FDB80CB00C15D57 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 90 | DEC374871FFEBE4000A8B04E /* WSWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WSWindow.swift; sourceTree = ""; }; 91 | /* End PBXFileReference section */ 92 | 93 | /* Begin PBXFrameworksBuildPhase section */ 94 | 25B222541C25328E00F848B7 /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | DD593BC41FDB7F4200C15D57 /* Frameworks */ = { 102 | isa = PBXFrameworksBuildPhase; 103 | buildActionMask = 2147483647; 104 | files = ( 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXFrameworksBuildPhase section */ 109 | 110 | /* Begin PBXGroup section */ 111 | 25B2224E1C25328E00F848B7 = { 112 | isa = PBXGroup; 113 | children = ( 114 | 25B222591C25328E00F848B7 /* WebShell */, 115 | ); 116 | sourceTree = ""; 117 | }; 118 | 25B222581C25328E00F848B7 /* Products */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 25B222571C25328E00F848B7 /* WebShell.app */, 122 | DD593BCD1FDB7F4200C15D57 /* Udemy.app */, 123 | ); 124 | name = Products; 125 | path = ..; 126 | sourceTree = ""; 127 | }; 128 | 25B222591C25328E00F848B7 /* WebShell */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | DD0D71071FDCF595002F50D1 /* Core */, 132 | DD0D713E1FDCF5FD002F50D1 /* Support */, 133 | DD593BD01FDB7F9100C15D57 /* Sites */, 134 | DD0D716C1FDCF7E5002F50D1 /* Resources */, 135 | DD0D716A1FDCF7C1002F50D1 /* Udemy-Info.plist */, 136 | DD0D716B1FDCF7C1002F50D1 /* WebShell-Info.plist */, 137 | 6E2DD7DD1CC56209000FA61A /* Credits.rtf */, 138 | 25B222581C25328E00F848B7 /* Products */, 139 | ); 140 | path = WebShell; 141 | sourceTree = ""; 142 | }; 143 | DD0D71071FDCF595002F50D1 /* Core */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | DD0D71191FDCF595002F50D1 /* WSAppDelegate.swift */, 147 | DD0D71171FDCF595002F50D1 /* WSApplication.swift */, 148 | DD0D71131FDCF595002F50D1 /* WSBaseSettings.swift */, 149 | DD0D71111FDCF595002F50D1 /* WSCore.swift */, 150 | DD0D71121FDCF595002F50D1 /* WSCustomInject.swift */, 151 | DD0D710F1FDCF595002F50D1 /* WSDebug.swift */, 152 | DD0D71091FDCF595002F50D1 /* WSDownloadManager.swift */, 153 | DD0D710D1FDCF595002F50D1 /* WSEventMonitor.swift */, 154 | DD0D71181FDCF595002F50D1 /* WSFileHandler.swift */, 155 | DD0D710A1FDCF595002F50D1 /* WSInjector.swift */, 156 | DD0D71151FDCF595002F50D1 /* WSPageActions.swift */, 157 | DD0D710C1FDCF595002F50D1 /* WSPasswordManager.swift */, 158 | DD0D71081FDCF595002F50D1 /* WSStringExtension.swift */, 159 | DD0D71161FDCF595002F50D1 /* WSTrackpadGestures.swift */, 160 | DD0D71101FDCF595002F50D1 /* WSViewController.swift */, 161 | DD0D710B1FDCF595002F50D1 /* WSWebViewFunctions.swift */, 162 | DEC374871FFEBE4000A8B04E /* WSWindow.swift */, 163 | ); 164 | path = Core; 165 | sourceTree = ""; 166 | }; 167 | DD0D713E1FDCF5FD002F50D1 /* Support */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | DD0D713F1FDCF5FD002F50D1 /* navigator_geolocation_getCurrentPosition.swift */, 171 | DD0D71401FDCF5FD002F50D1 /* Notifications.swift */, 172 | DD0D71411FDCF5FD002F50D1 /* Battery.swift */, 173 | ); 174 | path = Support; 175 | sourceTree = ""; 176 | }; 177 | DD0D716C1FDCF7E5002F50D1 /* Resources */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 25B2225E1C25328E00F848B7 /* Assets.xcassets */, 181 | 25B222601C25328E00F848B7 /* Main.storyboard */, 182 | ); 183 | name = Resources; 184 | sourceTree = ""; 185 | }; 186 | DD0D716D1FDCF80A002F50D1 /* WebShell */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 6EE64A951CCBEAA000E9DA9B /* Settings.swift */, 190 | ); 191 | path = WebShell; 192 | sourceTree = ""; 193 | }; 194 | DD593BD01FDB7F9100C15D57 /* Sites */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | DD0D716D1FDCF80A002F50D1 /* WebShell */, 198 | DD593BD41FDB80CB00C15D57 /* Udemy */, 199 | ); 200 | path = Sites; 201 | sourceTree = ""; 202 | }; 203 | DD593BD41FDB80CB00C15D57 /* Udemy */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | DD593BD51FDB80CB00C15D57 /* Settings.swift */, 207 | ); 208 | path = Udemy; 209 | sourceTree = ""; 210 | }; 211 | /* End PBXGroup section */ 212 | 213 | /* Begin PBXNativeTarget section */ 214 | 25B222561C25328E00F848B7 /* WebShell */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = 25B222661C25328E00F848B7 /* Build configuration list for PBXNativeTarget "WebShell" */; 217 | buildPhases = ( 218 | 25B222531C25328E00F848B7 /* Sources */, 219 | 25B222541C25328E00F848B7 /* Frameworks */, 220 | 25B222551C25328E00F848B7 /* Resources */, 221 | ); 222 | buildRules = ( 223 | ); 224 | dependencies = ( 225 | ); 226 | name = WebShell; 227 | productName = WebShell; 228 | productReference = 25B222571C25328E00F848B7 /* WebShell.app */; 229 | productType = "com.apple.product-type.application"; 230 | }; 231 | DD593BAD1FDB7F4200C15D57 /* Udemy */ = { 232 | isa = PBXNativeTarget; 233 | buildConfigurationList = DD593BCA1FDB7F4200C15D57 /* Build configuration list for PBXNativeTarget "Udemy" */; 234 | buildPhases = ( 235 | DD593BAE1FDB7F4200C15D57 /* Sources */, 236 | DD593BC41FDB7F4200C15D57 /* Frameworks */, 237 | DD593BC51FDB7F4200C15D57 /* Resources */, 238 | ); 239 | buildRules = ( 240 | ); 241 | dependencies = ( 242 | ); 243 | name = Udemy; 244 | productName = WebShell; 245 | productReference = DD593BCD1FDB7F4200C15D57 /* Udemy.app */; 246 | productType = "com.apple.product-type.application"; 247 | }; 248 | /* End PBXNativeTarget section */ 249 | 250 | /* Begin PBXProject section */ 251 | 25B2224F1C25328E00F848B7 /* Project object */ = { 252 | isa = PBXProject; 253 | attributes = { 254 | LastUpgradeCheck = 0900; 255 | ORGANIZATIONNAME = RandyLu; 256 | TargetAttributes = { 257 | 25B222561C25328E00F848B7 = { 258 | CreatedOnToolsVersion = 7.0.1; 259 | DevelopmentTeam = 4V2D72S45C; 260 | LastSwiftMigration = 0900; 261 | ProvisioningStyle = Automatic; 262 | }; 263 | DD593BAD1FDB7F4200C15D57 = { 264 | DevelopmentTeam = 4V2D72S45C; 265 | ProvisioningStyle = Automatic; 266 | }; 267 | }; 268 | }; 269 | buildConfigurationList = 25B222521C25328E00F848B7 /* Build configuration list for PBXProject "WebShell" */; 270 | compatibilityVersion = "Xcode 3.2"; 271 | developmentRegion = English; 272 | hasScannedForEncodings = 0; 273 | knownRegions = ( 274 | en, 275 | Base, 276 | ); 277 | mainGroup = 25B2224E1C25328E00F848B7; 278 | productRefGroup = 25B222581C25328E00F848B7 /* Products */; 279 | projectDirPath = ""; 280 | projectRoot = ""; 281 | targets = ( 282 | 25B222561C25328E00F848B7 /* WebShell */, 283 | DD593BAD1FDB7F4200C15D57 /* Udemy */, 284 | ); 285 | }; 286 | /* End PBXProject section */ 287 | 288 | /* Begin PBXResourcesBuildPhase section */ 289 | 25B222551C25328E00F848B7 /* Resources */ = { 290 | isa = PBXResourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 25B2225F1C25328E00F848B7 /* Assets.xcassets in Resources */, 294 | 6E2DD7DE1CC56209000FA61A /* Credits.rtf in Resources */, 295 | 25B222621C25328E00F848B7 /* Main.storyboard in Resources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | DD593BC51FDB7F4200C15D57 /* Resources */ = { 300 | isa = PBXResourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | DD593BC61FDB7F4200C15D57 /* Assets.xcassets in Resources */, 304 | DD593BC71FDB7F4200C15D57 /* Credits.rtf in Resources */, 305 | DD593BC81FDB7F4200C15D57 /* Main.storyboard in Resources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXResourcesBuildPhase section */ 310 | 311 | /* Begin PBXSourcesBuildPhase section */ 312 | 25B222531C25328E00F848B7 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | DD0D71291FDCF595002F50D1 /* WSApplication.swift in Sources */, 317 | DD0D71421FDCF5FD002F50D1 /* navigator_geolocation_getCurrentPosition.swift in Sources */, 318 | DD0D711E1FDCF595002F50D1 /* WSPasswordManager.swift in Sources */, 319 | DD0D711A1FDCF595002F50D1 /* WSStringExtension.swift in Sources */, 320 | DD0D711F1FDCF595002F50D1 /* WSEventMonitor.swift in Sources */, 321 | DD0D71251FDCF595002F50D1 /* WSBaseSettings.swift in Sources */, 322 | DD0D712B1FDCF595002F50D1 /* WSAppDelegate.swift in Sources */, 323 | DD0D71211FDCF595002F50D1 /* WSDebug.swift in Sources */, 324 | DD0D712A1FDCF595002F50D1 /* WSFileHandler.swift in Sources */, 325 | 6EE64A961CCBEAA000E9DA9B /* Settings.swift in Sources */, 326 | DD0D71221FDCF595002F50D1 /* WSViewController.swift in Sources */, 327 | DD0D71231FDCF595002F50D1 /* WSCore.swift in Sources */, 328 | DEC374881FFEBE4000A8B04E /* WSWindow.swift in Sources */, 329 | DD0D711C1FDCF595002F50D1 /* WSInjector.swift in Sources */, 330 | DD0D71431FDCF5FD002F50D1 /* Notifications.swift in Sources */, 331 | DD0D71271FDCF595002F50D1 /* WSPageActions.swift in Sources */, 332 | DD0D71441FDCF5FD002F50D1 /* Battery.swift in Sources */, 333 | DD0D711B1FDCF595002F50D1 /* WSDownloadManager.swift in Sources */, 334 | DD0D711D1FDCF595002F50D1 /* WSWebViewFunctions.swift in Sources */, 335 | DD0D71241FDCF595002F50D1 /* WSCustomInject.swift in Sources */, 336 | DD0D71281FDCF595002F50D1 /* WSTrackpadGestures.swift in Sources */, 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | }; 340 | DD593BAE1FDB7F4200C15D57 /* Sources */ = { 341 | isa = PBXSourcesBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | DD0D712C1FDCF5B6002F50D1 /* WSAppDelegate.swift in Sources */, 345 | DD0D713D1FDCF5B6002F50D1 /* WSWebViewFunctions.swift in Sources */, 346 | DD0D71451FDCF610002F50D1 /* navigator_geolocation_getCurrentPosition.swift in Sources */, 347 | DD0D71361FDCF5B6002F50D1 /* WSPageActions.swift in Sources */, 348 | DD0D71341FDCF5B6002F50D1 /* WSFileHandler.swift in Sources */, 349 | DD0D71391FDCF5B6002F50D1 /* WSStringExtension.swift in Sources */, 350 | DD0D71301FDCF5B6002F50D1 /* WSCustomInject.swift in Sources */, 351 | DD0D712F1FDCF5B6002F50D1 /* WSCore.swift in Sources */, 352 | DD0D71351FDCF5B6002F50D1 /* WSInjector.swift in Sources */, 353 | DD0D712D1FDCF5B6002F50D1 /* WSApplication.swift in Sources */, 354 | DD0D71311FDCF5B6002F50D1 /* WSDebug.swift in Sources */, 355 | DD0D712E1FDCF5B6002F50D1 /* WSBaseSettings.swift in Sources */, 356 | DEC374891FFEBE4000A8B04E /* WSWindow.swift in Sources */, 357 | DD0D71371FDCF5B6002F50D1 /* WSPasswordManager.swift in Sources */, 358 | DD0D71461FDCF610002F50D1 /* Notifications.swift in Sources */, 359 | DD0D71331FDCF5B6002F50D1 /* WSEventMonitor.swift in Sources */, 360 | DD0D71471FDCF610002F50D1 /* Battery.swift in Sources */, 361 | DD0D713B1FDCF5B6002F50D1 /* WSViewController.swift in Sources */, 362 | DD593BD91FDB80DC00C15D57 /* Settings.swift in Sources */, 363 | DD0D71321FDCF5B6002F50D1 /* WSDownloadManager.swift in Sources */, 364 | DD0D713A1FDCF5B6002F50D1 /* WSTrackpadGestures.swift in Sources */, 365 | ); 366 | runOnlyForDeploymentPostprocessing = 0; 367 | }; 368 | /* End PBXSourcesBuildPhase section */ 369 | 370 | /* Begin PBXVariantGroup section */ 371 | 25B222601C25328E00F848B7 /* Main.storyboard */ = { 372 | isa = PBXVariantGroup; 373 | children = ( 374 | 25B222611C25328E00F848B7 /* Base */, 375 | 6E3733301C2890A600CE0058 /* nl */, 376 | ); 377 | name = Main.storyboard; 378 | sourceTree = ""; 379 | }; 380 | /* End PBXVariantGroup section */ 381 | 382 | /* Begin XCBuildConfiguration section */ 383 | 25B222641C25328E00F848B7 /* Debug */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ALWAYS_SEARCH_USER_PATHS = NO; 387 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 388 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 389 | CLANG_CXX_LIBRARY = "libc++"; 390 | CLANG_ENABLE_MODULES = YES; 391 | CLANG_ENABLE_OBJC_ARC = YES; 392 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 393 | CLANG_WARN_BOOL_CONVERSION = YES; 394 | CLANG_WARN_COMMA = YES; 395 | CLANG_WARN_CONSTANT_CONVERSION = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_EMPTY_BODY = YES; 398 | CLANG_WARN_ENUM_CONVERSION = YES; 399 | CLANG_WARN_INFINITE_RECURSION = YES; 400 | CLANG_WARN_INT_CONVERSION = YES; 401 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 404 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 405 | CLANG_WARN_STRICT_PROTOTYPES = YES; 406 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 407 | CLANG_WARN_UNREACHABLE_CODE = YES; 408 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 409 | CODE_SIGN_IDENTITY = "-"; 410 | COPY_PHASE_STRIP = NO; 411 | DEBUG_INFORMATION_FORMAT = dwarf; 412 | ENABLE_STRICT_OBJC_MSGSEND = YES; 413 | ENABLE_TESTABILITY = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu99; 415 | GCC_DYNAMIC_NO_PIC = NO; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_OPTIMIZATION_LEVEL = 0; 418 | GCC_PREPROCESSOR_DEFINITIONS = ( 419 | "DEBUG=1", 420 | "$(inherited)", 421 | ); 422 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 423 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 424 | GCC_WARN_UNDECLARED_SELECTOR = YES; 425 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 426 | GCC_WARN_UNUSED_FUNCTION = YES; 427 | GCC_WARN_UNUSED_VARIABLE = YES; 428 | MACOSX_DEPLOYMENT_TARGET = 10.11; 429 | MTL_ENABLE_DEBUG_INFO = YES; 430 | ONLY_ACTIVE_ARCH = YES; 431 | SDKROOT = macosx; 432 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 433 | }; 434 | name = Debug; 435 | }; 436 | 25B222651C25328E00F848B7 /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ALWAYS_SEARCH_USER_PATHS = NO; 440 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 441 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 442 | CLANG_CXX_LIBRARY = "libc++"; 443 | CLANG_ENABLE_MODULES = YES; 444 | CLANG_ENABLE_OBJC_ARC = YES; 445 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 446 | CLANG_WARN_BOOL_CONVERSION = YES; 447 | CLANG_WARN_COMMA = YES; 448 | CLANG_WARN_CONSTANT_CONVERSION = YES; 449 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 450 | CLANG_WARN_EMPTY_BODY = YES; 451 | CLANG_WARN_ENUM_CONVERSION = YES; 452 | CLANG_WARN_INFINITE_RECURSION = YES; 453 | CLANG_WARN_INT_CONVERSION = YES; 454 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 455 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 456 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 457 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 458 | CLANG_WARN_STRICT_PROTOTYPES = YES; 459 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 460 | CLANG_WARN_UNREACHABLE_CODE = YES; 461 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 462 | CODE_SIGN_IDENTITY = "-"; 463 | COPY_PHASE_STRIP = NO; 464 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 465 | ENABLE_NS_ASSERTIONS = NO; 466 | ENABLE_STRICT_OBJC_MSGSEND = YES; 467 | GCC_C_LANGUAGE_STANDARD = gnu99; 468 | GCC_NO_COMMON_BLOCKS = YES; 469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 471 | GCC_WARN_UNDECLARED_SELECTOR = YES; 472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 473 | GCC_WARN_UNUSED_FUNCTION = YES; 474 | GCC_WARN_UNUSED_VARIABLE = YES; 475 | MACOSX_DEPLOYMENT_TARGET = 10.11; 476 | MTL_ENABLE_DEBUG_INFO = NO; 477 | SDKROOT = macosx; 478 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 479 | }; 480 | name = Release; 481 | }; 482 | 25B222671C25328E00F848B7 /* Debug */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 486 | CODE_SIGN_IDENTITY = "Mac Developer"; 487 | CODE_SIGN_STYLE = Automatic; 488 | COMBINE_HIDPI_IMAGES = YES; 489 | DEVELOPMENT_TEAM = 4V2D72S45C; 490 | INFOPLIST_FILE = "WebShell/WebShell-Info.plist"; 491 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 492 | PRODUCT_BUNDLE_IDENTIFIER = io.github.djyde.WebShell; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | PROVISIONING_PROFILE_SPECIFIER = ""; 495 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 496 | SWIFT_VERSION = 4.0; 497 | }; 498 | name = Debug; 499 | }; 500 | 25B222681C25328E00F848B7 /* Release */ = { 501 | isa = XCBuildConfiguration; 502 | buildSettings = { 503 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 504 | CODE_SIGN_IDENTITY = "Mac Developer"; 505 | CODE_SIGN_STYLE = Automatic; 506 | COMBINE_HIDPI_IMAGES = YES; 507 | DEVELOPMENT_TEAM = 4V2D72S45C; 508 | INFOPLIST_FILE = "WebShell/WebShell-Info.plist"; 509 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 510 | PRODUCT_BUNDLE_IDENTIFIER = io.github.djyde.WebShell; 511 | PRODUCT_NAME = "$(TARGET_NAME)"; 512 | PROVISIONING_PROFILE_SPECIFIER = ""; 513 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 514 | SWIFT_VERSION = 4.0; 515 | }; 516 | name = Release; 517 | }; 518 | DD593BCB1FDB7F4200C15D57 /* Debug */ = { 519 | isa = XCBuildConfiguration; 520 | buildSettings = { 521 | ASSETCATALOG_COMPILER_APPICON_NAME = Udemy; 522 | CODE_SIGN_IDENTITY = "Mac Developer"; 523 | CODE_SIGN_STYLE = Automatic; 524 | COMBINE_HIDPI_IMAGES = YES; 525 | DEVELOPMENT_TEAM = 4V2D72S45C; 526 | INFOPLIST_FILE = "WebShell/Udemy-Info.plist"; 527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 528 | PRODUCT_BUNDLE_IDENTIFIER = io.github.djyde.WebShell; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | PROVISIONING_PROFILE_SPECIFIER = ""; 531 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 532 | SWIFT_VERSION = 4.0; 533 | }; 534 | name = Debug; 535 | }; 536 | DD593BCC1FDB7F4200C15D57 /* Release */ = { 537 | isa = XCBuildConfiguration; 538 | buildSettings = { 539 | ASSETCATALOG_COMPILER_APPICON_NAME = Udemy; 540 | CODE_SIGN_IDENTITY = "Mac Developer"; 541 | CODE_SIGN_STYLE = Automatic; 542 | COMBINE_HIDPI_IMAGES = YES; 543 | DEVELOPMENT_TEAM = 4V2D72S45C; 544 | INFOPLIST_FILE = "WebShell/Udemy-Info.plist"; 545 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 546 | PRODUCT_BUNDLE_IDENTIFIER = io.github.djyde.WebShell; 547 | PRODUCT_NAME = "$(TARGET_NAME)"; 548 | PROVISIONING_PROFILE_SPECIFIER = ""; 549 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 550 | SWIFT_VERSION = 4.0; 551 | }; 552 | name = Release; 553 | }; 554 | /* End XCBuildConfiguration section */ 555 | 556 | /* Begin XCConfigurationList section */ 557 | 25B222521C25328E00F848B7 /* Build configuration list for PBXProject "WebShell" */ = { 558 | isa = XCConfigurationList; 559 | buildConfigurations = ( 560 | 25B222641C25328E00F848B7 /* Debug */, 561 | 25B222651C25328E00F848B7 /* Release */, 562 | ); 563 | defaultConfigurationIsVisible = 0; 564 | defaultConfigurationName = Release; 565 | }; 566 | 25B222661C25328E00F848B7 /* Build configuration list for PBXNativeTarget "WebShell" */ = { 567 | isa = XCConfigurationList; 568 | buildConfigurations = ( 569 | 25B222671C25328E00F848B7 /* Debug */, 570 | 25B222681C25328E00F848B7 /* Release */, 571 | ); 572 | defaultConfigurationIsVisible = 0; 573 | defaultConfigurationName = Release; 574 | }; 575 | DD593BCA1FDB7F4200C15D57 /* Build configuration list for PBXNativeTarget "Udemy" */ = { 576 | isa = XCConfigurationList; 577 | buildConfigurations = ( 578 | DD593BCB1FDB7F4200C15D57 /* Debug */, 579 | DD593BCC1FDB7F4200C15D57 /* Release */, 580 | ); 581 | defaultConfigurationIsVisible = 0; 582 | defaultConfigurationName = Release; 583 | }; 584 | /* End XCConfigurationList section */ 585 | }; 586 | rootObject = 25B2224F1C25328E00F848B7 /* Project object */; 587 | } 588 | -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "512x512", 45 | "idiom" : "mac", 46 | "filename" : "fileicon-514.png", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "size" : "512x512", 51 | "idiom" : "mac", 52 | "filename" : "fileicon-1024.png", 53 | "scale" : "2x" 54 | } 55 | ], 56 | "info" : { 57 | "version" : 1, 58 | "author" : "xcode" 59 | } 60 | } -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/AppIcon.appiconset/fileicon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/AppIcon.appiconset/fileicon-1024.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/AppIcon.appiconset/fileicon-514.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/AppIcon.appiconset/fileicon-514.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/MenuIcon.imageset/-webpage-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/MenuIcon.imageset/-webpage-1.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/MenuIcon.imageset/-webpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/MenuIcon.imageset/-webpage.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/MenuIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "-webpage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "filename" : "-webpage-1.png", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | }, 18 | "properties" : { 19 | "template-rendering-intent" : "template" 20 | } 21 | } -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "Iconizer", 4 | "version" : "1" 5 | }, 6 | "images" : [ 7 | { 8 | "size" : "512x512", 9 | "filename" : "icon_512x512@2x.png", 10 | "idiom" : "mac", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "size" : "512x512", 15 | "filename" : "icon_512x512.png", 16 | "idiom" : "mac", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "size" : "256x256", 21 | "filename" : "icon_256x256@2x.png", 22 | "idiom" : "mac", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "size" : "256x256", 27 | "filename" : "icon_256x256.png", 28 | "idiom" : "mac", 29 | "scale" : "1x" 30 | }, 31 | { 32 | "size" : "128x128", 33 | "filename" : "icon_128x128@2x.png", 34 | "idiom" : "mac", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "size" : "128x128", 39 | "filename" : "icon_128x128.png", 40 | "idiom" : "mac", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "size" : "32x32", 45 | "filename" : "icon_32x32@2x.png", 46 | "idiom" : "mac", 47 | "scale" : "2x" 48 | }, 49 | { 50 | "size" : "32x32", 51 | "filename" : "icon_32x32.png", 52 | "idiom" : "mac", 53 | "scale" : "1x" 54 | }, 55 | { 56 | "size" : "16x16", 57 | "filename" : "icon_16x16@2x.png", 58 | "idiom" : "mac", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "size" : "16x16", 63 | "filename" : "icon_16x16.png", 64 | "idiom" : "mac", 65 | "scale" : "1x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /WebShell/Assets.xcassets/Udemy.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djyde/WebShell/6cc7efc4e48146d9c0d3a297b1528b30feb8e779/WebShell/Assets.xcassets/Udemy.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /WebShell/Core/WSAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // WebShell 4 | // 5 | // Created by Randy on 15/12/19. 6 | // Copyright © 2015 RandyLu. All rights reserved. 7 | // 8 | // Wesley de Groot (@wdg), Added Notification and console.log Support 9 | 10 | import Cocoa 11 | import Foundation 12 | import NotificationCenter 13 | 14 | @NSApplicationMain 15 | class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate { 16 | 17 | var mainWindow: NSWindow! 18 | let popover = NSPopover() 19 | let statusItem = NSStatusBar.system.statusItem(withLength: -2) 20 | var eventMonitor: EventMonitor? 21 | 22 | func applicationDidFinishLaunching(_ aNotification: Notification) { 23 | // @wdg Merge Statut with WebShell. 24 | // Issue: #56 25 | if Settings.shared.menuBarApp { 26 | if let button = statusItem.button { 27 | button.image = NSImage(named: NSImage.Name(rawValue: "MenuIcon")) // StatusBarButtonImage 28 | button.action = #selector(AppDelegate.togglePopover(_:)) 29 | } 30 | 31 | let sb = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main") , bundle: nil) 32 | popover.contentViewController = sb.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "WSView")) as? NSViewController 33 | 34 | initialPopupSize() 35 | 36 | eventMonitor = EventMonitor(mask: [NSEvent.EventTypeMask.leftMouseDown, NSEvent.EventTypeMask.rightMouseDown]) { [unowned self] event in 37 | if self.popover.isShown { 38 | self.closePopover(event) 39 | } 40 | } 41 | eventMonitor?.start() 42 | } else { 43 | // Add Notification center to the app delegate. 44 | NSUserNotificationCenter.default.delegate = self 45 | mainWindow = NSApplication.shared.windows[0] 46 | } 47 | // Change menus 48 | let title = Settings.shared.title 49 | let app = NSApplication.shared 50 | let mainMenu = app.mainMenu 51 | if let items = mainMenu?.items { 52 | // App menu 53 | let itm0 = items[0] 54 | itm0.title = title 55 | // About 56 | let itm1 = itm0.submenu!.items[0] 57 | itm1.title = "About " + title 58 | // Hide 59 | let itm2 = itm0.submenu!.items[2] 60 | itm2.title = "Hide " + title 61 | // Quit 62 | let itm3 = itm0.submenu!.items[8] 63 | itm3.title = "Quit " + title 64 | } 65 | } 66 | 67 | // @wdg close app if window closes 68 | // Issue: #40 69 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 70 | if !Settings.shared.menuBarApp { 71 | return true 72 | } else { 73 | return false 74 | } 75 | } 76 | 77 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { 78 | if !flag { 79 | if !Settings.shared.menuBarApp { 80 | mainWindow!.makeKeyAndOrderFront(self) 81 | } 82 | } 83 | 84 | // clear badge 85 | NSApplication.shared.dockTile.badgeLabel = "" 86 | // @wdg Clear notification count 87 | // Issue: #34 88 | NSUserNotificationCenter.default.removeAllDeliveredNotifications() 89 | return true 90 | } 91 | 92 | // @wdg Add Notification Support 93 | // Issue: #2 94 | func userNotificationCenter(_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool { 95 | // We (i) want Notifications support 96 | return true 97 | } 98 | 99 | // @wdg Add 'click' on notification support 100 | // Issue: #26 101 | func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) { 102 | // Open window if user clicked on notification! 103 | if !Settings.shared.menuBarApp { 104 | mainWindow!.makeKeyAndOrderFront(self) 105 | } 106 | 107 | // @wdg Clear badge 108 | NSApplication.shared.dockTile.badgeLabel = "" 109 | // @wdg Clear notification count 110 | // Issue: #34 111 | NSUserNotificationCenter.default.removeAllDeliveredNotifications() 112 | } 113 | 114 | /** 115 | Print the current page 116 | 117 | - Parameter sender: the sender object 118 | */ 119 | @IBAction func printThisPage(_ sender: AnyObject) -> Void { 120 | NotificationCenter.default.post(name: Notification.Name(rawValue: "printThisPage"), object: nil) 121 | } 122 | 123 | /** 124 | Go to the given homepage as set in `Settings.swift` 125 | 126 | - Parameter sender: the sender object 127 | */ 128 | @IBAction func goHome(_ sender: AnyObject) -> Void { 129 | NotificationCenter.default.post(name: Notification.Name(rawValue: "goHome"), object: nil) 130 | } 131 | 132 | /** 133 | Reload the current page 134 | 135 | - Parameter sender: the sender object 136 | */ 137 | @IBAction func reload(_ sender: AnyObject) -> Void { 138 | NotificationCenter.default.post(name: Notification.Name(rawValue: "reload"), object: nil) 139 | } 140 | 141 | /** 142 | Copy the url of the current page 143 | 144 | - Parameter sender: the sender object 145 | */ 146 | @IBAction func copyUrl(_ sender: AnyObject) -> Void { 147 | NotificationCenter.default.post(name: Notification.Name(rawValue: "copyUrl"), object: nil) 148 | } 149 | 150 | /** 151 | Popover initial popup size 152 | */ 153 | func initialPopupSize() -> Void { 154 | popover.contentSize.width = CGFloat(Settings.shared.initialWindowWidth) 155 | popover.contentSize.height = CGFloat(Settings.shared.initialWindowHeight) 156 | } 157 | 158 | func applicationWillTerminate(_ aNotification: Notification) { 159 | // Insert code here to tear down your application 160 | } 161 | 162 | /** 163 | Show the popover screen 164 | 165 | - Parameter sender: the sender object 166 | */ 167 | func showPopover(_ sender: AnyObject?) { 168 | if let button = statusItem.button { 169 | popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) 170 | } 171 | eventMonitor?.start() 172 | } 173 | 174 | /** 175 | Close the popover screen 176 | 177 | - Parameter sender: the sender object 178 | */ 179 | func closePopover(_ sender: AnyObject?) { 180 | popover.performClose(sender) 181 | eventMonitor?.stop() 182 | } 183 | 184 | /** 185 | Toggle the popover screen 186 | 187 | - Parameter sender: the sender object 188 | */ 189 | @objc func togglePopover(_ sender: AnyObject?) { 190 | if (popover.isShown) { 191 | closePopover(sender) 192 | } else { 193 | showPopover(sender) 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /WebShell/Core/WSApplication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSApplication.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 20-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | import Cocoa 11 | 12 | /** 13 | Class WSApplication 14 | 15 | This is an NSApplication sub-class to support the WebShell media keys. \ 16 | \ 17 | !important note, this class can not communicate with the ViewController.\ 18 | The communication goes via NSUserDefaults. 19 | */ 20 | @objc(WSApplication) 21 | class WSApplication: NSApplication { 22 | override func sendEvent(_ event: NSEvent) { 23 | if event.type == .systemDefined && event.subtype.rawValue == 8 { 24 | let keyCode = ((event.data1 & 0xFFFF0000) >> 16) 25 | let keyFlags = (event.data1 & 0x0000FFFF) 26 | // Get the key state. 0xA is KeyDown, OxB is KeyUp 27 | let keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA 28 | let keyRepeat = NSNumber(value: (keyFlags & 0x1)) 29 | mediaKeyEvent(Int32(keyCode), state: keyState, keyRepeat: Bool(truncating: keyRepeat)) 30 | } 31 | super.sendEvent(event) 32 | } 33 | 34 | func mediaKeyEvent(_ key: Int32, state: Bool, keyRepeat: Bool) { 35 | // Only send events on KeyDown. Without this check, these events will happen twice 36 | if (state) { 37 | switch (key) { 38 | case NX_KEYTYPE_PLAY: // F8 / Play 39 | if Settings.shared.mkBackAndForward { 40 | self.goReloadPage() 41 | } else { 42 | let _ = self.playPausePressed() 43 | } 44 | break 45 | case NX_KEYTYPE_FAST, NX_KEYTYPE_NEXT: // F9 / Forward 46 | if Settings.shared.mkBackAndForward { 47 | self.goForwardIfPossible() 48 | } else { 49 | let _ = self.nextItem() 50 | } 51 | break 52 | case NX_KEYTYPE_REWIND, NX_KEYTYPE_PREVIOUS: // F7 / Backward 53 | if Settings.shared.mkBackAndForward { 54 | self.goBackIfPossible() 55 | } else { 56 | let _ = self.previousItem() 57 | } 58 | break 59 | default: 60 | break 61 | } 62 | } 63 | } 64 | 65 | /** 66 | goBackIfPossible 67 | 68 | Since we can't communicate with the ViewController.\ 69 | We'll set a NSUserDefaults, and the `WSMediaLoop` does the Job for us. 70 | */ 71 | func goBackIfPossible() { 72 | UserDefaults.standard.set(true, forKey: "WSGoBack") 73 | UserDefaults.standard.synchronize() 74 | } 75 | 76 | /** 77 | goForwardIfPossible 78 | 79 | Since we can't communicate with the ViewController.\ 80 | We'll set a NSUserDefaults, and the `WSMediaLoop` does the Job for us. 81 | */ 82 | func goForwardIfPossible() { 83 | UserDefaults.standard.set(true, forKey: "WSGoForward") 84 | UserDefaults.standard.synchronize() 85 | } 86 | 87 | /** 88 | goReloadPage 89 | 90 | Since we can't communicate with the ViewController.\ 91 | We'll set a NSUserDefaults, and the `WSMediaLoop` does the Job for us. 92 | */ 93 | func goReloadPage() { 94 | UserDefaults.standard.set(true, forKey: "WSGoReload") 95 | UserDefaults.standard.synchronize() 96 | } 97 | 98 | func nextItem() -> Bool { 99 | // ... 100 | return false 101 | } 102 | 103 | func previousItem() -> Bool { 104 | // ... 105 | return false 106 | } 107 | 108 | func playPausePressed() -> Bool { 109 | // ... 110 | return false 111 | } 112 | } 113 | 114 | extension WSViewController { 115 | /** 116 | Communication for the WSApplication class 117 | 118 | - Parameter Sender: AnyObject (used for #selector use self) 119 | */ 120 | @objc func WSMediaLoop(_ Sender: AnyObject) -> Void { 121 | self.perform(#selector(WSViewController.WSMediaLoop(_:)), with: nil, afterDelay: 0.5) 122 | 123 | if (UserDefaults.standard.bool(forKey: "WSGoBack")) { 124 | UserDefaults.standard.set(false, forKey: "WSGoBack") 125 | UserDefaults.standard.synchronize() 126 | self._goBack(self) 127 | } 128 | 129 | if (UserDefaults.standard.bool(forKey: "WSGoForward")) { 130 | UserDefaults.standard.set(false, forKey: "WSGoForward") 131 | UserDefaults.standard.synchronize() 132 | self._goForward(self) 133 | } 134 | 135 | if (UserDefaults.standard.bool(forKey: "WSGoReload")) { 136 | UserDefaults.standard.set(false, forKey: "WSGoReload") 137 | UserDefaults.standard.synchronize() 138 | self._reloadPage(self) 139 | } 140 | 141 | // @wdg Merge Statut with WebShell. 142 | // Issue: #56 143 | if settings.menuBarApp { 144 | if ((NSApplication.shared.keyWindow) != nil) { 145 | if (self.MustCloseWindow) { 146 | NSApplication.shared.keyWindow?.close() 147 | self.MustCloseWindow = false 148 | } 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /WebShell/Core/WSBaseSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSBaseSettings.swift 3 | // WebShell 4 | // 5 | // Created by Fahim Farook on 9/12/17. 6 | // Copyright © 2017 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class WSBaseSettings { 12 | // URL to browse to 13 | var url = "http://djyde.github.io/WebShell/WebShell/" 14 | 15 | // The last URL the app was on 16 | var lastURL: String { 17 | set { 18 | let def = UserDefaults.standard 19 | def.set(newValue, forKey: title + "-LastURL") 20 | def.synchronize() 21 | } 22 | get { 23 | let def = UserDefaults.standard 24 | if let url = def.value(forKey: title + "-LastURL") as? String { 25 | return url 26 | } 27 | return "" 28 | } 29 | } 30 | 31 | // set the app title 32 | var title = Bundle.main.infoDictionary!["CFBundleName"] as! String 33 | 34 | // if you want to use the default one then leave it default || default = title/version based on Safari/AppleWebKit (KHTML, like Gecko) 35 | // otherwise change it to a useragent you want. (Default: "default") 36 | var useragent = "default" 37 | 38 | // Do you want to use the document title? (Default: true) 39 | var useDocumentTitle = true 40 | 41 | // Multilanguage loading text! 42 | var launchingText = NSLocalizedString("Launching...", comment: "Launching...") 43 | 44 | // Note that the window min height is 640 and min width is 1000 by default. You could change it in Main.storyboard 45 | var initialWindowHeight = 640 46 | var initialWindowWidth = 1000 47 | 48 | // Open target=_blank in a new screen? (Default: false) 49 | var openInNewScreen = false 50 | 51 | // Do you want a loading bar? (Default: true) 52 | var showLoadingBar = true 53 | 54 | // Add console.log support? (Default: false) 55 | var consoleSupport = false 56 | 57 | // Does the app needs Location support (Default: false) 58 | // note: if true, then WebShell always uses location, whenever it is used or not 59 | var needLocation = false 60 | 61 | // run the app in debug mode? (Default: false) 62 | // will be overridden by Xcode (runs with -NSDocumentRevisionsDebugMode YES) 63 | var debugmode = false 64 | 65 | // Please paste here the JavaScript you want to load on a website (Default: "") 66 | var jsInject = "" 67 | 68 | // Please paste here the CSS you want to load on a website (Default: "") 69 | var cssInject = "" 70 | 71 | // Enable (inject) import (JS/CSS) Folder. (Default: true) 72 | var enableInjectImport = true 73 | 74 | // Menubar app (right side next to clock) (Default: false) 75 | var menuBarApp = false 76 | 77 | // Navigate trough trackpad (back/forward) (Default: true) 78 | var navigateViaTrackpad = true 79 | 80 | // Use a password manager (Default: true). 81 | var passwordManager = true 82 | 83 | // Media keys settings - Enable "Back" & "Forward" 84 | var mkBackAndForward = true 85 | 86 | // Media Player support (experimental) 87 | var mkMediaPlayers = false 88 | 89 | // Contextmenu settings - // Enable "Back" & "Forward" (Default: true) 90 | var cmBackAndForward = true 91 | 92 | // Enable "Download" (Default: true) 93 | var cmDownload = true 94 | 95 | // Enable "Reload" (Default: true) 96 | var cmReload = true 97 | 98 | // Enable "Open in a new window" (Default: true) 99 | var cmNewWindow = true 100 | 101 | // open with last url? (Default: false) 102 | var openLastUrl = false 103 | 104 | // The URL to start with - the last page you were on or the base URL 105 | func startURL() -> String { 106 | if lastURL.isEmpty { 107 | return url 108 | } 109 | return openLastUrl ? lastURL : url 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /WebShell/Core/WSCore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellCore.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | extension WSViewController { 13 | /** 14 | Quit the app (there must be a better way) 15 | */ 16 | func Quit(_ sender: AnyObject) { 17 | exit(0) 18 | } 19 | 20 | /** 21 | Function to call for the window.open (popup) 22 | 23 | - Parameter url: The url to open 24 | - Parameter height: The height for the window 25 | - Parameter width: The width for the window 26 | */ 27 | func openNewWindow(url: String, height: String, width: String) -> Void { 28 | // @wdg Replaced NSPipe for NSWorkspace 29 | // Issue: #48 30 | let ws = NSWorkspace.shared 31 | do { 32 | if settings.debugmode { 33 | try ws.launchApplication(at: URL(string: "file://\(CommandLine.arguments[0])")!, options: NSWorkspace.LaunchOptions.newInstance, configuration: [NSWorkspace.LaunchConfigurationKey.arguments: ["-NSDocumentRevisionsDebugMode", "YES", "-url", url, "-height", height, "-width", width]]) 34 | } else { 35 | try ws.launchApplication(at: URL(string: CommandLine.arguments[0])!, options: NSWorkspace.LaunchOptions.newInstance, configuration: [NSWorkspace.LaunchConfigurationKey.arguments: ["-url", url, "-height", height, "-width", width]]) 36 | } 37 | } 38 | catch { /* we'll never get this. */ } 39 | } 40 | 41 | /** 42 | Noop a.k.a. No operation. 43 | 44 | - Parameter ob: Any ... 45 | */ 46 | func noop(_ ob: Any ...) -> Void { } 47 | 48 | /** 49 | Delay a function 50 | 51 | - Parameter delay: Time to delay 52 | - Parameter closure: Code to run (in a escaping block) 53 | */ 54 | func delay(_ delay: Double, _ closure: @escaping () -> ()) { 55 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure) 56 | } 57 | 58 | /** 59 | Run on main thread. 60 | 61 | - Parameter run: Code to run (in a escaping block) 62 | */ 63 | func runOnMain(_ run: @escaping () -> ()) { 64 | DispatchQueue.main.async(execute: run) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /WebShell/Core/WSCustomInject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellCustomInject.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 14-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | extension WSViewController { 13 | /** 14 | _WSInjectJS 15 | 16 | Injects JavaScript in to a frame, or other position 17 | 18 | - Parameter jsContext: JSContext! 19 | 20 | - Note: @wdg #36 21 | */ 22 | internal func _WSInjectJS(_ jsContext: JSContext!) { 23 | // JSInject 24 | if !settings.jsInject.isEmpty { 25 | jsContext.evaluateScript(settings.jsInject) 26 | } 27 | _WSFindJS(jsContext) 28 | } 29 | 30 | /** 31 | _WSInjectCSS 32 | 33 | Injects CSS in to a frame, or other position 34 | 35 | - Parameter jsContext: JSContext! 36 | 37 | - Note: @wdg #36 38 | */ 39 | internal func _WSInjectCSS(_ jsContext: JSContext!) { 40 | // CSSInject 41 | if !settings.cssInject.isEmpty { 42 | let css = settings.cssInject 43 | .replacingOccurrences(of: "\n", with: "") 44 | .replacingOccurrences(of: "\r", with: "") 45 | .replacingOccurrences(of: "'", with: "\\'") 46 | 47 | jsContext.evaluateScript("var css='\(css)',head=document.head,style=document.createElement('style');style.type='text/css';if (style.styleSheet){style.styleSheet.cssText = css;}else{style.appendChild(document.createTextNode(css));}head.appendChild(style);") 48 | } 49 | _WSFindCSS(jsContext) 50 | } 51 | 52 | /** 53 | _WSFindCSS 54 | 55 | Find CSS to inject 56 | 57 | - Parameter jsContext: JSContext! 58 | 59 | - Note: @wdg #36 60 | */ 61 | internal func _WSFindCSS(_ jsContext: JSContext!) { 62 | if settings.enableInjectImport { // (EII) 63 | 64 | let Seperated = (CommandLine.arguments[0]).components(separatedBy: "/") 65 | var newPath = "" 66 | 67 | for i in 0 ... (Seperated.count - 3) { 68 | newPath = newPath + Seperated[i] + "/" 69 | } 70 | 71 | newPath = newPath + "Resources/CSS" 72 | 73 | if FileManager().fileExists(atPath: newPath) { 74 | do { 75 | let fm = FileManager.default 76 | let contents = try fm.contentsOfDirectory(atPath: newPath) 77 | let filter = NSPredicate(format: "self ENDSWITH '.css'", argumentArray: nil) 78 | let fileList = contents.filter { filter.evaluate(with: $0) } 79 | for injectFile in fileList { 80 | let fc = try String(contentsOfFile: newPath + "/" + injectFile, encoding: String.Encoding.utf8) 81 | .replacingOccurrences(of: "\n", with: "") 82 | .replacingOccurrences(of: "\r", with: "") 83 | .replacingOccurrences(of: "'", with: "\\'") 84 | jsContext.evaluateScript("var css='\(fc)',head=document.head,style=document.createElement('style');style.type='text/css';if (style.styleSheet){style.styleSheet.cssText = css;}else{style.appendChild(document.createTextNode(css));}head.appendChild(style);") 85 | } 86 | } 87 | catch { } 88 | // Look 89 | } else { 90 | // Create Directory 91 | do { 92 | try FileManager().createDirectory(atPath: newPath, withIntermediateDirectories: true, attributes: nil) 93 | } 94 | catch { 95 | print("Failed to create path.") 96 | } 97 | } 98 | } 99 | } 100 | 101 | /** 102 | _WSFindJS 103 | 104 | Find JS to inject 105 | 106 | - Parameter jsContext: JSContext! 107 | 108 | - Note: @wdg #36 109 | */ 110 | internal func _WSFindJS(_ jsContext: JSContext!) { 111 | if settings.enableInjectImport { // (EII) 112 | 113 | let Seperated = (CommandLine.arguments[0]).components(separatedBy: "/") 114 | var newPath = "" 115 | 116 | for i in 0 ... (Seperated.count - 3) { 117 | newPath = newPath + Seperated[i] + "/" 118 | } 119 | 120 | newPath = newPath + "Resources/JavaScript" 121 | 122 | if FileManager().fileExists(atPath: newPath) { 123 | do { 124 | let fm = FileManager.default 125 | let contents = try fm.contentsOfDirectory(atPath: newPath) 126 | let filter = NSPredicate(format: "self ENDSWITH '.js'", argumentArray: nil) 127 | let fileList = contents.filter { filter.evaluate(with: $0) } 128 | for injectFile in fileList { 129 | let fc = try String(contentsOfFile: newPath + "/" + injectFile, encoding: String.Encoding.utf8) 130 | jsContext.evaluateScript(fc) 131 | } 132 | } 133 | catch { } 134 | // Look 135 | } else { 136 | // Create Directory 137 | do { 138 | try FileManager().createDirectory(atPath: newPath, withIntermediateDirectories: true, attributes: nil) 139 | } 140 | catch { 141 | print("Failed to create path.") 142 | } 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /WebShell/Core/WSDebug.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellDebug.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | // This is generated by swift, i dont know the reason, 12 | // but i'm not removing it. 13 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 14 | switch (lhs, rhs) { 15 | case let (l?, r?): 16 | return l < r 17 | case (nil, _?): 18 | return true 19 | default: 20 | return false 21 | } 22 | } 23 | 24 | fileprivate func > (lhs: T?, rhs: T?) -> Bool { 25 | switch (lhs, rhs) { 26 | case let (l?, r?): 27 | return l > r 28 | default: 29 | return rhs < lhs 30 | } 31 | } 32 | 33 | 34 | // @wdg Add Debug support 35 | // Issue: None. 36 | // This extension will handle the Debugging options. 37 | extension WSViewController { 38 | 39 | /** 40 | Override settings via commandline 41 | 42 | Used for popups, and debug options. 43 | */ 44 | func checkSettings() -> Void { 45 | // Need to overwrite settings? 46 | if (CommandLine.argc > 0) { 47 | for i in stride(from: 1, to: Int(CommandLine.argc), by: 2) { 48 | // for (var i = 1; i < Int(Process.argc) ; i = i + 2) { 49 | if ((String(describing: CommandLine.arguments[i])) == "-NSDocumentRevisionsDebugMode") { 50 | if ((String(describing: CommandLine.arguments[i + 1])) == "YES") { 51 | settings.debugmode = true 52 | settings.consoleSupport = true 53 | } 54 | } 55 | 56 | if ((String(describing: Process().arguments?[i])).uppercased() == "-DEBUG") { 57 | if ((String(describing: Process().arguments![i + 1])).uppercased() == "YES" || (String(describing: Process().arguments?[i + 1])).uppercased() == "true") { 58 | settings.debugmode = true 59 | settings.consoleSupport = true 60 | } 61 | } 62 | 63 | if ((String(describing: CommandLine.arguments[i])) == "-dump-args") { 64 | self._debugDumpArguments("" as AnyObject) 65 | } 66 | 67 | if ((String(describing: CommandLine.arguments[i])) == "-url") { 68 | settings.url = String(CommandLine.arguments[i + 1]) 69 | } 70 | 71 | if ((String(describing: CommandLine.arguments[i])) == "-height") { 72 | settings.initialWindowHeight = (Int(CommandLine.arguments[i + 1]) > 250) ? Int(CommandLine.arguments[i + 1])! : 250 73 | } 74 | 75 | if ((String(describing: CommandLine.arguments[i])) == "-width") { 76 | settings.initialWindowWidth = (Int(CommandLine.arguments[i + 1]) > 250) ? Int(CommandLine.arguments[i + 1])! : 250 77 | } 78 | } 79 | } 80 | 81 | initWindow() 82 | } 83 | 84 | /** 85 | Edit contextmenu... 86 | 87 | @wdg Fix contextmenu (problem with the swift 3 update) 88 | 89 | Issue: #61 90 | */ 91 | func webView(_ sender: WebView!, contextMenuItemsForElement element: [AnyHashable : Any]!, defaultMenuItems: [Any]!) -> [Any]! { 92 | //Swift 2.. 93 | //func webView(_ sender: WebView!, contextMenuItemsForElement element: [NSObject: Any]!, defaultMenuItems: [Any]!) -> [Any]! 94 | 95 | // @wdg Fix contextmenu (problem with the swift 2 update #50) 96 | // Issue: #51 97 | var download = false 98 | 99 | for i in defaultMenuItems { 100 | // Oh! download link available! 101 | if (String(describing: (i as AnyObject).title).contains("Download")) { 102 | download = true 103 | } 104 | 105 | // Get inspect element! 106 | if (String(describing: (i as AnyObject).title).contains("Element")) { 107 | for x in 0 ..< defaultMenuItems.count { 108 | if (String(describing: defaultMenuItems[x]).contains("Element")) { 109 | IElement = defaultMenuItems[x] as! NSMenuItem 110 | } 111 | } 112 | } 113 | } 114 | 115 | var NewMenu: [AnyObject] = [AnyObject]() 116 | 117 | // if can back 118 | if settings.cmBackAndForward { 119 | if (mainWebview.canGoBack) { 120 | NewMenu.append(NSMenuItem(title: "Back", action: #selector(WSViewController._goBack(_:)), keyEquivalent: "")) 121 | } 122 | if (mainWebview.canGoForward) { 123 | NewMenu.append(NSMenuItem(title: "Forward", action: #selector(WSViewController._goForward(_:)), keyEquivalent: "")) 124 | } 125 | } 126 | if settings.cmReload { 127 | NewMenu.append(NSMenuItem(title: "Reload", action: #selector(WSViewController._reloadPage(_:)), keyEquivalent: "")) 128 | } 129 | 130 | if (download) { 131 | if (element["WebElementLinkURL"] != nil) { 132 | lastURL = element["WebElementLinkURL"]! as! URL 133 | 134 | if settings.cmDownload || settings.cmNewWindow { 135 | NewMenu.append(NSMenuItem.separator()) 136 | 137 | if settings.cmNewWindow { 138 | NewMenu.append(NSMenuItem(title: "Open Link in a new Window", action: #selector(WSViewController.createNewInstance(_:)), keyEquivalent: "")) 139 | } 140 | if settings.cmDownload { 141 | NewMenu.append(NSMenuItem(title: "Download Linked File", action: #selector(WSViewController.downloadFileWithURL(_:)), keyEquivalent: "")) 142 | } 143 | } 144 | } 145 | } 146 | 147 | NewMenu.append(NSMenuItem.separator()) 148 | // Add debug menu. (if enabled) 149 | 150 | if settings.debugmode { 151 | let debugMenu = NSMenu(title: "Debug") 152 | if (IElement.title != "NSMenuItem") { 153 | debugMenu.addItem(IElement) // <-- Inspect element... 154 | } 155 | debugMenu.addItem(NSMenuItem(title: "Open New window", action: #selector(WSViewController._debugNewWindow(_:)), keyEquivalent: "")) 156 | debugMenu.addItem(NSMenuItem(title: "Print arguments", action: #selector(WSViewController._debugDumpArguments(_:)), keyEquivalent: "")) 157 | debugMenu.addItem(NSMenuItem(title: "Open URL", action: #selector(WSViewController._openURL(_:)), keyEquivalent: "")) 158 | debugMenu.addItem(NSMenuItem(title: "Report an issue on this page", action: #selector(WSViewController._reportThisPage(_:)), keyEquivalent: "")) 159 | debugMenu.addItem(NSMenuItem(title: "Print this page", action: #selector(WSViewController._printThisPage(_:)), keyEquivalent: "")) // Stupid swift 2.2 does not look in extensions. 160 | debugMenu.addItem(NSMenuItem.separator()) 161 | debugMenu.addItem(NSMenuItem(title: "Fire some random Notifications", action: #selector(WSViewController.__sendNotifications(_:)), keyEquivalent: "")) 162 | debugMenu.addItem(NSMenuItem(title: "Reset localstorage", action: #selector(WSViewController.resetLocalStorage(_:)), keyEquivalent: "")) 163 | 164 | let WSdeveloperMenu = NSMenu(title: "WS Developer") 165 | WSdeveloperMenu.addItem(NSMenuItem(title: "Inject Javascript", action: #selector(WSViewController._injectJS(_:)), keyEquivalent: "")) 166 | WSdeveloperMenu.addItem(NSMenuItem(title: "What the web can do", action: #selector(WSViewController._WWCDT(_:)), keyEquivalent: "")) 167 | 168 | let WSDevMenu = NSMenuItem(title: "WebShell Developer", action: #selector(WSViewController._doNothing(_:)), keyEquivalent: "") 169 | WSDevMenu.submenu = WSdeveloperMenu 170 | debugMenu.addItem(WSDevMenu) 171 | 172 | let item = NSMenuItem(title: "Debug", action: #selector(WSViewController._doNothing(_:)), keyEquivalent: "") 173 | item.submenu = debugMenu 174 | 175 | NewMenu.append(item) 176 | NewMenu.append(NSMenuItem.separator()) 177 | } 178 | 179 | NewMenu.append(NSMenuItem(title: "Quit", action: #selector(WSViewController._quit(_:)), keyEquivalent: "")) 180 | 181 | return NewMenu 182 | } 183 | 184 | /** 185 | Debug: Quit WebShell 186 | 187 | - Parameter Sender: Anyobject 188 | */ 189 | 190 | @objc func _quit(_ Sender: AnyObject) -> Void { 191 | exit(0) 192 | } 193 | 194 | /** 195 | Debug: doNothing 196 | 197 | - Parameter Sender: Anyobject 198 | */ 199 | @objc func _doNothing(_ Sender: AnyObject) -> Void { 200 | // _doNothing 201 | } 202 | 203 | /** 204 | Debug: Open new window 205 | 206 | - Parameter Sender: Anyobject 207 | */ 208 | @objc func _debugNewWindow(_ Sender: AnyObject) -> Void { 209 | openNewWindow(url: "https://www.google.nl/search?client=WebShell&rls=en&q=new+window", height: "0", width: "0") 210 | } 211 | 212 | /** 213 | Debug: Print arguments 214 | 215 | - Parameter Sender: Anyobject 216 | */ 217 | @objc func _debugDumpArguments(_ Sender: AnyObject) -> Void { 218 | print(CommandLine.arguments) 219 | } 220 | 221 | /** 222 | Debug: Fire 10 notifications (Timer) 223 | 224 | - Parameter Sender: Anyobject 225 | */ 226 | @objc func __sendNotifications(_ Sender: AnyObject) -> Void { 227 | // Minimize app 228 | NSApplication.shared.keyWindow?.miniaturize(self) 229 | 230 | // Fire 10 Notifications 231 | Timer.scheduledTimer(timeInterval: TimeInterval(05), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 232 | Timer.scheduledTimer(timeInterval: TimeInterval(15), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 233 | Timer.scheduledTimer(timeInterval: TimeInterval(25), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 234 | Timer.scheduledTimer(timeInterval: TimeInterval(35), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 235 | Timer.scheduledTimer(timeInterval: TimeInterval(45), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 236 | Timer.scheduledTimer(timeInterval: TimeInterval(55), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 237 | Timer.scheduledTimer(timeInterval: TimeInterval(65), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 238 | Timer.scheduledTimer(timeInterval: TimeInterval(75), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 239 | Timer.scheduledTimer(timeInterval: TimeInterval(85), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 240 | Timer.scheduledTimer(timeInterval: TimeInterval(95), target: self, selector: #selector(WSViewController.___sendNotifications), userInfo: nil, repeats: false) 241 | } 242 | 243 | /** 244 | Debug: Send 10 Notifications (real sending) 245 | 246 | - Parameter Sender: Anyobject 247 | */ 248 | @objc func ___sendNotifications() -> Void { 249 | // Minimize app 250 | if (NSApplication.shared.keyWindow?.isMiniaturized == false) { 251 | NSApplication.shared.keyWindow?.miniaturize(self) 252 | } 253 | 254 | // Send Actual notification. 255 | makeNotification("Test Notification", message: "Hi!", icon: "https://camo.githubusercontent.com/ee999b2d8fa5413229fdc69e0b53144f02b7b840/687474703a2f2f376d6e6f79372e636f6d312e7a302e676c622e636c6f7564646e2e636f6d2f7765627368656c6c2f6c6f676f2e706e673f696d616765566965772f322f772f313238") 256 | } 257 | 258 | /** 259 | Debug: Open URL 260 | 261 | - Parameter Sender: Anyobject 262 | */ 263 | @objc func _openURL(_ Sender: AnyObject) -> Void { 264 | let msg = NSAlert() 265 | msg.addButton(withTitle: "OK") // 1st button 266 | msg.addButton(withTitle: "Cancel") // 2nd button 267 | msg.messageText = "URL" 268 | msg.informativeText = "Where you need to go?" 269 | 270 | let txt = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) 271 | txt.stringValue = "http://" 272 | 273 | msg.accessoryView = txt 274 | let response: NSApplication.ModalResponse = msg.runModal() 275 | 276 | if (response == NSApplication.ModalResponse.alertFirstButtonReturn) { 277 | self.loadUrl(txt.stringValue) 278 | } 279 | } 280 | 281 | /** 282 | Debug: WhatTheWebCanDo.Today 283 | 284 | - Parameter Sender: Anyobject 285 | */ 286 | @objc func _WWCDT(_ Sender: AnyObject) -> Void { 287 | self.loadUrl("https://whatwebcando.today") 288 | } 289 | 290 | /** 291 | Debug: Inject custom javascript 292 | 293 | - Parameter Sender: Anyobject 294 | */ 295 | @objc func _injectJS(_ Sender: AnyObject) -> Void { 296 | let msg = NSAlert() 297 | msg.addButton(withTitle: "OK") // 1st button 298 | msg.addButton(withTitle: "Cancel") // 2nd button 299 | msg.messageText = "Inject Javascript" 300 | msg.informativeText = "Inject Javascript\nBe Carefull!" 301 | 302 | let txt = NSTextField(frame: NSRect(x: 0, y: 0, width: 400, height: 400)) 303 | txt.stringValue = "" 304 | txt.translatesAutoresizingMaskIntoConstraints = true 305 | 306 | msg.accessoryView = txt 307 | let response: NSApplication.ModalResponse = msg.runModal() 308 | 309 | if (response == NSApplication.ModalResponse.alertFirstButtonReturn) { 310 | let JSReturn: String = mainWebview.stringByEvaluatingJavaScript(from: txt.stringValue) 311 | 312 | let RetVal = NSAlert() 313 | RetVal.addButton(withTitle: "OK") 314 | RetVal.messageText = "Injected Javascript" 315 | RetVal.informativeText = JSReturn != "" ? JSReturn : "Finished" 316 | RetVal.runModal() 317 | } 318 | } 319 | 320 | /** 321 | Debug: Report this page, as containing an error. 322 | 323 | - Parameter Sender: Anyobject 324 | */ 325 | @objc func _reportThisPage(_ Sender: AnyObject) -> Void { 326 | let currentUrl: String = (mainWebview.mainFrame.dataSource?.request.url?.absoluteString)! 327 | let host: String = (mainWebview.mainFrame.dataSource?.request.url?.host)! 328 | 329 | let issue: String = String("Problem loading \(host)").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!.replacingOccurrences(of: "&", with: "%26") 330 | var body: String = (String("There is a problem loading \(currentUrl)").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)?.replacingOccurrences(of: "&", with: "%26"))! 331 | body.append("%0D%0AThe%20problem%20is%3A%0D%0A...") 332 | 333 | let url: String = "https://github.com/djyde/WebShell/issues/new?title=\(issue)&body=\(body)" 334 | 335 | NSWorkspace.shared.open(URL(string: (url as String))!) 336 | } 337 | 338 | /** 339 | Print this page 340 | 341 | - Parameter Sender: Anyobject 342 | 343 | - Notes Stupid swift 2.2 & 3 does not look in extensions. 344 | - Notes so we'll copy again... 345 | - Notes @wdg Add Print Support 346 | - Notes Issue: #39 347 | */ 348 | @objc func _printThisPage(_ Sender: AnyObject? = nil) -> Void { 349 | let url = mainWebview.mainFrame.dataSource?.request?.url?.absoluteString 350 | 351 | let operation: NSPrintOperation = NSPrintOperation(view: mainWebview) 352 | operation.jobTitle = "Printing \(url!)" 353 | 354 | // If want to print landscape 355 | operation.printInfo.orientation = NSPrintInfo.PaperOrientation.landscape 356 | operation.printInfo.scalingFactor = 0.7 357 | 358 | if operation.run() { 359 | print("Printed?") 360 | } 361 | } 362 | 363 | /** 364 | Go Back 365 | 366 | - Parameter Sender: Anyobject 367 | */ 368 | @objc func _goBack(_ Sender: AnyObject) -> Void { 369 | if (mainWebview.canGoBack) { 370 | mainWebview.goBack(Sender) 371 | } 372 | } 373 | 374 | /** 375 | Go Forward 376 | 377 | - Parameter Sender: Anyobject 378 | */ 379 | @objc func _goForward(_ Sender: AnyObject) -> Void { 380 | if (mainWebview.canGoForward) { 381 | mainWebview.goForward(Sender) 382 | } 383 | } 384 | 385 | /** 386 | Reload page 387 | 388 | - Parameter Sender: Anyobject 389 | */ 390 | @objc func _reloadPage(_ Sender: AnyObject) -> Void { 391 | mainWebview.reload(Sender) 392 | } 393 | 394 | /** 395 | Debug: Open in a new window 396 | 397 | - Parameter Sender: Anyobject 398 | */ 399 | @objc func createNewInstance(_ Sender: AnyObject) -> Void { 400 | openNewWindow(url: "\(lastURL)", height: "0", width: "0") 401 | } 402 | 403 | /** 404 | Download file 405 | 406 | - Parameter Sender: Anyobject 407 | */ 408 | @objc func downloadFileWithURL(_ Sender: AnyObject) -> Void { 409 | let wsDM = WebShelllDownloadManager(url: lastURL) 410 | wsDM.endDownloadTask() 411 | } 412 | 413 | /** 414 | If in debugmode -> Print 415 | 416 | - Parameter S: Any 417 | */ 418 | func Dprint(_ S: Any) -> Void { 419 | if settings.debugmode { 420 | print(S) 421 | } 422 | } 423 | 424 | /** 425 | If in debugmode -> Dump 426 | 427 | - Parameter S: Any 428 | */ 429 | func Ddump(_ S: Any) -> Void { 430 | if settings.debugmode { 431 | dump(S) 432 | } 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /WebShell/Core/WSDownloadManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShelllDownloadManager.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 18-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | 13 | /** 14 | @wdg Add Download support 15 | 16 | Issue: #31 17 | 18 | This class will handle WebShell Downloads. 19 | 20 | It's a basic download manager, so no progress, and nothing else, just download. 21 | */ 22 | class WebShelllDownloadManager { 23 | var TURL: URL 24 | var Fname: String = "" 25 | var Session: URLSession = URLSession() 26 | var DFolder: URL 27 | 28 | /** 29 | init 30 | - Parameter url: URL to download 31 | */ 32 | init(url: URL) { 33 | TURL = url 34 | Fname = url.lastPathComponent 35 | 36 | DFolder = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! 37 | 38 | let downloadsURL = String(describing: DFolder) + url.lastPathComponent 39 | 40 | self.startDownload(TURL, savePath: URL(string: downloadsURL)) 41 | } 42 | 43 | /** 44 | Start the download 45 | - Parameter URL: The URL to download 46 | - Parameter savePath: The savePath 47 | */ 48 | func startDownload(_ URL: Foundation.URL, savePath: Foundation.URL!) { 49 | let sessionConfig = URLSessionConfiguration.default 50 | let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil) 51 | let request = NSMutableURLRequest(url: URL) 52 | request.httpMethod = "GET" 53 | 54 | noop(session) // temporary we want no stupid "fix-it" warnings. 55 | 56 | let task = session.dataTask(with: request as URLRequest, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in 57 | if (error == nil) { 58 | let statusCode = (response as! HTTPURLResponse).statusCode 59 | self.noop(statusCode as AnyObject) // For further use HTTP Status code. 60 | 61 | let saveData = NSData(data: data!) as Data 62 | try? saveData.write(to: savePath!, options: [.atomic]) 63 | 64 | // Ask the question on the main queue. 65 | OperationQueue.main.addOperation({ 66 | if (self.dialog("Download of \"\(self.Fname)\" complete", text: "Would you like to open the downloads folder?")) { 67 | NSWorkspace.shared.open(self.DFolder) 68 | } 69 | }) 70 | } 71 | else { 72 | // Failure 73 | print("Faulure: %@", error!.localizedDescription); 74 | } 75 | }) 76 | 77 | task.resume() 78 | } 79 | 80 | /** 81 | Display a nice dialog with a question.\ 82 | Please remember to use it only on the mainQueue 83 | 84 | - Parameter question: The question 85 | - Parameter text: The text you want to ask 86 | - Returns: Bool 87 | */ 88 | func dialog(_ question: String, text: String) -> Bool { 89 | let myPopup: NSAlert = NSAlert() 90 | myPopup.messageText = question 91 | myPopup.informativeText = text 92 | myPopup.alertStyle = NSAlert.Style.informational 93 | myPopup.addButton(withTitle: "Yes") 94 | myPopup.addButton(withTitle: "No") 95 | 96 | let res = myPopup.runModal() 97 | 98 | if res == NSApplication.ModalResponse.alertFirstButtonReturn { 99 | return true 100 | } 101 | 102 | return false 103 | } 104 | 105 | /** 106 | End the download task 107 | */ 108 | func endDownloadTask() -> Void { } 109 | 110 | /** 111 | Noop! 112 | */ 113 | func noop(_ ob: AnyObject) -> Void { } 114 | } 115 | -------------------------------------------------------------------------------- /WebShell/Core/WSEventMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellEventMonitor.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 26-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | // @wdg Merge Statut with WebShell. 12 | // Issue: #56 13 | class EventMonitor { 14 | fileprivate var monitor: Any? 15 | fileprivate let mask: NSEvent.EventTypeMask 16 | fileprivate let handler: (NSEvent?) -> () 17 | 18 | /** 19 | Init monitoring for events 20 | 21 | - Parameter mask: under which mask? 22 | - Parameter handler: with which handler? 23 | */ 24 | internal init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> ()) { 25 | self.mask = mask 26 | self.handler = handler 27 | } 28 | 29 | deinit { 30 | stop() 31 | } 32 | 33 | /** 34 | Starts monitoring for events 35 | */ 36 | internal func start() { 37 | monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler) 38 | } 39 | 40 | /** 41 | Removes event monitor 42 | */ 43 | internal func stop() { 44 | if monitor != nil { 45 | NSEvent.removeMonitor(monitor!) 46 | monitor = nil 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /WebShell/Core/WSFileHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellFileHandler.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | // @wdg: Enable file uploads. 13 | // Issue: #29 14 | // This extension will handle up & downloads 15 | extension WSViewController { 16 | 17 | // @wdg: Enable file uploads. 18 | // Issue: #29 19 | @objc(webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:) func webView(_ sender: WebView!, runOpenPanelForFileButtonWith resultListener: WebOpenPanelResultListener!, allowMultipleFiles: Bool) { 20 | // Init panel with options 21 | let panel = NSOpenPanel() 22 | panel.allowsMultipleSelection = allowMultipleFiles 23 | panel.canChooseDirectories = false 24 | panel.canCreateDirectories = false 25 | panel.canChooseFiles = true 26 | 27 | // On clicked on ok then... 28 | panel.begin {(result) -> Void in 29 | // User clicked OK 30 | if result.rawValue == NSFileHandlingPanelOKButton { 31 | 32 | // make the upload qeue named 'uploadQeue' 33 | let uploadQeue: NSMutableArray = NSMutableArray() 34 | for i in 0 ..< panel.urls.count 35 | { 36 | // Add to upload qeue, needing relativePath. 37 | uploadQeue.add(panel.urls[i].relativePath) 38 | } 39 | 40 | if (panel.urls.count == 1) { 41 | // One file 42 | resultListener.chooseFilename(String(describing: uploadQeue[0])) 43 | } else { 44 | // Multiple files 45 | resultListener.chooseFilenames(uploadQeue as [AnyObject]) 46 | } 47 | } 48 | } 49 | 50 | } 51 | 52 | /** 53 | Download window. 54 | 55 | - Parameter download: WebDownload! 56 | */ 57 | func downloadWindow(forAuthenticationSheet download: WebDownload!) -> NSWindow! { 58 | print("I'd like to download something") 59 | print(download) 60 | 61 | return NSWindow() 62 | } 63 | 64 | // Usefull for debugging.. 65 | @nonobjc func webView(_ sender: WebView!,mouseDidMoveOverElement elementInformation: [NSObject : Any]!, modifierFlags: Int) { 66 | //print("Sender=\(sender)\nEleInfo=\(elementInformation)\nModifier=\(modifierFlags)") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /WebShell/Core/WSInjector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellInjector.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | 13 | /** 14 | This extension will catch up with the webhooks! 15 | - Note: @wdg: Iframes, Webhooks, and more. (Issue: #23, #5, #2, #35, #38, #39 & More) 16 | */ 17 | @objc extension WSViewController { 18 | /** 19 | Loop Trough iFrames 20 | 21 | This function will loop trough different frames (if they exists) and will inject all the javascript what they want! 22 | 23 | - Note: @wdg Fix for iFrames (Issue #23) 24 | */ 25 | func loopThroughiFrames() { 26 | if (mainWebview.subviews.count > 0) { 27 | // We've got subViews! 28 | 29 | if (mainWebview.subviews[0].subviews.count > 0) { 30 | // mainWebview.subviews[0] = WebFrameView 31 | 32 | let goodKids = mainWebview.subviews[0].subviews[0] 33 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView (= goodKids) 34 | 35 | var children = goodKids.subviews[0] 36 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView.subviews[0] = WebClipView (= children) 37 | 38 | // We need > 0 subviews here, otherwise don't add them. and the script will continue 39 | if children.subviews.count > 0 { 40 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView.subviews[0] = WebClipView.subviews[0] = WebHTMLView 41 | children = goodKids.subviews[0].subviews[0] 42 | } 43 | 44 | // Finally. parsing those good old iframes 45 | // We don't check them for iframes, somewhere the fun must be ended. 46 | for child in children.subviews { 47 | Dprint("Found a Child...") 48 | Ddump(child) 49 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView.subviews[0] = WebClipView.subviews[0] = WebHTMLView.subviews[x] = WebFrameView (Finally) (name = child) 50 | if (child.isKind(of: WebFrameView.self)) { 51 | let frame: NSView = child 52 | let context: JSContext = frame.webFrame.javaScriptContext 53 | 54 | Dprint("Injecting hooks!") 55 | injectWebhooks(context) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | func injectLocalStorage (jsContext: JSContext!) { 63 | Dprint("Injecting localStorage hooks!") 64 | 65 | // @wdg Add localstorage Support 66 | // Issue: #25 67 | let saveToLocal: @convention(block)(NSString?, NSString?) -> Void = {(key: NSString?, value: NSString?) in 68 | let host: String = (self.mainWebview.mainFrame.dataSource?.request.url?.host)! 69 | let newKey = String(describing: "WSLS:\(host):\(key ?? "?")") 70 | 71 | UserDefaults.standard.setValue(value, forKey: newKey) 72 | } 73 | 74 | let getFromLocal: @convention(block)(NSString?) -> String = {(key: NSString!) in 75 | let host: String = (self.mainWebview.mainFrame.dataSource?.request.url?.host)! 76 | if let LSvalue = key { 77 | let newKey = "WSLS:\(host):\(LSvalue)" 78 | 79 | let val = UserDefaults.standard.value(forKey: newKey as String) 80 | 81 | if let myVal = val as? String { 82 | return String(myVal) 83 | } 84 | else { 85 | return "null" 86 | } 87 | } else { 88 | return "null" 89 | } 90 | } 91 | 92 | jsContext.objectForKeyedSubscript("localStorage").setObject(unsafeBitCast(saveToLocal, to: AnyObject.self), forKeyedSubscript: "setItem" as (NSCopying & NSObjectProtocol)!) 93 | jsContext.objectForKeyedSubscript("localStorage").setObject(unsafeBitCast(getFromLocal, to: AnyObject.self), forKeyedSubscript: "getItem" as (NSCopying & NSObjectProtocol)!) 94 | } 95 | 96 | /** 97 | InjectWebhooks 98 | 99 | Injects javascript in to a frame, or other position 100 | 101 | - Parameter jsContext: JSContext! 102 | 103 | - Note: @wdg Fixes a lot (Issues #23, #5, #2, #35, #38, #39 & More.) 104 | */ 105 | func injectWebhooks(_ jsContext: JSContext!) { 106 | // Injecting javascript (via jsContext) 107 | 108 | // @wdg Hack URL's if settings is set. 109 | // Issue: #5 110 | if settings.openInNewScreen { 111 | // _blank to external 112 | // JavaScript -> Select all 113 | jsContext.evaluateScript("document.querySelector('body').addEventListener('click', function(evt) { if ( evt.target.target==='_blank') { WSApp.openExternal(evt.target.href); }}, true);") 114 | } else { 115 | // _blank to internal 116 | // JavaScript -> Select all 117 | jsContext.evaluateScript("document.querySelector('body').addEventListener('click', function(evt) { if ( evt.target.target==='_blank') { WSApp.openInternal(evt.target.href); }}, true);") 118 | } 119 | 120 | // @wdg Add support for target=_blank 121 | // Issue: #5 122 | // Fake window.app Library. 123 | jsContext.evaluateScript("var WSApp={};") ; 124 | 125 | // _blank external 126 | let openInBrowser: @convention(block)(NSString?) -> Void = {(url: NSString!) in 127 | NSWorkspace.shared.open(URL(string: (url as String))!) 128 | } 129 | 130 | // _blank internal 131 | let openNow: @convention(block)(NSString?) -> Void = {(url: NSString!) in 132 | self.loadUrl((url as String)) 133 | } 134 | // _blank external 135 | jsContext.objectForKeyedSubscript("WSApp").setObject(unsafeBitCast(openInBrowser, to: AnyObject.self), forKeyedSubscript: "openExternal" as (NSCopying & NSObjectProtocol)!) 136 | 137 | // _blank internal 138 | jsContext.objectForKeyedSubscript("WSApp").setObject(unsafeBitCast(openNow, to: AnyObject.self), forKeyedSubscript: "openInternal" as (NSCopying & NSObjectProtocol)!) 139 | 140 | // @wdg Add Notification Support 141 | // Issue: #2, #35, #38 (webkitNotification) 142 | jsContext.evaluateScript("function Notification(myTitle, options){if(typeof options === 'object'){var body,icon,tag;if (typeof options['body'] !== 'undefined'){body=options['body']}if (typeof options['icon'] !== 'undefined'){Notification.note(myTitle, body, options['icon'])}else{Notification.note(myTitle, body)}}else{if(typeof options === 'string'){Notification.note(myTitle, options)}else{Notification.note(myTitle)}}}Notification.length=1;Notification.permission='granted';Notification.requestPermission=function(callback){if(typeof callback === 'function'){callback('granted');}else{return 'granted'}};window.Notification=Notification;window.webkitNotification=Notification;") 143 | let myNofification: @convention(block)(NSString?, NSString?, NSString?) -> Void = {(title: NSString?, message: NSString?, icon: NSString?) in 144 | self.makeNotification(title!, message: message!, icon: icon!) 145 | } 146 | jsContext.objectForKeyedSubscript("Notification").setObject(unsafeBitCast(myNofification, to: AnyObject.self), forKeyedSubscript: "note" as (NSCopying & NSObjectProtocol)!) 147 | 148 | // Add console.log ;) 149 | // Add Console.log (and console.error, and console.warn) 150 | if settings.consoleSupport { 151 | jsContext.evaluateScript("var console = {log: function () {var message = '';for (var i = 0; i < arguments.length; i++) {message += arguments[i] + ' '};console.print(message)},warn: function () {var message = '';for (var i = 0; i < arguments.length; i++) {message += arguments[i] + ' '};console.print(message)},error: function () {var message = '';for (var i = 0; i < arguments.length; i++){message += arguments[i] + ' '};console.print(message)}};") 152 | let logFunction: @convention(block)(NSString?) -> Void = {(message: NSString!) in 153 | print("JS: \(message)") 154 | } 155 | jsContext.objectForKeyedSubscript("console").setObject(unsafeBitCast(logFunction, to: AnyObject.self), forKeyedSubscript: "print" as (NSCopying & NSObjectProtocol)!) 156 | } 157 | 158 | // @wdg Add Print Support 159 | // Issue: #39 160 | // window.print() 161 | let printMe: @convention(block)(NSString?) -> Void = {(url: NSString?) in self.printThisPage(self)} 162 | jsContext.objectForKeyedSubscript("window").setObject(unsafeBitCast(printMe, to: AnyObject.self), forKeyedSubscript: "print" as (NSCopying & NSObjectProtocol)!) 163 | 164 | // navigator.getBattery() 165 | jsContext.objectForKeyedSubscript("navigator").setObject(BatteryManager.self, forKeyedSubscript: "battery" as (NSCopying & NSObjectProtocol)!) 166 | 167 | jsContext.evaluateScript("window.navigator.getBattery = window.navigator.battery.getBattery;") 168 | 169 | // navigator.vibrate 170 | let vibrateNow: @convention(block)(NSString?) -> Void = {(data: NSString!) in 171 | self.flashScreen(data) 172 | } 173 | jsContext.objectForKeyedSubscript("navigator").setObject(unsafeBitCast(vibrateNow, to: AnyObject.self), forKeyedSubscript: "vibrate" as (NSCopying & NSObjectProtocol)!) 174 | 175 | self.injectLocalStorage(jsContext: jsContext) 176 | 177 | // @wdg Support for window.open (popup) 178 | // Issue: #21 179 | // openNewWindow(url: "THEURL", height: "0", width: "0") 180 | // window.open(URL, name, specs, replace) 181 | let windowOpen: @convention(block)(NSString?, NSString?, NSString?, NSString?) -> Void = {(url: NSString?, target: NSString?, specs: NSString?, replace: NSString?) in 182 | self.parseWindowOpen(url! as String, options: specs! as String) 183 | } 184 | jsContext.objectForKeyedSubscript("window").setObject(unsafeBitCast(windowOpen, to: AnyObject.self), forKeyedSubscript: "open" as (NSCopying & NSObjectProtocol)!) 185 | 186 | // Get window.webshell 187 | let nsObject: Any? = Bundle.main.infoDictionary!["CFBundleShortVersionString"] 188 | jsContext.evaluateScript("window.webshell={version:'\(nsObject as! String)'};webshell=window.webshell;") 189 | 190 | // @wdg memorize credentials? 191 | // Issue: #74 192 | _injectPasswordFor(jsContext, website: jsContext.evaluateScript("window.location.host").toString()) 193 | _injectPasswordListener(jsContext, website: jsContext.evaluateScript("window.location.host").toString()) 194 | let savePassword: @convention(block)(NSString?, NSString?) -> Void = {(username: NSString!, password: NSString!) in 195 | self._savePasswordFor(jsContext, 196 | website: jsContext.evaluateScript("window.location.host").toString(), 197 | username: (username as String), 198 | password: (password as String) 199 | ) 200 | } 201 | jsContext.objectForKeyedSubscript("WSApp").setObject(unsafeBitCast(savePassword, to: AnyObject.self), forKeyedSubscript: "savePassword" as (NSCopying & NSObjectProtocol)!) 202 | 203 | _WSInjectCSS(jsContext) 204 | _WSInjectJS(jsContext) 205 | } 206 | 207 | /** 208 | Add Localstorage Support 209 | 210 | - Parameter Sender: AnyObject? 211 | 212 | - Note: @wdg #25 213 | */ 214 | func resetLocalStorage(_ Sender: AnyObject?) -> Void { 215 | UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) 216 | } 217 | 218 | /** 219 | Support for window.open (popup) 220 | 221 | - Parameter url: Url to open 222 | - Parameter options: Custom options, width, height 223 | 224 | - Note: @wdg #25 225 | */ 226 | func parseWindowOpen(_ url: String, options: String) -> Void { 227 | // We ignore x and y. (initial position on the screen) 228 | // Using specifications of W3Schools: http://www.w3schools.com/jsref/met_win_open.asp 229 | // "Open a new window called "MsgWindow", and write some text into it" is not (yet) supported! 230 | var width = "0" 231 | var height = "0" 232 | let options = Array(options.components(separatedBy: ",")) 233 | 234 | for i in 0 ..< options.count { 235 | var tmp = Array(options[i].components(separatedBy: "=")) 236 | 237 | if (tmp[0] == "width" || tmp[0] == " width") { 238 | width = tmp[1] 239 | print("width=\(tmp[1])") 240 | } 241 | if (tmp[0] == "height" || tmp[0] == " height") { 242 | height = tmp[1] 243 | print("height=\(tmp[1])") 244 | } 245 | } 246 | 247 | // After parsing call 248 | openNewWindow(url: url, height: "\(height)", width: "\(width)") 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /WebShell/Core/WSPageActions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellPageActions.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | extension WSViewController { 13 | /** 14 | Add Observers for menu items 15 | */ 16 | func addObservers() { 17 | // add menu action observers 18 | let observers = ["goHome", "reload", "copyUrl", "clearNotificationCount", "printThisPage"] 19 | 20 | for observer in observers { 21 | NotificationCenter.default.addObserver(self, selector: NSSelectorFromString(observer), name: NSNotification.Name(rawValue: observer), object: nil) 22 | } 23 | } 24 | 25 | /** 26 | Go to the home url 27 | */ 28 | func goHome() { 29 | loadUrl(settings.url) 30 | } 31 | 32 | /** 33 | Reload the current webpage 34 | */ 35 | func reload() { 36 | mainWebview.mainFrame.reload() 37 | } 38 | 39 | /** 40 | Copy the URL 41 | */ 42 | func copyUrl() { 43 | let currentUrl: String = (mainWebview.mainFrame.dataSource?.request.url?.absoluteString)! 44 | let clipboard: NSPasteboard = NSPasteboard.general 45 | clipboard.clearContents() 46 | 47 | clipboard.setString(currentUrl, forType: .string) 48 | } 49 | 50 | /** 51 | Initialize settings 52 | */ 53 | func initSettings() { 54 | // controll the progress bar 55 | if !settings.showLoadingBar { 56 | progressBar.isHidden = true // @wdg: Better progress indicator | Issue: #37 57 | } 58 | 59 | // @wdg Add Custom useragent support 60 | // Issue: #52 61 | if settings.useragent.lowercased() == "default" { 62 | var UA = Bundle.main.infoDictionary!["CFBundleName"] as! String 63 | UA = UA + "/" 64 | UA = UA + (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String) 65 | UA = UA + " based on Safari/AppleWebKit (KHTML, like Gecko)" 66 | 67 | UserDefaults.standard.register(defaults: ["UserAgent": UA]) // For iOS 68 | mainWebview.customUserAgent = UA // For Mac OS X 69 | } else { 70 | let UA = settings.useragent 71 | UserDefaults.standard.register(defaults: ["UserAgent": UA]) // For iOS 72 | mainWebview.customUserAgent = UA // For Mac OS X 73 | } 74 | 75 | // set launching text 76 | launchingLabel.stringValue = settings.launchingText 77 | } 78 | 79 | /** 80 | Initialize window 81 | */ 82 | func initWindow() { 83 | firstAppear = false 84 | // set window title 85 | mainWindow.window?.title = settings.title 86 | 87 | // Force some preferences before loading... 88 | mainWebview.preferences.isJavaScriptEnabled = true 89 | mainWebview.preferences.javaScriptCanOpenWindowsAutomatically = true 90 | mainWebview.preferences.arePlugInsEnabled = true 91 | } 92 | 93 | /** 94 | Load a specific URL 95 | 96 | - Parameter url: The url to load 97 | */ 98 | 99 | func loadUrl(_ url: String) { 100 | if settings.showLoadingBar { 101 | progressBar.isHidden = false 102 | progressBar.startAnimation(self) 103 | progressBar.maxValue = 100; 104 | progressBar.minValue = 1; 105 | progressBar.increment(by: 24) 106 | } 107 | let URL = Foundation.URL(string: url) 108 | mainWebview.mainFrame.load(URLRequest(url: URL!)) 109 | } 110 | 111 | /** 112 | Add Print Support (#39) [@wdg] 113 | 114 | - Parameter Sender: The sending object 115 | */ 116 | func printThisPage(_ Sender: AnyObject?) -> Void { 117 | let url = mainWebview.mainFrame.dataSource?.request?.url?.absoluteString 118 | 119 | let operation: NSPrintOperation = NSPrintOperation(view: mainWebview) 120 | operation.jobTitle = "Printing \(url!)" 121 | 122 | // If want to print landscape 123 | operation.printInfo.orientation = NSPrintInfo.PaperOrientation.landscape 124 | operation.printInfo.scalingFactor = 0.7 125 | 126 | if operation.run() { 127 | print("Printed?") 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /WebShell/Core/WSPasswordManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSPasswordManager.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 02-11-17. 6 | // Copyright © 2017 Wesley de Groot. All rights reserved. 7 | // 8 | // TODO: Create a safe password storage, instead of UserDefaults. 9 | 10 | import Foundation 11 | import WebKit 12 | 13 | extension WSViewController { 14 | /** 15 | Inject the password manager for a website (*via self.injectWebhooks*) 16 | 17 | @wdg memorize credentials (*Issue: #74*) 18 | 19 | - Parameter jsContext: JSContext! 20 | - Parameter website: String (site host) 21 | */ 22 | internal func _injectPasswordFor(_ jsContext: JSContext!, website: String) -> Void { 23 | let database = UserDefaults(suiteName: website) 24 | if let savedUsername = database?.object(forKey: "username") { 25 | if let savedPassword = database?.object(forKey: "password") { 26 | let loadUsernameJS = "var inputFields = document.querySelectorAll(\"input[name='username']\"); for (var i = inputFields.length >>> 0; i--;) { inputFields[i].value = \'\(savedUsername)\';}" 27 | let loadPasswordJS = "var inputFields = document.querySelectorAll(\"input[name='password']\"); for (var i = inputFields.length >>> 0; i--;) { inputFields[i].value = \'\(savedPassword)\';}" 28 | 29 | jsContext.evaluateScript(loadUsernameJS) 30 | jsContext.evaluateScript(loadPasswordJS) 31 | } 32 | } 33 | 34 | database?.synchronize() 35 | } 36 | 37 | /** 38 | Inject the password manager for a website (*grabber part*) 39 | 40 | @wdg memorize credentials (*Issue: #74*) 41 | 42 | - Parameter jsContext: JSContext! 43 | - Parameter website: String (site host) 44 | */ 45 | internal func _injectPasswordListener(_ jsContext: JSContext!, website: String) -> Void { 46 | let database = UserDefaults(suiteName: website) 47 | let listener = "var WSPasswordManager={currentSite:document.location.host,initialize:function(e,a){WSPasswordManager.checkForms()},checkForms:function(){for(var e=document.getElementsByTagName(\"form\"),a=0,t=e.length;t>a;a++)\"post\"===e[a].method.toLowerCase()&&e[a].setAttribute(\"onsubmit\",\"event.preventDefault();return WSPasswordManager.validate(this);\")},validate:function(e){var a=e.querySelectorAll(\"input[name='username']\"),t=e.querySelectorAll(\"input[name='password']\");return a.length>0&&t.length>0&&(\"undefined\"!=typeof WSApp?WSApp.savePassword(a[0].value,t[0].value):window.alert(\"Internal error\nPassword manager failed to initialize\")),!1}};WSPasswordManager.initialize();" 48 | if settings.passwordManager { 49 | jsContext.evaluateScript(listener) 50 | } 51 | database?.synchronize() 52 | } 53 | 54 | /** 55 | Save a password to the database (*via _injectPasswordListener*) 56 | 57 | @wdg memorize credentials (*Issue: #74*) 58 | 59 | - Parameter jsContext: JSContext! 60 | - Parameter website: String (site host) 61 | - Parameter username: String 62 | - Parameter password: String 63 | */ 64 | internal func _savePasswordFor(_ jsContext: JSContext!, website: String, username: String, password: String) -> Void { 65 | let database = UserDefaults(suiteName: website) 66 | database?.set(website, forKey: "website") 67 | database?.set(username, forKey: "username") 68 | database?.set(password, forKey: "password") 69 | database?.synchronize() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /WebShell/Core/WSStringExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtension.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 17-01-16. 6 | // Copyright © 2016 WDGWV. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Extensions for Strings 13 | */ 14 | public extension String { 15 | /** 16 | get string length 17 | */ 18 | public var length: Int { 19 | get { 20 | return self.count 21 | } 22 | } 23 | 24 | /** 25 | contains 26 | - Parameter s: String to check 27 | - Returns: true/false 28 | */ 29 | public func contains(_ s: String) -> Bool { 30 | return self.range(of: s) != nil ? true : false 31 | } 32 | 33 | /** 34 | Replace 35 | - Parameter target: String 36 | - Parameter withString: Replacement 37 | - Returns: Replaced string 38 | */ 39 | public func replace(_ target: String, withString: String) -> String { 40 | return self.replacingOccurrences(of: target, with: withString, options: NSString.CompareOptions.literal, range: nil) 41 | } 42 | 43 | /** 44 | Character At Index 45 | - Parameter index: The index 46 | - Returns Character 47 | */ 48 | func characterAtIndex(_ index: Int) -> Character! { 49 | var cur = 0 50 | for char in self { 51 | if cur == index { 52 | return char 53 | } 54 | cur += 1 55 | } 56 | return nil 57 | } 58 | 59 | /** 60 | Add subscript 61 | 62 | - Parameter i: Index 63 | */ 64 | public subscript(i: Int) -> Character { 65 | get { 66 | let index = self.index(self.startIndex, offsetBy: i) 67 | return self[index] 68 | } 69 | } 70 | /** 71 | Add subscript 72 | 73 | - Parameter r: Range 74 | */ 75 | public subscript(r: Range) -> String { 76 | get { 77 | let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound) 78 | let endIndex = self.index(self.startIndex, offsetBy: r.upperBound - 1) 79 | 80 | return String(self[startIndex..) -> String { 91 | get { 92 | let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound) 93 | let endIndex = self.index(self.startIndex, offsetBy: r.upperBound - 1) 94 | 95 | return String(self[startIndex.. [String: NSTouch]? { 67 | var twoFingersTouches: [String: NSTouch]? = nil // NSMutualDictionary was used before and didn't require casting id to string, revert if side-effects manifest 68 | if (event.type == NSEvent.EventType.gesture) { // could maybe be: EventTypeBeginGesture 69 | let touches: Set = event.touches(matching: NSTouch.Phase.any, in: view) // touchesMatchingPhase:NSTouchPhaseAny inView:self 70 | if (touches.count == 2){ 71 | twoFingersTouches = [String: NSTouch]() 72 | for touch in touches {// 73 | twoFingersTouches!["\((touch).identity)"] = touch //assigns each touch to the identity of the same touch 74 | } 75 | } 76 | } 77 | 78 | return twoFingersTouches 79 | } 80 | /** 81 | * Detects 2 finger (left/right) swipe gesture 82 | * NOTE: either of 3 enums is returned: .leftSwipe, .rightSwipe .none 83 | * TODO: also make up and down swipe detectors, and do more research into how this could be done easier. Maybe you even have some clues in the notes about gestures etc. 84 | * Conceptually: 85 | * 1. Record 2 .began touchEvents 86 | * 2. Record 2 .ended touchEvents 87 | * 3. Measure the distance between .began and .ended and assert if it is within threshold 88 | */ 89 | static func swipe(_ view: NSView, _ event: NSEvent, _ twoFingersTouches: [String: NSTouch]?) -> SwipeType { 90 | let endingTouches: Set = event.touches(matching: NSTouch.Phase.ended, in: view) 91 | if (endingTouches.count > 0 && twoFingersTouches != nil) { 92 | let beginningTouches: [String: NSTouch] = twoFingersTouches! // copy the twoFingerTouches data 93 | var magnitudes: [CGFloat] = [] // magnitude definition: the great size or extent of something 94 | for endingTouch in endingTouches { 95 | let beginningTouch:NSTouch? = beginningTouches["\(endingTouch.identity)"] 96 | if (beginningTouch == nil) { // skip if endingTouch doesn't have a matching beginningTouch 97 | continue 98 | } 99 | let magnitude: CGFloat = endingTouch.normalizedPosition.x - beginningTouch!.normalizedPosition.x 100 | magnitudes.append(magnitude) 101 | } 102 | var sum: CGFloat = 0 103 | for magnitude in magnitudes{ 104 | sum += magnitude 105 | } 106 | let absoluteSum: CGFloat = abs(sum) // force value to be positive 107 | let kSwipeMinimumLength: CGFloat = 0.1 108 | if (absoluteSum < kSwipeMinimumLength) { // Assert if the absolute sum is long enough to be considered a complete gesture 109 | return .none 110 | } 111 | if (sum > 0) { 112 | return .right 113 | }else /*if(sum < 0)*/ { 114 | return .left 115 | } 116 | } 117 | return .none // no swipe direction detected 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /WebShell/Core/WSViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSViewController.swift 3 | // WebShell 4 | // 5 | // Created by Randy on 15/12/19. 6 | // Copyright © 2015 RandyLu. All rights reserved. 7 | // 8 | // Wesley de Groot (@wdg), Added Notification and console.log Support 9 | 10 | import Cocoa 11 | import WebKit 12 | import Foundation 13 | import AppKit 14 | import AudioToolbox 15 | import IOKit.ps 16 | import Darwin 17 | import CoreLocation 18 | 19 | // @wdg Clean up code base 20 | // Issue: #43 21 | class WSViewController: NSViewController, WebFrameLoadDelegate, WebUIDelegate, WebResourceLoadDelegate, WebPolicyDelegate, CLLocationManagerDelegate, WebDownloadDelegate, NSURLDownloadDelegate, WebEditingDelegate { 22 | 23 | @IBOutlet var mainWindow: NSView! 24 | @IBOutlet weak var mainWebview: WebView! 25 | @IBOutlet weak var launchingLabel: NSTextField! 26 | @IBOutlet weak var progressBar: NSProgressIndicator! 27 | 28 | var settings = Settings.shared 29 | var firstLoadingStarted = false 30 | var firstAppear = true 31 | var notificationCount = 0 32 | var lastURL:URL! 33 | var IElement = NSMenuItem() 34 | let locationManager = CLLocationManager() 35 | var MustCloseWindow = true 36 | var WSgestureLog: [CGFloat] = [0, 0] 37 | var twoFingersTouches: [String: NSTouch]? 38 | 39 | override func viewDidAppear() { 40 | if (firstAppear) { 41 | initWindow() 42 | } 43 | } 44 | 45 | // @wdg Possible fix for Mavericks 46 | // Issue: #18 47 | override func awakeFromNib() { 48 | // if (![self respondsToSelector:@selector(viewWillAppear)]) { 49 | 50 | if (!NSViewController().responds(to: #selector(NSViewController.viewWillAppear))) { 51 | checkSettings() 52 | 53 | let myPopup: NSAlert = NSAlert() 54 | myPopup.messageText = "Hello!" 55 | myPopup.informativeText = "You are running mavericks?" 56 | myPopup.alertStyle = NSAlert.Style.informational 57 | myPopup.addButton(withTitle: "Yes") 58 | myPopup.addButton(withTitle: "No") 59 | 60 | let res = myPopup.runModal() 61 | 62 | 63 | print("MAVERICKS \(res)") 64 | 65 | // OS X 10.9 66 | if (firstAppear) { 67 | initWindow() 68 | } 69 | 70 | mainWebview.uiDelegate = self 71 | mainWebview.resourceLoadDelegate = self 72 | mainWebview.downloadDelegate = self 73 | mainWebview.editingDelegate = self 74 | mainWebview.policyDelegate = self 75 | //WebUIDelegate 76 | 77 | addObservers() 78 | initSettings() 79 | loadUrl(settings.startURL()) 80 | WSMediaLoop(self) 81 | WSinitSwipeGestures() 82 | } 83 | } 84 | 85 | override func viewDidLoad() { 86 | checkSettings() 87 | //self.view = goodView() 88 | super.viewDidLoad() 89 | 90 | mainWebview.uiDelegate = self 91 | mainWebview.resourceLoadDelegate = self 92 | mainWebview.downloadDelegate = self 93 | mainWebview.editingDelegate = self 94 | mainWebview.policyDelegate = self 95 | 96 | // WebShellSettings["WSMW"] = mainWebview; 97 | 98 | checkSettings() 99 | addObservers() 100 | initSettings() 101 | loadUrl(settings.startURL()) 102 | WSMediaLoop(self) 103 | WSinitSwipeGestures() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /WebShell/Core/WSWebViewFunctions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewFunctions.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | // See: #43 13 | extension WSViewController { 14 | func webView(_ sender: WebView!, runJavaScriptAlertPanelWithMessage message: String!, initiatedBy frame: WebFrame!) { 15 | let alert = NSAlert() 16 | alert.addButton(withTitle: "OK") 17 | alert.messageText = "Message" 18 | alert.informativeText = message 19 | alert.runModal() 20 | } 21 | 22 | // webview settings 23 | func webView(_ sender: WebView!, didStartProvisionalLoadFor frame: WebFrame!) { 24 | // @wdg: Better progress indicator | Issue: #37 25 | if settings.showLoadingBar { 26 | print("Started loading animation") 27 | progressBar.startAnimation(self) 28 | progressBar.maxValue = 100; 29 | progressBar.minValue = 1; 30 | progressBar.increment(by: 24) 31 | } 32 | 33 | if (!firstLoadingStarted) { 34 | firstLoadingStarted = true 35 | launchingLabel.isHidden = false 36 | } 37 | } 38 | 39 | // @wdg: Better progress indicator 40 | // Issue: #37 41 | func webView(_ sender: WebView!, willPerformClientRedirectTo URL: URL!, delay seconds: TimeInterval, fire date: Date!, for frame: WebFrame!) { 42 | if settings.showLoadingBar { 43 | progressBar.isHidden = false 44 | progressBar.startAnimation(self) 45 | progressBar.maxValue = 100; 46 | progressBar.minValue = 1; 47 | progressBar.increment(by: 24) 48 | } 49 | } 50 | 51 | // @wdg: Better progress indicator 52 | // Issue: #37 53 | func webView(_ webView: WebView!, decidePolicyForMIMEType type: String!, request: URLRequest!, frame: WebFrame!, decisionListener listener: WebPolicyDecisionListener!) { 54 | if settings.showLoadingBar { 55 | progressBar.isHidden = false 56 | progressBar.startAnimation(self) 57 | progressBar.maxValue = 100; 58 | progressBar.minValue = 1; 59 | progressBar.increment(by: 24) 60 | } 61 | } 62 | 63 | // @wdg: Better progress indicator 64 | // Issue: #37 65 | func webView(_ webView: WebView!, didFailLoadWithError error: NSError) { 66 | progressBar.increment(by: 50) 67 | progressBar.stopAnimation(self) 68 | progressBar.isHidden = true 69 | progressBar.doubleValue = 1; 70 | } 71 | 72 | // @wdg: Better progress indicator 73 | // Issue: #37 74 | func webView(_ sender: WebView!, didFinishLoadFor frame: WebFrame!) { 75 | progressBar.increment(by: 50) 76 | progressBar.stopAnimation(self) 77 | progressBar.isHidden = true // Hide after we're done. 78 | progressBar.doubleValue = 1; 79 | if (!launchingLabel.isHidden) { 80 | launchingLabel.isHidden = true 81 | } 82 | // Save URL for last navigated page 83 | if let url = mainWebview.mainFrame.dataSource?.request.url?.absoluteString { 84 | settings.lastURL = url 85 | } 86 | 87 | // Inject Webhooks 88 | self.injectWebhooks(mainWebview.mainFrame.javaScriptContext) 89 | self.loopThroughiFrames() 90 | 91 | // @wdg Add location support 92 | // Issue: #41 93 | if settings.needLocation { 94 | self.websiteWantsLocation() 95 | } else { 96 | self.locationInjector(false) // Says i don't have a location! 97 | } 98 | } 99 | 100 | func webView(_ sender: WebView!, didReceiveTitle title: String!, for frame: WebFrame!) { 101 | if settings.useDocumentTitle { 102 | mainWindow.window?.title = title 103 | } 104 | 105 | self.injectLocalStorage(jsContext: frame.javaScriptContext) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /WebShell/Core/WSWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSWindow.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 04/01/2018. 6 | // Copyright © 2018 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | import AppKit 12 | 13 | class WSNSWindow : NSWindow { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /WebShell/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf200 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | \margl1440\margr1440\vieww9000\viewh8400\viewkind0 6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 7 | 8 | \f0\fs24 \cf0 WebShell is a powerful application which makes it easy to integrate your web-app to a fully working app.\ 9 | WebShell is created with help of:\ 10 | Randy - https://github.com/djyde \ 11 | Wesley - https://github.com/wdg\ 12 | DreamPiggy - https://github.com/lizhuoli1126\ 13 | Viralis - https://github.com/viralis\ 14 | Visoar - https://github.com/Visoar} -------------------------------------------------------------------------------- /WebShell/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 23-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | WebShell Class 13 | 14 | This class is the main class for WebShell. 15 | */ 16 | class WebShell { 17 | /** 18 | The settings Dictionary 19 | */ 20 | var Settings: [String: Any] = [ 21 | // Url to browse to. 22 | "url": "http://djyde.github.io/WebShell/WebShell/", 23 | 24 | // set the app title 25 | "title": Bundle.main.infoDictionary!["CFBundleName"] as! String, 26 | 27 | // if you want to use the default one then leave it default || default = title/version based on Safari/AppleWebKit (KHTML, like Gecko) 28 | // otherwise change it to a useragent you want. (Default: "default") 29 | "useragent": "default", 30 | 31 | // Do you want to use the document title? (Default: true) 32 | "useDocumentTitle": true, 33 | 34 | // Multilanguage loading text! 35 | "launchingText": NSLocalizedString("Launching...", comment: "Launching..."), 36 | 37 | // Note that the window min height is 640 and min width is 1000 by default. You could change it in Main.storyboard 38 | "initialWindowHeight": 640, 39 | "initialWindowWidth": 1000, 40 | 41 | // Open target=_blank in a new screen? (Default: false) 42 | "openInNewScreen": false, 43 | 44 | // Do you want a loading bar? (Default: true) 45 | "showLoadingBar": true, 46 | 47 | // Add console.log support? (Default: false) 48 | "consoleSupport": false, 49 | 50 | // Does the app needs Location support (Default: false) 51 | // note: if true, then WebShell always uses location, whenever it is used or not 52 | "needLocation": false, 53 | 54 | // run the app in debug mode? (Default: false) 55 | // will be overridden by Xcode (runs with -NSDocumentRevisionsDebugMode YES) 56 | "debugmode": false, 57 | 58 | // Please paste here the JavaScript you want to load on a website (Default: "") 59 | "JSInject": "", 60 | 61 | // Please paste here the CSS you want to load on a website (Default: "") 62 | "CSSInject": "", 63 | 64 | // Enable (inject) import (JS/CSS) Folder. (Default: true) 65 | "EnableInjectImport": true, 66 | 67 | // Menubar app (right side next to clock) (Default: true) 68 | "MenuBarApp": false, 69 | 70 | // Navigate trough trackpad (back/forward) (Default: true) 71 | "navigateViaTrackpad": true, 72 | 73 | // Use a password manager (Default: true). 74 | "passwordManager": true, 75 | 76 | // Media keys settings 77 | "MediaKeys": [ 78 | // Enable "Back" & "Forward" 79 | "BackAndForward": true, 80 | 81 | // Media Player support (experimental) 82 | "MediaPlayers": false 83 | ], 84 | 85 | // Contextmenu settings 86 | "Contextmenu": [ 87 | // Enable "Back" & "Forward" (Default: true) 88 | "BackAndForward": true, 89 | 90 | // Enable "Download" (Default: true) 91 | "Download": true, 92 | 93 | // Enable "Reload" (Default: true) 94 | "Reload": true, 95 | 96 | // Enable "Open in a new window" (Default: true) 97 | "newWindow": true 98 | ], 99 | 100 | // Just a placeholder. (Default: true) 101 | "fake": true 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /WebShell/Sites/Udemy/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 23-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Settings: WSBaseSettings { 12 | static let shared = Settings() 13 | 14 | override private init() { 15 | super.init() 16 | // Override default settings for this particular target 17 | self.url = "http://udemy.com" 18 | 19 | // Save last URL 20 | self.openLastUrl = true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /WebShell/Sites/WebShell/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 23-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Settings: WSBaseSettings { 12 | static let shared = Settings() 13 | 14 | override private init() { 15 | super.init() 16 | // Override default settings for this particular target 17 | self.url = "http://djyde.github.io/WebShell/WebShell/" 18 | 19 | // set the app title 20 | self.title = Bundle.main.infoDictionary!["CFBundleName"] as! String 21 | 22 | // if you want to use the default one then leave it default || default = title/version based on Safari/AppleWebKit (KHTML, like Gecko) 23 | // otherwise change it to a useragent you want. (Default: "default") 24 | self.useragent = "default" 25 | 26 | // Do you want to use the document title? (Default: true) 27 | self.useDocumentTitle = true 28 | 29 | // Multilanguage loading text! 30 | self.launchingText = NSLocalizedString("Launching...", comment: "Launching...") 31 | 32 | // Open target=_blank in a new screen? (Default: false) 33 | self.openInNewScreen = false 34 | 35 | // Do you want a loading bar? (Default: true) 36 | self.showLoadingBar = true 37 | 38 | // Add console.log support? (Default: false) 39 | self.consoleSupport = false 40 | 41 | // Does the app needs Location support (Default: false) 42 | // note: if true, then WebShell always uses location, whenever it is used or not 43 | self.needLocation = false 44 | 45 | // run the app in debug mode? (Default: false) 46 | // will be overridden by Xcode (runs with -NSDocumentRevisionsDebugMode YES) 47 | self.debugmode = false 48 | 49 | // Please paste here the JavaScript you want to load on a website (Default: "") 50 | self.jsInject = "" 51 | 52 | // Please paste here the CSS you want to load on a website (Default: "") 53 | self.cssInject = "" 54 | 55 | // Enable (inject) import (JS/CSS) Folder. (Default: true) 56 | self.enableInjectImport = true 57 | 58 | // Menubar app (right side next to clock) (Default: false) 59 | self.menuBarApp = false 60 | 61 | // Navigate trough trackpad (back/forward) (Default: true) 62 | self.navigateViaTrackpad = true 63 | 64 | // Use a password manager (Default: true). 65 | self.passwordManager = true 66 | 67 | // Media keys settings - Enable "Back" & "Forward" 68 | self.mkBackAndForward = true 69 | 70 | // Media Player support (experimental) 71 | self.mkMediaPlayers = false 72 | 73 | // Contextmenu settings - // Enable "Back" & "Forward" (Default: true) 74 | self.cmBackAndForward = true 75 | 76 | // Enable "Download" (Default: true) 77 | self.cmDownload = true 78 | 79 | // Enable "Reload" (Default: true) 80 | self.cmReload = true 81 | 82 | // Enable "Open in a new window" (Default: true) 83 | self.cmNewWindow = true 84 | 85 | // open with last url? (Default: false) 86 | self.openLastUrl = false 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /WebShell/Support/Battery.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Battery.swift 3 | // WebShell 4 | // 5 | // Created by lizhuoli on 15/12/31. 6 | // Copyright © 2015年 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | @objc protocol BatteryManagerJSExports : JSExport { 13 | var charging: Bool { get set } 14 | var chargingTime: Int { get set } 15 | var dischargingTime: Int { get set } 16 | var level: Double { get set } 17 | 18 | static func getBattery() -> BatteryManager 19 | } 20 | 21 | @objc class BatteryManager : NSObject, BatteryManagerJSExports { 22 | internal var level: Double 23 | internal var chargingTime: Int 24 | 25 | dynamic var charging: Bool 26 | // dynamic var chargingTime: Int 27 | dynamic var dischargingTime: Int 28 | // dynamic var level: Double 29 | 30 | override init() { 31 | self.charging = true 32 | self.chargingTime = 0 33 | self.dischargingTime = 999 34 | self.level = 1.0 35 | } 36 | 37 | class func getBattery() -> BatteryManager { 38 | // Object to export to JavaScript 39 | let battery = BatteryManager() 40 | 41 | // Use IOKit to get battery infomation 42 | let blob = IOPSCopyPowerSourcesInfo().takeRetainedValue() 43 | let sources = IOPSCopyPowerSourcesList(blob).takeRetainedValue() 44 | let sourceArray:NSArray = sources 45 | if (sourceArray.count == 0) { // Could not retrieve battery information. 46 | return battery 47 | } else { 48 | let batterySource = sourceArray.object(at: 0) // just use first battery 49 | let pSource = IOPSGetPowerSourceDescription(blob, batterySource as CFTypeRef!).takeUnretainedValue() 50 | 51 | let batteryDic:NSDictionary = pSource 52 | 53 | let isCharge = batteryDic.object(forKey: kIOPSIsChargingKey) as! Int // 1 for charging, 0 for not 54 | let curCapacity = batteryDic.object(forKey: kIOPSCurrentCapacityKey) as! Int // current capacity 55 | let maxCapacity = batteryDic.object(forKey: kIOPSMaxCapacityKey) as! Int // max capacity 56 | let chargingTime = batteryDic.object(forKey: kIOPSTimeToEmptyKey) as! Int // time to empty(not charging) 57 | let dischargingTime = batteryDic.object(forKey: kIOPSTimeToFullChargeKey) as! Int // time to full(charging) 58 | let level = Double(curCapacity) / Double(maxCapacity) // current level 59 | 60 | battery.charging = isCharge == 1 ? true : false 61 | battery.chargingTime = chargingTime 62 | battery.dischargingTime = dischargingTime 63 | battery.level = level 64 | 65 | return battery 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /WebShell/Support/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | import AudioToolbox 12 | 13 | // @wdg Add Notification Support 14 | // Issue: #2 15 | // This extension will handle the HTML5 Notification API. 16 | extension WSViewController { 17 | func clearNotificationCount() -> Void { 18 | notificationCount = 0 19 | } 20 | 21 | // @wdg Add Notification Support 22 | // Issue: #2 23 | func makeNotification(_ title: NSString, message: NSString, icon: NSString) -> Void { 24 | let notification: NSUserNotification = NSUserNotification() // Set up Notification 25 | 26 | // If has no message (title = message) 27 | if (message.isEqual(to: "undefined")) { 28 | notification.title = Bundle.main.infoDictionary!["CFBundleName"] as? String // Use App name! 29 | notification.informativeText = title as String // Title = string 30 | } else { 31 | notification.title = title as String // Title = string 32 | notification.informativeText = message as String // Message = string 33 | } 34 | 35 | 36 | notification.soundName = NSUserNotificationDefaultSoundName // Default sound 37 | notification.deliveryDate = Date(timeIntervalSinceNow: 0) // Now! 38 | notification.actionButtonTitle = "Close" 39 | 40 | // Notification has a icon, so add it! 41 | if (!icon.isEqual(to: "undefined")) { 42 | notification.contentImage = NSImage(contentsOf: URL(string: icon as String)!) ; 43 | } 44 | 45 | let notificationcenter: NSUserNotificationCenter? = NSUserNotificationCenter.default // Notification centre 46 | notificationcenter?.scheduleNotification(notification) // Pushing to notification centre 47 | 48 | notificationCount += 1 49 | 50 | NSApplication.shared.dockTile.badgeLabel = String(notificationCount) 51 | } 52 | 53 | // @wdg Add Notification Support 54 | // Issue: #2 55 | func flashScreen(_ data: NSString) -> Void { 56 | if ((Int(data as String)) != nil || data.isEqual(to: "undefined")) { 57 | AudioServicesPlaySystemSound(kSystemSoundID_FlashScreen) ; 58 | } else { 59 | let time: [String] = (data as String).components(separatedBy: ",") 60 | for i in 0 ..< time.count { 61 | // @wdg Fix flashScreen(...) 62 | // Issue: #66 63 | let timeAsNumber = NumberFormatter().number(from: time[i])?.intValue 64 | Timer.scheduledTimer(timeInterval: TimeInterval(timeAsNumber!), target: self, selector: #selector(WSViewController.flashScreenNow), userInfo: nil, repeats: false) 65 | } 66 | } 67 | } 68 | 69 | // @wdg Add Notification Support 70 | // Issue: #2 71 | @objc func flashScreenNow() -> Void { 72 | AudioServicesPlaySystemSound(kSystemSoundID_FlashScreen) ; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /WebShell/Support/navigator_geolocation_getCurrentPosition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // navigator_geolocation_getCurrentPosition.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 29-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | // @wdg Add location support 13 | // Issue: #41 14 | // This extension will handle all the location services. 15 | extension WSViewController { 16 | 17 | /** 18 | websiteWantsLocation 19 | 20 | the requested website wants/needs location services, so start it up 21 | */ 22 | func websiteWantsLocation() -> Void { 23 | locationManager.delegate = self 24 | locationManager.desiredAccuracy = kCLLocationAccuracyBest 25 | locationManager.startUpdatingLocation() 26 | } 27 | 28 | /** 29 | locationManager got some locations for us! 30 | 31 | - Parameter manager: CLLocationManager 32 | - Parameter locations: AnyObject 33 | */ 34 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 35 | let location: CLLocation = locations[0] 36 | self.locationInjector(true, location) 37 | } 38 | 39 | /** 40 | locationManager got a error! 41 | 42 | - Parameter manager: CLLocationManager 43 | - Parameter error: NSError 44 | */ 45 | func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { 46 | self.locationInjector(false) 47 | } 48 | 49 | /** 50 | locationManager got some locations for us! 51 | 52 | - Parameter haveLocation: (Bool) have the location? 53 | - Parameter location: (CLLocation) the location 54 | */ 55 | func locationInjector(_ haveLocation: Bool, _ location: CLLocation? = CLLocation()) { 56 | // Ok inject new java Thing! (Cool!) 57 | let navigatorGeolocationGetCurrentPosition: @convention(block)(String?, String?, String?) -> String = {(correct: String?, invalid: String?, extra: String?) in 58 | // Checked with. 59 | // navigator.geolocation.getCurrentPosition(function(position) {console.log(position);},function(position) {console.log(position);}); 60 | 61 | // X.coords.longitude 62 | // Safari Demo: > var x={coords: {longitude: 2, latitude: 1}}; console.log(x.coords.longitude); 63 | // Safari Demo: [Log] 2 64 | 65 | if (haveLocation) { 66 | let check_correct: String = correct!.lowercased()[0...8] 67 | let returnAs: String = "{coords: {coords: 'Coordinates', accuracy: 10, altitude: \(location!.altitude), altitudeAccuracy: 10, heading: '\(location!.course)', longitude: \(location!.coordinate.longitude), latitude: \(location!.coordinate.latitude), speed: \(location!.speed)}}" 68 | 69 | if (check_correct == "function") { 70 | // Begin with function (all lowercase) 71 | var newFunction = correct!.lowercased()[0...8] 72 | // Make the function named _WSLRD (WebShell Location Return Data) 73 | newFunction = newFunction + " _WSLRD" 74 | // Check if has space or not, otherwise begin 1 character later 75 | newFunction = newFunction + (correct?[(correct?.lowercased()[8] == " " ? 8 : 7)...(correct?.count)!])! 76 | // Call the function 77 | newFunction = newFunction + "\n;_WSLRD(\(returnAs))" // Insert what to return 78 | 79 | self.mainWebview.mainFrame.javaScriptContext.evaluateScript(newFunction) // Call & Done. 80 | } else { 81 | // call something else if it is a function, otherwise throw a error. 82 | 83 | let checkAndRun: String = String(describing: "if (typeof \(String(describing: correct)) === 'function'){\(String(describing: correct))(\(returnAs))}else{console.error('\(String(describing: correct)) is not a function')}") 84 | self.mainWebview.mainFrame.javaScriptContext.evaluateScript(checkAndRun) // Call & Done. 85 | } 86 | } else { 87 | if (invalid != nil) { 88 | let check_invalid: String = invalid!.lowercased()[0...8] 89 | let returnAs: String = "{coords: {coords: null, accuracy: null, altitude: null, altitudeAccuracy: null, heading: null, longitude: null, latitude: null, speed: null}}" 90 | 91 | if (check_invalid == "function") { 92 | // Begin with function (all lowercase) 93 | var newFunction = invalid!.lowercased()[0...8] 94 | // Make the function named _WSLRD (WebShell Location Return Data) 95 | newFunction = newFunction + " _WSLRD" 96 | // Check if has space or not, otherwise begin 1 character later 97 | newFunction = newFunction + invalid![(invalid!.lowercased()[8] == " " ? 8 : 7)...(invalid!.count)] 98 | // Call the function 99 | newFunction = newFunction + "\n;_WSLRD(\(returnAs))" // Insert what to return 100 | 101 | self.mainWebview.mainFrame.javaScriptContext.evaluateScript(newFunction) // Call & Done. 102 | } else { 103 | // call something else if it is a function, otherwise throw a error. 104 | 105 | let checkAndRun: String = "if (typeof \(invalid!) === 'function'){\(invalid!)(\(returnAs))}else{console.error('\(invalid!) is not a function')}" 106 | self.mainWebview.mainFrame.javaScriptContext.evaluateScript(checkAndRun) // Call & Done. 107 | } 108 | } 109 | } 110 | 111 | return "undefined" 112 | } 113 | self.mainWebview.mainFrame.javaScriptContext.objectForKeyedSubscript("navigator").objectForKeyedSubscript("geolocation").setObject(unsafeBitCast(navigatorGeolocationGetCurrentPosition, to: AnyObject.self), forKeyedSubscript: "getCurrentPosition" as (NSCopying & NSObjectProtocol)!) 114 | } 115 | } 116 | // TEST 117 | /* 118 | Inject via webinfo 119 | 120 | > navigator.geolocation.getCurrentPosition(function(position) {console.log(position.coords.latitude);},function(position) {console.log(position);}); 121 | < "undefined" = $2 122 | 123 | Xcode: 124 | JS: 52.3593145320769 (Your current latitude) 125 | */ 126 | -------------------------------------------------------------------------------- /WebShell/Udemy-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 201605242200 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSHumanReadableCopyright 33 | Copyright © 2015 RandyLu. All rights reserved. 34 | NSMainStoryboardFile 35 | Main 36 | NSPrincipalClass 37 | WSApplication 38 | 39 | 40 | -------------------------------------------------------------------------------- /WebShell/WSCore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellCore.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | extension ViewController { 13 | /** 14 | Quit the app (there must be a better way) 15 | */ 16 | func Quit(_ sender: AnyObject) { 17 | exit(0) 18 | } 19 | 20 | /** 21 | Function to call for the window.open (popup) 22 | 23 | - Parameter url: The url to open 24 | - Parameter height: The height for the window 25 | - Parameter width: The width for the window 26 | */ 27 | func openNewWindow(url: String, height: String, width: String) -> Void { 28 | // @wdg Replaced NSPipe for NSWorkspace 29 | // Issue: #48 30 | let ws = NSWorkspace.shared 31 | do { 32 | if (WebShellSettings["debugmode"] as! Bool) { 33 | try ws.launchApplication(at: URL(string: "file://\(CommandLine.arguments[0])")!, options: NSWorkspace.LaunchOptions.newInstance, configuration: [NSWorkspace.LaunchConfigurationKey.arguments: ["-NSDocumentRevisionsDebugMode", "YES", "-url", url, "-height", height, "-width", width]]) 34 | } else { 35 | try ws.launchApplication(at: URL(string: CommandLine.arguments[0])!, options: NSWorkspace.LaunchOptions.newInstance, configuration: [NSWorkspace.LaunchConfigurationKey.arguments: ["-url", url, "-height", height, "-width", width]]) 36 | } 37 | } 38 | catch { /* we'll never get this. */ } 39 | } 40 | 41 | /** 42 | Noop a.k.a. No operation. 43 | 44 | - Parameter ob: Any ... 45 | */ 46 | func noop(_ ob: Any ...) -> Void { } 47 | 48 | /** 49 | Delay a function 50 | 51 | - Parameter delay: Time to delay 52 | - Parameter closure: Code to run (in a escaping block) 53 | */ 54 | func delay(_ delay: Double, _ closure: @escaping () -> ()) { 55 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure) 56 | } 57 | 58 | /** 59 | Run on main thread. 60 | 61 | - Parameter run: Code to run (in a escaping block) 62 | */ 63 | func runOnMain(_ run: @escaping () -> ()) { 64 | DispatchQueue.main.async(execute: run) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /WebShell/WSDebug.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellDebug.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | // This is generated by swift, i dont know the reason, 12 | // but i'm not removing it. 13 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 14 | switch (lhs, rhs) { 15 | case let (l?, r?): 16 | return l < r 17 | case (nil, _?): 18 | return true 19 | default: 20 | return false 21 | } 22 | } 23 | 24 | fileprivate func > (lhs: T?, rhs: T?) -> Bool { 25 | switch (lhs, rhs) { 26 | case let (l?, r?): 27 | return l > r 28 | default: 29 | return rhs < lhs 30 | } 31 | } 32 | 33 | 34 | // @wdg Add Debug support 35 | // Issue: None. 36 | // This extension will handle the Debugging options. 37 | extension ViewController { 38 | 39 | /** 40 | Override settings via commandline 41 | 42 | Used for popups, and debug options. 43 | */ 44 | func checkSettings() -> Void { 45 | // Need to overwrite settings? 46 | if (CommandLine.argc > 0) { 47 | for i in stride(from: 1, to: Int(CommandLine.argc), by: 2) { 48 | // for (var i = 1; i < Int(Process.argc) ; i = i + 2) { 49 | if ((String(describing: CommandLine.arguments[i])) == "-NSDocumentRevisionsDebugMode") { 50 | if ((String(describing: CommandLine.arguments[i + 1])) == "YES") { 51 | WebShellSettings["debugmode"] = true 52 | WebShellSettings["consoleSupport"] = true 53 | } 54 | } 55 | 56 | if ((String(describing: Process().arguments?[i])).uppercased() == "-DEBUG") { 57 | if ((String(describing: Process().arguments![i + 1])).uppercased() == "YES" || (String(describing: Process().arguments?[i + 1])).uppercased() == "true") { 58 | WebShellSettings["debugmode"] = true 59 | WebShellSettings["consoleSupport"] = true 60 | } 61 | } 62 | 63 | if ((String(describing: CommandLine.arguments[i])) == "-dump-args") { 64 | self._debugDumpArguments("" as AnyObject) 65 | } 66 | 67 | if ((String(describing: CommandLine.arguments[i])) == "-url") { 68 | WebShellSettings["url"] = String(CommandLine.arguments[i + 1]) 69 | } 70 | 71 | if ((String(describing: CommandLine.arguments[i])) == "-height") { 72 | WebShellSettings["initialWindowHeight"] = (Int(CommandLine.arguments[i + 1]) > 250) ? Int(CommandLine.arguments[i + 1]) : Int(250) 73 | } 74 | 75 | if ((String(describing: CommandLine.arguments[i])) == "-width") { 76 | WebShellSettings["initialWindowWidth"] = (Int(CommandLine.arguments[i + 1]) > 250) ? Int(CommandLine.arguments[i + 1]) : Int(250) 77 | } 78 | } 79 | } 80 | 81 | initWindow() 82 | } 83 | 84 | /** 85 | Edit contextmenu... 86 | 87 | @wdg Fix contextmenu (problem with the swift 3 update) 88 | 89 | Issue: #61 90 | */ 91 | func webView(_ sender: WebView!, contextMenuItemsForElement element: [AnyHashable : Any]!, defaultMenuItems: [Any]!) -> [Any]! { 92 | //Swift 2.. 93 | //func webView(_ sender: WebView!, contextMenuItemsForElement element: [NSObject: Any]!, defaultMenuItems: [Any]!) -> [Any]! 94 | 95 | // @wdg Fix contextmenu (problem with the swift 2 update #50) 96 | // Issue: #51 97 | var download = false 98 | 99 | for i in defaultMenuItems { 100 | // Oh! download link available! 101 | if (String(describing: (i as AnyObject).title).contains("Download")) { 102 | download = true 103 | } 104 | 105 | // Get inspect element! 106 | if (String(describing: (i as AnyObject).title).contains("Element")) { 107 | for x in 0 ..< defaultMenuItems.count { 108 | if (String(describing: defaultMenuItems[x]).contains("Element")) { 109 | IElement = defaultMenuItems[x] as! NSMenuItem 110 | } 111 | } 112 | } 113 | } 114 | 115 | var NewMenu: [AnyObject] = [AnyObject]() 116 | let contextMenu = WebShellSettings["Contextmenu"] as! [String: Bool] 117 | 118 | // if can back 119 | if (contextMenu["BackAndForward"]!) { 120 | if (mainWebview.canGoBack) { 121 | NewMenu.append(NSMenuItem.init(title: "Back", action: #selector(ViewController._goBack(_:)), keyEquivalent: "")) 122 | } 123 | if (mainWebview.canGoForward) { 124 | NewMenu.append(NSMenuItem.init(title: "Forward", action: #selector(ViewController._goForward(_:)), keyEquivalent: "")) 125 | } 126 | } 127 | if (contextMenu["Reload"]!) { 128 | NewMenu.append(NSMenuItem.init(title: "Reload", action: #selector(ViewController._reloadPage(_:)), keyEquivalent: "")) 129 | } 130 | 131 | if (download) { 132 | if (element["WebElementLinkURL"] != nil) { 133 | lastURL = element["WebElementLinkURL"]! as! URL 134 | 135 | if (contextMenu["Download"]! || contextMenu["newWindow"]!) { 136 | NewMenu.append(NSMenuItem.separator()) 137 | 138 | if (contextMenu["newWindow"]!) { 139 | NewMenu.append(NSMenuItem.init(title: "Open Link in a new Window", action: #selector(ViewController.createNewInstance(_:)), keyEquivalent: "")) 140 | } 141 | if (contextMenu["Download"]!) { 142 | NewMenu.append(NSMenuItem.init(title: "Download Linked File", action: #selector(ViewController.downloadFileWithURL(_:)), keyEquivalent: "")) 143 | } 144 | } 145 | } 146 | } 147 | 148 | NewMenu.append(NSMenuItem.separator()) 149 | // Add debug menu. (if enabled) 150 | 151 | if (WebShellSettings["debugmode"] as! Bool) { 152 | let debugMenu = NSMenu(title: "Debug") 153 | if (IElement.title != "NSMenuItem") { 154 | debugMenu.addItem(IElement) // <-- Inspect element... 155 | } 156 | debugMenu.addItem(NSMenuItem.init(title: "Open New window", action: #selector(ViewController._debugNewWindow(_:)), keyEquivalent: "")) 157 | debugMenu.addItem(NSMenuItem.init(title: "Print arguments", action: #selector(ViewController._debugDumpArguments(_:)), keyEquivalent: "")) 158 | debugMenu.addItem(NSMenuItem.init(title: "Open URL", action: #selector(ViewController._openURL(_:)), keyEquivalent: "")) 159 | debugMenu.addItem(NSMenuItem.init(title: "Report an issue on this page", action: #selector(ViewController._reportThisPage(_:)), keyEquivalent: "")) 160 | debugMenu.addItem(NSMenuItem.init(title: "Print this page", action: #selector(ViewController._printThisPage(_:)), keyEquivalent: "")) // Stupid swift 2.2 does not look in extensions. 161 | debugMenu.addItem(NSMenuItem.separator()) 162 | debugMenu.addItem(NSMenuItem.init(title: "Fire some random Notifications", action: #selector(ViewController.__sendNotifications(_:)), keyEquivalent: "")) 163 | debugMenu.addItem(NSMenuItem.init(title: "Reset localstorage", action: #selector(ViewController.resetLocalStorage(_:)), keyEquivalent: "")) 164 | 165 | let WSdeveloperMenu = NSMenu(title: "WS Developer") 166 | WSdeveloperMenu.addItem(NSMenuItem.init(title: "Inject Javascript", action: #selector(ViewController._injectJS(_:)), keyEquivalent: "")) 167 | WSdeveloperMenu.addItem(NSMenuItem.init(title: "What the web can do", action: #selector(ViewController._WWCDT(_:)), keyEquivalent: "")) 168 | 169 | let WSDevMenu = NSMenuItem.init(title: "WebShell Developer", action: #selector(ViewController._doNothing(_:)), keyEquivalent: "") 170 | WSDevMenu.submenu = WSdeveloperMenu 171 | debugMenu.addItem(WSDevMenu) 172 | 173 | let item = NSMenuItem.init(title: "Debug", action: #selector(ViewController._doNothing(_:)), keyEquivalent: "") 174 | item.submenu = debugMenu 175 | 176 | NewMenu.append(item) 177 | NewMenu.append(NSMenuItem.separator()) 178 | } 179 | 180 | NewMenu.append(NSMenuItem.init(title: "Quit", action: #selector(ViewController._quit(_:)), keyEquivalent: "")) 181 | 182 | return NewMenu 183 | } 184 | 185 | /** 186 | Debug: Quit WebShell 187 | 188 | - Parameter Sender: Anyobject 189 | */ 190 | 191 | @objc func _quit(_ Sender: AnyObject) -> Void { 192 | exit(0) 193 | } 194 | 195 | /** 196 | Debug: doNothing 197 | 198 | - Parameter Sender: Anyobject 199 | */ 200 | @objc func _doNothing(_ Sender: AnyObject) -> Void { 201 | // _doNothing 202 | } 203 | 204 | /** 205 | Debug: Open new window 206 | 207 | - Parameter Sender: Anyobject 208 | */ 209 | @objc func _debugNewWindow(_ Sender: AnyObject) -> Void { 210 | openNewWindow(url: "https://www.google.nl/search?client=WebShell&rls=en&q=new+window", height: "0", width: "0") 211 | } 212 | 213 | /** 214 | Debug: Print arguments 215 | 216 | - Parameter Sender: Anyobject 217 | */ 218 | @objc func _debugDumpArguments(_ Sender: AnyObject) -> Void { 219 | print(CommandLine.arguments) 220 | } 221 | 222 | /** 223 | Debug: Fire 10 notifications (Timer) 224 | 225 | - Parameter Sender: Anyobject 226 | */ 227 | @objc func __sendNotifications(_ Sender: AnyObject) -> Void { 228 | // Minimize app 229 | NSApplication.shared.keyWindow?.miniaturize(self) 230 | 231 | // Fire 10 Notifications 232 | Timer.scheduledTimer(timeInterval: TimeInterval(05), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 233 | Timer.scheduledTimer(timeInterval: TimeInterval(15), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 234 | Timer.scheduledTimer(timeInterval: TimeInterval(25), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 235 | Timer.scheduledTimer(timeInterval: TimeInterval(35), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 236 | Timer.scheduledTimer(timeInterval: TimeInterval(45), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 237 | Timer.scheduledTimer(timeInterval: TimeInterval(55), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 238 | Timer.scheduledTimer(timeInterval: TimeInterval(65), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 239 | Timer.scheduledTimer(timeInterval: TimeInterval(75), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 240 | Timer.scheduledTimer(timeInterval: TimeInterval(85), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 241 | Timer.scheduledTimer(timeInterval: TimeInterval(95), target: self, selector: #selector(ViewController.___sendNotifications), userInfo: nil, repeats: false) 242 | } 243 | 244 | /** 245 | Debug: Send 10 Notifications (real sending) 246 | 247 | - Parameter Sender: Anyobject 248 | */ 249 | @objc func ___sendNotifications() -> Void { 250 | // Minimize app 251 | if (NSApplication.shared.keyWindow?.isMiniaturized == false) { 252 | NSApplication.shared.keyWindow?.miniaturize(self) 253 | } 254 | 255 | // Send Actual notification. 256 | makeNotification("Test Notification", message: "Hi!", icon: "https://camo.githubusercontent.com/ee999b2d8fa5413229fdc69e0b53144f02b7b840/687474703a2f2f376d6e6f79372e636f6d312e7a302e676c622e636c6f7564646e2e636f6d2f7765627368656c6c2f6c6f676f2e706e673f696d616765566965772f322f772f313238") 257 | } 258 | 259 | /** 260 | Debug: Open URL 261 | 262 | - Parameter Sender: Anyobject 263 | */ 264 | @objc func _openURL(_ Sender: AnyObject) -> Void { 265 | let msg = NSAlert() 266 | msg.addButton(withTitle: "OK") // 1st button 267 | msg.addButton(withTitle: "Cancel") // 2nd button 268 | msg.messageText = "URL" 269 | msg.informativeText = "Where you need to go?" 270 | 271 | let txt = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) 272 | txt.stringValue = "http://" 273 | 274 | msg.accessoryView = txt 275 | let response: NSApplication.ModalResponse = msg.runModal() 276 | 277 | if (response == NSApplication.ModalResponse.alertFirstButtonReturn) { 278 | self.loadUrl(txt.stringValue) 279 | } 280 | } 281 | 282 | /** 283 | Debug: WhatTheWebCanDo.Today 284 | 285 | - Parameter Sender: Anyobject 286 | */ 287 | @objc func _WWCDT(_ Sender: AnyObject) -> Void { 288 | self.loadUrl("https://whatwebcando.today") 289 | } 290 | 291 | /** 292 | Debug: Inject custom javascript 293 | 294 | - Parameter Sender: Anyobject 295 | */ 296 | @objc func _injectJS(_ Sender: AnyObject) -> Void { 297 | let msg = NSAlert() 298 | msg.addButton(withTitle: "OK") // 1st button 299 | msg.addButton(withTitle: "Cancel") // 2nd button 300 | msg.messageText = "Inject Javascript" 301 | msg.informativeText = "Inject Javascript\nBe Carefull!" 302 | 303 | let txt = NSTextField(frame: NSRect(x: 0, y: 0, width: 400, height: 400)) 304 | txt.stringValue = "" 305 | txt.translatesAutoresizingMaskIntoConstraints = true 306 | 307 | msg.accessoryView = txt 308 | let response: NSApplication.ModalResponse = msg.runModal() 309 | 310 | if (response == NSApplication.ModalResponse.alertFirstButtonReturn) { 311 | let JSReturn: String = mainWebview.stringByEvaluatingJavaScript(from: txt.stringValue) 312 | 313 | let RetVal = NSAlert() 314 | RetVal.addButton(withTitle: "OK") 315 | RetVal.messageText = "Injected Javascript" 316 | RetVal.informativeText = JSReturn != "" ? JSReturn : "Finished" 317 | RetVal.runModal() 318 | } 319 | } 320 | 321 | /** 322 | Debug: Report this page, as containing an error. 323 | 324 | - Parameter Sender: Anyobject 325 | */ 326 | @objc func _reportThisPage(_ Sender: AnyObject) -> Void { 327 | let currentUrl: String = (mainWebview.mainFrame.dataSource?.request.url?.absoluteString)! 328 | let host: String = (mainWebview.mainFrame.dataSource?.request.url?.host)! 329 | 330 | let issue: String = String("Problem loading \(host)").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!.replacingOccurrences(of: "&", with: "%26") 331 | var body: String = (String("There is a problem loading \(currentUrl)").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)?.replacingOccurrences(of: "&", with: "%26"))! 332 | body.append("%0D%0AThe%20problem%20is%3A%0D%0A...") 333 | 334 | let url: String = "https://github.com/djyde/WebShell/issues/new?title=\(issue)&body=\(body)" 335 | 336 | NSWorkspace.shared.open(URL(string: (url as String))!) 337 | } 338 | 339 | /** 340 | Print this page 341 | 342 | - Parameter Sender: Anyobject 343 | 344 | - Notes Stupid swift 2.2 & 3 does not look in extensions. 345 | - Notes so we'll copy again... 346 | - Notes @wdg Add Print Support 347 | - Notes Issue: #39 348 | */ 349 | @objc func _printThisPage(_ Sender: AnyObject? = nil) -> Void { 350 | let url = mainWebview.mainFrame.dataSource?.request?.url?.absoluteString 351 | 352 | let operation: NSPrintOperation = NSPrintOperation.init(view: mainWebview) 353 | operation.jobTitle = "Printing \(url!)" 354 | 355 | // If want to print landscape 356 | operation.printInfo.orientation = NSPrintInfo.PaperOrientation.landscape 357 | operation.printInfo.scalingFactor = 0.7 358 | 359 | if operation.run() { 360 | print("Printed?") 361 | } 362 | } 363 | 364 | /** 365 | Go Back 366 | 367 | - Parameter Sender: Anyobject 368 | */ 369 | @objc func _goBack(_ Sender: AnyObject) -> Void { 370 | if (mainWebview.canGoBack) { 371 | mainWebview.goBack(Sender) 372 | } 373 | } 374 | 375 | /** 376 | Go Forward 377 | 378 | - Parameter Sender: Anyobject 379 | */ 380 | @objc func _goForward(_ Sender: AnyObject) -> Void { 381 | if (mainWebview.canGoForward) { 382 | mainWebview.goForward(Sender) 383 | } 384 | } 385 | 386 | /** 387 | Reload page 388 | 389 | - Parameter Sender: Anyobject 390 | */ 391 | @objc func _reloadPage(_ Sender: AnyObject) -> Void { 392 | mainWebview.reload(Sender) 393 | } 394 | 395 | /** 396 | Debug: Open in a new window 397 | 398 | - Parameter Sender: Anyobject 399 | */ 400 | @objc func createNewInstance(_ Sender: AnyObject) -> Void { 401 | openNewWindow(url: "\(lastURL)", height: "0", width: "0") 402 | } 403 | 404 | /** 405 | Download file 406 | 407 | - Parameter Sender: Anyobject 408 | */ 409 | @objc func downloadFileWithURL(_ Sender: AnyObject) -> Void { 410 | let wsDM = WebShelllDownloadManager.init(url: lastURL) 411 | wsDM.endDownloadTask() 412 | } 413 | 414 | /** 415 | If in debugmode -> Print 416 | 417 | - Parameter S: Any 418 | */ 419 | func Dprint(_ S: Any) -> Void { 420 | if (WebShellSettings["debugmode"] as! Bool) { 421 | print(S) 422 | } 423 | } 424 | 425 | /** 426 | If in debugmode -> Dump 427 | 428 | - Parameter S: Any 429 | */ 430 | func Ddump(_ S: Any) -> Void { 431 | if (WebShellSettings["debugmode"] as! Bool) { 432 | dump(S) 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /WebShell/WSDownloadManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShelllDownloadManager.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 18-04-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | 13 | /** 14 | @wdg Add Download support 15 | 16 | Issue: #31 17 | 18 | This class will handle WebShell Downloads. 19 | 20 | It's a basic download manager, so no progress, and nothing else, just download. 21 | */ 22 | class WebShelllDownloadManager { 23 | var TURL: URL 24 | var Fname: String = "" 25 | var Session: URLSession = URLSession() 26 | var DFolder: URL 27 | 28 | /** 29 | init 30 | - Parameter url: URL to download 31 | */ 32 | init(url: URL) { 33 | TURL = url 34 | Fname = url.lastPathComponent 35 | 36 | DFolder = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! 37 | 38 | let downloadsURL = String(describing: DFolder) + url.lastPathComponent 39 | 40 | self.startDownload(TURL, savePath: URL(string: downloadsURL)) 41 | } 42 | 43 | /** 44 | Start the download 45 | - Parameter URL: The URL to download 46 | - Parameter savePath: The savePath 47 | */ 48 | func startDownload(_ URL: Foundation.URL, savePath: Foundation.URL!) { 49 | let sessionConfig = URLSessionConfiguration.default 50 | let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil) 51 | let request = NSMutableURLRequest(url: URL) 52 | request.httpMethod = "GET" 53 | 54 | noop(session) // temporary we want no stupid "fix-it" warnings. 55 | 56 | let task = session.dataTask(with: request as URLRequest, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in 57 | if (error == nil) { 58 | let statusCode = (response as! HTTPURLResponse).statusCode 59 | self.noop(statusCode as AnyObject) // For further use HTTP Status code. 60 | 61 | let saveData = NSData.init(data: data!) as Data 62 | try? saveData.write(to: savePath!, options: [.atomic]) 63 | 64 | // Ask the question on the main queue. 65 | OperationQueue.main.addOperation({ 66 | if (self.dialog("Download of \"\(self.Fname)\" complete", text: "Would you like to open the downloads folder?")) { 67 | NSWorkspace.shared.open(self.DFolder) 68 | } 69 | }) 70 | } 71 | else { 72 | // Failure 73 | print("Faulure: %@", error!.localizedDescription); 74 | } 75 | }) 76 | 77 | task.resume() 78 | } 79 | 80 | /** 81 | Display a nice dialog with a question.\ 82 | Please remember to use it only on the mainQueue 83 | 84 | - Parameter question: The question 85 | - Parameter text: The text you want to ask 86 | - Returns: Bool 87 | */ 88 | func dialog(_ question: String, text: String) -> Bool { 89 | let myPopup: NSAlert = NSAlert() 90 | myPopup.messageText = question 91 | myPopup.informativeText = text 92 | myPopup.alertStyle = NSAlert.Style.informational 93 | myPopup.addButton(withTitle: "Yes") 94 | myPopup.addButton(withTitle: "No") 95 | 96 | let res = myPopup.runModal() 97 | 98 | if res == NSApplication.ModalResponse.alertFirstButtonReturn { 99 | return true 100 | } 101 | 102 | return false 103 | } 104 | 105 | /** 106 | End the download task 107 | */ 108 | func endDownloadTask() -> Void { } 109 | 110 | /** 111 | Noop! 112 | */ 113 | func noop(_ ob: AnyObject) -> Void { } 114 | } 115 | -------------------------------------------------------------------------------- /WebShell/WSFileHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellFileHandler.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | // @wdg: Enable file uploads. 13 | // Issue: #29 14 | // This extension will handle up & downloads 15 | extension ViewController { 16 | 17 | // @wdg: Enable file uploads. 18 | // Issue: #29 19 | @objc(webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:) func webView(_ sender: WebView!, runOpenPanelForFileButtonWith resultListener: WebOpenPanelResultListener!, allowMultipleFiles: Bool) { 20 | // Init panel with options 21 | let panel = NSOpenPanel() 22 | panel.allowsMultipleSelection = allowMultipleFiles 23 | panel.canChooseDirectories = false 24 | panel.canCreateDirectories = false 25 | panel.canChooseFiles = true 26 | 27 | // On clicked on ok then... 28 | panel.begin {(result) -> Void in 29 | // User clicked OK 30 | if result.rawValue == NSFileHandlingPanelOKButton { 31 | 32 | // make the upload qeue named 'uploadQeue' 33 | let uploadQeue: NSMutableArray = NSMutableArray() 34 | for i in 0 ..< panel.urls.count 35 | { 36 | // Add to upload qeue, needing relativePath. 37 | uploadQeue.add(panel.urls[i].relativePath) 38 | } 39 | 40 | if (panel.urls.count == 1) { 41 | // One file 42 | resultListener.chooseFilename(String(describing: uploadQeue[0])) 43 | } else { 44 | // Multiple files 45 | resultListener.chooseFilenames(uploadQeue as [AnyObject]) 46 | } 47 | } 48 | } 49 | 50 | } 51 | 52 | /** 53 | Download window. 54 | 55 | - Parameter download: WebDownload! 56 | */ 57 | func downloadWindow(forAuthenticationSheet download: WebDownload!) -> NSWindow! { 58 | print("I'd like to download something") 59 | print(download) 60 | 61 | return NSWindow.init() 62 | } 63 | 64 | // Usefull for debugging.. 65 | @nonobjc func webView(_ sender: WebView!,mouseDidMoveOverElement elementInformation: [NSObject : Any]!, modifierFlags: Int) { 66 | //print("Sender=\(sender)\nEleInfo=\(elementInformation)\nModifier=\(modifierFlags)") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /WebShell/WSInjector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellInjector.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | 13 | /** 14 | This extension will catch up with the webhooks! 15 | - Note: @wdg: Iframes, Webhooks, and more. (Issue: #23, #5, #2, #35, #38, #39 & More) 16 | */ 17 | @objc extension ViewController { 18 | /** 19 | Loop Trough iFrames 20 | 21 | This function will loop trough different frames (if they exists) and will inject all the javascript what they want! 22 | 23 | - Note: @wdg Fix for iFrames (Issue #23) 24 | */ 25 | func loopThroughiFrames() { 26 | if (mainWebview.subviews.count > 0) { 27 | // We've got subViews! 28 | 29 | if (mainWebview.subviews[0].subviews.count > 0) { 30 | // mainWebview.subviews[0] = WebFrameView 31 | 32 | let goodKids = mainWebview.subviews[0].subviews[0] 33 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView (= goodKids) 34 | 35 | var children = goodKids.subviews[0] 36 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView.subviews[0] = WebClipView (= children) 37 | 38 | // We need > 0 subviews here, otherwise don't add them. and the script will continue 39 | if children.subviews.count > 0 { 40 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView.subviews[0] = WebClipView.subviews[0] = WebHTMLView 41 | children = goodKids.subviews[0].subviews[0] 42 | } 43 | 44 | // Finally. parsing those good old iframes 45 | // We don't check them for iframes, somewhere the fun must be ended. 46 | for child in children.subviews { 47 | Dprint("Found a Child...") 48 | Ddump(child) 49 | // mainWebview.subviews[0] = WebFrameView.subviews[0] = WebDynamicScrollBarsView.subviews[0] = WebClipView.subviews[0] = WebHTMLView.subviews[x] = WebFrameView (Finally) (name = child) 50 | if (child.isKind(of: WebFrameView.self)) { 51 | let frame: NSView = child 52 | let context: JSContext = frame.webFrame.javaScriptContext 53 | 54 | Dprint("Injecting hooks!") 55 | injectWebhooks(context) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | /** 63 | InjectWebhooks 64 | 65 | Injects javascript in to a frame, or other position 66 | 67 | - Parameter jsContext: JSContext! 68 | 69 | - Note: @wdg Fixes a lot (Issues #23, #5, #2, #35, #38, #39 & More.) 70 | */ 71 | func injectWebhooks(_ jsContext: JSContext!) { 72 | // Injecting javascript (via jsContext) 73 | 74 | // @wdg Hack URL's if settings is set. 75 | // Issue: #5 76 | if ((WebShellSettings["openInNewScreen"] as? Bool) != false) { 77 | // _blank to external 78 | // JavaScript -> Select all 79 | jsContext.evaluateScript("var links=document.querySelectorAll('a');for(var i=0;i Select all 83 | jsContext.evaluateScript("var links=document.querySelectorAll('a');for(var i=0;i Void = {(title: NSString?, message: NSString?, icon: NSString?) in 90 | self.makeNotification(title!, message: message!, icon: icon!) 91 | } 92 | jsContext.objectForKeyedSubscript("Notification").setObject(unsafeBitCast(myNofification, to: AnyObject.self), forKeyedSubscript: "note" as (NSCopying & NSObjectProtocol)!) 93 | 94 | // Add console.log ;) 95 | // Add Console.log (and console.error, and console.warn) 96 | if (WebShellSettings["consoleSupport"] as! Bool) { 97 | jsContext.evaluateScript("var console = {log: function () {var message = '';for (var i = 0; i < arguments.length; i++) {message += arguments[i] + ' '};console.print(message)},warn: function () {var message = '';for (var i = 0; i < arguments.length; i++) {message += arguments[i] + ' '};console.print(message)},error: function () {var message = '';for (var i = 0; i < arguments.length; i++){message += arguments[i] + ' '};console.print(message)}};") 98 | let logFunction: @convention(block)(NSString!) -> Void = {(message: NSString!) in 99 | print("JS: \(message)") 100 | } 101 | jsContext.objectForKeyedSubscript("console").setObject(unsafeBitCast(logFunction, to: AnyObject.self), forKeyedSubscript: "print" as (NSCopying & NSObjectProtocol)!) 102 | } 103 | 104 | // @wdg Add support for target=_blank 105 | // Issue: #5 106 | // Fake window.app Library. 107 | jsContext.evaluateScript("var WSApp={};") ; 108 | 109 | // _blank external 110 | let openInBrowser: @convention(block)(NSString!) -> Void = {(url: NSString!) in 111 | NSWorkspace.shared.open(URL(string: (url as String))!) 112 | } 113 | 114 | // _blank internal 115 | let openNow: @convention(block)(NSString!) -> Void = {(url: NSString!) in 116 | self.loadUrl((url as String)) 117 | } 118 | // _blank external 119 | jsContext.objectForKeyedSubscript("WSApp").setObject(unsafeBitCast(openInBrowser, to: AnyObject.self), forKeyedSubscript: "openExternal" as (NSCopying & NSObjectProtocol)!) 120 | 121 | // _blank internal 122 | jsContext.objectForKeyedSubscript("WSApp").setObject(unsafeBitCast(openNow, to: AnyObject.self), forKeyedSubscript: "openInternal" as (NSCopying & NSObjectProtocol)!) 123 | 124 | // @wdg Add Print Support 125 | // Issue: #39 126 | // window.print() 127 | let printMe: @convention(block)(NSString?) -> Void = {(url: NSString?) in self.printThisPage(self)} 128 | jsContext.objectForKeyedSubscript("window").setObject(unsafeBitCast(printMe, to: AnyObject.self), forKeyedSubscript: "print" as (NSCopying & NSObjectProtocol)!) 129 | 130 | // navigator.getBattery() 131 | jsContext.objectForKeyedSubscript("navigator").setObject(BatteryManager.self, forKeyedSubscript: "battery" as (NSCopying & NSObjectProtocol)!) 132 | 133 | jsContext.evaluateScript("window.navigator.getBattery = window.navigator.battery.getBattery;") 134 | 135 | // navigator.vibrate 136 | let vibrateNow: @convention(block)(NSString!) -> Void = {(data: NSString!) in 137 | self.flashScreen(data) 138 | } 139 | jsContext.objectForKeyedSubscript("navigator").setObject(unsafeBitCast(vibrateNow, to: AnyObject.self), forKeyedSubscript: "vibrate" as (NSCopying & NSObjectProtocol)!) 140 | 141 | // @wdg Add localstorage Support 142 | // Issue: #25 143 | let saveToLocal: @convention(block)(NSString?, NSString?) -> Void = {(key: NSString?, value: NSString?) in 144 | let host: String = (self.mainWebview.mainFrame.dataSource?.request.url?.host)! 145 | let newKey = String(describing: "WSLS:\(host):\(key ?? "?")") 146 | 147 | UserDefaults.standard.setValue(value, forKey: newKey) 148 | } 149 | 150 | let getFromLocal: @convention(block)(NSString!) -> String = {(key: NSString!) in 151 | let host: String = (self.mainWebview.mainFrame.dataSource?.request.url?.host)! 152 | if let LSvalue = key { 153 | let newKey = "WSLS:\(host):\(LSvalue)" 154 | 155 | let val = UserDefaults.standard.value(forKey: newKey as String) 156 | 157 | if let myVal = val as? String { 158 | return String(myVal) 159 | } 160 | else { 161 | return "null" 162 | } 163 | } else { 164 | return "null" 165 | } 166 | } 167 | 168 | jsContext.objectForKeyedSubscript("localStorage").setObject(unsafeBitCast(saveToLocal, to: AnyObject.self), forKeyedSubscript: "setItem" as (NSCopying & NSObjectProtocol)!) 169 | jsContext.objectForKeyedSubscript("localStorage").setObject(unsafeBitCast(getFromLocal, to: AnyObject.self), forKeyedSubscript: "getItem" as (NSCopying & NSObjectProtocol)!) 170 | 171 | // @wdg Support for window.open (popup) 172 | // Issue: #21 173 | // openNewWindow(url: "THEURL", height: "0", width: "0") 174 | // window.open(URL, name, specs, replace) 175 | let windowOpen: @convention(block)(NSString?, NSString?, NSString?, NSString?) -> Void = {(url: NSString?, target: NSString?, specs: NSString?, replace: NSString?) in 176 | self.parseWindowOpen(url! as String, options: specs! as String) 177 | } 178 | jsContext.objectForKeyedSubscript("window").setObject(unsafeBitCast(windowOpen, to: AnyObject.self), forKeyedSubscript: "open" as (NSCopying & NSObjectProtocol)!) 179 | 180 | // Get window.webshell 181 | let nsObject: Any? = Bundle.main.infoDictionary!["CFBundleShortVersionString"] 182 | jsContext.evaluateScript("window.webshell={version:'\(nsObject as! String)'};webshell=window.webshell;") 183 | 184 | // @wdg memorize credentials? 185 | // Issue: #74 186 | _injectPasswordFor(jsContext, website: jsContext.evaluateScript("window.location.host").toString()) 187 | _injectPasswordListener(jsContext, website: jsContext.evaluateScript("window.location.host").toString()) 188 | let savePassword: @convention(block)(NSString?, NSString?) -> Void = {(username: NSString!, password: NSString!) in 189 | self._savePasswordFor(jsContext, 190 | website: jsContext.evaluateScript("window.location.host").toString(), 191 | username: (username as String), 192 | password: (password as String) 193 | ) 194 | } 195 | jsContext.objectForKeyedSubscript("WSApp").setObject(unsafeBitCast(savePassword, to: AnyObject.self), forKeyedSubscript: "savePassword" as (NSCopying & NSObjectProtocol)!) 196 | 197 | _WSInjectCSS(jsContext) 198 | _WSInjectJS(jsContext) 199 | } 200 | 201 | /** 202 | Add Localstorage Support 203 | 204 | - Parameter Sender: AnyObject? 205 | 206 | - Note: @wdg #25 207 | */ 208 | func resetLocalStorage(_ Sender: AnyObject?) -> Void { 209 | UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) 210 | } 211 | 212 | /** 213 | Support for window.open (popup) 214 | 215 | - Parameter url: Url to open 216 | - Parameter options: Custom options, width, height 217 | 218 | - Note: @wdg #25 219 | */ 220 | func parseWindowOpen(_ url: String, options: String) -> Void { 221 | // We ignore x and y. (initial position on the screen) 222 | // Using specifications of W3Schools: http://www.w3schools.com/jsref/met_win_open.asp 223 | // "Open a new window called "MsgWindow", and write some text into it" is not (yet) supported! 224 | var width = "0" 225 | var height = "0" 226 | let options = Array(options.components(separatedBy: ",")) 227 | 228 | for i in 0 ..< options.count { 229 | var tmp = Array(options[i].components(separatedBy: "=")) 230 | 231 | if (tmp[0] == "width" || tmp[0] == " width") { 232 | width = tmp[1] 233 | print("width=\(tmp[1])") 234 | } 235 | if (tmp[0] == "height" || tmp[0] == " height") { 236 | height = tmp[1] 237 | print("height=\(tmp[1])") 238 | } 239 | } 240 | 241 | // After parsing call 242 | openNewWindow(url: url, height: "\(height)", width: "\(width)") 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /WebShell/WSPageActions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebShellPageActions.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 31-01-16. 6 | // Copyright © 2016 RandyLu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | extension ViewController { 13 | /** 14 | Add Observers for menu items 15 | */ 16 | func addObservers() { 17 | // add menu action observers 18 | let observers = ["goHome", "reload", "copyUrl", "clearNotificationCount", "printThisPage"] 19 | 20 | for observer in observers { 21 | NotificationCenter.default.addObserver(self, selector: NSSelectorFromString(observer), name: NSNotification.Name(rawValue: observer), object: nil) 22 | } 23 | } 24 | 25 | /** 26 | Go to the home url 27 | */ 28 | func goHome() { 29 | loadUrl((WebShellSettings["url"] as? String)!) 30 | } 31 | 32 | /** 33 | Reload the current webpage 34 | */ 35 | func reload() { 36 | mainWebview.mainFrame.reload() 37 | } 38 | 39 | /** 40 | Copy the URL 41 | */ 42 | func copyUrl() { 43 | let currentUrl: String = (mainWebview.mainFrame.dataSource?.request.url?.absoluteString)! 44 | let clipboard: NSPasteboard = NSPasteboard.general 45 | clipboard.clearContents() 46 | 47 | clipboard.setString(currentUrl, forType: .string) 48 | } 49 | 50 | /** 51 | Initialize settings 52 | */ 53 | func initSettings() { 54 | // controll the progress bar 55 | if (!(WebShellSettings["showLoadingBar"] as? Bool)!) { 56 | progressBar.isHidden = true // @wdg: Better progress indicator | Issue: #37 57 | } 58 | 59 | // @wdg Add Custom useragent support 60 | // Issue: #52 61 | if ((WebShellSettings["useragent"] as! String).lowercased() == "default") { 62 | var UA: String = Bundle.main.infoDictionary!["CFBundleName"] as! String 63 | UA = UA + "/" 64 | UA = UA + (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String) 65 | UA = UA + " based on Safari/AppleWebKit (KHTML, like Gecko)" 66 | 67 | UserDefaults.standard.register(defaults: ["UserAgent": UA]) // For iOS 68 | mainWebview.customUserAgent = UA // For Mac OS X 69 | } else { 70 | let UA: String = WebShellSettings["useragent"] as! String 71 | UserDefaults.standard.register(defaults: ["UserAgent": UA]) // For iOS 72 | mainWebview.customUserAgent = UA // For Mac OS X 73 | } 74 | 75 | // set launching text 76 | launchingLabel.stringValue = (WebShellSettings["launchingText"] as? String)! 77 | } 78 | 79 | /** 80 | Initialize window 81 | */ 82 | func initWindow() { 83 | firstAppear = false 84 | 85 | // set window size 86 | var frame: NSRect = mainWindow.frame 87 | 88 | let WIDTH: CGFloat = CGFloat(WebShellSettings["initialWindowWidth"] as! Int), 89 | HEIGHT: CGFloat = CGFloat(WebShellSettings["initialWindowHeight"] as! Int) 90 | 91 | frame.size.width = WIDTH 92 | frame.size.height = HEIGHT 93 | 94 | // @wdg Fixed screen position (now it centers) 95 | // Issue: #19 96 | // Note: do not use HEIGHT, WIDTH for some strange reason the window will be positioned 25px from bottom! 97 | let ScreenHeight: CGFloat = (NSScreen.main?.frame.size.width)!, 98 | WindowHeight: CGFloat = CGFloat(WebShellSettings["initialWindowWidth"] as! Int), // do not use HEIGHT! 99 | ScreenWidth: CGFloat = (NSScreen.main?.frame.size.height)!, 100 | WindowWidth: CGFloat = CGFloat(WebShellSettings["initialWindowHeight"] as! Int) // do not use WIDTH! 101 | frame.origin.x = (ScreenHeight / 2 - WindowHeight / 2) 102 | frame.origin.y = (ScreenWidth / 2 - WindowWidth / 2) 103 | 104 | // @froge-xyz Fixed initial window size 105 | // Issue: #1, #45 106 | mainWindow.window?.setFrame(frame, display: true) 107 | 108 | // defims Fixed the initial window size. 109 | mainWindow.frame = frame 110 | 111 | // set window title 112 | mainWindow.window?.title = WebShellSettings["title"] as! String 113 | 114 | // Force some preferences before loading... 115 | mainWebview.preferences.isJavaScriptEnabled = true 116 | mainWebview.preferences.javaScriptCanOpenWindowsAutomatically = true 117 | mainWebview.preferences.arePlugInsEnabled = true 118 | } 119 | 120 | /** 121 | Load a specific URL 122 | 123 | - Parameter url: The url to load 124 | */ 125 | 126 | func loadUrl(_ url: String) { 127 | if ((WebShellSettings["showLoadingBar"] as? Bool)!) { 128 | progressBar.isHidden = false 129 | progressBar.startAnimation(self) 130 | progressBar.maxValue = 100; 131 | progressBar.minValue = 1; 132 | progressBar.increment(by: 24) 133 | } 134 | let URL = Foundation.URL(string: url) 135 | mainWebview.mainFrame.load(URLRequest(url: URL!)) 136 | } 137 | 138 | /** 139 | Add Print Support (#39) [@wdg] 140 | 141 | - Parameter Sender: The sending object 142 | */ 143 | func printThisPage(_ Sender: AnyObject?) -> Void { 144 | let url = mainWebview.mainFrame.dataSource?.request?.url?.absoluteString 145 | 146 | let operation: NSPrintOperation = NSPrintOperation.init(view: mainWebview) 147 | operation.jobTitle = "Printing \(url!)" 148 | 149 | // If want to print landscape 150 | operation.printInfo.orientation = NSPrintInfo.PaperOrientation.landscape 151 | operation.printInfo.scalingFactor = 0.7 152 | 153 | if operation.run() { 154 | print("Printed?") 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /WebShell/WSStringExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtension.swift 3 | // WebShell 4 | // 5 | // Created by Wesley de Groot on 17-01-16. 6 | // Copyright © 2016 WDGWV. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Extensions for Strings 13 | */ 14 | public extension String { 15 | /** 16 | get string length 17 | */ 18 | public var length: Int { 19 | get { 20 | return self.characters.count 21 | } 22 | } 23 | 24 | /** 25 | contains 26 | - Parameter s: String to check 27 | - Returns: true/false 28 | */ 29 | public func contains(_ s: String) -> Bool { 30 | return self.range(of: s) != nil ? true : false 31 | } 32 | 33 | /** 34 | Replace 35 | - Parameter target: String 36 | - Parameter withString: Replacement 37 | - Returns: Replaced string 38 | */ 39 | public func replace(_ target: String, withString: String) -> String { 40 | return self.replacingOccurrences(of: target, with: withString, options: NSString.CompareOptions.literal, range: nil) 41 | } 42 | 43 | /** 44 | Character At Index 45 | - Parameter index: The index 46 | - Returns Character 47 | */ 48 | func characterAtIndex(_ index: Int) -> Character! { 49 | var cur = 0 50 | for char in self.characters { 51 | if cur == index { 52 | return char 53 | } 54 | cur += 1 55 | } 56 | return nil 57 | } 58 | 59 | /** 60 | Add subscript 61 | 62 | - Parameter i: Index 63 | */ 64 | public subscript(i: Int) -> Character { 65 | get { 66 | let index = self.characters.index(self.startIndex, offsetBy: i) 67 | return self[index] 68 | } 69 | } 70 | /** 71 | Add subscript 72 | 73 | - Parameter r: Range 74 | */ 75 | public subscript(r: Range) -> String { 76 | get { 77 | let startIndex = self.characters.index(self.startIndex, offsetBy: r.lowerBound) 78 | let endIndex = self.characters.index(self.startIndex, offsetBy: r.upperBound - 1) 79 | 80 | return String(self[startIndex..) -> String { 91 | get { 92 | let startIndex = self.characters.index(self.startIndex, offsetBy: r.lowerBound) 93 | let endIndex = self.characters.index(self.startIndex, offsetBy: r.upperBound - 1) 94 | 95 | return String(self[startIndex.. [String: NSTouch]? { 67 | var twoFingersTouches: [String: NSTouch]? = nil // NSMutualDictionary was used before and didn't require casting id to string, revert if side-effects manifest 68 | if (event.type == NSEvent.EventType.gesture) { // could maybe be: EventTypeBeginGesture 69 | let touches: Set = event.touches(matching: NSTouch.Phase.any, in: view) // touchesMatchingPhase:NSTouchPhaseAny inView:self 70 | if (touches.count == 2){ 71 | twoFingersTouches = [String: NSTouch]() 72 | for touch in touches {// 73 | twoFingersTouches!["\((touch).identity)"] = touch //assigns each touch to the identity of the same touch 74 | } 75 | } 76 | } 77 | 78 | return twoFingersTouches 79 | } 80 | /** 81 | * Detects 2 finger (left/right) swipe gesture 82 | * NOTE: either of 3 enums is returned: .leftSwipe, .rightSwipe .none 83 | * TODO: also make up and down swipe detectors, and do more research into how this could be done easier. Maybe you even have some clues in the notes about gestures etc. 84 | * Conceptually: 85 | * 1. Record 2 .began touchEvents 86 | * 2. Record 2 .ended touchEvents 87 | * 3. Measure the distance between .began and .ended and assert if it is within threshold 88 | */ 89 | static func swipe(_ view: NSView, _ event: NSEvent, _ twoFingersTouches: [String: NSTouch]?) -> SwipeType { 90 | let endingTouches: Set = event.touches(matching: NSTouch.Phase.ended, in: view) 91 | if (endingTouches.count > 0 && twoFingersTouches != nil) { 92 | let beginningTouches: [String: NSTouch] = twoFingersTouches! // copy the twoFingerTouches data 93 | var magnitudes: [CGFloat] = [] // magnitude definition: the great size or extent of something 94 | for endingTouch in endingTouches { 95 | let beginningTouch:NSTouch? = beginningTouches["\(endingTouch.identity)"] 96 | if (beginningTouch == nil) { // skip if endingTouch doesn't have a matching beginningTouch 97 | continue 98 | } 99 | let magnitude: CGFloat = endingTouch.normalizedPosition.x - beginningTouch!.normalizedPosition.x 100 | magnitudes.append(magnitude) 101 | } 102 | var sum: CGFloat = 0 103 | for magnitude in magnitudes{ 104 | sum += magnitude 105 | } 106 | let absoluteSum: CGFloat = abs(sum) // force value to be positive 107 | let kSwipeMinimumLength: CGFloat = 0.1 108 | if (absoluteSum < kSwipeMinimumLength) { // Assert if the absolute sum is long enough to be considered a complete gesture 109 | return .none 110 | } 111 | if (sum > 0) { 112 | return .right 113 | }else /*if(sum < 0)*/ { 114 | return .left 115 | } 116 | } 117 | return .none // no swipe direction detected 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /WebShell/WebShell-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 201605242200 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSHumanReadableCopyright 33 | Copyright © 2015 RandyLu. All rights reserved. 34 | NSMainStoryboardFile 35 | Main 36 | NSPrincipalClass 37 | WSApplication 38 | 39 | 40 | -------------------------------------------------------------------------------- /WebShell/nl.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "0cq-vj-EeT"; */ 3 | "0cq-vj-EeT.title" = "Slim Kopiëren/plakken"; 4 | 5 | /* Class = "NSMenuItem"; title = "WebShell"; ObjectID = "1Xt-HY-uBw"; */ 6 | "1Xt-HY-uBw.title" = "WebShell"; 7 | 8 | /* Class = "NSMenuItem"; title = "Quit WebShell"; ObjectID = "4sb-4s-VLi"; */ 9 | "4sb-4s-VLi.title" = "Verlaat WebShell"; 10 | 11 | /* Class = "NSMenuItem"; title = "About WebShell"; ObjectID = "5kV-Vb-QxS"; */ 12 | "5kV-Vb-QxS.title" = "Over WebShell"; 13 | 14 | /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "AYs-Jm-OWX"; */ 15 | "AYs-Jm-OWX.title" = "Slimme Dashes"; 16 | 17 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 18 | "AYu-sK-qS6.title" = "Menu"; 19 | 20 | /* Class = "NSMenuItem"; title = "Control"; ObjectID = "Ca3-nV-juN"; */ 21 | "Ca3-nV-juN.title" = "Control"; 22 | 23 | /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "Clr-cV-qUy"; */ 24 | "Clr-cV-qUy.title" = "Vervangen"; 25 | 26 | /* Class = "NSMenu"; title = "Transformations"; ObjectID = "DpD-Qi-s84"; */ 27 | "DpD-Qi-s84.title" = "Transformaties"; 28 | 29 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "E0V-gE-2XR"; */ 30 | "E0V-gE-2XR.title" = "Sluit"; 31 | 32 | /* Class = "NSMenuItem"; title = "Copy current URL"; ObjectID = "F42-Mv-oVO"; */ 33 | "F42-Mv-oVO.title" = "Sluit huidige URL"; 34 | 35 | /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "H1w-Pb-bQf"; */ 36 | "H1w-Pb-bQf.title" = "Laat spellingscontrole zien"; 37 | 38 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "HAk-2k-oTs"; */ 39 | "HAk-2k-oTs.title" = "Plakken"; 40 | 41 | /* Class = "NSWindow"; title = "WebShell"; ObjectID = "IQv-IB-iLA"; */ 42 | "IQv-IB-iLA.title" = "WebShell"; 43 | 44 | /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "Jms-Vz-UXk"; */ 45 | "Jms-Vz-UXk.title" = "Transformaties"; 46 | 47 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "KZX-VJ-OIl"; */ 48 | "KZX-VJ-OIl.title" = "Alles Selecteren"; 49 | 50 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 51 | "Kd2-mp-pUS.title" = "Laat alles zien"; 52 | 53 | /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "NQd-4F-F7u"; */ 54 | "NQd-4F-F7u.title" = "Spellingscontrole"; 55 | 56 | /* Class = "NSMenuItem"; title = "Hide WebShell"; ObjectID = "Olw-nP-bQN"; */ 57 | "Olw-nP-bQN.title" = "Verberg WebShell"; 58 | 59 | /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "Owh-gY-yb3"; */ 60 | "Owh-gY-yb3.title" = "Ga naar sectie"; 61 | 62 | /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Q1d-Fv-HmW"; */ 63 | "Q1d-Fv-HmW.title" = "Zoeken…"; 64 | 65 | /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "QFF-U6-rj3"; */ 66 | "QFF-U6-rj3.title" = "Slimme Quotes"; 67 | 68 | /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "RbA-n6-mv4"; */ 69 | "RbA-n6-mv4.title" = "Data Detectors"; 70 | 71 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "T3K-M0-pds"; */ 72 | "T3K-M0-pds.title" = "Undo"; 73 | 74 | /* Class = "NSMenu"; title = "Speech"; ObjectID = "Ukk-qQ-spw"; */ 75 | "Ukk-qQ-spw.title" = "Spraak"; 76 | 77 | /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "VYp-LM-iqJ"; */ 78 | "VYp-LM-iqJ.title" = "Spellingscontrole"; 79 | 80 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 81 | "Vdr-fp-XzO.title" = "Verberg andere"; 82 | 83 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "Wah-OC-UhV"; */ 84 | "Wah-OC-UhV.title" = "Bewerk"; 85 | 86 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "Wah-va-YHm"; */ 87 | "Wah-va-YHm.title" = "Plakken en stijl matchen"; 88 | 89 | /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "XKU-CU-oCg"; */ 90 | "XKU-CU-oCg.title" = "Tekst vervangen"; 91 | 92 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "XfS-gH-udC"; */ 93 | "XfS-gH-udC.title" = "Verwijder"; 94 | 95 | /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "Xx1-fz-mt3"; */ 96 | "Xx1-fz-mt3.title" = "Vind Volgende"; 97 | 98 | /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "Zqd-ID-Z6K"; */ 99 | "Zqd-ID-Z6K.title" = "Maak Lower Case"; 100 | 101 | /* Class = "NSMenu"; title = "Find"; ObjectID = "aYU-p3-vbI"; */ 102 | "aYU-p3-vbI.title" = "Vind"; 103 | 104 | /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "bcd-NF-0kD"; */ 105 | "bcd-NF-0kD.title" = "Spellingscontrole"; 106 | 107 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "cHO-Y8-CkV"; */ 108 | "cHO-Y8-CkV.title" = "Kopiëren"; 109 | 110 | /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "caM-nD-RQd"; */ 111 | "caM-nD-RQd.title" = "Spellingscontrole"; 112 | 113 | /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "dG3-Ol-VGb"; */ 114 | "dG3-Ol-VGb.title" = "Start Spreken"; 115 | 116 | /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "eZE-iS-Gbc"; */ 117 | "eZE-iS-Gbc.title" = "Stop Spreken"; 118 | 119 | /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "elw-9g-erD"; */ 120 | "elw-9g-erD.title" = "Controleer document nu"; 121 | 122 | /* Class = "NSMenuItem"; title = "Home"; ObjectID = "fWh-O7-fkt"; */ 123 | "fWh-O7-fkt.title" = "Home"; 124 | 125 | /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "fcK-mU-woF"; */ 126 | "fcK-mU-woF.title" = "Vervanginen"; 127 | 128 | /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "hGZ-Nz-DIt"; */ 129 | "hGZ-Nz-DIt.title" = "Hoofdletters"; 130 | 131 | /* Class = "NSMenu"; title = "Spelling"; ObjectID = "hbN-l2-7R3"; */ 132 | "hbN-l2-7R3.title" = "Spelling"; 133 | 134 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "i4s-n8-wXz"; */ 135 | "i4s-n8-wXz.title" = "Knippen"; 136 | 137 | /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "i9D-5H-yVn"; */ 138 | "i9D-5H-yVn.title" = "Gebruik selectie om te zoeken"; 139 | 140 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "kM9-IE-xVd"; */ 141 | "kM9-IE-xVd.title" = "Bewerken"; 142 | 143 | /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "p8c-hB-c6m"; */ 144 | "p8c-hB-c6m.title" = "Vind vorige"; 145 | 146 | /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "qER-IV-GIL"; */ 147 | "qER-IV-GIL.title" = "Maak Upper Case"; 148 | 149 | /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "qMm-Ld-wUf"; */ 150 | "qMm-Ld-wUf.title" = "Spraak"; 151 | 152 | /* Class = "NSMenu"; title = "Control"; ObjectID = "qkf-xU-qKS"; */ 153 | "qkf-xU-qKS.title" = "Control"; 154 | 155 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "qqN-o9-bKr"; */ 156 | "qqN-o9-bKr.title" = "Opnieuw"; 157 | 158 | /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "tgC-ra-RuK"; */ 159 | "tgC-ra-RuK.title" = "Laat vervangingen zien"; 160 | 161 | /* Class = "NSMenu"; title = "WebShell"; ObjectID = "uQy-DD-JDr"; */ 162 | "uQy-DD-JDr.title" = "WebShell"; 163 | 164 | /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "vSL-Er-Dzn"; */ 165 | "vSL-Er-Dzn.title" = "Slimme links"; 166 | 167 | /* Class = "NSMenuItem"; title = "Find"; ObjectID = "vur-Ee-IsR"; */ 168 | "vur-Ee-IsR.title" = "Vind"; 169 | 170 | /* Class = "NSTextFieldCell"; title = "Launching..."; ObjectID = "y40-R0-LYw"; */ 171 | "y40-R0-LYw.title" = "Opstarten..."; 172 | 173 | /* Class = "NSMenuItem"; title = "Reload"; ObjectID = "ynQ-Ma-mpv"; */ 174 | "ynQ-Ma-mpv.title" = "Herladen"; 175 | 176 | /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "zd7-xr-xod"; */ 177 | "zd7-xr-xod.title" = "Zoek en vervang…"; 178 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing / Making issues 2 | If you experience problems with a specific web(app) 3 | 4 | *(visible by running via Xcode or by running with parameter -debug true)* 5 | 6 | Please use the `Debug` -> `Report an issue on this page` button, so we know the full url. 7 | 8 | ![Debug - Report an issue on this page](http://m83.imgup.net/ReportIssuf311.png) 9 | 10 | When pressed the report your **default** webbrowser will open and redirect you to the github issues page, with some pre-filled fields, then you can enter what exactly the problem is. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WebShell 2 | 3 | WebShell is an OS X WebView shell, which help you easily bundle the Web Apps to native OS X app without coding. 4 | 5 | ![](https://badges.gitter.im/djyde/WebShell.svg) 6 | 7 | ![](http://7mnoy7.com1.z0.glb.clouddn.com/github/workflow-with-frame.png?imageView/2/w/1280) 8 | 9 | ## Requirements 10 | 11 | - Xcode 7+ (Swift 2.0+ support) 12 | 13 | ## Quick Start 14 | 15 | ```bash 16 | 17 | $ git clone https://github.com/djyde/WebShell.git APP_NAME 18 | 19 | $ cd APP_NAME && open WebShell.xcodeproj 20 | 21 | ``` 22 | 23 | Edit `Sites/WebShell/Settings.swiftt` and change the url whatever you like. 24 | 25 | 26 | Finally click the `run` button to run the app. 27 | 28 | ## Bonus Features 29 | 30 | - Standard [Notification API](https://developer.mozilla.org/en-US/docs/Web/API/notification) support 31 | 32 | - Standard [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API) support 33 | 34 | ## Demo 35 | 36 | - [JS Bin](http://7mnoy7.com1.z0.glb.clouddn.com/github/JSBin.zip) 37 | 38 | - [StackEdit](http://7mnoy7.com1.z0.glb.clouddn.com/github/StackEdit.zip) - A markdown editor 39 | 40 | ## Document 41 | 42 | For more detail configurations, please see [document](https://github.com/djyde/WebShell/wiki/How-to-build-a-WebShell-based-application) 43 | 44 | ## Who's using WebShell 45 | 46 | If you built any wonderful app with `WebShell`, just let me know! 47 | 48 | # License 49 | 50 | MIT License 51 | --------------------------------------------------------------------------------