├── .gitattributes
├── smile.gif
├── web
├── img
│ └── iphone.png
├── js
│ ├── ios.js
│ └── reconnecting-websocket.js
└── index.html
├── smile.sh
├── LICENSE
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-vendored
2 | *.sh linguist-vendored=false
3 |
--------------------------------------------------------------------------------
/smile.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobileboxlab/ios-bash-streaming/HEAD/smile.gif
--------------------------------------------------------------------------------
/web/img/iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobileboxlab/ios-bash-streaming/HEAD/web/img/iphone.png
--------------------------------------------------------------------------------
/smile.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | while [ true ]; do
4 | idevicescreenshot -u $1 $1.png > /dev/null 2>&1
5 | magick convert $1.png -resize 50% $1.jpg
6 | echo $(cat $1.jpg | base64)
7 | done
8 |
--------------------------------------------------------------------------------
/web/js/ios.js:
--------------------------------------------------------------------------------
1 | function WebSocketTest() {
2 | if ("WebSocket" in window) {
3 | console.log(location.host);
4 | var ws = new ReconnectingWebSocket('wss://' + location.host + '/');
5 | ws.onopen = function() {
6 | console.log("Open");
7 | };
8 |
9 | ws.onmessage = function (evt) {
10 | document.getElementById("img").src = "data:image/jpg;base64," + evt.data;
11 | };
12 |
13 | ws.onclose = function() {
14 | document.getElementById("img").src = "img/iphone.png";
15 | };
16 | } else {
17 | alert("WebSocket NOT supported by your Browser!");
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |

30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 MobileBoxLab
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ___
4 | iOS screen mirroring through a socket interface for streaming real time screen capture data out of iOS devices.
5 |
6 | 
7 |
8 |
9 |
10 | ## Requirements
11 |
12 | 1. bash, any recent version should work
13 | 2. Install [imagemagick](http://macappstore.org/imagemagick/)
14 | 3. Install [websocketd](https://github.com/joewalnes/websocketd)
15 | 4. Install [libimobiledevice](http://macappstore.org/libimobiledevice/)
16 | 5. [Lightning cable](https://en.wikipedia.org/wiki/Lightning_(connector))
17 |
18 | ## Getting started
19 |
20 | ```bash
21 | chmod +x smile.sh
22 | ```
23 |
24 | Check all available devices:
25 |
26 | ```bash
27 | instruments -s devices
28 | ```
29 |
30 | Run the below command to start the service (you need to provide the device id):
31 |
32 | ```bash
33 | websocketd --port=9231 --staticdir=web sh smile.sh DEVICE-ID
34 | ```
35 |
36 | Go to [http://127.0.0.1:9231/](http://127.0.0.1:9231/) You will see the connected device.
37 |
38 | ## Share Your Screen with Others
39 |
40 | Optionally you can share your screen with others through [Serveo](https://serveo.net) that expose the local server to the internet without installation, no signup and free!
41 |
42 | Try it! Copy and paste this into your terminal:
43 |
44 | ```bash
45 | ssh -R 80:localhost:9231 serveo.net
46 | ```
47 |
48 | The **-R** option instructs your SSH client to request port forwarding from the server and proxy requests to the specified host and port (usually localhost). A subdomain of serveo.net will be assigned to forward HTTP traffic.
49 |
50 | ## Frequently Asked Questions
51 |
52 | **Is this for real?**
53 |
54 | Yes. It’s not big, and it’s not clever but this is a real iOS screen mirroring in five lines of bash.
55 |
56 | **How do I install it?**
57 |
58 | You don't install it.
59 |
60 | **Does it support websockets?**
61 |
62 | Yes.
63 |
64 | **Limitations?**
65 |
66 | Does not do a high framerate.
67 |
68 | **Does it work on Windows?**
69 |
70 | Mmm... maybe.
71 |
72 |
73 | ## Contribution
74 |
75 | Any ideas are welcome. Feel free to submit any issues or pull requests.
76 |
77 | [](http://makeapullrequest.com)
78 |
79 | ---
80 | **ios-bash-streaming** is developed and maintained by [Mobilebox](http://mobileboxlab.com) team.
81 |
--------------------------------------------------------------------------------
/web/js/reconnecting-websocket.js:
--------------------------------------------------------------------------------
1 | // MIT License:
2 | //
3 | // Copyright (c) 2010-2012, Joe Walnes
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | /**
24 | * This behaves like a WebSocket in every way, except if it fails to connect,
25 | * or it gets disconnected, it will repeatedly poll until it succesfully connects
26 | * again.
27 | *
28 | * It is API compatible, so when you have:
29 | * ws = new WebSocket('ws://....');
30 | * you can replace with:
31 | * ws = new ReconnectingWebSocket('ws://....');
32 | *
33 | * The event stream will typically look like:
34 | * onconnecting
35 | * onopen
36 | * onmessage
37 | * onmessage
38 | * onclose // lost connection
39 | * onconnecting
40 | * onopen // sometime later...
41 | * onmessage
42 | * onmessage
43 | * etc...
44 | *
45 | * It is API compatible with the standard WebSocket API.
46 | *
47 | * Latest version: https://github.com/joewalnes/reconnecting-websocket/
48 | * - Joe Walnes
49 | */
50 | function ReconnectingWebSocket(url, protocols) {
51 | protocols = protocols || [];
52 |
53 | // These can be altered by calling code.
54 | this.debug = false;
55 | this.reconnectInterval = 1000;
56 | this.timeoutInterval = 2000;
57 |
58 | var self = this;
59 | var ws;
60 | var forcedClose = false;
61 | var timedOut = false;
62 |
63 | this.url = url;
64 | this.protocols = protocols;
65 | this.readyState = WebSocket.CONNECTING;
66 | this.URL = url; // Public API
67 |
68 | this.onopen = function(event) {
69 | };
70 |
71 | this.onclose = function(event) {
72 | };
73 |
74 | this.onconnecting = function(event) {
75 | };
76 |
77 | this.onmessage = function(event) {
78 | };
79 |
80 | this.onerror = function(event) {
81 | };
82 |
83 | function connect(reconnectAttempt) {
84 | ws = new WebSocket(url, protocols);
85 |
86 | self.onconnecting();
87 | if (self.debug || ReconnectingWebSocket.debugAll) {
88 | console.debug('ReconnectingWebSocket', 'attempt-connect', url);
89 | }
90 |
91 | var localWs = ws;
92 | var timeout = setTimeout(function() {
93 | if (self.debug || ReconnectingWebSocket.debugAll) {
94 | console.debug('ReconnectingWebSocket', 'connection-timeout', url);
95 | }
96 | timedOut = true;
97 | localWs.close();
98 | timedOut = false;
99 | }, self.timeoutInterval);
100 |
101 | ws.onopen = function(event) {
102 | clearTimeout(timeout);
103 | if (self.debug || ReconnectingWebSocket.debugAll) {
104 | console.debug('ReconnectingWebSocket', 'onopen', url);
105 | }
106 | self.readyState = WebSocket.OPEN;
107 | reconnectAttempt = false;
108 | self.onopen(event);
109 | };
110 |
111 | ws.onclose = function(event) {
112 | clearTimeout(timeout);
113 | ws = null;
114 | if (forcedClose) {
115 | self.readyState = WebSocket.CLOSED;
116 | self.onclose(event);
117 | } else {
118 | self.readyState = WebSocket.CONNECTING;
119 | self.onconnecting();
120 | if (!reconnectAttempt && !timedOut) {
121 | if (self.debug || ReconnectingWebSocket.debugAll) {
122 | console.debug('ReconnectingWebSocket', 'onclose', url);
123 | }
124 | self.onclose(event);
125 | }
126 | setTimeout(function() {
127 | connect(true);
128 | }, self.reconnectInterval);
129 | }
130 | };
131 | ws.onmessage = function(event) {
132 | if (self.debug || ReconnectingWebSocket.debugAll) {
133 | console.debug('ReconnectingWebSocket', 'onmessage', url, event.data);
134 | }
135 | self.onmessage(event);
136 | };
137 | ws.onerror = function(event) {
138 | if (self.debug || ReconnectingWebSocket.debugAll) {
139 | console.debug('ReconnectingWebSocket', 'onerror', url, event);
140 | }
141 | self.onerror(event);
142 | };
143 | }
144 | connect(url);
145 |
146 | this.send = function(data) {
147 | if (ws) {
148 | if (self.debug || ReconnectingWebSocket.debugAll) {
149 | console.debug('ReconnectingWebSocket', 'send', url, data);
150 | }
151 | return ws.send(data);
152 | } else {
153 | throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
154 | }
155 | };
156 |
157 | this.close = function() {
158 | if (ws) {
159 | forcedClose = true;
160 | ws.close();
161 | }
162 | };
163 |
164 | /**
165 | * Additional public API method to refresh the connection if still open (close, re-open).
166 | * For example, if the app suspects bad data / missed heart beats, it can try to refresh.
167 | */
168 | this.refresh = function() {
169 | if (ws) {
170 | ws.close();
171 | }
172 | };
173 | }
174 |
175 | /**
176 | * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
177 | */
178 | ReconnectingWebSocket.debugAll = false;
179 |
180 |
--------------------------------------------------------------------------------