├── .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 | ![Bryan](http://open.mobileboxlab.com/img/ios-screen.gif) 2 | 3 | ___ 4 | iOS screen mirroring through a socket interface for streaming real time screen capture data out of iOS devices. 5 | 6 | ![Alt Text](https://github.com/mobileboxlab/ios-bash-streaming/blob/master/smile.gif?raw=true) 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 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](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 | --------------------------------------------------------------------------------