├── .gitignore ├── KeyGripNet ├── KeyGripNet.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── KeyGripNet.xccheckout │ └── xcshareddata │ │ └── xcschemes │ │ └── KeyGripNet.xcscheme ├── KeyGripNet │ ├── KeyGripNet-Prefix.pch │ ├── KeyGripNet.h │ ├── KeyGripNet.m │ ├── RCWBonjourConnection.h │ ├── RCWBonjourConnection.m │ ├── RCWBonjourConnectionFinder.h │ ├── RCWBonjourConnectionFinder.m │ ├── RCWDataStreamCollector.h │ ├── RCWDataStreamCollector.m │ ├── RCWDataStreamEmitter.h │ ├── RCWDataStreamEmitter.m │ ├── RCWKGClientAPI.h │ ├── RCWKGClientAPI.m │ ├── RCWKGProtocolCommand.h │ ├── RCWKGProtocolCommand.m │ ├── RCWKGServerAPI.h │ ├── RCWKGServerAPI.m │ ├── RCWSocketClientConnection.h │ ├── RCWSocketClientConnection.m │ ├── RCWSocketServerConnection.h │ ├── RCWSocketServerConnection.m │ └── RCWStreamConnection.h └── KeyGripNetTests │ ├── KeyGripNetTests-Info.plist │ ├── RCWKGClientTest.m │ ├── RCWKGConnectionFinderTest.m │ ├── RCWKGConnectionTest.m │ ├── RCWKGDataStreamCollectorEmitterTest.m │ ├── RCWKGProtocolCommandTest.m │ ├── RCWKGSocketConnectionTest.m │ ├── RCWXCTAsyncHelpers.h │ ├── TestCaseStreamConnection.h │ ├── TestCaseStreamConnection.m │ ├── XCTAsyncTestCase.h │ ├── XCTAsyncTestCase.m │ └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── README.md ├── client ├── KeyGrip.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── KeyGrip.xccheckout │ │ │ └── StageClip.xccheckout │ └── xcshareddata │ │ └── xcschemes │ │ └── KeyGrip.xcscheme ├── KeyGrip │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-76.png │ │ │ └── Icon-76@2x.png │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── KeyGrip-Info.plist │ ├── KeyGrip-Prefix.pch │ ├── LaunchScreen.storyboard │ ├── RCWAppDelegate.h │ ├── RCWAppDelegate.m │ ├── RCWContentViewController.h │ ├── RCWContentViewController.m │ ├── RCWGentleErrorNotificationView-bg.png │ ├── RCWGentleErrorNotificationView.h │ ├── RCWGentleErrorNotificationView.m │ ├── RCWGentleErrorNotificationView.xib │ ├── RCWHealthMeter.h │ ├── RCWHealthMeter.m │ ├── RCWLabelCell.h │ ├── RCWLabelCell.m │ ├── RCWSettingsViewController.h │ ├── RCWSettingsViewController.m │ ├── RCWTextFieldCell.h │ ├── RCWTextFieldCell.m │ ├── RCWWebView.h │ ├── RCWWebView.m │ ├── Storyboard_iPhone.storyboard │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── KeyGripTests │ ├── KeyGripTests-Info.plist │ └── en.lproj │ │ └── InfoPlist.strings └── assets │ └── RCWGenterErrorNotificationView-bg.png ├── demo.gif ├── examples └── demo.md ├── scheme.gif └── server ├── KeyGripServer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── KeyGripServer.xccheckout └── xcshareddata │ └── xcschemes │ ├── Install KeyGripServer.xcscheme │ └── KeyGripServer.xcscheme ├── KeyGripServer ├── API.h ├── API.m ├── Base.lproj │ └── MainMenu.xib ├── Images.xcassets │ └── AppIcon.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 ├── KeyGrip-Info.plist ├── KeyGrip-Prefix.pch ├── KeyGrip.entitlements ├── KeyGripServer-Prefix.pch ├── KeyGripServer.entitlements ├── RCWAppDelegate.h ├── RCWAppDelegate.m ├── RCWDroplessTextView.h ├── RCWDroplessTextView.m ├── RCWKGHTMLImporter.h ├── RCWKGHTMLImporter.m ├── RCWKeyGripView.h ├── RCWKeyGripView.m ├── display.css ├── main.m ├── markdown.pl ├── selections.js ├── test.html └── zepto.js └── KeyGripServerTests ├── KeyGripServerTests-Info.plist ├── KeyGripServerTests-Prefix.pch └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata 3 | 4 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet.xcodeproj/project.xcworkspace/xcshareddata/KeyGripNet.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | B31116FA-C68E-49D9-948D-70BAA09D9A08 9 | IDESourceControlProjectName 10 | KeyGripNet 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 48EECA7F-68EB-4ED8-A04B-71F4AA38E12C 14 | ssh://bitbucket.org/rubbercitywizards/key-grip.git 15 | 16 | IDESourceControlProjectPath 17 | KeyGripNet/KeyGripNet.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 48EECA7F-68EB-4ED8-A04B-71F4AA38E12C 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | ssh://bitbucket.org/rubbercitywizards/key-grip.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | 48EECA7F-68EB-4ED8-A04B-71F4AA38E12C 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 48EECA7F-68EB-4ED8-A04B-71F4AA38E12C 36 | IDESourceControlWCCName 37 | KeyGrip 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet.xcodeproj/xcshareddata/xcschemes/KeyGripNet.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 54 | 55 | 61 | 62 | 64 | 65 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/KeyGripNet-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/KeyGripNet.h: -------------------------------------------------------------------------------- 1 | // 2 | // KeyGripNet.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface KeyGripNet : NSObject 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/KeyGripNet.m: -------------------------------------------------------------------------------- 1 | // 2 | // KeyGripNet.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "KeyGripNet.h" 22 | 23 | @implementation KeyGripNet 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWBonjourConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWBonjourConnection.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWStreamConnection.h" 23 | 24 | @interface RCWBonjourConnection : NSObject 25 | 26 | 27 | @property (nonatomic, readonly) NSString *identifier; 28 | 29 | - (instancetype)initWithIdentifier:(NSString *)identifier; 30 | 31 | - (void)shareConnection; 32 | - (void)findConnection; 33 | 34 | @property (nonatomic, strong) NSError *lastInputError; 35 | @property (nonatomic, strong) NSError *lastOutputError; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWBonjourConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWBonjourConnection.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWBonjourConnection.h" 23 | #import "RCWBonjourConnectionFinder.h" 24 | #import "RCWDataStreamCollector.h" 25 | #import "RCWDataStreamEmitter.h" 26 | 27 | NSString * const RCWBonjourConnectionErrorDomain = @"RCWBonjourConnectionErrorDomain"; 28 | 29 | @interface RCWBonjourConnection () 30 | 31 | 32 | @property (nonatomic, readwrite) BOOL isConnected; 33 | @property (nonatomic, strong) RCWBonjourConnectionFinder *finder; 34 | 35 | @property (nonatomic, strong) NSInputStream *input; 36 | @property (nonatomic, strong) NSOutputStream *output; 37 | @property (nonatomic) BOOL inputStreamOpen, outputStreamOpen; 38 | 39 | @property (nonatomic, strong) RCWDataStreamEmitter *emitter; 40 | @property (nonatomic, strong) RCWDataStreamCollector *collector; 41 | 42 | @end 43 | 44 | @implementation RCWBonjourConnection 45 | 46 | @synthesize identifier=_identifier; 47 | @synthesize delegate=_delegate; 48 | @synthesize lastInputError=_lastInputError; 49 | @synthesize lastOutputError=_lastOutputError; 50 | 51 | - (instancetype)initWithIdentifier:(NSString *)identifier 52 | { 53 | NSAssert(identifier, @"An identifier to identify this connection must be set!"); 54 | if (self = [super init]) { 55 | _identifier = identifier; 56 | _emitter = [[RCWDataStreamEmitter alloc] init]; 57 | _collector = [[RCWDataStreamCollector alloc] init]; 58 | 59 | __weak typeof(self) weakSelf = self; 60 | _collector.callback = ^(NSData *data) { 61 | [weakSelf.delegate connection:weakSelf didReceiveData:data]; 62 | }; 63 | } 64 | return self; 65 | } 66 | 67 | - (id)init 68 | { 69 | NSAssert(false, @"Use initWithIdentifier:"); 70 | return nil; 71 | } 72 | 73 | - (void)dealloc 74 | { 75 | [self reset]; 76 | } 77 | 78 | - (void)shareConnection 79 | { 80 | [self.finder startBroadcasting]; 81 | } 82 | 83 | - (void)findConnection 84 | { 85 | [self.finder startListening]; 86 | } 87 | 88 | - (void)finder:(RCWBonjourConnectionFinder *)finder connectedWithInput:(NSInputStream *)input output:(NSOutputStream *)output 89 | { 90 | self.input = input; 91 | self.output = output; 92 | 93 | self.input.delegate = self; 94 | self.output.delegate = self; 95 | 96 | [self.input scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 97 | [self.output scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 98 | 99 | [self.input open]; 100 | [self.output open]; 101 | } 102 | 103 | - (void)finder:(RCWBonjourConnectionFinder *)finder didError:(NSError *)error 104 | { 105 | [self stop]; 106 | [self.delegate connection:self failedWith:error]; 107 | } 108 | 109 | - (void)sendData:(NSData *)data 110 | { 111 | [self.emitter emitData:data]; 112 | [self writePendingBytesToOutputStream:self.output]; 113 | } 114 | 115 | - (void)stop 116 | { 117 | [self reset]; 118 | } 119 | 120 | - (void)reset 121 | { 122 | self.isConnected = NO; 123 | 124 | self.input.delegate = nil; 125 | self.output.delegate = nil; 126 | [self.input close]; 127 | [self.output close]; 128 | self.input = nil; 129 | self.output = nil; 130 | 131 | [_finder stop]; 132 | self.finder = nil; 133 | 134 | [self.emitter reset]; 135 | [self.collector reset]; 136 | } 137 | 138 | #pragma mark - Lazy Properties 139 | 140 | - (RCWBonjourConnectionFinder *)finder 141 | { 142 | if (!_finder) { 143 | _finder = [[RCWBonjourConnectionFinder alloc] initWithIdentifier:self.identifier]; 144 | _finder.delegate = self; 145 | _finder.debugLogging = YES; 146 | } 147 | return _finder; 148 | } 149 | 150 | #pragma mark - NSStreamDelegate 151 | 152 | - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode 153 | { 154 | switch (eventCode) { 155 | case NSStreamEventOpenCompleted: 156 | if (self.input == aStream) { 157 | self.inputStreamOpen = YES; 158 | } else if (self.output == aStream) { 159 | self.outputStreamOpen = YES; 160 | } 161 | if (self.inputStreamOpen && self.outputStreamOpen && !self.isConnected) { 162 | self.isConnected = YES; 163 | [self.delegate connectionDidConnect:self]; 164 | } 165 | break; 166 | 167 | case NSStreamEventErrorOccurred: { 168 | if (aStream == self.input) { 169 | self.lastInputError = aStream.streamError; 170 | } else { 171 | self.lastOutputError = aStream.streamError; 172 | } 173 | [self reset]; 174 | [self.delegate connection:self failedWith:aStream.streamError]; 175 | break; 176 | } 177 | 178 | case NSStreamEventEndEncountered: { 179 | [self reset]; 180 | NSError *error = [NSError errorWithDomain:RCWBonjourConnectionErrorDomain code:2 userInfo:@{NSLocalizedDescriptionKey: @"End of stream encountered."}]; 181 | [self.delegate connection:self failedWith:error]; 182 | break; 183 | } 184 | 185 | case NSStreamEventHasBytesAvailable: 186 | [self readBytesFromInputStream:(NSInputStream *)aStream]; 187 | break; 188 | 189 | case NSStreamEventHasSpaceAvailable: 190 | [self writePendingBytesToOutputStream:(NSOutputStream *)aStream]; 191 | break; 192 | 193 | case NSStreamEventNone: 194 | break; 195 | } 196 | } 197 | 198 | - (void)readBytesFromInputStream:(NSInputStream *)stream 199 | { 200 | if (!stream.hasBytesAvailable) { return; } 201 | 202 | size_t const bufferSize = 4096; 203 | uint8_t buffer[bufferSize]; 204 | 205 | while (stream.hasBytesAvailable) { 206 | memset(buffer, 0, sizeof(uint8_t) * bufferSize); 207 | NSInteger bytesRead = [stream read:buffer maxLength:bufferSize]; 208 | if (bytesRead < 0) { 209 | [self reset]; 210 | NSError *error = [NSError errorWithDomain:RCWBonjourConnectionErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Could not read bytes from stream. %ld", (long)bytesRead]}]; 211 | [self.delegate connection:self failedWith:error]; 212 | return; 213 | } 214 | 215 | NSError *error = nil; 216 | if ([self.collector write:buffer length:bytesRead error:&error] == -1) { 217 | [self reset]; 218 | [self.delegate connection:self failedWith:error]; 219 | return; 220 | } 221 | } 222 | } 223 | 224 | - (void)writePendingBytesToOutputStream:(NSOutputStream *)stream 225 | { 226 | size_t bufferSize = 4096; 227 | uint8_t buffer[bufferSize]; 228 | 229 | while (stream.hasSpaceAvailable && self.emitter.hasData) { 230 | memset(buffer, 0, sizeof(uint8_t) * bufferSize); 231 | 232 | NSUInteger count = [self.emitter attemptToRead:buffer maxLength:bufferSize]; 233 | NSUInteger bytesWritten = [stream write:buffer maxLength:count]; 234 | [self.emitter markActualByteCountRead:bytesWritten]; 235 | } 236 | } 237 | 238 | @end 239 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWBonjourConnectionFinder.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWBonjourConnectionFinder.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @class RCWBonjourConnectionFinder; 24 | 25 | @protocol RCWBonjourConnectionFinderDelegate 26 | - (void)finder:(RCWBonjourConnectionFinder *)finder connectedWithInput:(NSInputStream *)input output:(NSOutputStream *)output; 27 | - (void)finder:(RCWBonjourConnectionFinder *)finder didError:(NSError *)error; 28 | @end 29 | 30 | @interface RCWBonjourConnectionFinder : NSObject 31 | 32 | - (instancetype)initWithIdentifier:(NSString *)identifier; 33 | 34 | @property (atomic) BOOL debugLogging; 35 | @property (atomic) NSString *identifier; 36 | @property (atomic, weak) id delegate; 37 | 38 | /// Starts broadcasting the net service and looking for clients 39 | - (void)startBroadcasting; 40 | 41 | /// Starts listening for broadcasting servers. 42 | - (void)startListening; 43 | 44 | /// Stops everything, either listening or publishing. 45 | - (void)stop; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWDataStreamCollector.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWDataStreamCollector.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | typedef void(^RCWDataStreamCollectorReceivedCallback)(NSData *data); 24 | 25 | /** 26 | `RCWDataStreamCollector` acts like a ring buffer to collect and frame data according to the KeyGrip protocol. Just pump bytes into it from the socket and once a data frame is complete, the collector calls the `callback` block with fully formed data. 27 | 28 | If you dig into this class, you'll see that we instantiate a lot of `NSMutableData` objects to pull this off. Yeah, yeah. It's not the most efficient thing ever. It works and it is fast enough for this very lightweight communication protocol. Make a pull request with a better implementation and tests first before you complain. :) 29 | */ 30 | @interface RCWDataStreamCollector : NSObject 31 | 32 | @property (nonatomic, copy) RCWDataStreamCollectorReceivedCallback callback; 33 | 34 | - (void)reset; 35 | 36 | - (NSInteger)write:(const uint8_t *)buffer length:(NSUInteger)length error:(NSError **)error; 37 | 38 | @end 39 | 40 | 41 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWDataStreamCollector.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWDataStreamCollector.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWDataStreamCollector.h" 22 | 23 | @interface RCWDataStreamCollector () 24 | @property (nonatomic, strong) NSMutableData *collection; 25 | @property (nonatomic) BOOL needsToCollectLengthPreamble; 26 | @property (nonatomic) NSUInteger expectedDataLength; 27 | @end 28 | 29 | @implementation RCWDataStreamCollector 30 | 31 | - (instancetype)init 32 | { 33 | if (self = [super init]) { 34 | _needsToCollectLengthPreamble = YES; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)reset 40 | { 41 | self.needsToCollectLengthPreamble = YES; 42 | self.expectedDataLength = 0; 43 | self.collection = nil; 44 | } 45 | 46 | - (NSInteger)write:(const uint8_t *)buffer length:(NSUInteger)length error:(NSError **)error 47 | { 48 | NSAssert(self.callback, @"This object isn't very useful if you don't set a callback."); 49 | 50 | if (length == 0) { return 0; }; 51 | 52 | if (self.collection == nil) { 53 | self.collection = [NSMutableData data]; 54 | } 55 | 56 | [self.collection appendBytes:buffer length:length]; 57 | 58 | if (self.needsToCollectLengthPreamble) { 59 | uint32_t dataLength = 0; 60 | uint8_t bytesSoFar[30]; 61 | memset(bytesSoFar, 1, 30); 62 | [self.collection getBytes:bytesSoFar length:MIN(self.collection.length, 30)]; 63 | int position = 0; 64 | int result = sscanf((char *)bytesSoFar, "%u%n", &dataLength, &position); 65 | position = (int)MIN(position, self.collection.length); 66 | if (bytesSoFar[position] == 0 && result == 1) { 67 | self.needsToCollectLengthPreamble = NO; 68 | self.expectedDataLength = dataLength; 69 | int preambleLength = (int)strlen(self.collection.bytes) + 1; 70 | self.collection = [NSMutableData dataWithBytes:self.collection.bytes + preambleLength 71 | length:self.collection.length - preambleLength]; 72 | } else if (result != 1) { 73 | if (error) { 74 | *error = [NSError errorWithDomain:@"RCWDataStreamCollector" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Could not read length preamble from data packet."}]; 75 | } 76 | return -1; 77 | } 78 | } 79 | 80 | if (!self.needsToCollectLengthPreamble && self.collection.length >= self.expectedDataLength) { 81 | NSData *output = [NSData dataWithBytes:self.collection.bytes length:self.expectedDataLength]; 82 | self.collection = [NSMutableData dataWithBytes:self.collection.bytes + self.expectedDataLength 83 | length:self.collection.length - self.expectedDataLength]; 84 | self.needsToCollectLengthPreamble = YES; 85 | self.expectedDataLength = 0; 86 | self.callback(output); 87 | } 88 | 89 | return length; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWDataStreamEmitter.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWDataStreamEmitter.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | /** 24 | `RCWDataStreamEmitter` acts like a ring buffer to produce frame data according to the KeyGrip protocol. Just pump `NSData` objects into it and read data from it to pump into a socket. It will be properly framed and sits ready to give data when you need it and the socket is free. 25 | 26 | If you dig into this class, you'll see that we instantiate a lot of `NSMutableData` objects to pull this off. Yeah, yeah. It's not the most efficient thing ever. It works and it is fast enough for this very lightweight communication protocol. Make a pull request with a better implementation and tests first before you complain. :) 27 | */ 28 | @interface RCWDataStreamEmitter : NSObject 29 | 30 | @property (nonatomic, readonly) BOOL hasData; 31 | 32 | - (void)reset; 33 | - (void)emitData:(NSData *)data; 34 | - (NSInteger)attemptToRead:(uint8_t *)buffer maxLength:(NSUInteger)len; 35 | - (void)markActualByteCountRead:(NSUInteger)len; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWDataStreamEmitter.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWDataStreamEmitter.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWDataStreamEmitter.h" 22 | 23 | @interface RCWDataStreamEmitter () 24 | @property (nonatomic, strong) NSMutableData *dataQueue; 25 | @end 26 | 27 | @implementation RCWDataStreamEmitter 28 | 29 | - (instancetype)init 30 | { 31 | if (self = [super init]) { 32 | _dataQueue = [NSMutableData data]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)reset 38 | { 39 | self.dataQueue = [NSMutableData data]; 40 | } 41 | 42 | - (BOOL)hasData 43 | { 44 | return self.dataQueue.length > 0; 45 | } 46 | 47 | - (void)emitData:(NSData *)data 48 | { 49 | size_t headerLength = 100; 50 | char lengthString[headerLength]; 51 | memset(lengthString, 0, sizeof(uint8_t) * headerLength); 52 | sprintf(lengthString, "%lu", (unsigned long)data.length); 53 | [self.dataQueue appendBytes:lengthString length:strlen(lengthString)+1]; 54 | [self.dataQueue appendData:data]; 55 | } 56 | 57 | - (NSInteger)attemptToRead:(uint8_t *)buffer maxLength:(NSUInteger)len 58 | { 59 | if (!self.hasData) return 0; 60 | 61 | NSInteger amountToRead = MIN(self.dataQueue.length, len); 62 | 63 | [self.dataQueue getBytes:buffer length:amountToRead]; 64 | return amountToRead; 65 | } 66 | 67 | - (void)markActualByteCountRead:(NSUInteger)len 68 | { 69 | self.dataQueue = [NSMutableData dataWithBytes:self.dataQueue.bytes + len 70 | length:self.dataQueue.length - len]; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWKGClientAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGClientAPI.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWStreamConnection.h" 23 | 24 | @class RCWKGClientAPI; 25 | 26 | /** 27 | `RCWKGClientAPIDelegate` defines the callback methods for different events the API receives when talking to the server. They are all required methods because it's a good idea to handle them all. Seriously. 28 | */ 29 | @protocol RCWKGClientAPIDelegate 30 | 31 | /// Called when a connection successfully completes with the server. 32 | - (void)clientDidConnect:(RCWKGClientAPI *)client; 33 | 34 | /// Called every time the heartbeat ping is received from the server. 35 | - (void)clientReceivedServerPing:(RCWKGClientAPI *)client; 36 | 37 | /// Called when the server sends us a new HTML script. Includes the filename that we can display to the user of the client if need be. 38 | - (void)client:(RCWKGClientAPI *)client receivedScript:(NSString *)html named:(NSString *)filename; 39 | 40 | /// Called when the server successfully put a clip with the given `textID` into the pastboard of the server's host machine. 41 | - (void)client:(RCWKGClientAPI *)client notifiedOfPastedTextID:(NSString *)textID; 42 | 43 | /// Told of an error on server side (like file is not valid format). Not catastrophic to the connection. 44 | - (void)client:(RCWKGClientAPI *)client notifiedOfServerError:(NSError *)error; 45 | 46 | /// Client couldn't communicate with server at all. Catastrophic. Connection is terminated. 47 | - (void)client:(RCWKGClientAPI *)client failedWithError:(NSError *)error; 48 | 49 | @end 50 | 51 | /** 52 | `RCWKGClientAPI` is an object that handles the asynchronous communication with and callbacks from the `RCWKGServerAPI` on the other end of the connection. 53 | */ 54 | @interface RCWKGClientAPI : NSObject 55 | 56 | @property (nonatomic, weak) id delegate; 57 | 58 | /// Initializes the API object with the connection to talk with the server. 59 | - (instancetype)initWithConnection:(id)connection; 60 | 61 | /// Stops the API client. Kills any existing connection. Can be called multiple times with no ill effects. 62 | - (void)stop; 63 | 64 | /// Sends a heartbeat ping to the server. These pings are not required but are a great way to regularly test if the connection is broken or not. 65 | - (void)sendPing; 66 | 67 | /// Sends a command to the server to send back the script. 68 | - (void)askServerForScript; 69 | 70 | /// Asks the server to put a clip with the given `textID` in the pastboard of the server's host machine. 71 | - (void)pasteTextWithID:(NSString *)textID; 72 | 73 | /// Tell the server that something went wrong on the client end. 74 | - (void)notifyServerOfErrorMessage:(NSString *)msg; 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWKGClientAPI.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGClient.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWKGClientAPI.h" 22 | #import "RCWKGProtocolCommand.h" 23 | 24 | @interface RCWKGClientAPI () 25 | 26 | @property (nonatomic, strong) id connection; 27 | @property (nonatomic, strong) NSTimer *heartbeatTimer; 28 | @end 29 | 30 | @implementation RCWKGClientAPI 31 | 32 | - (instancetype)initWithConnection:(id)connection 33 | { 34 | if (self = [super init]) { 35 | _connection = connection; 36 | _connection.delegate = self; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)stop 42 | { 43 | [self.heartbeatTimer invalidate]; 44 | self.heartbeatTimer = nil; 45 | [self.connection stop]; 46 | } 47 | 48 | - (void)sendPing 49 | { 50 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"ping"]; 51 | [self.connection sendData:[command convertToData]]; 52 | } 53 | 54 | - (void)askServerForScript 55 | { 56 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"sendScript"]; 57 | [self.connection sendData:[command convertToData]]; 58 | } 59 | 60 | - (void)pasteTextWithID:(NSString *)textID 61 | { 62 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"pasteText"]; 63 | command.payload = @{@"textID": textID}; 64 | [self.connection sendData:[command convertToData]]; 65 | } 66 | 67 | - (void)notifyServerOfErrorMessage:(NSString *)msg 68 | { 69 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"error"]; 70 | command.payload = @{@"message": msg}; 71 | [self.connection sendData:[command convertToData]]; 72 | } 73 | 74 | - (void)connectionDidConnect:(id)connection 75 | { 76 | self.heartbeatTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(sendPing) userInfo:nil repeats:YES]; 77 | [self.delegate clientDidConnect:self]; 78 | } 79 | 80 | - (void)connection:(id)connection didReceiveData:(NSData *)data 81 | { 82 | NSError *error = nil; 83 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandFromData:data error:&error]; 84 | if (!command) { 85 | [self.delegate client:self failedWithError:error]; 86 | return; 87 | } 88 | 89 | if ([command.commandName isEqualToString:@"ping"]) { 90 | [self.delegate clientReceivedServerPing:self]; 91 | } else if ([command.commandName isEqualToString:@"pastedText"]) { 92 | NSString *textID = command.payload[@"textID"]; 93 | [self.delegate client:self notifiedOfPastedTextID:textID]; 94 | } else if ([command.commandName isEqualToString:@"loadScriptHTML"]) { 95 | NSString *html = command.payload[@"html"]; 96 | NSString *name = command.payload[@"name"]; 97 | [self.delegate client:self receivedScript:html named:name]; 98 | } else if ([command.commandName isEqualToString:@"error"]) { 99 | NSError *error = [NSError errorWithDomain:@"RCWServerSideError" code:0 userInfo:@{NSLocalizedDescriptionKey: command.payload[@"message"]}]; 100 | [self.delegate client:self notifiedOfServerError:error]; 101 | } else { 102 | NSLog(@"Unknown command received from server: %@\n\n%@", command.commandName, command.payload); 103 | } 104 | } 105 | 106 | - (void)connection:(id)connection failedWith:(NSError *)error 107 | { 108 | [self stop]; 109 | [self.delegate client:self failedWithError:error]; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWKGProtocolCommand.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGProtocolCommand.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | typedef NS_ENUM(NSInteger, RCWKGProtocolCommandErrorCode) { 24 | RCWKGProtocolUnrecognized, 25 | RCWKGProtocolUnparsable, 26 | RCWKGProtocolMissingMetadata 27 | }; 28 | 29 | /** 30 | `RCWKGProtocolCommand` is a simple object to make it easy to send a payload of data with a command name over the wire. 31 | */ 32 | @interface RCWKGProtocolCommand : NSObject 33 | 34 | /// Some string to describe the command. 35 | @property (nonatomic, copy) NSString *commandName; 36 | /// Some payload 37 | @property (nonatomic, strong) NSDictionary *payload; 38 | 39 | /// Helper method to create a command to send with a given name. Same as initializing a fresh command object and setting the `commandName` property. 40 | + (instancetype)commandWithName:(NSString *)name; 41 | 42 | /// Creates an object with a command name and payload from an `NSData` object. 43 | + (instancetype)commandFromData:(NSData *)data error:(NSError **)error; 44 | 45 | /// Convert the command object to `NSData` for sending. 46 | - (NSData *)convertToData; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWKGProtocolCommand.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGProtocolCommand.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWKGProtocolCommand.h" 22 | 23 | @implementation RCWKGProtocolCommand 24 | 25 | + (instancetype)commandWithName:(NSString *)name 26 | { 27 | RCWKGProtocolCommand *command = [[self alloc] init]; 28 | command.commandName = name; 29 | return command; 30 | } 31 | 32 | + (instancetype)commandFromData:(NSData *)data error:(NSError *__autoreleasing *)error 33 | { 34 | NSDictionary *dict = nil; 35 | @try { 36 | dict = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 37 | if (![dict isKindOfClass:[NSDictionary class]]) { 38 | if (error) { 39 | *error = [NSError errorWithDomain:@"RCWKGProtocolCommand" code:RCWKGProtocolUnrecognized userInfo:@{NSLocalizedDescriptionKey:@"Unrecognized data format."}]; 40 | } 41 | return nil; 42 | } 43 | } 44 | @catch (NSException *exception) { 45 | if ([exception.name isEqualToString:NSInvalidArgumentException]) { 46 | if (error) { 47 | *error = [NSError errorWithDomain:@"RCWKGProtocolCommand" code:RCWKGProtocolUnparsable userInfo:@{NSLocalizedDescriptionKey:@"Unparsable data format."}]; 48 | } 49 | return nil; 50 | } 51 | @throw exception; 52 | } 53 | 54 | RCWKGProtocolCommand *command = [[self alloc] init]; 55 | 56 | command.commandName = dict[@"_command"]; 57 | if (!command.commandName || ![command.commandName isKindOfClass:[NSString class]]) { 58 | if (error) { 59 | *error = [NSError errorWithDomain:@"RCWKGProtocolCommand" code:RCWKGProtocolMissingMetadata userInfo:@{NSLocalizedDescriptionKey:@"Missing command metadata."}]; 60 | } 61 | return nil; 62 | } 63 | command.payload = dict[@"_payload"]; 64 | return command; 65 | } 66 | 67 | - (NSData *)convertToData 68 | { 69 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 70 | dict[@"_command"] = self.commandName; 71 | if (self.payload) { 72 | dict[@"_payload"] = self.payload; 73 | } 74 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dict]; 75 | return data; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWKGServerAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGServerAPI.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWStreamConnection.h" 23 | 24 | @class RCWKGServerAPI; 25 | 26 | /** 27 | `RCWKGServerAPIDelegate` defines the callback methods for different events the API receives when talking to the client. They are all required methods because it's a good idea to handle them all. Seriously. 28 | */ 29 | @protocol RCWKGServerAPIDelegate 30 | 31 | /// Called when a connection successfully completes with the client. 32 | - (void)serverDidConnect:(RCWKGServerAPI *)server; 33 | 34 | /// Called every time the heartbeat ping is received from the client. 35 | - (void)serverReceivedClientPing:(RCWKGServerAPI *)server; 36 | 37 | /// Called when the client asks us to (re)send the HTML script. 38 | - (void)serverAskedForScript:(RCWKGServerAPI *)server; 39 | 40 | /// Called when the client is asking the server to put the text with the given `textID` on the pastboard. 41 | - (void)server:(RCWKGServerAPI *)server askedToPasteTextWithId:(NSString *)textID; 42 | 43 | /// Told of an error on client side. Not catastrophic to the connection. 44 | - (void)server:(RCWKGServerAPI *)client notifiedOfClientError:(NSError *)error; 45 | 46 | /// Client couldn't communicate with client at all. Catastrophic. Connection terminated. 47 | - (void)server:(RCWKGServerAPI *)server failedWithError:(NSError *)error; 48 | 49 | @end 50 | 51 | /** 52 | `RCWKGServerAPI` is an object that handles the asynchronous communication with and callbacks from the `RCWKGClientAPI` on the other end of the connection. 53 | */ 54 | @interface RCWKGServerAPI : NSObject 55 | 56 | /// Property that delegates down to the connection object to find out if there is actually a connection. 57 | @property (nonatomic, readonly) BOOL isConnected; 58 | @property (nonatomic, weak) id delegate; 59 | 60 | /// Initializes the API object with the connection to talk with the server. 61 | - (instancetype)initWithConnection:(id)connection; 62 | 63 | /// Stops the API server. Kills any existing connection. Can be called multiple times with no ill effects. 64 | - (void)stop; 65 | 66 | /// Sends a heartbeat ping to the client. These pings are not required but are a great way to regularly test if the connection is broken or not. 67 | - (void)sendPing; 68 | 69 | /// Tells the client that a clip with the given `textID` has been placed on to the pasteboard. 70 | - (void)pastedTextWithID:(NSString *)textID; 71 | 72 | /// Sends the given HTML script with the given name to the client. 73 | - (void)sendScriptHTML:(NSString *)html named:(NSString *)name; 74 | 75 | /// Tells the client about a problem. The client can then display this to the user if need be. 76 | - (void)notifyClientOfErrorMessage:(NSString *)msg; 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWKGServerAPI.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGServerAPI.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWKGServerAPI.h" 22 | #import "RCWKGProtocolCommand.h" 23 | 24 | @interface RCWKGServerAPI () 25 | 26 | @property (nonatomic, strong) id connection; 27 | @property (nonatomic, strong) NSTimer *heartbeatTimer; 28 | @end 29 | 30 | @implementation RCWKGServerAPI 31 | 32 | - (instancetype)initWithConnection:(id)connection 33 | { 34 | if (self = [super init]) { 35 | _connection = connection; 36 | _connection.delegate = self; 37 | } 38 | return self; 39 | } 40 | 41 | - (BOOL)isConnected 42 | { 43 | return self.connection.isConnected; 44 | } 45 | 46 | - (void)stop 47 | { 48 | [self.heartbeatTimer invalidate]; 49 | self.heartbeatTimer = nil; 50 | [self.connection stop]; 51 | } 52 | 53 | - (void)sendPing 54 | { 55 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"ping"]; 56 | [self.connection sendData:[command convertToData]]; 57 | } 58 | 59 | - (void)pastedTextWithID:(NSString *)textID 60 | { 61 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"pastedText"]; 62 | command.payload = @{@"textID": textID}; 63 | [self.connection sendData:[command convertToData]]; 64 | } 65 | 66 | - (void)sendScriptHTML:(NSString *)html named:(NSString *)name 67 | { 68 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"loadScriptHTML"]; 69 | command.payload = @{@"html": html, @"name": name}; 70 | [self.connection sendData:[command convertToData]]; 71 | } 72 | 73 | - (void)notifyClientOfErrorMessage:(NSString *)msg 74 | { 75 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"error"]; 76 | command.payload = @{@"message": msg}; 77 | [self.connection sendData:[command convertToData]]; 78 | } 79 | 80 | - (void)connection:(id)connection didReceiveData:(NSData *)data 81 | { 82 | NSError *error = nil; 83 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandFromData:data error:&error]; 84 | if (!command) { 85 | [self.delegate server:self failedWithError:error]; 86 | return; 87 | } 88 | 89 | if ([command.commandName isEqualToString:@"ping"]) { 90 | [self.delegate serverReceivedClientPing:self]; 91 | } else if ([command.commandName isEqualToString:@"sendScript"]) { 92 | [self.delegate serverAskedForScript:self]; 93 | } else if ([command.commandName isEqualToString:@"pasteText"]) { 94 | NSString *textID = command.payload[@"textID"]; 95 | [self.delegate server:self askedToPasteTextWithId:textID]; 96 | } else if ([command.commandName isEqualToString:@"error"]) { 97 | NSError *error = [NSError errorWithDomain:@"RCWClientSideError" code:0 userInfo:@{NSLocalizedDescriptionKey: command.payload[@"message"]}]; 98 | [self.delegate server:self notifiedOfClientError:error]; 99 | } else { 100 | NSLog(@"Unknown command received from server: %@\n\n%@", command.commandName, command.payload); 101 | } 102 | } 103 | 104 | - (void)connection:(id)connection failedWith:(NSError *)error 105 | { 106 | [self stop]; 107 | [self.delegate server:self failedWithError:error]; 108 | } 109 | 110 | - (void)connectionDidConnect:(id)connection 111 | { 112 | self.heartbeatTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(sendPing) userInfo:nil repeats:YES]; 113 | [self.delegate serverDidConnect:self]; 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWSocketClientConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWSocketClientConnection.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWStreamConnection.h" 23 | 24 | typedef NSUInteger RCWSocketClientConnectionPort; 25 | 26 | @interface RCWSocketClientConnection : NSObject 27 | 28 | 29 | @property (nonatomic, readonly) NSString *host; 30 | @property (nonatomic, readonly) RCWSocketClientConnectionPort port; 31 | 32 | - (instancetype)initClientWithHost:(NSString *)host port:(RCWSocketClientConnectionPort)port; 33 | 34 | - (void)attemptToConnect; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWSocketClientConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWSocketClientConnection.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWSocketClientConnection.h" 22 | #import "RCWDataStreamCollector.h" 23 | #import "RCWDataStreamEmitter.h" 24 | 25 | @interface RCWSocketClientConnection () 26 | 27 | 28 | @property (nonatomic, strong) RCWDataStreamEmitter *emitter; 29 | @property (nonatomic, strong) RCWDataStreamCollector *collector; 30 | 31 | @property (nonatomic, readwrite, strong) NSString *host; 32 | @property (nonatomic, readwrite, assign) NSUInteger port; 33 | @property (nonatomic, readwrite, assign) BOOL isConnected; 34 | 35 | @property (nonatomic, strong) NSInputStream *input; 36 | @property (nonatomic, strong) NSOutputStream *output; 37 | 38 | @property (nonatomic) BOOL inputStreamOpen, outputStreamOpen; 39 | 40 | @property (nonatomic, strong) NSError *lastInputError, *lastOutputError; 41 | @end 42 | 43 | @implementation RCWSocketClientConnection 44 | @synthesize delegate=_delegate; 45 | @synthesize host=_host; 46 | @synthesize port=_port; 47 | 48 | - (instancetype)initClientWithHost:(NSString *)host port:(NSUInteger)port 49 | { 50 | if (self = [self init]) { 51 | _host = [host copy]; 52 | _port = port; 53 | } 54 | return self; 55 | } 56 | 57 | - (id)init 58 | { 59 | if (self = [super init]) { 60 | _emitter = [[RCWDataStreamEmitter alloc] init]; 61 | _collector = [[RCWDataStreamCollector alloc] init]; 62 | 63 | __weak typeof(self) weakSelf = self; 64 | _collector.callback = ^(NSData *data) { 65 | [weakSelf.delegate connection:weakSelf didReceiveData:data]; 66 | }; 67 | } 68 | return self; 69 | } 70 | 71 | - (void)dealloc 72 | { 73 | [self stop]; 74 | } 75 | 76 | - (void)attemptToConnect 77 | { 78 | NSInputStream *i = nil; 79 | NSOutputStream *o = nil; 80 | 81 | [self getStreamsToHostNamed:self.host port:self.port inputStream:&i outputStream:&o]; 82 | 83 | NSAssert(i && o, @"Unable to open connection. FIX THIS! TODO"); 84 | 85 | self.input = i; 86 | self.output = o; 87 | 88 | self.input.delegate = self; 89 | self.output.delegate = self; 90 | 91 | [self.input scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 92 | [self.output scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 93 | 94 | [self.input open]; 95 | [self.output open]; 96 | } 97 | 98 | - (void)stop 99 | { 100 | self.input.delegate = nil; 101 | self.output.delegate = nil; 102 | [self.input close]; 103 | [self.output close]; 104 | self.input = nil; 105 | self.output = nil; 106 | 107 | self.isConnected = NO; 108 | 109 | [self.emitter reset]; 110 | [self.collector reset]; 111 | } 112 | 113 | - (void)sendData:(NSData *)data 114 | { 115 | [self.emitter emitData:data]; 116 | [self writePendingBytesToOutputStream:self.output]; 117 | } 118 | 119 | #pragma mark - NSStreamDelegate 120 | 121 | - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode 122 | { 123 | switch (eventCode) { 124 | case NSStreamEventOpenCompleted: 125 | if (self.input == aStream) { 126 | self.inputStreamOpen = YES; 127 | } else if (self.output == aStream) { 128 | self.outputStreamOpen = YES; 129 | } 130 | if (self.inputStreamOpen && self.outputStreamOpen && !self.isConnected) { 131 | self.isConnected = YES; 132 | [self.delegate connectionDidConnect:self]; 133 | } 134 | break; 135 | 136 | case NSStreamEventErrorOccurred: { 137 | if (aStream == self.input) { 138 | self.lastInputError = aStream.streamError; 139 | } else { 140 | self.lastOutputError = aStream.streamError; 141 | } 142 | [self stop]; 143 | [self.delegate connection:self failedWith:aStream.streamError]; 144 | break; 145 | } 146 | 147 | case NSStreamEventEndEncountered: { 148 | [self stop]; 149 | NSError *error = [NSError errorWithDomain:@"RCWSocketServerDomain" code:2 userInfo:@{NSLocalizedDescriptionKey: @"End of stream encountered."}]; 150 | [self.delegate connection:self failedWith:error]; 151 | break; 152 | } 153 | 154 | case NSStreamEventHasBytesAvailable: 155 | [self readBytesFromInputStream:(NSInputStream *)aStream]; 156 | break; 157 | 158 | case NSStreamEventHasSpaceAvailable: 159 | [self writePendingBytesToOutputStream:(NSOutputStream *)aStream]; 160 | break; 161 | 162 | case NSStreamEventNone: 163 | break; 164 | } 165 | } 166 | 167 | - (void)readBytesFromInputStream:(NSInputStream *)stream 168 | { 169 | if (!stream.hasBytesAvailable) { return; } 170 | 171 | size_t bufferSize = 4096; 172 | uint8_t buffer[bufferSize]; 173 | 174 | while (stream.hasBytesAvailable) { 175 | memset(buffer, 0, sizeof(uint8_t) * bufferSize); 176 | NSInteger bytesRead = [stream read:buffer maxLength:bufferSize]; 177 | if (bytesRead < 0) { 178 | [self stop]; 179 | NSError *error = [NSError errorWithDomain:@"RCWSocketServerDomain" code:0 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Could not read bytes from stream. %ld", (long)bytesRead]}]; 180 | [self.delegate connection:self failedWith:error]; 181 | return; 182 | } 183 | 184 | NSError *error = nil; 185 | if ([self.collector write:buffer length:bytesRead error:&error] == -1) { 186 | [self stop]; 187 | [self.delegate connection:self failedWith:error]; 188 | return; 189 | } 190 | } 191 | } 192 | 193 | - (void)writePendingBytesToOutputStream:(NSOutputStream *)stream 194 | { 195 | size_t const bufferSize = 4096; 196 | uint8_t buffer[bufferSize]; 197 | 198 | while (stream.hasSpaceAvailable && self.emitter.hasData) { 199 | memset(buffer, 0, sizeof(uint8_t) * bufferSize); 200 | 201 | NSUInteger count = [self.emitter attemptToRead:buffer maxLength:bufferSize]; 202 | NSUInteger bytesWritten = [stream write:buffer maxLength:count]; 203 | [self.emitter markActualByteCountRead:bytesWritten]; 204 | } 205 | } 206 | 207 | // https://developer.apple.com/library/ios/qa/qa1652/_index.html 208 | - (void)getStreamsToHostNamed:(NSString *)hostName 209 | port:(NSUInteger)port 210 | inputStream:(out NSInputStream **)inputStreamPtr 211 | outputStream:(out NSOutputStream **)outputStreamPtr 212 | { 213 | CFReadStreamRef readStream; 214 | CFWriteStreamRef writeStream; 215 | 216 | if (hostName == nil) return; 217 | if ((port <= 0) || (port > 65535)) return; 218 | assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) ); 219 | 220 | readStream = NULL; 221 | writeStream = NULL; 222 | 223 | CFStreamCreatePairWithSocketToHost( 224 | NULL, 225 | (__bridge CFStringRef) hostName, 226 | (UInt32)port, 227 | ((inputStreamPtr != NULL) ? &readStream : NULL), 228 | ((outputStreamPtr != NULL) ? &writeStream : NULL) 229 | ); 230 | 231 | if (inputStreamPtr != NULL) { 232 | *inputStreamPtr = CFBridgingRelease(readStream); 233 | } 234 | if (outputStreamPtr != NULL) { 235 | *outputStreamPtr = CFBridgingRelease(writeStream); 236 | } 237 | } 238 | 239 | @end 240 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWSocketServerConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWSocketServerConnection.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWStreamConnection.h" 23 | 24 | static NSUInteger const kRCWKGSocketServerConnectionAnyPort = 0; 25 | 26 | typedef NSUInteger RCWKGSocketServerPort; 27 | 28 | @interface RCWSocketServerConnection : NSObject 29 | 30 | 31 | @property (nonatomic, readonly) RCWKGSocketServerPort port; 32 | 33 | - (void)bindToPort:(RCWKGSocketServerPort)port; 34 | - (void)startListening; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWSocketServerConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWSocketServerConnection.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWSocketServerConnection.h" 22 | #import "RCWDataStreamCollector.h" 23 | #import "RCWDataStreamEmitter.h" 24 | #include 25 | #include 26 | 27 | @interface RCWSocketServerConnection () 28 | @property (nonatomic, strong) RCWDataStreamEmitter *emitter; 29 | @property (nonatomic, strong) RCWDataStreamCollector *collector; 30 | 31 | @property (atomic, readwrite, strong) NSString *host; 32 | @property (atomic, readwrite, assign) NSUInteger port; 33 | @property (atomic, readwrite, assign) BOOL isConnected; 34 | @property (atomic, readwrite, assign) BOOL isListening; 35 | 36 | @property (assign) int server_socket; 37 | @property (assign) int server_connection; 38 | 39 | @property (atomic) dispatch_queue_t socketQueue; 40 | 41 | @end 42 | 43 | @implementation RCWSocketServerConnection 44 | @synthesize delegate=_delegate; 45 | @synthesize port=_port; 46 | 47 | - (id)init 48 | { 49 | if (self = [super init]) { 50 | _emitter = [[RCWDataStreamEmitter alloc] init]; 51 | _collector = [[RCWDataStreamCollector alloc] init]; 52 | 53 | __weak typeof(self) weakSelf = self; 54 | _collector.callback = ^(NSData *data) { 55 | dispatch_sync(dispatch_get_main_queue(), ^{ 56 | [weakSelf.delegate connection:weakSelf didReceiveData:data]; 57 | }); 58 | }; 59 | 60 | self.socketQueue = dispatch_queue_create("com.rubbercitywizards.keygrip.server.socket", DISPATCH_QUEUE_SERIAL); 61 | } 62 | return self; 63 | } 64 | 65 | - (void)dealloc 66 | { 67 | [self stop]; 68 | } 69 | 70 | - (void)bindToPort:(RCWKGSocketServerPort)port 71 | { 72 | [self stop]; 73 | _port = port; 74 | 75 | self.server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 76 | struct sockaddr_in sin; 77 | memset(&sin, 0, sizeof(sin)); 78 | sin.sin_len = sizeof(sin); 79 | sin.sin_family = AF_INET; 80 | sin.sin_port = htons(self.port); 81 | sin.sin_addr.s_addr= INADDR_ANY; 82 | 83 | int set = 1; 84 | setsockopt(self.server_socket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)); 85 | 86 | int res = bind(self.server_socket, (struct sockaddr *)&sin, sizeof(sin)); 87 | assert(res == 0); 88 | 89 | socklen_t len = sizeof(sin); 90 | // We do this just to get the port 91 | getsockname(self.server_socket, (struct sockaddr *)&sin, &len); 92 | 93 | listen(self.server_socket, 5); 94 | self.port = ntohs(sin.sin_port); 95 | } 96 | 97 | // http://www.minek.com/files/unix_examples/poll.html 98 | - (void)startListening 99 | { 100 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 101 | struct sockaddr sock_conn; 102 | socklen_t c; 103 | self.isListening = YES; 104 | self.server_connection = accept(self.server_socket, &sock_conn, &c); 105 | 106 | self.isConnected = YES; 107 | self.isListening = NO; 108 | 109 | dispatch_sync(dispatch_get_main_queue(), ^{ 110 | [self.delegate connectionDidConnect:self]; 111 | }); 112 | 113 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 114 | UInt8 buffer[2048]; 115 | 116 | [self writePendingBytes]; 117 | while (self.isConnected) { 118 | /* Set up polling using select. */ 119 | struct timeval timeOut; 120 | timeOut.tv_sec = 1; 121 | fd_set fileDescriptorsToPoll; 122 | int maxfd = self.server_connection+1; 123 | FD_ZERO(&fileDescriptorsToPoll); 124 | FD_SET(self.server_connection,&fileDescriptorsToPoll); 125 | 126 | /* Wait for some input. */ 127 | select(maxfd, &fileDescriptorsToPoll, (fd_set *) 0, (fd_set *) 0, &timeOut); 128 | 129 | if( FD_ISSET(self.server_connection, &fileDescriptorsToPoll)) 130 | { 131 | memset(buffer, 0, sizeof(buffer)); 132 | ssize_t bytesRead = recv(self.server_connection, buffer, sizeof(buffer), 0); 133 | /* If error or eof, terminate. */ 134 | if (bytesRead < 0) { 135 | [self stop]; 136 | NSError *error = [NSError errorWithDomain:@"RCWSocketServer" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Socket connection failed."}]; 137 | dispatch_sync(dispatch_get_main_queue(), ^{ 138 | [self.delegate connection:self failedWith:error]; 139 | }); 140 | } else if (bytesRead > 0) { 141 | NSError *error = nil; 142 | if ([self.collector write:buffer length:bytesRead error:&error] == -1) { 143 | [self stop]; 144 | dispatch_sync(dispatch_get_main_queue(), ^{ 145 | [self.delegate connection:self failedWith:error]; 146 | }); 147 | } 148 | } 149 | } 150 | } 151 | [self closeUnixBits]; 152 | }); 153 | }); 154 | } 155 | 156 | - (void)closeUnixBits 157 | { 158 | if (self.server_connection > 0) { 159 | close(self.server_connection); 160 | close(self.server_socket); 161 | self.server_connection = 0; 162 | self.server_socket = 0; 163 | } 164 | } 165 | 166 | - (void)stop 167 | { 168 | [self closeUnixBits]; 169 | 170 | self.isListening = NO; 171 | self.isConnected = NO; 172 | 173 | [self.emitter reset]; 174 | [self.collector reset]; 175 | } 176 | 177 | - (void)sendData:(NSData *)data 178 | { 179 | [self.emitter emitData:data]; 180 | [self writePendingBytes]; 181 | } 182 | 183 | - (void)writePendingBytes 184 | { 185 | dispatch_async(self.socketQueue, ^{ 186 | size_t const bufferSize = 4096; 187 | uint8_t buffer[bufferSize]; 188 | 189 | while (self.isConnected && self.emitter.hasData) { 190 | memset(buffer, 0, sizeof(uint8_t) * bufferSize); 191 | 192 | NSUInteger count = [self.emitter attemptToRead:buffer maxLength:bufferSize]; 193 | ssize_t bytesWritten = send(self.server_connection, buffer, count, 0); 194 | [self.emitter markActualByteCountRead:bytesWritten]; 195 | } 196 | }); 197 | } 198 | 199 | @end 200 | 201 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNet/RCWStreamConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWStreamConnection.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @protocol RCWStreamConnection; 24 | 25 | @protocol RCWConnectionDelegate 26 | - (void)connectionDidConnect:(id)connection; 27 | - (void)connection:(id)connection failedWith:(NSError *)error; 28 | - (void)connection:(id)connection didReceiveData:(NSData *)data; 29 | @end 30 | 31 | 32 | /// RCWStreamConnection declares the protocol for a simple stream based pipe that searches for others on the network with the given identifier. 33 | @protocol RCWStreamConnection 34 | 35 | @property (nonatomic, weak) id delegate; 36 | @property (nonatomic, readonly) BOOL isConnected; 37 | 38 | - (void)sendData:(NSData *)data; 39 | - (void)stop; 40 | 41 | @end 42 | 43 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/KeyGripNetTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.rubbercitywizards.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/RCWKGClientTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGClientTest.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWKGClientAPI.h" 23 | #import "RCWKGServerAPI.h" 24 | #import "TestCaseStreamConnection.h" 25 | #import "RCWKGProtocolCommand.h" 26 | 27 | @interface RCWKGClientTest : XCTestCase 28 | 29 | @property (nonatomic, strong) RCWKGClientAPI *client; 30 | @property (nonatomic, strong) RCWKGServerAPI *server; 31 | 32 | @property (nonatomic, strong) NSError *lastClientErrorReceived; 33 | @property (nonatomic, strong) NSError *lastServerErrorReceived; 34 | 35 | // Helper properties to assert that commands were received by client 36 | @property (nonatomic) BOOL clientConnected; 37 | @property (nonatomic) BOOL clientReceivedPing; 38 | @property (nonatomic, strong) NSString *clientReceivedTextID; 39 | @property (nonatomic, strong) NSString *clientReceivedHTML; 40 | @property (nonatomic, strong) NSString *clientReceivedScriptName; 41 | @property (nonatomic, strong) NSString *clientToldItemWasPasted; 42 | 43 | // Helper properties to assert that comamnds were received by server 44 | @property (nonatomic) BOOL serverConnected; 45 | @property (nonatomic) BOOL serverReceivedPing; 46 | @property (nonatomic) BOOL serverReceivedRequestForScript; 47 | @property (nonatomic) NSString *serverAskedToPasteItem; 48 | @end 49 | 50 | @implementation RCWKGClientTest 51 | 52 | - (void)setUp 53 | { 54 | [super setUp]; 55 | TestCaseStreamConnection *clientConnection = [[TestCaseStreamConnection alloc] init]; 56 | TestCaseStreamConnection *serverConnection = [[TestCaseStreamConnection alloc] init]; 57 | clientConnection.otherConnection = serverConnection; 58 | serverConnection.otherConnection = clientConnection; 59 | 60 | self.client = [[RCWKGClientAPI alloc] initWithConnection:clientConnection]; 61 | self.client.delegate = self; 62 | self.server = [[RCWKGServerAPI alloc] initWithConnection:serverConnection]; 63 | self.server.delegate = self; 64 | 65 | [clientConnection start]; 66 | [serverConnection start]; 67 | } 68 | 69 | - (void)tearDown 70 | { 71 | [super tearDown]; 72 | [self.client stop]; 73 | [self.server stop]; 74 | } 75 | 76 | - (void)testClientConnected 77 | { 78 | XCTAssertTrue(self.clientConnected); 79 | } 80 | 81 | - (void)testServerConnected 82 | { 83 | XCTAssertTrue(self.serverConnected); 84 | } 85 | 86 | - (void)testServerPing 87 | { 88 | [self.server sendPing]; 89 | XCTAssertTrue(self.clientReceivedPing); 90 | } 91 | 92 | - (void)testClientPing 93 | { 94 | [self.client sendPing]; 95 | XCTAssertTrue(self.serverReceivedPing); 96 | } 97 | 98 | - (void)testAskingServerForScriptItems 99 | { 100 | [self.client askServerForScript]; 101 | XCTAssertTrue(self.serverReceivedRequestForScript); 102 | } 103 | 104 | - (void)testTellingServerToPasteItem 105 | { 106 | [self.client pasteTextWithID:@"23"]; 107 | XCTAssertEqualObjects(self.serverAskedToPasteItem, @"23"); 108 | } 109 | 110 | - (void)testClientToldWhichItemWasPasted 111 | { 112 | [self.server pastedTextWithID:@"24"]; 113 | XCTAssertEqualObjects(self.clientToldItemWasPasted, @"24"); 114 | } 115 | 116 | - (void)testClientReceivedScriptItems 117 | { 118 | [self.server sendScriptHTML:@"some html" named:@"script name"]; 119 | 120 | XCTAssertEqualObjects(self.clientReceivedHTML, @"some html"); 121 | XCTAssertEqualObjects(self.clientReceivedScriptName, @"script name"); 122 | } 123 | 124 | - (void)testSendingErrorMessageToServer 125 | { 126 | [self.client notifyServerOfErrorMessage:@"some message"]; 127 | XCTAssertEqualObjects(self.lastClientErrorReceived.localizedDescription, @"some message"); 128 | } 129 | 130 | - (void)testSendingErrorMessageToClient 131 | { 132 | [self.server notifyClientOfErrorMessage:@"other message"]; 133 | XCTAssertEqualObjects(self.lastServerErrorReceived.localizedDescription, @"other message"); 134 | } 135 | 136 | 137 | #pragma mark - Client Delegate 138 | 139 | - (void)clientDidConnect:(RCWKGClientAPI *)client 140 | { 141 | self.clientConnected = YES; 142 | } 143 | 144 | - (void)clientReceivedServerPing:(RCWKGClientAPI *)client 145 | { 146 | self.clientReceivedPing = YES; 147 | } 148 | 149 | - (void)client:(RCWKGClientAPI *)client failedWithError:(NSError *)error 150 | { 151 | } 152 | 153 | - (void)client:(RCWKGClientAPI *)client notifiedOfServerError:(NSError *)error 154 | { 155 | self.lastServerErrorReceived = error; 156 | } 157 | 158 | - (void)client:(RCWKGClientAPI *)client notifiedOfPastedTextID:(NSString *)textID 159 | { 160 | self.clientToldItemWasPasted = textID; 161 | } 162 | 163 | - (void)client:(RCWKGClientAPI *)client receivedScript:(NSString *)html named:(NSString *)filename 164 | { 165 | self.clientReceivedHTML = html; 166 | self.clientReceivedScriptName = filename; 167 | } 168 | 169 | 170 | #pragma mark - Server Delegate 171 | 172 | - (void)serverDidConnect:(RCWKGServerAPI *)server 173 | { 174 | self.serverConnected = YES; 175 | } 176 | 177 | - (void)serverReceivedClientPing:(RCWKGServerAPI *)server 178 | { 179 | self.serverReceivedPing = YES; 180 | } 181 | 182 | - (void)server:(RCWKGServerAPI *)server failedWithError:(NSError *)error 183 | { 184 | } 185 | 186 | - (void)server:(RCWKGClientAPI *)server notifiedOfClientError:(NSError *)error 187 | { 188 | self.lastClientErrorReceived = error; 189 | } 190 | 191 | - (void)serverAskedForScript:(RCWKGServerAPI *)server 192 | { 193 | self.serverReceivedRequestForScript = YES; 194 | } 195 | 196 | - (void)server:(RCWKGServerAPI *)server askedToPasteTextWithId:(NSString *)textId 197 | { 198 | self.serverAskedToPasteItem = textId; 199 | } 200 | 201 | @end 202 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/RCWKGConnectionFinderTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGConnectionFinderTest.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "XCTAsyncTestCase.h" 23 | #import "RCWBonjourConnectionFinder.h" 24 | 25 | // Declare allegiance to this property here for a test 26 | @interface RCWBonjourConnectionFinder () 27 | @end 28 | 29 | @interface RCWKGConnectionFinderTest : XCTAsyncTestCase 30 | 31 | @property (nonatomic, strong) NSError *lastErrorSeen; 32 | 33 | @property (nonatomic, strong) NSInputStream *inputFound; 34 | @property (nonatomic, strong) NSOutputStream *outputFound; 35 | @end 36 | 37 | @implementation RCWKGConnectionFinderTest 38 | 39 | - (void)testListeningOverNetServices 40 | { 41 | [self prepare]; 42 | 43 | RCWBonjourConnectionFinder *broadcaster = [[RCWBonjourConnectionFinder alloc] initWithIdentifier:@"listenTest"]; 44 | RCWBonjourConnectionFinder *listener = [[RCWBonjourConnectionFinder alloc] initWithIdentifier:@"listenTest"]; 45 | listener.delegate = self; 46 | 47 | [broadcaster startBroadcasting]; 48 | [listener startListening]; 49 | 50 | [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:3]; 51 | 52 | XCTAssertNil(self.lastErrorSeen); 53 | XCTAssertNotNil(self.inputFound); 54 | XCTAssertNotNil(self.outputFound); 55 | } 56 | 57 | - (void)testMustHaveSameIdentifier 58 | { 59 | [self prepare]; 60 | 61 | RCWBonjourConnectionFinder *broadcaster = [[RCWBonjourConnectionFinder alloc] initWithIdentifier:@"one"]; 62 | RCWBonjourConnectionFinder *listener = [[RCWBonjourConnectionFinder alloc] initWithIdentifier:@"two"]; 63 | listener.delegate = self; 64 | 65 | [broadcaster startBroadcasting]; 66 | [listener startListening]; 67 | 68 | [self waitForTimeout:3]; 69 | 70 | XCTAssertNil(self.lastErrorSeen); 71 | XCTAssertNil(self.inputFound); 72 | XCTAssertNil(self.outputFound); 73 | } 74 | 75 | - (void)testFailureToPublish 76 | { 77 | [self prepare]; 78 | 79 | RCWBonjourConnectionFinder *finder = [[RCWBonjourConnectionFinder alloc] initWithIdentifier:@"listenTest"]; 80 | finder.delegate = self; 81 | [finder startBroadcasting]; 82 | 83 | NSNetService *service = [[NSNetService alloc] initWithDomain:@"domain" type:@"type" name:@"name" port:1]; 84 | [finder netService:service 85 | didNotPublish:@{NSNetServicesErrorCode: @(1234), 86 | NSNetServicesErrorDomain: @"some domain"}]; 87 | 88 | [self waitForStatus:kXCTUnitWaitStatusFailure timeout:1]; 89 | 90 | XCTAssertNil(self.inputFound); 91 | XCTAssertNil(self.outputFound); 92 | XCTAssertEqual(self.lastErrorSeen.code, 1234); 93 | XCTAssertEqualObjects(self.lastErrorSeen.domain, @"some domain"); 94 | } 95 | 96 | 97 | #pragma mark - RCWBonjourConnectionFinderDelegate methods 98 | 99 | - (void)finder:(RCWBonjourConnectionFinder *)finder connectedWithInput:(NSInputStream *)input output:(NSOutputStream *)output 100 | { 101 | self.inputFound = input; 102 | self.outputFound = output; 103 | [self notify:kXCTUnitWaitStatusSuccess]; 104 | } 105 | 106 | - (void)finder:(RCWBonjourConnectionFinder *)finder didError:(NSError *)error 107 | { 108 | self.lastErrorSeen = error; 109 | [self notify:kXCTUnitWaitStatusFailure]; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/RCWKGConnectionTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGConnectionTest.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "XCTAsyncTestCase.h" 23 | #import "RCWBonjourConnection.h" 24 | 25 | @interface RCWKGConnectionTest : XCTAsyncTestCase 26 | 27 | @property (nonatomic, strong) RCWBonjourConnection *serverConnection; 28 | @property (nonatomic, strong) RCWBonjourConnection *clientConnection; 29 | 30 | @property (nonatomic, strong) NSError *lastErrorSeenByClient; 31 | @property (nonatomic, strong) NSError *lastErrorSeenByServer; 32 | @property (nonatomic, strong) NSString *lastStringReceivedByClient; 33 | @property (nonatomic, strong) NSString *lastStringReceivedByServer; 34 | @end 35 | 36 | @implementation RCWKGConnectionTest 37 | 38 | - (void)setUp 39 | { 40 | [super setUp]; 41 | self.serverConnection = [[RCWBonjourConnection alloc] initWithIdentifier:@"connectionIntegrationTest"]; 42 | self.serverConnection.delegate = self; 43 | self.clientConnection = [[RCWBonjourConnection alloc] initWithIdentifier:@"connectionIntegrationTest"]; 44 | self.clientConnection.delegate = self; 45 | } 46 | 47 | - (void)testIntegrationBetweenClientAndServerConnection 48 | { 49 | [self.serverConnection shareConnection]; 50 | [self.clientConnection findConnection]; 51 | 52 | [self prepare]; 53 | [self.serverConnection sendData:[@"from server" dataUsingEncoding:NSUTF8StringEncoding]]; 54 | [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:10]; 55 | XCTAssertEqualObjects(self.lastStringReceivedByClient, @"from server"); 56 | 57 | [self prepare]; 58 | [self.clientConnection sendData:[@"from client" dataUsingEncoding:NSUTF8StringEncoding]]; 59 | [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:10]; 60 | XCTAssertEqualObjects(self.lastStringReceivedByServer, @"from client"); 61 | } 62 | 63 | #pragma mark - Delegate Methods 64 | 65 | - (void)connectionDidConnect:(RCWBonjourConnection *)connection 66 | { 67 | // noop 68 | } 69 | 70 | - (void)connection:(RCWBonjourConnection *)connection failedWith:(NSError *)error 71 | { 72 | if (connection == self.clientConnection) { 73 | self.lastErrorSeenByClient = error; 74 | } else if (connection == self.serverConnection) { 75 | self.lastErrorSeenByServer = error; 76 | } 77 | [self notify:kXCTUnitWaitStatusFailure]; 78 | } 79 | 80 | - (void)connection:(RCWBonjourConnection *)connection didReceiveData:(NSData *)data 81 | { 82 | NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 83 | if (connection == self.clientConnection) { 84 | self.lastStringReceivedByClient = string; 85 | } else if (connection == self.serverConnection) { 86 | self.lastStringReceivedByServer = string; 87 | } 88 | [self notify:kXCTUnitWaitStatusSuccess]; 89 | } 90 | 91 | // test that connections can't find each other with different identifiers 92 | // test that connections find each other even with other connections open 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/RCWKGDataStreamCollectorEmitterTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGDataStreamCollectorEmitterTest.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWDataStreamEmitter.h" 23 | #import "RCWDataStreamCollector.h" 24 | 25 | @interface RCWKGDataStreamCollectorEmitterTest : XCTestCase 26 | @property (nonatomic, strong) RCWDataStreamEmitter *emitter; 27 | @property (nonatomic, strong) RCWDataStreamCollector *collector; 28 | @end 29 | 30 | @implementation RCWKGDataStreamCollectorEmitterTest 31 | 32 | - (void)setUp 33 | { 34 | [super setUp]; 35 | self.emitter = [[RCWDataStreamEmitter alloc] init]; 36 | self.collector = [[RCWDataStreamCollector alloc] init]; 37 | } 38 | 39 | - (void)testEmittingDataWithLengthPreamble 40 | { 41 | NSData *data = [@"get me?" dataUsingEncoding:NSUTF8StringEncoding]; 42 | [self.emitter emitData:data]; 43 | 44 | uint8_t buffer[200]; 45 | memset(buffer, 0, 200); 46 | 47 | NSInteger count = [self.emitter attemptToRead:buffer maxLength:200]; 48 | [self.emitter markActualByteCountRead:count]; 49 | XCTAssertEqual(count, (NSInteger)9); 50 | XCTAssertEqual(0, strcmp("7", (char *)buffer)); 51 | XCTAssertEqual(0, strcmp("get me?", (char *)buffer + 2)); 52 | } 53 | 54 | - (void)testEmittingLargeDataWithLengthPreamble 55 | { 56 | NSMutableString *str = [NSMutableString string]; 57 | for (int i = 0; i < 10000; i++) { 58 | if (arc4random_uniform(1)) { 59 | [str appendString:@"a"]; 60 | } else { 61 | [str appendString:@"b"]; 62 | } 63 | } 64 | NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; 65 | 66 | [self.emitter emitData:data]; 67 | 68 | char buffer[10]; 69 | memset(buffer, 0, 10); 70 | 71 | NSInteger count = [self.emitter attemptToRead:(uint8_t *)buffer maxLength:6]; 72 | [self.emitter markActualByteCountRead:count]; 73 | XCTAssertEqual(count, 6); 74 | XCTAssertEqual(buffer[5], 0); 75 | XCTAssertEqual(0, strcmp("10000", buffer)); 76 | 77 | NSMutableData *outputData = [NSMutableData data]; 78 | size_t const bufsize = 97; 79 | uint8_t readBuffer[bufsize]; 80 | NSInteger readCount = 0; 81 | 82 | do { 83 | memset(readBuffer, 0, bufsize); 84 | readCount = [self.emitter attemptToRead:readBuffer maxLength:bufsize]; 85 | [outputData appendBytes:readBuffer length:readCount]; 86 | [self.emitter markActualByteCountRead:readCount]; 87 | } while (readCount > 0); 88 | 89 | NSString *output = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; 90 | XCTAssertEqualObjects(str, output); 91 | } 92 | 93 | - (void)testCollectingAnObject 94 | { 95 | NSData *data = [@"round the rock" dataUsingEncoding:NSUTF8StringEncoding]; 96 | [self.emitter emitData:data]; 97 | 98 | uint8_t buffer[50]; 99 | memset(buffer, 0, 50); 100 | 101 | __block NSString *output = nil; 102 | self.collector.callback = ^(NSData *data) { 103 | output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 104 | }; 105 | NSError *error = nil; 106 | NSInteger count = [self.emitter attemptToRead:buffer maxLength:50]; 107 | [self.emitter markActualByteCountRead:count]; 108 | XCTAssertEqual(count, [self.collector write:buffer length:count error:&error]); 109 | XCTAssertNil(error); 110 | XCTAssertEqualObjects(output, @"round the rock"); 111 | } 112 | 113 | - (void)testCollectingAnObjectOverTime 114 | { 115 | NSData *data = [@"round the rock and stuff" dataUsingEncoding:NSUTF8StringEncoding]; 116 | [self.emitter emitData:data]; 117 | 118 | // Keep this *really* short so we're also testing the preamble collection code 119 | size_t const readEachLoop = 1; 120 | uint8_t buffer[readEachLoop]; 121 | 122 | __block NSString *output = nil; 123 | self.collector.callback = ^(NSData *data) { 124 | output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 125 | }; 126 | 127 | NSInteger count = 0; 128 | do { 129 | NSError *error = nil; 130 | memset(buffer, 0, readEachLoop); 131 | count = [self.emitter attemptToRead:buffer maxLength:readEachLoop]; 132 | [self.emitter markActualByteCountRead:count]; 133 | XCTAssertEqual(count, [self.collector write:buffer length:count error:&error]); 134 | XCTAssertNil(error); 135 | } while (count > 0); 136 | 137 | XCTAssertEqualObjects(output, @"round the rock and stuff"); 138 | } 139 | 140 | - (void)testCollectingTwoObjectsAtOnce 141 | { 142 | NSData *obj1 = [@"object 1" dataUsingEncoding:NSUTF8StringEncoding]; 143 | NSData *obj2 = [@"object 2" dataUsingEncoding:NSUTF8StringEncoding]; 144 | 145 | [self.emitter emitData:obj1]; 146 | [self.emitter emitData:obj2]; 147 | 148 | // Keep this *really* short so we're also testing the preamble collection code 149 | size_t const readEachLoop = 1; 150 | uint8_t buffer[readEachLoop]; 151 | 152 | NSMutableArray *collectedOutput = [NSMutableArray array]; 153 | self.collector.callback = ^(NSData *data) { 154 | NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 155 | [collectedOutput addObject:output]; 156 | }; 157 | 158 | NSInteger count = 0; 159 | do { 160 | NSError *error = nil; 161 | memset(buffer, 0, readEachLoop); 162 | count = [self.emitter attemptToRead:buffer maxLength:readEachLoop]; 163 | NSInteger count = [self.emitter attemptToRead:buffer maxLength:readEachLoop]; 164 | [self.emitter markActualByteCountRead:count]; 165 | XCTAssertEqual(count, [self.collector write:buffer length:count error:&error]); 166 | XCTAssertNil(error); 167 | } while (count > 0); 168 | 169 | XCTAssertEqualObjects(collectedOutput[0], @"object 1"); 170 | XCTAssertEqualObjects(collectedOutput[1], @"object 2"); 171 | } 172 | 173 | - (void)testWithInvalidPreamble 174 | { 175 | __block BOOL called = NO; 176 | self.collector.callback = ^(NSData *data) { 177 | called = YES; 178 | }; 179 | 180 | NSError *error = nil; 181 | [self.collector write:(uint8_t *)"abc" length:4 error:&error]; 182 | 183 | XCTAssertNotNil(error); 184 | XCTAssertEqualObjects(@"Could not read length preamble from data packet.", error.localizedDescription); 185 | 186 | XCTAssertFalse(called, @"Should never call back."); 187 | } 188 | 189 | @end 190 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/RCWKGProtocolCommandTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGProtocolCommandTest.m 3 | // KeyGripNet 4 | // 5 | // KeyGrip - Remote pasteboard and presentation note tool 6 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | // 21 | 22 | #import 23 | #import "RCWKGProtocolCommand.h" 24 | 25 | @interface RCWKGProtocolCommandTest : XCTestCase 26 | 27 | @end 28 | 29 | @implementation RCWKGProtocolCommandTest 30 | 31 | - (void)testConvertingToAndFromData 32 | { 33 | RCWKGProtocolCommand *command = [RCWKGProtocolCommand commandWithName:@"something"]; 34 | 35 | NSData *data = [command convertToData]; 36 | 37 | NSError *error = nil; 38 | RCWKGProtocolCommand *command2 = [RCWKGProtocolCommand commandFromData:data error:&error]; 39 | XCTAssert(command2); 40 | XCTAssertNil(error); 41 | XCTAssertEqualObjects(command2.commandName, @"something"); 42 | } 43 | 44 | - (void)testLoadingBadData 45 | { 46 | NSData *data = [@"bad data" dataUsingEncoding:NSUTF8StringEncoding]; 47 | NSError *error = nil; 48 | XCTAssertNil([RCWKGProtocolCommand commandFromData:data error:&error]); 49 | XCTAssertNotNil(error); 50 | XCTAssertEqualObjects(@"Unparsable data format.", error.localizedDescription); 51 | XCTAssertEqual(error.code, RCWKGProtocolUnparsable); 52 | } 53 | 54 | - (void)testImportingWrongType 55 | { 56 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@[@1]]; 57 | NSError *error = nil; 58 | XCTAssertNil([RCWKGProtocolCommand commandFromData:data error:&error]); 59 | XCTAssertNotNil(error); 60 | XCTAssertEqualObjects(@"Unrecognized data format.", error.localizedDescription); 61 | XCTAssertEqual(error.code, RCWKGProtocolUnrecognized); 62 | } 63 | 64 | - (void)testImportingDictionaryWithoutCommandMetadata 65 | { 66 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@{}]; 67 | NSError *error = nil; 68 | XCTAssertNil([RCWKGProtocolCommand commandFromData:data error:&error]); 69 | XCTAssertNotNil(error); 70 | XCTAssertEqualObjects(@"Missing command metadata.", error.localizedDescription); 71 | XCTAssertEqual(error.code, RCWKGProtocolMissingMetadata); 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/RCWKGSocketConnectionTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGSocketConnectionTest.m 3 | // KeyGripNet 4 | // 5 | // KeyGrip - Remote pasteboard and presentation note tool 6 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | // 21 | 22 | #import 23 | #import "XCTAsyncTestCase.h" 24 | #import "RCWSocketServerConnection.h" 25 | #import "RCWSocketClientConnection.h" 26 | 27 | @interface RCWKGSocketConnectionTest : XCTAsyncTestCase 28 | 29 | @property (nonatomic, strong) RCWSocketServerConnection *serverConnection; 30 | @property (nonatomic, strong) RCWSocketClientConnection *clientConnection; 31 | 32 | @property (nonatomic, strong) NSError *lastErrorSeenByClient; 33 | @property (nonatomic, strong) NSError *lastErrorSeenByServer; 34 | @property (nonatomic, strong) NSString *lastStringReceivedByClient; 35 | @property (nonatomic, strong) NSString *lastStringReceivedByServer; 36 | @property (nonatomic) BOOL serverDidConnect, clientDidConnect; 37 | @end 38 | 39 | @implementation RCWKGSocketConnectionTest 40 | 41 | - (void)setUp 42 | { 43 | [super setUp]; 44 | self.serverConnection = [[RCWSocketServerConnection alloc] init]; 45 | [self.serverConnection bindToPort:kRCWKGSocketServerConnectionAnyPort]; 46 | self.serverConnection.delegate = self; 47 | 48 | self.clientConnection = [[RCWSocketClientConnection alloc] initClientWithHost:@"127.0.0.1" port:self.serverConnection.port]; 49 | self.clientConnection.delegate = self; 50 | } 51 | 52 | - (void)tearDown 53 | { 54 | [super tearDown]; 55 | [self.clientConnection stop]; 56 | [self.serverConnection stop]; 57 | } 58 | 59 | - (void)testIntegrationBetweenClientAndServerConnection 60 | { 61 | [self.serverConnection startListening]; 62 | [self.clientConnection attemptToConnect]; 63 | 64 | [self prepare]; 65 | [self.serverConnection sendData:[@"from server" dataUsingEncoding:NSUTF8StringEncoding]]; 66 | [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:10]; 67 | XCTAssertEqualObjects(self.lastStringReceivedByClient, @"from server"); 68 | 69 | [self prepare]; 70 | [self.clientConnection sendData:[@"from client" dataUsingEncoding:NSUTF8StringEncoding]]; 71 | [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:10]; 72 | XCTAssertEqualObjects(self.lastStringReceivedByServer, @"from client"); 73 | 74 | XCTAssert(self.serverDidConnect); 75 | XCTAssert(self.clientDidConnect); 76 | } 77 | 78 | #pragma mark - Delegate Methods 79 | 80 | - (void)connectionDidConnect:(id)connection 81 | { 82 | if (connection == self.clientConnection) { 83 | self.clientDidConnect = YES; 84 | } else if (connection == self.serverConnection) { 85 | self.serverDidConnect = YES; 86 | } 87 | } 88 | 89 | - (void)connection:(id)connection failedWith:(NSError *)error 90 | { 91 | if (connection == self.clientConnection) { 92 | self.lastErrorSeenByClient = error; 93 | } else if (connection == self.serverConnection) { 94 | self.lastErrorSeenByServer = error; 95 | } 96 | [self notify:kXCTUnitWaitStatusFailure]; 97 | } 98 | 99 | - (void)connection:(id)connection didReceiveData:(NSData *)data 100 | { 101 | NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 102 | if (connection == self.clientConnection) { 103 | self.lastStringReceivedByClient = string; 104 | } else if (connection == self.serverConnection) { 105 | self.lastStringReceivedByServer = string; 106 | } 107 | [self notify:kXCTUnitWaitStatusSuccess]; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/RCWXCTAsyncHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWXCTAsyncHelpers.h 3 | // KeyGripNet 4 | // 5 | // KeyGrip - Remote pasteboard and presentation note tool 6 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | // 21 | 22 | #import 23 | 24 | #define StartAsync() self.asyncCount++ 25 | #define FinishAsync() self.asyncCount-- 26 | 27 | #define WaitUntilAsyncCompletes(timeout) WaitWhile((self.asyncCount > 0), (timeout)) 28 | 29 | #define WaitWhile(condition, timeout) \ 30 | do { \ 31 | NSTimeInterval start = [[NSDate date] timeIntervalSinceReferenceDate]; \ 32 | while(condition) { \ 33 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ 34 | NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate]; \ 35 | if (now - start > (timeout)) { \ 36 | XCTFail(@"Timed out waiting for asynchronous test to finish."); \ 37 | break; \ 38 | } \ 39 | } \ 40 | } while(0) 41 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/TestCaseStreamConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestCaseStreamConnection.h 3 | // KeyGripNet 4 | // 5 | // KeyGrip - Remote pasteboard and presentation note tool 6 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with this program. If not, see . 20 | // 21 | 22 | #import 23 | #import "RCWStreamConnection.h" 24 | 25 | @interface TestCaseStreamConnection : NSObject 26 | @property (nonatomic, weak) TestCaseStreamConnection *otherConnection; 27 | 28 | - (void)start; 29 | @end 30 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/TestCaseStreamConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestCaseStreamConnection.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "TestCaseStreamConnection.h" 22 | 23 | @implementation TestCaseStreamConnection { 24 | BOOL _stopped; 25 | } 26 | 27 | @synthesize delegate=_delegate; 28 | 29 | - (void)sendData:(NSData *)data 30 | { 31 | NSAssert(!_stopped, @"This connection has been stopped. It cannot be used. Build a new one."); 32 | [self.otherConnection receiveData:data]; 33 | } 34 | 35 | - (void)receiveData:(NSData *)data 36 | { 37 | NSAssert(!_stopped, @"This connection has been stopped. It cannot be used. Build a new one."); 38 | [self.delegate connection:self didReceiveData:data]; 39 | } 40 | 41 | - (void)start 42 | { 43 | [self.delegate connectionDidConnect:self]; 44 | } 45 | 46 | - (void)stop 47 | { 48 | _stopped = YES; 49 | } 50 | 51 | - (BOOL)isConnected 52 | { 53 | return !_stopped && self.otherConnection != nil; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/XCTAsyncTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTAsyncTestCase.h 3 | // Modified by Sveinung Kval Bakken on 16/10/13 4 | // to serve as a drop-in replacement for GHAsyncTestCase using XCTest 5 | // Original copyright notice below: 6 | // 7 | // GHAsyncTestCase.h 8 | // GHUnit 9 | // 10 | // Created by Gabriel Handford on 4/8/09. 11 | // Copyright 2009. All rights reserved. 12 | // 13 | // Permission is hereby granted, free of charge, to any person 14 | // obtaining a copy of this software and associated documentation 15 | // files (the "Software"), to deal in the Software without 16 | // restriction, including without limitation the rights to use, 17 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | // copies of the Software, and to permit persons to whom the 19 | // Software is furnished to do so, subject to the following 20 | // conditions: 21 | // 22 | // The above copyright notice and this permission notice shall be 23 | // included in all copies or substantial portions of the Software. 24 | // 25 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | // OTHER DEALINGS IN THE SOFTWARE. 33 | // 34 | 35 | #import 36 | 37 | /*! 38 | Common wait statuses to use with waitForStatus:timeout:. 39 | */ 40 | enum { 41 | kXCTUnitWaitStatusUnknown = 0, // Unknown wait status 42 | kXCTUnitWaitStatusSuccess, // Wait status success 43 | kXCTUnitWaitStatusFailure, // Wait status failure 44 | kXCTUnitWaitStatusCancelled // Wait status cancelled 45 | }; 46 | 47 | /*! 48 | Asynchronous test case with wait and notify. 49 | 50 | If notify occurs before wait has started (if it was a synchronous call), this test 51 | case will still work. 52 | 53 | Be sure to call prepare before the asynchronous method (otherwise an exception will raise). 54 | 55 | @interface MyAsyncTest : GHAsyncTestCase { } 56 | @end 57 | 58 | @implementation MyAsyncTest 59 | 60 | - (void)testSuccess { 61 | // Prepare for asynchronous call 62 | [self prepare]; 63 | 64 | // Do asynchronous task here 65 | [self performSelector:@selector(_succeed) withObject:nil afterDelay:0.1]; 66 | 67 | // Wait for notify 68 | [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:1.0]; 69 | } 70 | 71 | - (void)_succeed { 72 | // Notify the wait. Notice the forSelector points to the test above. 73 | // This is so that stray notifies don't error or falsely succeed other tests. 74 | // To ignore the check, forSelector can be NULL. 75 | [self notify:kXCTUnitWaitStatusSuccess forSelector:@selector(testSuccess)]; 76 | } 77 | 78 | @end 79 | 80 | */ 81 | @interface XCTAsyncTestCase : XCTestCase { 82 | 83 | NSInteger waitForStatus_; 84 | NSInteger notifiedStatus_; 85 | 86 | BOOL prepared_; // Whether prepared was called before waitForStatus:timeout: 87 | NSRecursiveLock *lock_; // Lock to synchronize on 88 | SEL waitSelector_; // The selector we are waiting on 89 | 90 | NSArray *_runLoopModes; 91 | } 92 | 93 | /*! 94 | Run loop modes to run while waiting; 95 | Defaults to NSDefaultRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode 96 | */ 97 | @property (strong, nonatomic) NSArray *runLoopModes; 98 | 99 | /*! 100 | Prepare before calling the asynchronous method. 101 | */ 102 | - (void)prepare; 103 | 104 | /*! 105 | Prepare and specify the selector we will use in notify. 106 | 107 | @param selector Selector 108 | */ 109 | - (void)prepare:(SEL)selector; 110 | 111 | /*! 112 | Wait for notification of status or timeout. 113 | 114 | Be sure to prepare before calling your asynchronous method. 115 | For example, 116 | 117 | - (void)testFoo { 118 | [self prepare]; 119 | 120 | // Do asynchronous task here 121 | 122 | [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:1.0]; 123 | } 124 | 125 | @param status kXCTUnitWaitStatusSuccess, kXCTUnitWaitStatusFailure or custom status 126 | @param timeout Timeout in seconds 127 | */ 128 | - (void)waitForStatus:(NSInteger)status timeout:(NSTimeInterval)timeout; 129 | 130 | /*! 131 | @param status kXCTUnitWaitStatusSuccess, kXCTUnitWaitStatusFailure or custom status 132 | @param timeout Timeout in seconds 133 | @deprecated Use waitForTimeout: 134 | */ 135 | - (void)waitFor:(NSInteger)status timeout:(NSTimeInterval)timeout; 136 | 137 | /*! 138 | Wait for timeout to occur. 139 | Fails if we did _NOT_ timeout. 140 | 141 | @param timeout Timeout 142 | */ 143 | - (void)waitForTimeout:(NSTimeInterval)timeout; 144 | 145 | /*! 146 | Notify waiting of status for test selector. 147 | 148 | @param status Status, for example, kXCTUnitWaitStatusSuccess 149 | @param selector If not NULL, then will verify this selector is where we are waiting. This prevents stray asynchronous callbacks to fail a later test. 150 | */ 151 | - (void)notify:(NSInteger)status forSelector:(SEL)selector; 152 | 153 | /*! 154 | Notify waiting of status for any selector. 155 | 156 | @param status Status, for example, kXCTUnitWaitStatusSuccess 157 | */ 158 | - (void)notify:(NSInteger)status; 159 | 160 | /*! 161 | Run the run loops for the specified interval. 162 | 163 | @param interval Interval 164 | @author Adapted from Robert Palmer, pauseForTimeout 165 | */ 166 | - (void)runForInterval:(NSTimeInterval)interval; 167 | 168 | @end -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/XCTAsyncTestCase.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCTAsyncTestCase.m 3 | // Modified by Sveinung Kval Bakken on 16/10/13 4 | // to serve as a drop-in replacement for GHAsyncTestCase using XCTest 5 | // Original copyright notice below: 6 | // 7 | // GHAsyncTestCase.m 8 | // GHUnit 9 | // 10 | // Created by Gabriel Handford on 4/8/09. 11 | // Copyright 2009. All rights reserved. 12 | // 13 | // Permission is hereby granted, free of charge, to any person 14 | // obtaining a copy of this software and associated documentation 15 | // files (the "Software"), to deal in the Software without 16 | // restriction, including without limitation the rights to use, 17 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | // copies of the Software, and to permit persons to whom the 19 | // Software is furnished to do so, subject to the following 20 | // conditions: 21 | // 22 | // The above copyright notice and this permission notice shall be 23 | // included in all copies or substantial portions of the Software. 24 | // 25 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | // OTHER DEALINGS IN THE SOFTWARE. 33 | // 34 | 35 | #import "XCTAsyncTestCase.h" 36 | #import 37 | 38 | typedef enum { 39 | kXCTUnitAsyncErrorNone, 40 | kXCTUnitAsyncErrorUnprepared, 41 | kXCTUnitAsyncErrorTimedOut, 42 | kXCTUnitAsyncErrorInvalidStatus 43 | } XCTUnitAsyncError; 44 | 45 | @implementation XCTAsyncTestCase 46 | 47 | @synthesize runLoopModes=_runLoopModes; 48 | 49 | 50 | // Internal GHUnit setUp 51 | - (void)setUp { 52 | [super setUp]; 53 | lock_ = [[NSRecursiveLock alloc] init]; 54 | prepared_ = NO; 55 | notifiedStatus_ = kXCTUnitWaitStatusUnknown; 56 | } 57 | 58 | // Internal GHUnit tear down 59 | - (void)tearDown { 60 | [super tearDown]; 61 | waitSelector_ = NULL; 62 | if (prepared_) [lock_ unlock]; // If we prepared but never waited we need to unlock 63 | lock_ = nil; 64 | } 65 | 66 | - (void)prepare { 67 | [self prepare:self.selector]; 68 | } 69 | 70 | - (void)prepare:(SEL)selector { 71 | [lock_ lock]; 72 | prepared_ = YES; 73 | waitSelector_ = selector; 74 | notifiedStatus_ = kXCTUnitWaitStatusUnknown; 75 | } 76 | 77 | - (XCTUnitAsyncError)_waitFor:(NSInteger)status timeout:(NSTimeInterval)timeout { 78 | if (!prepared_) { 79 | return kXCTUnitAsyncErrorUnprepared; 80 | } 81 | prepared_ = NO; 82 | 83 | waitForStatus_ = status; 84 | 85 | if (!_runLoopModes) 86 | _runLoopModes = [NSArray arrayWithObjects:NSDefaultRunLoopMode, NSRunLoopCommonModes, nil]; 87 | 88 | NSTimeInterval checkEveryInterval = 0.05; 89 | NSDate *runUntilDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; 90 | BOOL timedOut = NO; 91 | NSUInteger runIndex = 0; 92 | while(notifiedStatus_ == kXCTUnitWaitStatusUnknown) { 93 | NSString *mode = [_runLoopModes objectAtIndex:(runIndex++ % [_runLoopModes count])]; 94 | 95 | [lock_ unlock]; 96 | @autoreleasepool { 97 | if (!mode || ![[NSRunLoop currentRunLoop] runMode:mode beforeDate:[NSDate dateWithTimeIntervalSinceNow:checkEveryInterval]]) 98 | // If there were no run loop sources or timers then we should sleep for the interval 99 | [NSThread sleepForTimeInterval:checkEveryInterval]; 100 | } 101 | [lock_ lock]; 102 | 103 | // If current date is after the run until date 104 | if ([runUntilDate compare:[NSDate date]] == NSOrderedAscending) { 105 | timedOut = YES; 106 | break; 107 | } 108 | } 109 | [lock_ unlock]; 110 | 111 | if (timedOut) { 112 | return kXCTUnitAsyncErrorTimedOut; 113 | } else if (waitForStatus_ != notifiedStatus_) { 114 | return kXCTUnitAsyncErrorInvalidStatus; 115 | } 116 | 117 | return kXCTUnitAsyncErrorNone; 118 | } 119 | 120 | - (void)waitFor:(NSInteger)status timeout:(NSTimeInterval)timeout { 121 | [NSException raise:NSDestinationInvalidException format:@"Deprecated; Use waitForStatus:timeout:"]; 122 | } 123 | 124 | - (void)waitForStatus:(NSInteger)status timeout:(NSTimeInterval)timeout { 125 | XCTUnitAsyncError error = [self _waitFor:status timeout:timeout]; 126 | if (error == kXCTUnitAsyncErrorTimedOut) { 127 | XCTFail(@"Request timed out"); 128 | } else if (error == kXCTUnitAsyncErrorInvalidStatus) { 129 | XCTFail(@"Request finished with the wrong status: %ld != %ld", status, notifiedStatus_); 130 | } else if (error == kXCTUnitAsyncErrorUnprepared) { 131 | XCTFail(@"Call prepare before calling asynchronous method and waitForStatus:timeout:"); 132 | } 133 | } 134 | 135 | - (void)waitForTimeout:(NSTimeInterval)timeout { 136 | XCTUnitAsyncError error = [self _waitFor:-1 timeout:timeout]; 137 | if (error != kXCTUnitAsyncErrorTimedOut) { 138 | XCTFail(@"Request should have timed out"); 139 | } 140 | } 141 | 142 | // Similar to _waitFor:timeout: but just runs the loops 143 | // From Robert Palmer, pauseForTimeout 144 | - (void)runForInterval:(NSTimeInterval)interval { 145 | NSTimeInterval checkEveryInterval = 0.05; 146 | NSDate *runUntilDate = [NSDate dateWithTimeIntervalSinceNow:interval]; 147 | 148 | if (!_runLoopModes) 149 | _runLoopModes = [NSArray arrayWithObjects:NSDefaultRunLoopMode, NSRunLoopCommonModes, nil]; 150 | 151 | NSUInteger runIndex = 0; 152 | 153 | while ([runUntilDate compare:[NSDate dateWithTimeIntervalSinceNow:0]] == NSOrderedDescending) { 154 | NSString *mode = [_runLoopModes objectAtIndex:(runIndex++ % [_runLoopModes count])]; 155 | 156 | [lock_ unlock]; 157 | @autoreleasepool { 158 | if (!mode || ![[NSRunLoop currentRunLoop] runMode:mode beforeDate:[NSDate dateWithTimeIntervalSinceNow:checkEveryInterval]]) 159 | // If there were no run loop sources or timers then we should sleep for the interval 160 | [NSThread sleepForTimeInterval:checkEveryInterval]; 161 | } 162 | [lock_ lock]; 163 | } 164 | } 165 | 166 | - (void)notify:(NSInteger)status { 167 | [self notify:status forSelector:NULL]; 168 | } 169 | 170 | - (void)notify:(NSInteger)status forSelector:(SEL)selector { 171 | // Note: If this is called from a stray thread or delayed call, we may not be in an autorelease pool 172 | @autoreleasepool { 173 | 174 | // Make sure the notify is for the currently waiting test 175 | if (selector != NULL && !sel_isEqual(waitSelector_, selector)) { 176 | NSLog(@"Warning: Notified from %@ but we were waiting for %@", NSStringFromSelector(selector), NSStringFromSelector(waitSelector_)); 177 | } else { 178 | [lock_ lock]; 179 | notifiedStatus_ = status; 180 | [lock_ unlock]; 181 | } 182 | 183 | } 184 | } 185 | 186 | @end 187 | -------------------------------------------------------------------------------- /KeyGripNet/KeyGripNetTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KeyGrip 2 | 3 | KeyGrip is a simple remote pasteboard and presentation notes app. You write your notes in [Markdown][md], drop the file into the Mac server application, fire up the companion iOS app on the same Wifi network, and *boom*. You see your presentation notes and can tap on text snippets to have them automatically transfered to the Mac's pasteboard. 4 | 5 | [md]: https://daringfireball.net/projects/markdown/ 6 | 7 | ![Demo GIF](demo.gif) 8 | 9 | 10 | ## Why Would I Use This? 11 | 12 | Giving talks with live code demos is useful but hard to do for two reasons: Typing correctly in real time is hard. And telling a story with your demo is also hard. 13 | 14 | KeyGrip helps by being a remote pasteboard on an iOS device and also a collection of presentation notes. You can use the notes to keep focused on the story you were trying to tell with the demonstration, and you can tap on the blocks of text to populate the Mac's pasteboard and simply paste in the next step of the demonstration. No live typing means fewer hiccups on the fly. Think of this as a live performance tool. 15 | 16 | 17 | ## Installation 18 | 19 | There are two pieces, the Mac server app and the iOS client app. 20 | 21 | You can [download the KeyGripServer as a signed Mac application][app] ready to go. Alas, we don't have a way to easily distribute the iOS app yet. Follow the instructions below for building the client iOS app. 22 | 23 | [app]: https://github.com/rubbercitywizards/KeyGrip/releases/download/v1.0/KeyGripServer-1.0.zip 24 | 25 | ### Building It Yourself 26 | 27 | You'll need Xcode 5.1, a Mac running Mavericks and an iOS 7.1 device in order to use this. In order to be able to build and install the app on your iOS device, you'll need to have a paid Apple developer account. 28 | 29 | #### iOS Client App 30 | 31 | Open `client/KeyGrip.xcodeproj`. Then use the usual process to build and run to install on your attached iOS device. Once the app runs, tap the `Settings` button and make sure you type in the same Bonjour identifier as you typed in the Mac server app. 32 | 33 | #### Mac Server App 34 | 35 | Open `server/KeyGripServer.xcodeproj`. Then choose the "Install KeyGripServer" scheme from the scheme chooser. Simply build the project and this scheme will copy the `KeyGripServer.app` bundle ready to go into the `/Applications` directory. (Because the app is sandboxed you'll need to either sign it with your developer account, or turn off the sandboxing entitlement. Or just [download the pre-built app][app].) 36 | 37 | ![Choosing A Scheme](scheme.gif) 38 | 39 | When you double click to run the app, you'll want to type in a Bonjour identifier into the text field. You'll use this same identifier on the iOS client. 40 | 41 | 42 | ## How To Use 43 | 44 | Write a [Markdown][md] document and make sure it has a `.txt`, `.md`, or `.markdown` extension. Indent any code block or text snippet you want to paste with four spaces, like the following: 45 | 46 | This is a "code block" in Markdown. It will be tappable in the iOS client. 47 | 48 | Launch the KeyGripServer app. Then drag and drop the file onto the application window. You're now serving the file. 49 | 50 | Launch the companion iOS client application. Make sure both the client and server are on the same Wifi network and using the same Bonjour identifier. They will immediately connect and you'll see the HTML version of your document in the iOS client. 51 | 52 | Now, tap on any indented code block. You'll see it appear in the preview pane of the server application and the text is now on your Mac's pasteboard. 53 | 54 | If you edit your document and save your changes the server app will notice the changes, reload the document, and send it over to the client automatically. 55 | 56 | Enjoy! 57 | 58 | 59 | ## Contributing 60 | 61 | Feel free to fork the project and submit a pull request with bug fixes. If you have architecture changes or new feature ideas in mind, then please open an issue to discuss it first. That gives some time to weigh in on options before those pull requests start coming in. 62 | 63 | 64 | ## Licensing 65 | 66 | KeyGrip is licensed under GPLv3. Please see the LICENSE file for more info. 67 | 68 | Short answer: feel free to fork the project but you can't bundle any of the source in your own proprietary applications. 69 | 70 | 71 | ## Donations 72 | 73 | ... TODO 74 | 75 | 76 | ## Special Thanks 77 | 78 | Thanks to Gabriel Handford and Sveinung Kval Bakken for their work on the `XCTAsyncTestCase` class used in the network protocol tests. Check the license on that source file for more info. 79 | 80 | Thanks to John Gruber's [original][md] `markdown.pl` script which is bundled with the server to generate the HTML for display on the client. 81 | 82 | [Derek Briggs][db] from [Neo Innovation](http://www.neo.com) did an awesome job on the icon. Give him some love and a hug. 83 | 84 | [db]: http://twitter.com/PixelJanitor 85 | 86 | Thanks also to the early beta testers! 87 | 88 | - [Delisa Mason](http://twitter.com/kattrali) 89 | - [Joseph DeCarlo](https://twitter.com/jdecarlo) 90 | - [Brandon Alexander](http://twitter.com/balexander) 91 | - [Brian Hardy](https://twitter.com/lyricsboy) 92 | - [Brian Partridge](https://twitter.com/brianpartridge) 93 | - [Matthew Morey](https://twitter.com/xzolian) 94 | - Yukio Goto 95 | 96 | -------------------------------------------------------------------------------- /client/KeyGrip.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/KeyGrip.xcodeproj/project.xcworkspace/xcshareddata/KeyGrip.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | BD3587E2-E283-482E-AB93-75F9D370318F 9 | IDESourceControlProjectName 10 | KeyGrip 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | BFEAAE34-347C-4582-A58E-22690222D7B9 14 | ssh://github.com/rubbercitywizards/KeyGrip.git 15 | 16 | IDESourceControlProjectPath 17 | client/KeyGrip.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | BFEAAE34-347C-4582-A58E-22690222D7B9 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | ssh://github.com/rubbercitywizards/KeyGrip.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | BFEAAE34-347C-4582-A58E-22690222D7B9 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | BFEAAE34-347C-4582-A58E-22690222D7B9 36 | IDESourceControlWCCName 37 | KeyGrip 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /client/KeyGrip.xcodeproj/project.xcworkspace/xcshareddata/StageClip.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 58BFBF02-A3D5-49AF-81E0-5BC243ED79E7 9 | IDESourceControlProjectName 10 | StageClip 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C4207B4C-0CC3-450E-AB24-7D2F54041C4B 14 | ssh://bitbucket.org/rubbercitywizards/key-grip.git 15 | 16 | IDESourceControlProjectPath 17 | client/StageClip.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C4207B4C-0CC3-450E-AB24-7D2F54041C4B 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | ssh://bitbucket.org/rubbercitywizards/key-grip.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | C4207B4C-0CC3-450E-AB24-7D2F54041C4B 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C4207B4C-0CC3-450E-AB24-7D2F54041C4B 36 | IDESourceControlWCCName 37 | KeyGrip 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /client/KeyGrip.xcodeproj/xcshareddata/xcschemes/KeyGrip.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /client/KeyGrip/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "size" : "60x60", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-60@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "idiom" : "ipad", 21 | "size" : "29x29", 22 | "scale" : "1x" 23 | }, 24 | { 25 | "idiom" : "ipad", 26 | "size" : "29x29", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "idiom" : "ipad", 31 | "size" : "40x40", 32 | "scale" : "1x" 33 | }, 34 | { 35 | "idiom" : "ipad", 36 | "size" : "40x40", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "76x76", 41 | "idiom" : "ipad", 42 | "filename" : "Icon-76.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "76x76", 47 | "idiom" : "ipad", 48 | "filename" : "Icon-76@2x.png", 49 | "scale" : "2x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /client/KeyGrip/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/client/KeyGrip/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /client/KeyGrip/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/client/KeyGrip/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /client/KeyGrip/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/client/KeyGrip/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /client/KeyGrip/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /client/KeyGrip/KeyGrip-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.rubbercitywizards.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Storyboard_iPhone 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /client/KeyGrip/KeyGrip-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /client/KeyGrip/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWAppDelegate.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWAppDelegate : UIResponder 24 | 25 | @property (strong, nonatomic) UIWindow *window; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWAppDelegate.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWAppDelegate.h" 22 | 23 | @implementation RCWAppDelegate 24 | 25 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 26 | { 27 | // In honor of Minecraft's default user name 28 | [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"bonjourIdentifier": @"Steve"}]; 29 | 30 | application.idleTimerDisabled = YES; 31 | return YES; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWContentViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWContentViewController.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWContentViewController : UIViewController 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWContentViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWContentViewController.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWContentViewController.h" 23 | #import "RCWKGClientAPI.h" 24 | #import "RCWHealthMeter.h" 25 | #import "RCWGentleErrorNotificationView.h" 26 | #import "RCWBonjourConnection.h" 27 | #import "RCWSocketClientConnection.h" 28 | #import "RCWWebView.h" 29 | 30 | @interface RCWContentViewController () 31 | 32 | 33 | @property (nonatomic, strong) IBOutlet RCWWebView *webView; 34 | @property (nonatomic, strong) RCWKGClientAPI *client; 35 | @property (nonatomic, strong) RCWHealthMeter *meter; 36 | @property (nonatomic, weak) RCWGentleErrorNotificationView *errorView; 37 | 38 | @end 39 | 40 | @implementation RCWContentViewController 41 | 42 | #pragma mark - View Controller Lifecycle 43 | 44 | - (void)viewDidLoad 45 | { 46 | [super viewDidLoad]; 47 | [self setUpHealthMeter]; 48 | } 49 | 50 | - (void)viewDidAppear:(BOOL)animated 51 | { 52 | [super viewDidAppear:animated]; 53 | [self establishConnection]; 54 | } 55 | 56 | - (void)viewWillDisappear:(BOOL)animated 57 | { 58 | [super viewWillDisappear:animated]; 59 | [self.client stop]; 60 | } 61 | 62 | 63 | #pragma - RCWKGClientAPIDelegate methods 64 | 65 | - (void)clientDidConnect:(RCWKGClientAPI *)client 66 | { 67 | [self.meter setHealthy:YES]; 68 | [self refresh:nil]; 69 | } 70 | 71 | - (void)clientReceivedServerPing:(RCWKGClientAPI *)client 72 | { 73 | [self.meter pulseAnimation]; 74 | } 75 | 76 | - (void)client:(RCWKGClientAPI *)client failedWithError:(NSError *)error 77 | { 78 | [self.meter setHealthy:NO]; 79 | [self displayError:error]; 80 | [self establishConnection]; 81 | } 82 | 83 | - (void)client:(RCWKGClientAPI *)client notifiedOfServerError:(NSError *)error 84 | { 85 | [self displayError:error]; 86 | } 87 | 88 | - (void)client:(RCWKGClientAPI *)client receivedScript:(NSString *)html named:(NSString *)filename 89 | { 90 | [self setTitleBarText:filename]; 91 | [self.webView useHTMLScript:html]; 92 | } 93 | 94 | - (void)client:(RCWKGClientAPI *)client notifiedOfPastedTextID:(NSString *)textID 95 | { 96 | [self.webView pastedCodeWithTextID:textID]; 97 | } 98 | 99 | 100 | #pragma mark - RCWWebViewDelegate 101 | 102 | - (void)webView:(RCWWebView *)webView didSelectTextWithID:(NSString *)textID 103 | { 104 | [self.client pasteTextWithID:textID]; 105 | } 106 | 107 | 108 | #pragma mark - Helper Methods 109 | 110 | - (void)setUpHealthMeter 111 | { 112 | self.meter = [[RCWHealthMeter alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; 113 | [self.meter setHealthy:NO]; 114 | UIBarButtonItem *meterItem = [[UIBarButtonItem alloc] initWithCustomView:self.meter]; 115 | 116 | self.navigationItem.leftBarButtonItems = @[self.navigationItem.leftBarButtonItem, meterItem]; 117 | } 118 | 119 | - (void)establishConnection 120 | { 121 | [self.meter setHealthy:NO]; 122 | [self showTitleBarSpinner]; 123 | 124 | [self.client stop]; 125 | 126 | NSString *identifier = [[NSUserDefaults standardUserDefaults] stringForKey:@"bonjourIdentifier"]; 127 | RCWBonjourConnection *connection = [[RCWBonjourConnection alloc] initWithIdentifier:identifier]; 128 | self.client = [[RCWKGClientAPI alloc] initWithConnection:connection]; 129 | self.client.delegate = self; 130 | 131 | [connection shareConnection]; 132 | } 133 | 134 | - (void)showTitleBarSpinner 135 | { 136 | UIActivityIndicatorView *activity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 137 | [activity startAnimating]; 138 | self.navigationItem.titleView = activity; 139 | } 140 | 141 | - (void)setTitleBarText:(NSString *)text 142 | { 143 | UILabel *titleLabel = [[UILabel alloc] init]; 144 | titleLabel.font = [UIFont boldSystemFontOfSize:18]; 145 | titleLabel.minimumScaleFactor = 0.5; 146 | titleLabel.adjustsFontSizeToFitWidth = YES; 147 | titleLabel.text = [text lastPathComponent]; 148 | [titleLabel sizeToFit]; 149 | self.navigationItem.titleView = titleLabel; 150 | } 151 | 152 | - (void)displayError:(NSError *)error 153 | { 154 | [self clearExistingError]; 155 | 156 | self.errorView = [RCWGentleErrorNotificationView viewFromNib]; 157 | [self.view addSubview:self.errorView]; 158 | [self.errorView displayError:error]; 159 | } 160 | 161 | - (void)clearExistingError 162 | { 163 | [self.errorView animateAndRemove]; 164 | } 165 | 166 | 167 | #pragma mark - IBActions 168 | 169 | - (IBAction)refresh:(id)sender 170 | { 171 | [self showTitleBarSpinner]; 172 | [self.client askServerForScript]; 173 | } 174 | 175 | - (IBAction)openSettings:(id)sender 176 | { 177 | UINavigationController *settingsNav = [self.storyboard instantiateViewControllerWithIdentifier:@"settingsNavigationController"]; 178 | [self presentViewController:settingsNav animated:YES completion:nil]; 179 | } 180 | 181 | 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWGentleErrorNotificationView-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/client/KeyGrip/RCWGentleErrorNotificationView-bg.png -------------------------------------------------------------------------------- /client/KeyGrip/RCWGentleErrorNotificationView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWGentleErrorNotificationView.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @class RCWGentleErrorNotificationView; 24 | 25 | @interface RCWGentleErrorNotificationView : UIView 26 | 27 | + (instancetype)viewFromNib; 28 | 29 | - (void)displayError:(NSError *)error; 30 | - (void)animateAndRemove; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWGentleErrorNotificationView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWGentleErrorNotificationView.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWGentleErrorNotificationView.h" 22 | #import 23 | @import QuartzCore; 24 | 25 | @interface RCWGENVThumb : UIView 26 | @end 27 | 28 | @interface RCWThumbGestureRecognizer : UIPanGestureRecognizer 29 | @end 30 | 31 | @interface RCWGentleErrorNotificationView () 32 | @property (nonatomic) BOOL isOpen; 33 | @property (nonatomic) CGPoint centerAtGestureStart; 34 | @property (nonatomic) IBOutlet UIView *thumbView; 35 | @property (nonatomic) IBOutlet UILabel *messageLabel; 36 | @end 37 | 38 | @implementation RCWGentleErrorNotificationView 39 | 40 | + (instancetype)viewFromNib 41 | { 42 | UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:nil]; 43 | NSArray *items = [nib instantiateWithOwner:nil options:nil]; 44 | return items[0]; 45 | } 46 | 47 | - (IBAction)panned:(RCWThumbGestureRecognizer *)recognizer 48 | { 49 | CGPoint translation; 50 | 51 | switch (recognizer.state) { 52 | case UIGestureRecognizerStateBegan: 53 | self.centerAtGestureStart = self.center; 54 | [recognizer setTranslation:self.center inView:self.superview]; 55 | break; 56 | case UIGestureRecognizerStateChanged: 57 | translation = [recognizer translationInView:self.superview]; 58 | translation.y = self.center.y; 59 | CGFloat minX = self.superview.bounds.size.width - self.bounds.size.width / 2; 60 | if (translation.x < minX) { 61 | translation.x = minX; 62 | } 63 | 64 | self.center = translation; 65 | break; 66 | case UIGestureRecognizerStateEnded: 67 | case UIGestureRecognizerStateCancelled: 68 | if (abs(self.center.x - self.centerAtGestureStart.x) > 20) { 69 | if (self.isOpen) { 70 | [self animateToClosed]; 71 | } else { 72 | [self animateToOpen]; 73 | } 74 | } else { 75 | if (self.isOpen) { 76 | [self animateToOpen]; 77 | } else { 78 | [self animateToClosed]; 79 | } 80 | } 81 | break; 82 | default: 83 | break; 84 | } 85 | } 86 | 87 | - (IBAction)tapped:(UITapGestureRecognizer *)recognizer 88 | { 89 | [self animateToOpen]; 90 | } 91 | 92 | - (void)didMoveToSuperview 93 | { 94 | [super didMoveToSuperview]; 95 | if (self.superview) { 96 | [self centerVerticallyInSuperview]; 97 | } 98 | } 99 | 100 | - (void)layoutSubviews 101 | { 102 | [super layoutSubviews]; 103 | if (self.superview) { 104 | [self centerVerticallyInSuperview]; 105 | } 106 | } 107 | 108 | - (void)centerVerticallyInSuperview 109 | { 110 | CGRect frame = self.frame; 111 | frame.origin.y = self.superview.bounds.size.height / 2 - frame.size.height / 2; 112 | self.frame = frame; 113 | } 114 | 115 | - (void)displayError:(NSError *)error 116 | { 117 | [self displayMessage:error.localizedDescription]; 118 | } 119 | 120 | - (void)displayMessage:(NSString *)message 121 | { 122 | self.messageLabel.text = message; 123 | 124 | CGRect frame = self.frame; 125 | frame.origin.x = self.superview.bounds.size.width; 126 | self.frame = frame; 127 | 128 | [UIView animateWithDuration:0.1 animations:^{ 129 | CGRect frame = self.frame; 130 | frame.origin.x = self.superview.bounds.size.width - self.thumbView.bounds.size.width - 15; 131 | self.frame = frame; 132 | } completion:^(BOOL finished) { 133 | [UIView animateWithDuration:0.1 animations:^{ 134 | CGRect frame = self.frame; 135 | frame.origin.x = self.superview.bounds.size.width - self.thumbView.bounds.size.width; 136 | self.frame = frame; 137 | }]; 138 | }]; 139 | } 140 | 141 | - (void)animateAndRemove 142 | { 143 | [self animateToClosed]; 144 | } 145 | 146 | - (void)animateToOpen 147 | { 148 | [UIView animateWithDuration:0.2 animations:^{ 149 | CGRect frame = self.frame; 150 | frame.origin.x = self.superview.bounds.size.width - self.bounds.size.width; 151 | self.frame = frame; 152 | self.isOpen = YES; 153 | }]; 154 | } 155 | 156 | - (void)animateToClosed 157 | { 158 | [UIView animateWithDuration:0.2 animations:^{ 159 | CGRect frame = self.frame; 160 | frame.origin.x = self.superview.bounds.size.width; 161 | self.frame = frame; 162 | self.isOpen = NO; 163 | } completion:^(BOOL finished) { 164 | [self removeFromSuperview]; 165 | }]; 166 | } 167 | 168 | @end 169 | 170 | @implementation RCWThumbGestureRecognizer 171 | 172 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 173 | [super touchesBegan:touches withEvent:event]; 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWGentleErrorNotificationView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWHealthMeter.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWHealthMeter.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWHealthMeter : UIView 24 | - (void)setHealthy:(BOOL)isHealthy; 25 | - (void)pulseAnimation; 26 | @end 27 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWHealthMeter.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWHealthMeter.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWHealthMeter.h" 22 | 23 | @interface RCWHealthMeter () 24 | @property (nonatomic, strong) UIView *orb; 25 | @end 26 | 27 | @implementation RCWHealthMeter 28 | 29 | - (id)initWithFrame:(CGRect)frame 30 | { 31 | self = [super initWithFrame:frame]; 32 | if (self) { 33 | self.opaque = NO; 34 | self.backgroundColor = [UIColor clearColor]; 35 | self.orb = [[UIView alloc] initWithFrame:self.bounds]; 36 | self.orb.layer.masksToBounds = YES; 37 | self.orb.layer.borderColor = [UIColor darkGrayColor].CGColor; 38 | self.orb.layer.borderWidth = 1; 39 | [self addSubview:self.orb]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)layoutSubviews 45 | { 46 | [super layoutSubviews]; 47 | self.orb.layer.cornerRadius = self.bounds.size.width/2; 48 | } 49 | 50 | - (void)setHealthy:(BOOL)isHealthy 51 | { 52 | if (isHealthy) { 53 | self.orb.backgroundColor = [UIColor greenColor]; 54 | } else { 55 | self.orb.backgroundColor = [UIColor redColor]; 56 | } 57 | } 58 | 59 | - (void)pulseAnimation 60 | { 61 | [UIView animateWithDuration:0.2 animations:^{ 62 | self.orb.transform = CGAffineTransformMakeScale(1.3, 1.3); 63 | } completion:^(BOOL finished) { 64 | [UIView animateWithDuration:0.2 animations:^{ 65 | self.orb.transform = CGAffineTransformIdentity; 66 | }]; 67 | }]; 68 | } 69 | 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWLabelCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWLabelCell.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWLabelCell : UITableViewCell 24 | @property (nonatomic, weak) IBOutlet UILabel *leftLabel; 25 | @property (nonatomic, weak) IBOutlet UILabel *rightLabel; 26 | @end 27 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWLabelCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWLabelCell.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWLabelCell.h" 22 | 23 | @implementation RCWLabelCell 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWSettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWSettingsViewController.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWSettingsViewController : UITableViewController 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWSettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWSettingsViewController.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWSettingsViewController.h" 22 | #import "RCWTextFieldCell.h" 23 | #import "RCWLabelCell.h" 24 | 25 | @interface RCWSettingsViewController () 26 | 27 | 28 | // Table cells 29 | 30 | @property (nonatomic, strong) RCWLabelCell *appVersionCell; 31 | @property (nonatomic, strong) RCWTextFieldCell *bonjourIdentifierCell; 32 | @end 33 | 34 | @implementation RCWSettingsViewController 35 | 36 | - (void)viewDidLoad 37 | { 38 | [super viewDidLoad]; 39 | 40 | [self setUpCellProperties]; 41 | } 42 | 43 | - (IBAction)donePressed:(id)sender 44 | { 45 | [self dismissViewControllerAnimated:YES completion:nil]; 46 | } 47 | 48 | #pragma mark - UITableViewDataSource methods 49 | 50 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 51 | { 52 | return 2; 53 | } 54 | 55 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 56 | { 57 | return nil; 58 | } 59 | 60 | - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section 61 | { 62 | if (section == 1) { 63 | return @"Give the server the same identifier so they can find each other on the network."; 64 | } 65 | return nil; 66 | } 67 | 68 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 69 | { 70 | switch (section) { 71 | case 0: 72 | return 1; 73 | case 1: 74 | return 1; 75 | default: 76 | NSAssert(false, @"Unknown section number: %ld", (long)section); 77 | break; 78 | } 79 | return -1; 80 | } 81 | 82 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 83 | { 84 | switch (indexPath.section) { 85 | case 0: 86 | switch (indexPath.row) { 87 | case 0: 88 | return self.appVersionCell; 89 | default: 90 | NSAssert(false, @"Unknown row in server section: %@", indexPath); 91 | break; 92 | } 93 | case 1: 94 | switch (indexPath.row) { 95 | case 0: 96 | return self.bonjourIdentifierCell; 97 | default: 98 | NSAssert(false, @"Unknown row in server section: %@", indexPath); 99 | break; 100 | } 101 | default: 102 | NSAssert(false, @"Unknown section number: %ld", (long)indexPath.section); 103 | break; 104 | } 105 | return nil; 106 | } 107 | 108 | - (void)setUpCellProperties 109 | { 110 | self.appVersionCell = [self.tableView dequeueReusableCellWithIdentifier:@"LabelCell"]; 111 | NSAssert(self.appVersionCell, @"Could not load label cell"); 112 | NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary]; 113 | NSString* version = [infoDict objectForKey:@"CFBundleVersion"]; 114 | self.appVersionCell.leftLabel.text = @"App Version"; 115 | self.appVersionCell.rightLabel.text = version; 116 | 117 | self.bonjourIdentifierCell = [self.tableView dequeueReusableCellWithIdentifier:@"TextFieldCell"]; 118 | NSAssert(self.bonjourIdentifierCell, @"Could not load text field cell for server section"); 119 | self.bonjourIdentifierCell.label.text = @"Bonjour ID"; 120 | self.bonjourIdentifierCell.textField.text = [[NSUserDefaults standardUserDefaults] stringForKey:@"bonjourIdentifier"]; 121 | self.bonjourIdentifierCell.textField.placeholder = @"Bonjour Identifier for Server"; 122 | self.bonjourIdentifierCell.textField.delegate = self; 123 | } 124 | 125 | 126 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 127 | { 128 | if (textField == self.bonjourIdentifierCell.textField) { 129 | [textField resignFirstResponder]; 130 | return YES; 131 | } 132 | 133 | return YES; 134 | } 135 | 136 | - (void)textFieldDidEndEditing:(UITextField *)textField 137 | { 138 | [self updateUserDefaultsFromTextFields]; 139 | } 140 | 141 | - (void)updateUserDefaultsFromTextFields 142 | { 143 | [[NSUserDefaults standardUserDefaults] setObject:self.bonjourIdentifierCell.textField.text 144 | forKey:@"bonjourIdentifier"]; 145 | } 146 | 147 | - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath 148 | { 149 | return NO; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWTextFieldCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWTextFieldCell.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWTextFieldCell : UITableViewCell 24 | @property (nonatomic, weak) IBOutlet UILabel *label; 25 | @property (nonatomic, weak) IBOutlet UITextField *textField; 26 | @end 27 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWTextFieldCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWTextFieldCell.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWTextFieldCell.h" 22 | 23 | @implementation RCWTextFieldCell 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWWebView.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @class RCWWebView; 24 | 25 | @protocol RCWWebViewJavascriptDelegate 26 | - (void)webView:(RCWWebView *)webView didSelectTextWithID:(NSString *)textID; 27 | @end 28 | 29 | @interface RCWWebView : UIWebView 30 | @property (nonatomic, weak) IBOutlet id javascriptDelegate; 31 | - (void)pastedCodeWithTextID:(NSString *)textID; 32 | - (void)useHTMLScript:(NSString *)html; 33 | @end 34 | -------------------------------------------------------------------------------- /client/KeyGrip/RCWWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWWebView.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWWebView.h" 22 | 23 | @interface RCWWebView() 24 | @property (nonatomic) CGFloat lastYPosition; 25 | @end 26 | 27 | @implementation RCWWebView 28 | 29 | - (void)awakeFromNib 30 | { 31 | self.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal; 32 | self.delegate = self; 33 | } 34 | 35 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 36 | { 37 | NSArray *pathComponents = request.URL.pathComponents; 38 | if ([pathComponents[1] isEqualToString:@"paste"]) { 39 | [self.javascriptDelegate webView:self didSelectTextWithID:pathComponents[2]]; 40 | return NO; 41 | } else { 42 | return YES; 43 | } 44 | } 45 | 46 | - (void)pastedCodeWithTextID:(NSString *)textID 47 | { 48 | NSString *javascript = [NSString stringWithFormat:@"pastedCodeWithID(\"%@\")", textID]; 49 | [self stringByEvaluatingJavaScriptFromString:javascript]; 50 | } 51 | 52 | - (void)useHTMLScript:(NSString *)html 53 | { 54 | self.lastYPosition = [[self stringByEvaluatingJavaScriptFromString:@"document.body.scrollTop"] integerValue]; 55 | 56 | // Fade out and back in when done because otherwise it flickers while returning to scroll position 57 | [UIView animateWithDuration:0.2 animations:^{ 58 | self.alpha = 0; 59 | } completion:^(BOOL finished) { 60 | if (finished) { 61 | [self loadHTMLString:html baseURL:nil]; 62 | } 63 | }]; 64 | } 65 | 66 | - (void)webViewDidFinishLoad:(UIWebView *)webView 67 | { 68 | NSString *command = [NSString stringWithFormat:@"document.body.scrollTop = %0.0f;", self.lastYPosition]; 69 | [self stringByEvaluatingJavaScriptFromString:command]; 70 | [UIView animateWithDuration:0.2 delay:0.1 options:0 animations:^{ 71 | self.alpha = 1; 72 | } completion:nil]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /client/KeyGrip/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /client/KeyGrip/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | #import "RCWAppDelegate.h" 24 | 25 | int main(int argc, char * argv[]) 26 | { 27 | @autoreleasepool { 28 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([RCWAppDelegate class])); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client/KeyGripTests/KeyGripTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.rubbercitywizards.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /client/KeyGripTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /client/assets/RCWGenterErrorNotificationView-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/client/assets/RCWGenterErrorNotificationView-bg.png -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/demo.gif -------------------------------------------------------------------------------- /examples/demo.md: -------------------------------------------------------------------------------- 1 | # Key Grip 2 | 3 | These notes help keep me on track while I know what to share before and after pasting each clip. 4 | 5 | Imagine selecting text on your iPad and pasting it on your Mac. 6 | 7 | The point is to help weave a story through a live code demo, but with the safety of knowing exactly what code will be pasted in. 8 | 9 | Imagine a script that helps you remember where you were going as you paste in code snippets to tell a story. 10 | 11 | It's similar to what Apple did with their app DemoMonkey app, but it uses Bonjour over Wifi for amazingly simple synchronization with the companion iOS app. 12 | 13 | You're imagining KeyGrip. 14 | 15 |   16 | 17 |   18 | 19 |   20 | 21 |   22 | -------------------------------------------------------------------------------- /scheme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/scheme.gif -------------------------------------------------------------------------------- /server/KeyGripServer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/KeyGripServer.xcodeproj/project.xcworkspace/xcshareddata/KeyGripServer.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 61656141-854C-448C-9B8F-A4464442DC7E 9 | IDESourceControlProjectName 10 | KeyGripServer 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | BFEAAE34-347C-4582-A58E-22690222D7B9 14 | ssh://github.com/rubbercitywizards/KeyGrip.git 15 | 16 | IDESourceControlProjectPath 17 | server/KeyGripServer.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | BFEAAE34-347C-4582-A58E-22690222D7B9 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | ssh://github.com/rubbercitywizards/KeyGrip.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | BFEAAE34-347C-4582-A58E-22690222D7B9 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | BFEAAE34-347C-4582-A58E-22690222D7B9 36 | IDESourceControlWCCName 37 | KeyGrip 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /server/KeyGripServer.xcodeproj/xcshareddata/xcschemes/Install KeyGripServer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /server/KeyGripServer.xcodeproj/xcshareddata/xcschemes/KeyGripServer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /server/KeyGripServer/API.h: -------------------------------------------------------------------------------- 1 | // 2 | // API.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface API : NSObject 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /server/KeyGripServer/API.m: -------------------------------------------------------------------------------- 1 | // 2 | // API.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "API.h" 22 | 23 | @implementation API 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubbercitywizards/KeyGrip/e60578d54b2a1104da479a3d96e377d4dc8049eb/server/KeyGripServer/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /server/KeyGripServer/KeyGrip-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | txt 13 | md 14 | markdown 15 | 16 | CFBundleTypeName 17 | Script Files 18 | CFBundleTypeRole 19 | Viewer 20 | LSItemContentTypes 21 | 22 | public.text 23 | 24 | 25 | 26 | CFBundleExecutable 27 | ${EXECUTABLE_NAME} 28 | CFBundleIconFile 29 | 30 | CFBundleIdentifier 31 | com.rubbercitywizards.${PRODUCT_NAME:rfc1034identifier} 32 | CFBundleInfoDictionaryVersion 33 | 6.0 34 | CFBundleName 35 | ${PRODUCT_NAME} 36 | CFBundlePackageType 37 | APPL 38 | CFBundleShortVersionString 39 | 1.0 40 | CFBundleSignature 41 | ???? 42 | CFBundleVersion 43 | 1.0 44 | LSApplicationCategoryType 45 | public.app-category.productivity 46 | LSMinimumSystemVersion 47 | ${MACOSX_DEPLOYMENT_TARGET} 48 | NSHumanReadableCopyright 49 | Copyright © 2013 Josh Smith. All rights reserved. 50 | NSMainNibFile 51 | MainMenu 52 | NSPrincipalClass 53 | NSApplication 54 | 55 | 56 | -------------------------------------------------------------------------------- /server/KeyGripServer/KeyGrip-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /server/KeyGripServer/KeyGrip.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /server/KeyGripServer/KeyGripServer-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /server/KeyGripServer/KeyGripServer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.bookmarks.document-scope 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.network.server 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /server/KeyGripServer/RCWAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWAppDelegate.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWAppDelegate : NSObject 24 | 25 | - (void)handleDroppedFile:(NSURL *)file; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /server/KeyGripServer/RCWDroplessTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWDroplessTextView.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWDroplessTextView : NSTextView 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /server/KeyGripServer/RCWDroplessTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWDroplessTextView.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWDroplessTextView.h" 22 | 23 | @implementation RCWDroplessTextView 24 | 25 | - (NSArray *)acceptableDragTypes 26 | { 27 | return nil; 28 | } 29 | 30 | - (NSDragOperation)draggingEntered:(id)sender 31 | { 32 | return NSDragOperationNone; 33 | } 34 | 35 | - (NSDragOperation)draggingUpdated:(id)sender 36 | { 37 | return NSDragOperationNone; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /server/KeyGripServer/RCWKGHTMLImporter.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGHTMLImporter.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @class RCWKGHTMLImporter; 24 | 25 | @protocol RCWKGHTMLImporterDelegate 26 | - (void)importer:(RCWKGHTMLImporter *)importer didReloadFile:(NSURL *)file; 27 | - (void)importer:(RCWKGHTMLImporter *)importer didFailToLoadFile:(NSURL *)file error:(NSError *)error; 28 | @end 29 | 30 | @interface RCWKGHTMLImporter : NSObject 31 | 32 | /** 33 | Reads in the file and converts it to html. Currently, it uses the embedded `markdown` perl script to do the translation using NSTask. Someday this can be made to swap out other processor scrips. As long as the script reads from stdin and prints to stdout, it will work with this class. 34 | 35 | \param fileURL The file url to read in. 36 | */ 37 | - (BOOL)importFile:(NSURL *)fileURL error:(NSError **)error; 38 | 39 | - (NSString *)textBlockWithID:(NSString *)itemId; 40 | 41 | @property (readonly) NSString *htmlOutput; 42 | 43 | @property (weak) id delegate; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /server/KeyGripServer/RCWKGHTMLImporter.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKGHTMLImporter.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | #import "RCWKGHTMLImporter.h" 23 | 24 | @interface RCWKGHTMLImporter () 25 | 26 | @property (strong) NSURL *conversionToolURL; 27 | @property (strong) NSURL *fileURL; 28 | @property (strong) NSDate *fileLastModifiedDate; 29 | @property (strong, readwrite) NSString *htmlOutput; 30 | @property (strong) WebView *webView; 31 | @end 32 | 33 | @implementation RCWKGHTMLImporter 34 | 35 | - (instancetype)init 36 | { 37 | if (self = [super init]) { 38 | // Here's where we could experiment with different parsing scripts 39 | _conversionToolURL = [[NSBundle mainBundle] URLForResource:@"markdown" withExtension:@"pl"]; 40 | _webView = [[WebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)dealloc 46 | { 47 | [NSFileCoordinator removeFilePresenter:self]; 48 | } 49 | 50 | - (NSURL *)presentedItemURL 51 | { 52 | return self.fileURL; 53 | } 54 | 55 | - (NSOperationQueue *)presentedItemOperationQueue 56 | { 57 | return [NSOperationQueue mainQueue]; 58 | } 59 | 60 | - (void)presentedItemDidChange 61 | { 62 | NSError *error = nil; 63 | NSDate *newDate = [self getFileURLLastModified:self.fileURL error:&error]; 64 | 65 | if (!newDate) { 66 | [self.delegate importer:self didFailToLoadFile:self.fileURL error:error]; 67 | } else { 68 | if ([newDate timeIntervalSinceReferenceDate] > [self.fileLastModifiedDate timeIntervalSinceReferenceDate]) { 69 | self.fileLastModifiedDate = newDate; 70 | if (![self importFile:self.fileURL error:&error]) { 71 | [self.delegate importer:self didFailToLoadFile:self.fileURL error:error]; 72 | } else { 73 | [self.delegate importer:self didReloadFile:self.fileURL]; 74 | } 75 | } 76 | } 77 | } 78 | 79 | - (void)presentedItemDidMoveToURL:(NSURL *)newURL 80 | { 81 | self.fileLastModifiedDate = nil; 82 | self.fileURL = newURL; 83 | [self presentedItemDidChange]; 84 | } 85 | 86 | - (BOOL)importFile:(NSURL *)fileURL error:(NSError *__autoreleasing *)error 87 | { 88 | [NSFileCoordinator removeFilePresenter:self]; 89 | 90 | if (fileURL == nil) { 91 | if (error) { 92 | *error = [NSError errorWithDomain:@"RCWKGHTMLImporter" code:1 93 | userInfo:@{NSLocalizedDescriptionKey: @"No file given"}]; 94 | } 95 | return NO; 96 | } 97 | 98 | self.fileLastModifiedDate = [self getFileURLLastModified:fileURL error:error]; 99 | if (!self.fileLastModifiedDate) { return NO; } 100 | 101 | self.fileURL = fileURL; 102 | 103 | NSTask *conversionTask = [[NSTask alloc] init]; 104 | [conversionTask setLaunchPath:self.conversionToolURL.path]; 105 | 106 | NSPipe *inPipe = [NSPipe pipe]; 107 | NSPipe *outPipe = [NSPipe pipe]; 108 | 109 | [conversionTask setStandardInput:inPipe]; 110 | [conversionTask setStandardOutput:outPipe]; 111 | 112 | [conversionTask launch]; 113 | 114 | [fileURL startAccessingSecurityScopedResource]; 115 | NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:NSDataReadingUncached error:error]; 116 | [fileURL stopAccessingSecurityScopedResource]; 117 | if (!fileData) { 118 | return NO; 119 | } 120 | [[inPipe fileHandleForWriting] writeData:fileData]; 121 | [[inPipe fileHandleForWriting] closeFile]; 122 | 123 | [conversionTask waitUntilExit]; 124 | NSData *outputData = [[outPipe fileHandleForReading] readDataToEndOfFile]; 125 | 126 | NSString *javascript = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"selections" withExtension:@"js"] encoding:NSUTF8StringEncoding error:error]; 127 | if (!javascript) { return NO; } 128 | NSString *zepto = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"zepto" withExtension:@"js"] encoding:NSUTF8StringEncoding error:error]; 129 | if (!zepto) { return NO; } 130 | NSString *css = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"display" withExtension:@"css"] encoding:NSUTF8StringEncoding error:error]; 131 | if (!css) { return NO; } 132 | NSString *html = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; 133 | 134 | self.htmlOutput = [NSString stringWithFormat:@"%@\n\n", 135 | html, 136 | zepto, 137 | javascript, 138 | css]; 139 | 140 | [self.webView.mainFrame loadHTMLString:self.htmlOutput baseURL:[[NSBundle mainBundle] resourceURL]]; 141 | 142 | [NSFileCoordinator addFilePresenter:self]; 143 | 144 | return YES; 145 | } 146 | 147 | - (NSString *)textBlockWithID:(NSString *)itemId 148 | { 149 | NSString *script = [NSString stringWithFormat:@"contentForCodeWithID(\"%@\")", itemId]; 150 | NSString *result = [[self.webView windowScriptObject] evaluateWebScript:script]; 151 | return result; 152 | } 153 | 154 | - (NSDate *)getFileURLLastModified:(NSURL *)url error:(NSError *__autoreleasing *)error 155 | { 156 | NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self]; 157 | 158 | __block NSDate *date = nil; 159 | 160 | [url startAccessingSecurityScopedResource]; 161 | [coordinator coordinateReadingItemAtURL:url options:0 error:error byAccessor:^(NSURL *newURL) { 162 | [newURL getResourceValue:&date forKey:NSURLContentModificationDateKey error:error]; 163 | }]; 164 | [url stopAccessingSecurityScopedResource]; 165 | 166 | if (error && *error) { return nil; } 167 | else { return date; } 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /server/KeyGripServer/RCWKeyGripView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKeyGripView.h 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | @interface RCWKeyGripView : NSView 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /server/KeyGripServer/RCWKeyGripView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCWKeyGripView.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import "RCWKeyGripView.h" 22 | #import "RCWAppDelegate.h" 23 | 24 | @interface RCWKeyGripView () 25 | @property BOOL highlight; 26 | @end 27 | 28 | @implementation RCWKeyGripView 29 | 30 | - (NSSet *)extensions 31 | { 32 | return [NSSet setWithArray:@[@"txt", @"md", @"markdown"]]; 33 | } 34 | 35 | - (id)initWithFrame:(NSRect)frame 36 | { 37 | self = [super initWithFrame:frame]; 38 | if (self) { 39 | [self registerForDraggedTypes:@[NSURLPboardType]]; 40 | } 41 | return self; 42 | } 43 | 44 | - (NSDragOperation)draggingEntered:(id )sender 45 | { 46 | self.highlight = YES; 47 | [self setNeedsDisplay: YES]; 48 | 49 | NSPasteboard *pboard = [sender draggingPasteboard]; 50 | if ([[pboard types] containsObject:NSURLPboardType]) { 51 | NSURL *draggedFile = [NSURL URLFromPasteboard:pboard]; 52 | if ([self.extensions containsObject:[draggedFile.pathExtension lowercaseString]]) { 53 | return NSDragOperationGeneric; 54 | } else { 55 | return NSDragOperationNone; 56 | } 57 | } else { 58 | return NSDragOperationNone; 59 | } 60 | } 61 | 62 | - (void)draggingExited:(id )sender 63 | { 64 | self.highlight = NO; 65 | [self setNeedsDisplay: YES]; 66 | } 67 | 68 | - (BOOL)prepareForDragOperation:(id )sender 69 | { 70 | self.highlight = NO; 71 | [self setNeedsDisplay: YES]; 72 | return YES; 73 | } 74 | 75 | - (BOOL)performDragOperation:(id < NSDraggingInfo >)sender 76 | { 77 | NSPasteboard *pboard = [sender draggingPasteboard]; 78 | if ([[pboard types] containsObject:NSURLPboardType]) { 79 | NSURL *draggedFile = [NSURL URLFromPasteboard:pboard]; 80 | if ([self.extensions containsObject:[draggedFile.pathExtension lowercaseString]]) { 81 | return YES; 82 | } else { 83 | return NO; 84 | } 85 | } else { 86 | return NO; 87 | } 88 | } 89 | 90 | - (void)concludeDragOperation:(id )sender 91 | { 92 | NSPasteboard *pboard = [sender draggingPasteboard]; 93 | if ([[pboard types] containsObject:NSURLPboardType]) { 94 | NSURL *draggedFile = [NSURL URLFromPasteboard:pboard].filePathURL; 95 | if ([self.extensions containsObject:[draggedFile.pathExtension lowercaseString]]) { 96 | RCWAppDelegate *delegate = (RCWAppDelegate *)[NSApplication sharedApplication].delegate; 97 | [delegate handleDroppedFile:draggedFile]; 98 | } 99 | } 100 | } 101 | 102 | - (void)drawRect:(NSRect)rect 103 | { 104 | [super drawRect:rect]; 105 | if (self.highlight) { 106 | [[NSColor grayColor] set]; 107 | [NSBezierPath setDefaultLineWidth:5]; 108 | [NSBezierPath strokeRect:[self bounds]]; 109 | } 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /server/KeyGripServer/display.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Avenir, Helvetica, sans-serif; 3 | -webkit-touch-callout: none; 4 | -webkit-user-select: none; 5 | font-size: 140% 6 | } 7 | 8 | pre { 9 | background-color: #eee; 10 | border: solid 1px #ddd; 11 | overflow-x: scroll; 12 | padding: 10px 9px; 13 | margin-left: -9px; 14 | margin-right: -9px; 15 | } 16 | 17 | pre.selecting { 18 | background-color: #ddd; 19 | } 20 | 21 | pre.selected { 22 | background-color: #99ada0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /server/KeyGripServer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // 4 | // KeyGrip - Remote pasteboard and presentation note tool 5 | // Copyright (C) 2014 Rubber City Wizards, Ltd. 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | // 20 | 21 | #import 22 | 23 | int main(int argc, const char * argv[]) 24 | { 25 | return NSApplicationMain(argc, argv); 26 | } 27 | -------------------------------------------------------------------------------- /server/KeyGripServer/selections.js: -------------------------------------------------------------------------------- 1 | var activeTouch = null; 2 | 3 | $("pre").each(function(i) { 4 | this.id = "preTag" + (i+1); 5 | }); 6 | 7 | $(document.body).on('touchstart', 'pre,code', function(event) { 8 | if (activeTouch) { return; } 9 | 10 | var touch = event.touches[0]; 11 | var node = $(touch.target).closest('pre')[0]; 12 | 13 | if (node) { 14 | $(node).addClass('selecting'); 15 | activeTouch = { 16 | identifier: touch.identifier, 17 | startX: event.pageX, 18 | startY: event.pageY, 19 | node: node 20 | }; 21 | } 22 | }); 23 | 24 | $(document.body).on('touchmove', 'pre,code', function(event) { 25 | if (!activeTouch) { return; } 26 | var touch = touchWithIdentifier(event.touches, activeTouch.identifier); 27 | if (!touch) { return; } 28 | 29 | if (Math.abs(touch.pageX - activeTouch.startX) > 10 || 30 | Math.abs(touch.pageY - activeTouch.startY) > 10) { 31 | $(activeTouch.node).removeClass('selecting'); 32 | activeTouch = null; 33 | } 34 | }); 35 | 36 | $(document.body).on('touchcancel', 'pre,code', function(event) { 37 | if (activeTouch && touchWithIdentifier(event.changedTouches, activeTouch.identifier)) { 38 | $(activeTouch.node).removeClass('selecting'); 39 | activeTouch = null; 40 | } 41 | }); 42 | 43 | $(document.body).on('touchend', 'pre,code', function(event) { 44 | if (!activeTouch) { return; } 45 | var touch = touchWithIdentifier(event.changedTouches, activeTouch.identifier); 46 | if (!touch) { return; } 47 | 48 | var node = activeTouch.node; 49 | $(node).addClass('selected').removeClass('selecting'); 50 | activeTouch = null; 51 | 52 | pasteCodeWithID(node.id); 53 | }); 54 | 55 | function touchWithIdentifier(touches, identifier) 56 | { 57 | for (var i = 0; i < touches.length; i++) { 58 | var touch = touches[i]; 59 | if (touch.identifier === identifier) { return touch; } 60 | } 61 | } 62 | 63 | function pasteCodeWithID(id) 64 | { 65 | location.href = "keygrip:///paste/" + id; 66 | } 67 | 68 | function pastedCodeWithID(id) 69 | { 70 | setTimeout(function() { 71 | $("#" + id).removeClass('selected').removeClass('selecting'); 72 | }, 100); 73 | } 74 | 75 | function contentForCodeWithID(id) 76 | { 77 | return $("#" + id).text(); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /server/KeyGripServerTests/KeyGripServerTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.rubbercitywizards.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /server/KeyGripServerTests/KeyGripServerTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /server/KeyGripServerTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------