Skip to content

Commit a3d506f

Browse files
authored
Fix multi device support, Fix Mac OS X, Output JSON (#34)
* add param * fix docopt * make devices command output json * fix log message * add udid to all methods * add udid to all methods * fix activation output * use correct format specifier for boolean * fix activate command, fix log output * refactor usb code * fix hanging bug * remove global context variable * refactor and clean * refactor to use err * improve help text * remove unused vars
1 parent 96f8d34 commit a3d506f

File tree

5 files changed

+234
-180
lines changed

5 files changed

+234
-180
lines changed

main.go

Lines changed: 84 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"bufio"
5+
"encoding/json"
6+
"fmt"
57
"os"
68
"os/signal"
79

@@ -12,73 +14,93 @@ import (
1214
log "github.com/sirupsen/logrus"
1315
)
1416

17+
const version = "v0.1-alpha"
18+
1519
func main() {
16-
usage := `Q.uickTime V.ideo H.ack or qvh client v0.01
17-
If you do not specify a udid, the first device will be taken by default.
20+
usage := fmt.Sprintf(`Q.uickTime V.ideo H.ack (qvh) %s
1821
1922
Usage:
20-
qvh devices
21-
qvh activate
22-
qvh record <h264file> <wavfile>
23-
qvh gstreamer
23+
qvh devices [-v]
24+
qvh activate [--udid=<udid>] [-v]
25+
qvh record <h264file> <wavfile> [-v]
26+
qvh gstreamer [-v]
27+
qvh --version | version
28+
2429
2530
Options:
26-
-h --help Show this screen.
27-
--version Show version.
28-
-u=<udid>, --udid UDID of the device.
29-
-o=<filepath>, --output
31+
-h --help Show this screen.
32+
-v Enable verbose mode (debug logging).
33+
--version Show version.
34+
--udid=<udid> UDID of the device. If not specified, the first found device will be used automatically.
3035
3136
The commands work as following:
3237
devices lists iOS devices attached to this host and tells you if video streaming was activated for them
33-
activate only enables the video streaming config for the given device
38+
activate enables the video streaming config for the device specified by --udid
3439
record will start video&audio recording. Video will be saved in a raw h264 file playable by VLC.
3540
Audio will be saved in a uncompressed wav file.
3641
Run like: "qvh record /home/yourname/out.h264 /home/yourname/out.wav"
37-
gstreamer qvh start an AppSrc and push AV data to gstreamer.
38-
`
42+
gstreamer qvh will open a new window and push AV data to gstreamer.
43+
`, version)
3944
arguments, _ := docopt.ParseDoc(usage)
40-
//TODO: add verbose switch to conf this
41-
log.SetLevel(log.DebugLevel)
42-
udid, _ := arguments.String("--udid")
43-
//TODO:add device selection here
44-
log.Info(udid)
45+
46+
verboseLoggingEnabled, _ := arguments.Bool("-v")
47+
if verboseLoggingEnabled {
48+
log.Info("Set Debug mode")
49+
log.SetLevel(log.DebugLevel)
50+
}
51+
shouldPrintVersionNoDashes, _ := arguments.Bool("version")
52+
shouldPrintVersion, _ := arguments.Bool("--version")
53+
if shouldPrintVersionNoDashes || shouldPrintVersion {
54+
printVersion()
55+
return
56+
}
4557

4658
devicesCommand, _ := arguments.Bool("devices")
4759
if devicesCommand {
4860
devices()
4961
return
5062
}
5163

64+
udid, _ := arguments.String("--udid")
65+
log.Debugf("requested udid:'%s'", udid)
66+
5267
activateCommand, _ := arguments.Bool("activate")
5368
if activateCommand {
54-
activate()
69+
activate(udid)
5570
return
5671
}
5772

58-
rawStreamCommand, _ := arguments.Bool("record")
59-
if rawStreamCommand {
73+
recordCommand, _ := arguments.Bool("record")
74+
if recordCommand {
6075
h264FilePath, err := arguments.String("<h264file>")
6176
if err != nil {
62-
log.Error("Missing <h264file> parameter. Please specify a valid path like '/home/me/out.h264'")
77+
printErrJSON(err, "Missing <h264file> parameter. Please specify a valid path like '/home/me/out.h264'")
6378
return
6479
}
6580
waveFilePath, err := arguments.String("<wavfile>")
6681
if err != nil {
67-
log.Error("Missing <wavfile> parameter. Please specify a valid path like '/home/me/out.raw'")
82+
printErrJSON(err, "Missing <wavfile> parameter. Please specify a valid path like '/home/me/out.raw'")
6883
return
6984
}
70-
record(h264FilePath, waveFilePath)
85+
record(h264FilePath, waveFilePath, udid)
7186
}
7287
gstreamerCommand, _ := arguments.Bool("gstreamer")
7388
if gstreamerCommand {
74-
startGStreamer()
89+
startGStreamer(udid)
90+
}
91+
}
92+
93+
func printVersion() {
94+
versionMap := map[string]interface{}{
95+
"version": version,
7596
}
97+
printJSON(versionMap)
7698
}
7799

78-
func startGStreamer() {
79-
log.Infof("Starting Gstreamer")
100+
func startGStreamer(udid string) {
101+
log.Debug("Starting Gstreamer")
80102
gStreamer := gstadapter.New()
81-
startWithConsumer(gStreamer)
103+
startWithConsumer(gStreamer, udid)
82104
}
83105

84106
func waitForSigInt(stopSignalChannel chan interface{}) {
@@ -95,51 +117,37 @@ func waitForSigInt(stopSignalChannel chan interface{}) {
95117

96118
// Just dump a list of what was discovered to the console
97119
func devices() {
98-
cleanup := screencapture.Init()
99120
deviceList, err := screencapture.FindIosDevices()
100-
defer cleanup()
101-
log.Infof("(%d) iOS Devices with UsbMux Endpoint:", len(deviceList))
102-
103121
if err != nil {
104-
log.Fatal("Error finding iOS Devices", err)
122+
printErrJSON(err, "Error finding iOS Devices")
105123
}
106-
output := screencapture.PrintDeviceDetails(deviceList)
107-
log.Info(output)
108-
}
124+
log.Debugf("Found (%d) iOS Devices with UsbMux Endpoint", len(deviceList))
109125

110-
// This command is for testing if we can enable the hidden Quicktime device config
111-
func activate() {
112-
cleanup := screencapture.Init()
113-
deviceList, err := screencapture.FindIosDevices()
114-
defer cleanup()
115126
if err != nil {
116-
log.Fatal("Error finding iOS Devices", err)
127+
printErrJSON(err, "Error finding iOS Devices")
117128
}
129+
output := screencapture.PrintDeviceDetails(deviceList)
118130

119-
log.Info("iOS Devices with UsbMux Endpoint:")
131+
printJSON(map[string]interface{}{"devices": output})
132+
}
120133

121-
output := screencapture.PrintDeviceDetails(deviceList)
122-
log.Info(output)
134+
// This command is for testing if we can enable the hidden Quicktime device config
135+
func activate(udid string) {
136+
device, err := screencapture.FindIosDevice(udid)
123137

124-
err = screencapture.EnableQTConfig(deviceList)
138+
log.Debugf("Enabling device: %v", device)
139+
device, err = screencapture.EnableQTConfig(device)
125140
if err != nil {
126141
log.Fatal("Error enabling QT config", err)
127142
}
128143

129-
qtDevices, err := screencapture.FindIosDevicesWithQTEnabled()
130-
if err != nil {
131-
log.Fatal("Error finding QT Devices", err)
132-
}
133-
qtOutput := screencapture.PrintDeviceDetails(qtDevices)
134-
if len(qtDevices) != len(deviceList) {
135-
log.Warnf("Less qt devices (%d) than plain usbmux devices (%d)", len(qtDevices), len(deviceList))
136-
}
137-
log.Info("iOS Devices with QT Endpoint:")
138-
log.Info(qtOutput)
144+
printJSON(map[string]interface{}{
145+
"device_activated": device.DetailsMap(),
146+
})
139147
}
140148

141-
func record(h264FilePath string, wavFilePath string) {
142-
log.Infof("Writing video output to:'%s' and audio to: %s", h264FilePath, wavFilePath)
149+
func record(h264FilePath string, wavFilePath string, udid string) {
150+
log.Debugf("Writing video output to:'%s' and audio to: %s", h264FilePath, wavFilePath)
143151

144152
h264File, err := os.Create(h264FilePath)
145153
if err != nil {
@@ -173,14 +181,14 @@ func record(h264FilePath string, wavFilePath string) {
173181
}
174182

175183
}()
176-
startWithConsumer(writer)
184+
startWithConsumer(writer, udid)
177185
}
178186

179-
func startWithConsumer(consumer screencapture.CmSampleBufConsumer) {
180-
activate()
181-
cleanup := screencapture.Init()
187+
func startWithConsumer(consumer screencapture.CmSampleBufConsumer, udid string) {
188+
activate(udid)
189+
182190
deviceList, err := screencapture.FindIosDevices()
183-
defer cleanup()
191+
184192
if err != nil {
185193
log.Fatal("Error finding iOS Devices", err)
186194
}
@@ -194,3 +202,16 @@ func startWithConsumer(consumer screencapture.CmSampleBufConsumer) {
194202

195203
adapter.StartReading(dev, &mp, stopSignal)
196204
}
205+
func printErrJSON(err error, msg string) {
206+
printJSON(map[string]interface{}{
207+
"originalError": err.Error(),
208+
"message": msg,
209+
})
210+
}
211+
func printJSON(output map[string]interface{}) {
212+
text, err := json.Marshal(output)
213+
if err != nil {
214+
log.Fatalf("Broken json serialization, error: %s", err)
215+
}
216+
println(string(text))
217+
}

screencapture/activator.go

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,63 @@ package screencapture
22

33
import (
44
"fmt"
5+
"time"
6+
57
"github.com/google/gousb"
68
log "github.com/sirupsen/logrus"
7-
"time"
89
)
910

1011
// EnableQTConfig enables the hidden QuickTime Device configuration that will expose two new bulk endpoints.
1112
// We will send a control transfer to the device via USB which will cause the device to disconnect and then
1213
// re-connect with a new device configuration. Usually the usbmuxd will automatically enable that new config
1314
// as it will detect it as the device's preferredConfig.
14-
func EnableQTConfig(devices []IosDevice) error {
15-
for _, device := range devices {
16-
err := enableQTConfigSingleDevice(device)
17-
if err != nil {
18-
return err
19-
}
20-
}
21-
return nil
22-
}
23-
24-
func enableQTConfigSingleDevice(device IosDevice) error {
15+
func EnableQTConfig(device IosDevice) (IosDevice, error) {
2516
udid := device.SerialNumber
26-
if isValidIosDeviceWithActiveQTConfig(device.usbDevice.Desc) {
17+
ctx := gousb.NewContext()
18+
usbDevice, err := ctx.OpenDeviceWithVIDPID(device.VID, device.PID)
19+
if err != nil {
20+
return IosDevice{}, err
21+
}
22+
if isValidIosDeviceWithActiveQTConfig(usbDevice.Desc) {
2723
log.Debugf("Skipping %s because it already has an active QT config", udid)
28-
return nil
24+
return device, nil
2925
}
3026

31-
err := sendQTConfigControlRequest(device)
27+
err = sendQTConfigControlRequest(usbDevice)
3228
if err != nil {
33-
return err
29+
return IosDevice{}, err
3430
}
3531

3632
var i int
3733
for {
38-
log.Infof("Checking for active QT config for %s", udid)
39-
time.Sleep(500 * time.Millisecond)
34+
log.Debugf("Checking for active QT config for %s", udid)
35+
4036
err = ctx.Close()
4137
if err != nil {
4238
log.Warn("failed closing context", err)
4339
}
40+
time.Sleep(500 * time.Millisecond)
4441
log.Debug("Reopening Context")
4542
ctx = gousb.NewContext()
46-
device.usbDevice, err = findBySerialNumber(udid)
43+
device, err = device.ReOpen(ctx)
4744
if err != nil {
4845
log.Debugf("device not found:%s", err)
4946
continue
5047
}
5148
i++
5249
if i > 10 {
53-
log.Error("Failed activating config")
54-
return fmt.Errorf("could not activate Quicktime Config for %s", udid)
50+
log.Debug("Failed activating config")
51+
return IosDevice{}, fmt.Errorf("could not activate Quicktime Config for %s", udid)
5552
}
5653
break
5754
}
58-
log.Infof("QTConfig for %s activated", udid)
59-
return err
55+
log.Debugf("QTConfig for %s activated", udid)
56+
return device, err
6057
}
6158

62-
func sendQTConfigControlRequest(device IosDevice) error {
59+
func sendQTConfigControlRequest(device *gousb.Device) error {
6360
response := make([]byte, 0)
64-
val, err := device.usbDevice.Control(0x40, 0x52, 0x00, 0x02, response)
61+
val, err := device.Control(0x40, 0x52, 0x00, 0x02, response)
6562

6663
if err != nil {
6764
log.Warn("Failed sending control transfer for enabling hidden QT config. Seems like this happens sometimes but it still works usually.", err)
@@ -70,9 +67,9 @@ func sendQTConfigControlRequest(device IosDevice) error {
7067
return nil
7168
}
7269

73-
func sendQTDisableConfigControlRequest(device IosDevice) error {
70+
func sendQTDisableConfigControlRequest(device *gousb.Device) error {
7471
response := make([]byte, 0)
75-
val, err := device.usbDevice.Control(0x40, 0x52, 0x00, 0x00, response)
72+
val, err := device.Control(0x40, 0x52, 0x00, 0x00, response)
7673

7774
if err != nil {
7875
log.Fatal("Failed sending control transfer for disabling hidden QT config", err)

0 commit comments

Comments
 (0)