├── LICENSE ├── Makefile ├── go.mod ├── go.sum ├── main.go └── nanowriter.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David Helkowski 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = ios_video_pull 2 | 3 | all: $(TARGET) 4 | 5 | $(TARGET): main.go go.sum nanowriter.go 6 | go build -o $(TARGET) . 7 | 8 | go.sum: 9 | go get 10 | go get . 11 | 12 | clean: 13 | $(RM) $(TARGET) go.sum 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nanoscopic/ios_video_pull 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/danielpaulus/quicktime_video_hack v0.0.0-20200514194616-c4570b6b687c 7 | github.com/nanomsg/mangos v2.0.0+incompatible 8 | github.com/sirupsen/logrus v1.6.0 9 | go.nanomsg.org/mangos/v3 v3.0.1 10 | nanomsg.org/go/mangos/v2 v2.0.8 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= 2 | github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= 3 | github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 4 | github.com/danielpaulus/go-ios v0.0.0-20190926190740-cc977db05eea/go.mod h1:/I8WMN9JIALHl5vtg939USw/gDASCwlC9D+K10QdGB4= 5 | github.com/danielpaulus/gst v0.0.0-20200201205042-e6d2974fceb8/go.mod h1:JbhjLST5AaUXpKQK65g9144BK8QHftbpuFoYuhDuONw= 6 | github.com/danielpaulus/quicktime_video_hack v0.0.0-20200514194616-c4570b6b687c h1:0k6TgaLsY7zlnLR0uO9aTliVXgZ8pYjJCfPHLmmDAvk= 7 | github.com/danielpaulus/quicktime_video_hack v0.0.0-20200514194616-c4570b6b687c/go.mod h1:RWoT+WsCcDOx9Is/2yNKAQ9NuxgN5D40HcXzxqCDQCI= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 11 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 12 | github.com/gdamore/optopia v0.2.0/go.mod h1:YKYEwo5C1Pa617H7NlPcmQXl+vG6YnSSNB44n8dNL0Q= 13 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/google/gousb v0.0.0-20190812193832-18f4c1d8a750 h1:DVKHLo3yE4psTjD9aM2pY7EHoicaQbgmaxxvvHC6ZSM= 16 | github.com/google/gousb v0.0.0-20190812193832-18f4c1d8a750/go.mod h1:Tl4HdAs1ThE3gECkNwz+1MWicX6FXddhJEw7L8jRDiI= 17 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 18 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 19 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 20 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 21 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 22 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 23 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 24 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 25 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 26 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 27 | github.com/lijo-jose/glib v0.0.0-20191012030101-93ee72d7d646/go.mod h1:wzypjnJX+g/LKnKDVvJni/u0gNlQestTwv6Kt/Qf3fk= 28 | github.com/lijo-jose/gst v0.0.0-20191012030143-e3a5557394c7/go.mod h1:CAqd8DUUbXc086paYmyPzUF0K42o6tFpIzoelwMioEI= 29 | github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= 30 | github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= 31 | github.com/nanomsg/mangos v1.4.0 h1:MzIsygkOj2Kr1ls/57qRavh+HkPSpXqUQFfRPs1uulQ= 32 | github.com/nanomsg/mangos v2.0.0+incompatible h1:Ct9iruN9lalGiisGk+BENfMHXMtWD5o4gbkW0QDbrAU= 33 | github.com/nanomsg/mangos v2.0.0+incompatible/go.mod h1:hVOgn/Rhv/QRnsVeM+4sAolFUXWdpooG/l9vrBaxmSI= 34 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 35 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 36 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 37 | github.com/pion/datachannel v1.4.12/go.mod h1:Ulrx2j4r8c0Za5ltWFv/hZvSpc3ZpvOvcz46tvnt+PY= 38 | github.com/pion/dtls v1.5.2/go.mod h1:v4ULmyyV65geAZQBBckCjgMhmngTqz7HQVsQVYnfkGo= 39 | github.com/pion/ice v0.7.1/go.mod h1:fPnWLWO3B83fJmO6Sci5Mv3ypN4Vd956Py4JlbJfVwU= 40 | github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= 41 | github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= 42 | github.com/pion/mdns v0.0.3/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE= 43 | github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k= 44 | github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM= 45 | github.com/pion/rtp v1.1.3/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE= 46 | github.com/pion/rtp v1.1.4/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE= 47 | github.com/pion/sctp v1.7.2/go.mod h1:HlTD+15FeLYYQTTDO35uKEeRLVq5L2AY/ef6ZSvpIXc= 48 | github.com/pion/sdp/v2 v2.3.1/go.mod h1:jccXVYW0fuK6ds2pwKr89SVBDYlCjhgMI6nucl5R5rA= 49 | github.com/pion/srtp v1.2.6/go.mod h1:rd8imc5htjfs99XiEoOjLMEOcVjME63UHx9Ek9IGst0= 50 | github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= 51 | github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE= 52 | github.com/pion/transport v0.8.9/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8= 53 | github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= 54 | github.com/pion/turn v1.4.0/go.mod h1:aDSi6hWX/hd1+gKia9cExZOR0MU95O7zX9p3Gw/P2aU= 55 | github.com/pion/webrtc/v2 v2.1.11/go.mod h1:spr7E544asF+Y3ajLasHi9fSJTiDjUERRBeTeRimlfE= 56 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 58 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 59 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 60 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 61 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 62 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 63 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 64 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 65 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 66 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 67 | go.nanomsg.org/mangos v1.4.0 h1:+RcT4HcAKQwm3mGYmLwMe07z/hT1cPEYGV/BIfD1W0c= 68 | go.nanomsg.org/mangos v2.0.0+incompatible h1:Ll6GIzeGGld6/bFrVgBB8CjwibhHXZtF5jon+GoH1bE= 69 | go.nanomsg.org/mangos/v3 v3.0.1 h1:xR8nca0ZeAvwsoRWjeEHuR2/B0N+Po/ZJpGNCpDz6To= 70 | go.nanomsg.org/mangos/v3 v3.0.1/go.mod h1:RxVwsn46YtfJ74mF8MeVo+MFjg545KCI50NuZrFXmzc= 71 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 72 | golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 73 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 74 | golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 75 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 76 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 77 | golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 78 | golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 79 | golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 80 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 81 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 82 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 83 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 84 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 85 | golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 86 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 89 | golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0= 90 | golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 91 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 92 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 93 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 94 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 95 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 96 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 97 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 98 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 99 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 100 | howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= 101 | nanomsg.org/go/mangos v1.4.0 h1:44ACRfueBqYxeZKFsHo87lZaLn1fBYCvnYpqRHsuAIE= 102 | nanomsg.org/go/mangos v2.0.0+incompatible h1:LE83iAKZmIY8CP0gI9ETWgGL9X2m0d4WH0oyIRuAJ7o= 103 | nanomsg.org/go/mangos/v2 v2.0.8 h1:Nnc5gCNPd8sSyxgfMTdlKK020p4nxLAxcQrhLVnjGQ8= 104 | nanomsg.org/go/mangos/v2 v2.0.8/go.mod h1:gngxudWUZkxqHN+8n/2y9gWZPcwmSbliFYJsYG8mbKs= 105 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "os/signal" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/danielpaulus/quicktime_video_hack/screencapture" 15 | "github.com/google/gousb" 16 | 17 | "go.nanomsg.org/mangos/v3" 18 | "go.nanomsg.org/mangos/v3/protocol/push" 19 | // register transports 20 | _ "github.com/nanomsg/mangos/transport/all" 21 | 22 | log "github.com/sirupsen/logrus" 23 | ) 24 | 25 | func main() { 26 | var udid = flag.String( "udid" , "" , "Device UDID" ) 27 | var devicesCmd = flag.Bool( "devices" , false , "List devices then exit" ) 28 | var decimalFlag= flag.Bool( "decimal" , false , "Show product ID / vendor ID in decimal" ) 29 | var jsonFlag = flag.Bool( "json" , false , "Use JSON output when listing devices" ) 30 | var pullCmd = flag.Bool( "pull" , false , "Pull video" ) 31 | var pushSpec = flag.String( "pushSpec" , "tcp://127.0.0.1:7878", "NanoMsg spec to push h264 nalus to" ) 32 | var file = flag.String( "file", "" , "File to save h264 nalus into" ) 33 | var verbose = flag.Bool( "v" , false , "Verbose Debugging" ) 34 | var enableCmd = flag.Bool( "enable", false, "Enable device" ) 35 | var disableCmd = flag.Bool( "disable", false, "Disable device" ) 36 | flag.Parse() 37 | 38 | log.SetFormatter(&log.JSONFormatter{}) 39 | 40 | if *verbose { 41 | log.Info("Set Debug mode") 42 | log.SetLevel(log.DebugLevel) 43 | } 44 | 45 | if *devicesCmd { 46 | devices( *jsonFlag, *udid, *decimalFlag ); return 47 | } else if *pullCmd { 48 | gopull( *pushSpec, *file, *udid ) 49 | } else if *enableCmd { 50 | enable( *udid ) 51 | } else if *disableCmd { 52 | disable( *udid ) 53 | } else { 54 | flag.Usage() 55 | } 56 | } 57 | 58 | func devices( useJson bool, udid string, useDecimal bool ) { 59 | ctx := gousb.NewContext() 60 | 61 | devs, err := findIosDevices(ctx) 62 | if err != nil { log.Errorf("Error finding iOS Devices - %s",err) } 63 | 64 | if useJson && udid == "" { fmt.Printf("[\n") } 65 | for _,dev := range devs { 66 | serial, _ := dev.SerialNumber() 67 | if udid != "" && serial != udid { continue } 68 | product, _ := dev.Product() 69 | subcs := getVendorSubclasses( dev.Desc ) 70 | activated := 0 71 | for _,subc := range subcs { 72 | if int(subc)==42 { activated=1 } 73 | } 74 | pidHex := dev.Desc.Product.String() 75 | vidHex := dev.Desc.Vendor.String() 76 | pid := "" 77 | vid := "" 78 | if useDecimal { 79 | pid64, _ := strconv.ParseInt( pidHex, 16, 16 ) 80 | vid64, _ := strconv.ParseInt( vidHex, 16, 16 ) 81 | pid = strconv.Itoa( int( pid64 ) ) 82 | vid = strconv.Itoa( int( vid64 ) ) 83 | } else { 84 | pid = pidHex 85 | vid = vidHex 86 | } 87 | if useJson { 88 | name := strings.Replace( product, `"`,`\"`, -1 ) 89 | fmt.Printf(`{"bus":%d,"addr":%d,"port":%d,"udid":"%s","name":"%s","vid":"%s","pid":"%s","activated":%d}`, 90 | dev.Desc.Bus, dev.Desc.Address, dev.Desc.Port, serial, name, vid, pid, activated ) 91 | if udid == "" { fmt.Printf(",") } 92 | fmt.Printf("\n") 93 | } else { 94 | fmt.Printf( "Bus: %d, Address: %d, Port: %d, UDID:%s, Name:%s, VID=%s, PID=%s, Activated=%d\n", dev.Desc.Bus, dev.Desc.Address, dev.Desc.Port, serial, product, vid, pid, activated ) 95 | } 96 | dev.Close() 97 | } 98 | if useJson && udid == "" { fmt.Printf("]\n") } 99 | 100 | ctx.Close() 101 | } 102 | 103 | func openDevice( ctx *gousb.Context, uuid string ) ( *gousb.Device, bool ) { 104 | devs, err := findIosDevices(ctx) 105 | if err != nil { log.Errorf("Error finding iOS Devices - %s",err) } 106 | var foundDevice *gousb.Device = nil 107 | activated := false 108 | for _,dev := range devs { 109 | serial, _ := dev.SerialNumber() 110 | if serial == uuid { 111 | foundDevice = dev 112 | subcs := getVendorSubclasses( dev.Desc ) 113 | for _,subc := range subcs { 114 | if int(subc)==42 { activated=true } 115 | } 116 | } else { 117 | dev.Close() 118 | } 119 | } 120 | return foundDevice, activated 121 | } 122 | 123 | func findIosDevices( ctx *gousb.Context ) ( [] *gousb.Device, error ) { 124 | return ctx.OpenDevices( func(dev *gousb.DeviceDesc) bool { 125 | for _, subc := range getVendorSubclasses( dev ) { 126 | if subc == gousb.ClassApplication { return true } 127 | } 128 | return false 129 | } ) 130 | } 131 | 132 | func getVendorSubclasses( desc *gousb.DeviceDesc ) ([]gousb.Class) { 133 | subClasses := []gousb.Class{} 134 | for _, conf := range desc.Configs { 135 | for _, iface := range conf.Interfaces { 136 | if iface.AltSettings[0].Class == gousb.ClassVendorSpec { 137 | subClass := iface.AltSettings[0].SubClass 138 | subClasses = append( subClasses, subClass ) 139 | } 140 | } 141 | } 142 | return subClasses 143 | } 144 | 145 | func gopull( pushSpec string, filename string, udid string ) { 146 | stopChannel := make( chan interface {} ) 147 | stopChannel2 := make( chan interface {} ) 148 | stopChannel3 := make( chan bool ) 149 | waitForSigInt( stopChannel, stopChannel2, stopChannel3 ) 150 | 151 | var fh *os.File 152 | var err error 153 | var writer screencapture.CmSampleBufConsumer 154 | if filename == "" { 155 | pushSock := setup_nanomsg_sockets( pushSpec ) 156 | writer = NewNanoWriter( pushSock ) 157 | } else { 158 | fh, err = os.Create( filename ) 159 | if err != nil { 160 | log.Errorf("Error creating file %s:%s", filename, err) 161 | } 162 | writer = NewFileWriter( bufio.NewWriter( fh ) ) 163 | } 164 | 165 | attempt := 1 166 | for { 167 | success := startWithConsumer( writer, udid, stopChannel, stopChannel2 ) 168 | if success { 169 | break 170 | } 171 | fmt.Printf("Attempt %i to start streaming\n", attempt) 172 | if attempt >= 4 { 173 | log.WithFields( log.Fields{ 174 | "type": "stream_start_failed", 175 | } ).Fatal("Socket new error") 176 | } 177 | attempt++ 178 | time.Sleep( time.Second * 1 ) 179 | } 180 | 181 | <- stopChannel3 182 | writer.Stop() 183 | } 184 | 185 | func setup_nanomsg_sockets( pushSpec string ) ( pushSock mangos.Socket ) { 186 | var err error 187 | if pushSock, err = push.NewSocket(); err != nil { 188 | log.WithFields( log.Fields{ 189 | "type": "err_socket_new", 190 | "spec": pushSpec, 191 | "err": err, 192 | } ).Fatal("Socket new error") 193 | } 194 | if err = pushSock.Dial(pushSpec); err != nil { 195 | log.WithFields( log.Fields{ 196 | "type": "err_socket_connect", 197 | "spec": pushSpec, 198 | "err": err, 199 | } ).Fatal("Socket connect error") 200 | } 201 | 202 | return pushSock 203 | } 204 | 205 | func enable( udid string ) { 206 | ctx := gousb.NewContext() 207 | 208 | var usbDevice *gousb.Device = nil 209 | var activated bool 210 | if udid == "" { 211 | devs, err := findIosDevices(ctx) 212 | if err != nil { log.Errorf("Error finding iOS Devices - %s",err) } 213 | for _,dev := range devs { 214 | oneActivated := false 215 | oneUdid := "" 216 | if usbDevice == nil { 217 | oneUdid, _ = dev.SerialNumber() 218 | subcs := getVendorSubclasses( dev.Desc ) 219 | for _,subc := range subcs { 220 | if int(subc)==42 { oneActivated=true } 221 | } 222 | if oneActivated == false { 223 | usbDevice = dev; udid = oneUdid; activated = oneActivated 224 | } else { 225 | dev.Close() 226 | } 227 | } else { 228 | dev.Close() 229 | } 230 | } 231 | if udid != "" { log.Infof("Using first disabled device; uuid=%s", udid ) } 232 | } else { 233 | usbDevice, activated = openDevice( ctx, udid ) 234 | log.Info("Opened device") 235 | } 236 | 237 | if usbDevice == nil { 238 | log.Info("Could not find a disabled device to activate") 239 | ctx.Close() 240 | return 241 | } 242 | 243 | if activated == true { 244 | log.Info("Device already enabled") 245 | usbDevice.Close() 246 | ctx.Close() 247 | return 248 | } 249 | 250 | sendQTEnable( usbDevice ) 251 | 252 | usbDevice.Close() 253 | ctx.Close() 254 | } 255 | 256 | func disable( udid string ) { 257 | ctx := gousb.NewContext() 258 | 259 | var usbDevice *gousb.Device = nil 260 | var activated bool 261 | if udid == "" { 262 | devs, err := findIosDevices(ctx) 263 | if err != nil { log.Errorf("Error finding iOS Devices - %s",err) } 264 | for _,dev := range devs { 265 | oneActivated := true 266 | oneUdid := "" 267 | if usbDevice == nil { 268 | oneUdid, _ = dev.SerialNumber() 269 | subcs := getVendorSubclasses( dev.Desc ) 270 | for _,subc := range subcs { 271 | if int(subc)==42 { oneActivated=true } 272 | } 273 | if oneActivated == true { 274 | usbDevice = dev; udid = oneUdid; activated = oneActivated 275 | } else { 276 | dev.Close() 277 | } 278 | } else { 279 | dev.Close() 280 | } 281 | } 282 | if udid != "" { log.Infof("Using first enabled device; uuid=%s", udid ) } 283 | } else { 284 | usbDevice, activated = openDevice( ctx, udid ) 285 | 286 | log.Info("Opened device") 287 | } 288 | 289 | if usbDevice == nil { 290 | log.Info("Could not find a enabled device to disabled") 291 | ctx.Close() 292 | return 293 | } 294 | 295 | if activated == false { 296 | log.Info("Device already disabled") 297 | usbDevice.Close() 298 | ctx.Close() 299 | return 300 | } 301 | 302 | usbDevice.Reset() 303 | //sendQTDisable( usbDevice ) 304 | 305 | usbDevice.Close() 306 | ctx.Close() 307 | } 308 | 309 | func startWithConsumer( consumer screencapture.CmSampleBufConsumer, udid string, stopChannel chan interface{}, stopChannel2 chan interface{} ) bool { 310 | ctx := gousb.NewContext() 311 | 312 | var usbDevice *gousb.Device = nil 313 | var activated bool = false 314 | if udid == "" { 315 | devs, err := findIosDevices(ctx) 316 | if err != nil { log.Errorf("Error finding iOS Devices - %s",err) } 317 | for _,dev := range devs { 318 | if usbDevice == nil { 319 | udid, _ = dev.SerialNumber() 320 | subcs := getVendorSubclasses( dev.Desc ) 321 | for _,subc := range subcs { 322 | if int(subc)==42 { activated=true } 323 | } 324 | usbDevice = dev 325 | } else { 326 | dev.Close() 327 | } 328 | } 329 | } else { 330 | usbDevice, activated = openDevice( ctx, udid ) 331 | log.Info("Opened device") 332 | } 333 | 334 | if !activated { 335 | log.Info("Not activated; attempting to activate") 336 | sendQTEnable( usbDevice ) 337 | 338 | var i int = 0 339 | for { 340 | time.Sleep(500 * time.Millisecond) 341 | usbDevice.Close() 342 | var activated bool 343 | usbDevice, activated = openDevice( ctx, udid ) 344 | if activated { break } 345 | i++ 346 | if i > 5 { 347 | log.Debug("Failed activating config") 348 | return false 349 | } 350 | } 351 | } 352 | 353 | adapter := UsbAdapter{} 354 | 355 | mp := screencapture.NewMessageProcessor( &adapter, stopChannel, consumer ) 356 | 357 | err := startReading( &adapter, usbDevice, &mp, stopChannel2 ) 358 | if err != nil { 359 | log.Errorf("startReading failure - %s",err) 360 | log.Info("Closing device") 361 | usbDevice.Close() 362 | ctx.Close() 363 | return false 364 | } 365 | 366 | log.Info("Closing device") 367 | usbDevice.Close() 368 | 369 | ctx.Close() 370 | 371 | return true 372 | } 373 | 374 | func sendQTEnable( device *gousb.Device ) { 375 | val, err := device.Control(0x40, 0x52, 0x00, 0x02, []byte{}) 376 | if err != nil { 377 | log.Warnf("Failed sending control transfer for enabling hidden QT config. Seems like this happens sometimes but it still works usually: %d, %s", val, err) 378 | } 379 | log.Debugf("Enabling QT config RC:%d", val) 380 | } 381 | 382 | func sendQTDisable( device *gousb.Device ) { 383 | val, err := device.Control(0x40, 0x52, 0x00, 0x00, []byte{}) 384 | if err != nil { 385 | log.Warnf("Failed sending control transfer for enabling hidden QT config. Seems like this happens sometimes but it still works usually: $d, %s", val, err) 386 | } 387 | log.Debugf("Dsiabling QT config RC:%d", val) 388 | } 389 | 390 | func waitForSigInt( stopChannel chan interface{}, stopChannel2 chan interface{}, stopChannel3 chan bool ) { 391 | c := make(chan os.Signal, 1) 392 | signal.Notify(c, os.Interrupt) 393 | go func() { 394 | for sig := range c { 395 | fmt.Printf("Got signal %s\n", sig) 396 | go func() { stopChannel3 <- true }() 397 | go func() { 398 | stopChannel2 <- true 399 | stopChannel2 <- true 400 | }() 401 | go func() { 402 | stopChannel <- true 403 | stopChannel <- true 404 | }() 405 | 406 | } 407 | }() 408 | } 409 | 410 | // Stuff below more or less copied from quicktime_video_hack/screencapture/usbadapter.go and other files in that directory 411 | // All of these stuff has to be copied in order to alter startReading due to non-exposed functions and variables 412 | 413 | type UsbAdapter struct { 414 | outEndpoint *gousb.OutEndpoint 415 | } 416 | 417 | func (usa UsbAdapter) WriteDataToUsb(bytes []byte) { 418 | _, err := usa.outEndpoint.Write(bytes) 419 | if err != nil { log.Error("failed sending to usb", err) } 420 | } 421 | 422 | func startReading(usa *UsbAdapter, usbDevice *gousb.Device, receiver screencapture.UsbDataReceiver, stopSignal chan interface{}) error { 423 | var confignum int = 6 424 | 425 | config, err := usbDevice.Config(confignum) 426 | if err != nil { return errors.New("Could not retrieve config") } 427 | 428 | log.Debugf("QT Config is active: %s", config.String()) 429 | 430 | /*val, err := usbDevice.Control(0x02, 0x01, 0, 0x86, make([]byte, 0)) 431 | if err != nil { 432 | log.Debug("failed control", err) 433 | } 434 | log.Debugf("Clear Feature RC: %d", val) 435 | 436 | val, err = usbDevice.Control(0x02, 0x01, 0, 0x05, make([]byte, 0)) 437 | if err != nil { 438 | log.Debug("failed control", err) 439 | } 440 | log.Debugf("Clear Feature RC: %d", val)*/ 441 | 442 | success, iface := findInterfaceForSubclass( config, 0x2a ) 443 | if !success || iface == nil { log.Debug("could not get Quicktime Interface"); return err } 444 | log.Debugf("Got QT iface:%s", iface.String()) 445 | 446 | inboundBulkEndpointIndex, err := grabInBulk(iface.Setting) 447 | if err != nil { return err } 448 | inEndpoint, err := iface.InEndpoint(inboundBulkEndpointIndex) 449 | if err != nil { log.Error("couldnt get InEndpoint"); return err } 450 | log.Debugf("Inbound Bulk: %s", inEndpoint.String()) 451 | 452 | outboundBulkEndpointIndex, err := grabOutBulk(iface.Setting) 453 | if err != nil { return err } 454 | outEndpoint, err := iface.OutEndpoint(outboundBulkEndpointIndex) 455 | if err != nil { log.Error("couldnt get OutEndpoint"); return err } 456 | log.Debugf("Outbound Bulk: %s", outEndpoint.String()) 457 | usa.outEndpoint = outEndpoint 458 | 459 | stream, err := inEndpoint.NewStream(4096, 5) 460 | if err != nil { log.Fatal("couldnt create stream"); return err } 461 | log.Debug("Endpoint claimed") 462 | udid, _ := usbDevice.SerialNumber() 463 | log.Infof("Device '%s' ready to stream ( click 'Settings-Developer-Reset Media Services' if nothing happens )", udid ) 464 | 465 | go func() { 466 | frameExtractor := screencapture.NewLengthFieldBasedFrameExtractor() 467 | for { 468 | buffer := make([]byte, 65536) 469 | 470 | n, err := stream.Read(buffer) 471 | if err != nil { 472 | log.Error("couldn't read bytes", err) 473 | return 474 | } 475 | 476 | frame, isCompleteFrame := frameExtractor.ExtractFrame(buffer[:n]) 477 | if isCompleteFrame { 478 | receiver.ReceiveData(frame) 479 | } 480 | } 481 | }() 482 | 483 | <-stopSignal 484 | receiver.CloseSession() 485 | log.Info("Closing usb stream") 486 | 487 | err = stream.Close() 488 | if err != nil { log.Error("Error closing stream", err) } 489 | log.Info("Closing usb interface") 490 | iface.Close() 491 | 492 | log.Info("Closing config") 493 | config.Close() 494 | 495 | sendQTDisable(usbDevice) 496 | 497 | return nil 498 | } 499 | 500 | func grabOutBulk(setting gousb.InterfaceSetting) (int, error) { 501 | for _, v := range setting.Endpoints { 502 | if v.Direction == gousb.EndpointDirectionOut { 503 | return v.Number, nil 504 | } 505 | } 506 | return 0, errors.New("Outbound Bulkendpoint not found") 507 | } 508 | 509 | func grabInBulk(setting gousb.InterfaceSetting) (int, error) { 510 | for _, v := range setting.Endpoints { 511 | if v.Direction == gousb.EndpointDirectionIn { 512 | return v.Number, nil 513 | } 514 | } 515 | return 0, errors.New("Inbound Bulkendpoint not found") 516 | } 517 | 518 | func findInterfaceForSubclass(config *gousb.Config, subClass gousb.Class) (bool, *gousb.Interface) { 519 | for _, ifaced := range config.Desc.Interfaces { 520 | if ifaced.AltSettings[0].Class == gousb.ClassVendorSpec && 521 | ifaced.AltSettings[0].SubClass == subClass { 522 | iface, _ := config.Interface( ifaced.Number, 0 ) 523 | return true, iface 524 | } 525 | } 526 | return false, nil 527 | } -------------------------------------------------------------------------------- /nanowriter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | //"fmt" 8 | "strconv" 9 | "time" 10 | cm "github.com/danielpaulus/quicktime_video_hack/screencapture/coremedia" 11 | 12 | "go.nanomsg.org/mangos/v3" 13 | // register transports 14 | _ "go.nanomsg.org/mangos/v3/transport/all" 15 | ) 16 | 17 | var startCode = []byte{00, 00, 00, 01} 18 | 19 | //ZMQWriter writes nalus into a file using 0x00000001 as a separator (h264 ANNEX B) and raw pcm audio into a wav file 20 | type NanoWriter struct { 21 | socket mangos.Socket 22 | buffer bytes.Buffer 23 | fh io.Writer 24 | } 25 | 26 | func NewNanoWriter( socket mangos.Socket ) NanoWriter { 27 | return NanoWriter{ socket: socket } 28 | } 29 | 30 | func NewFileWriter( fh io.Writer ) NanoWriter { 31 | return NanoWriter{ fh: fh } 32 | } 33 | 34 | //Consume writes PPS and SPS as well as sample bufs into a annex b .h264 file and audio samples into a wav file 35 | func (avfw NanoWriter) Consume(buf cm.CMSampleBuffer) error { 36 | if buf.MediaType == cm.MediaTypeSound { 37 | return avfw.consumeAudio(buf) 38 | } 39 | return avfw.consumeVideo(buf) 40 | } 41 | 42 | func (self NanoWriter) Stop() {} 43 | 44 | func (self NanoWriter) consumeVideo(buf cm.CMSampleBuffer) error { 45 | if buf.HasFormatDescription { 46 | err := self.writeNalu(buf.FormatDescription.PPS) 47 | if err != nil { 48 | return err 49 | } 50 | err = self.writeNalu(buf.FormatDescription.SPS) 51 | if err != nil { 52 | return err 53 | } 54 | } 55 | if !buf.HasSampleData() { 56 | return nil 57 | } 58 | return self.writeNalus(buf.SampleData) 59 | } 60 | 61 | func (self NanoWriter) writeNalus(bytes []byte) error { 62 | slice := bytes 63 | for len(slice) > 0 { 64 | length := binary.BigEndian.Uint32(slice) 65 | err := self.writeNalu(slice[4 : length+4]) 66 | if err != nil { 67 | return err 68 | } 69 | slice = slice[length+4:] 70 | } 71 | return nil 72 | } 73 | 74 | func (self NanoWriter) writeNalu(naluBytes []byte) error { 75 | now := strconv.FormatInt( time.Now().UnixNano()/1000000, 10 ) 76 | json := "{\"nalBytes\":" + strconv.Itoa( len( naluBytes ) + 4 ) + ",\"time\":" + now + "}"; 77 | 78 | var jsonLen uint16 = uint16( len( json ) ) 79 | binary.Write( &self.buffer, binary.LittleEndian, jsonLen ) 80 | self.buffer.Write( []byte( json ) ) 81 | 82 | _, err := self.buffer.Write(startCode) 83 | if err != nil { 84 | return err 85 | } 86 | _, err = self.buffer.Write(naluBytes) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | if self.fh == nil { 92 | self.socket.Send( self.buffer.Bytes() ) 93 | } else { 94 | self.fh.Write( self.buffer.Bytes() ) 95 | } 96 | self.buffer.Reset() 97 | return nil 98 | } 99 | 100 | func (self NanoWriter) consumeAudio(buffer cm.CMSampleBuffer) error { 101 | return nil 102 | } 103 | --------------------------------------------------------------------------------