├── .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 | 
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 | 
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 |
--------------------------------------------------------------------------------