diff options
Diffstat (limited to 'tools/idevicescreenshot.c')
-rw-r--r-- | tools/idevicescreenshot.c | 259 |
1 files changed, 181 insertions, 78 deletions
diff --git a/tools/idevicescreenshot.c b/tools/idevicescreenshot.c index 8567f77..0e694c7 100644 --- a/tools/idevicescreenshot.c +++ b/tools/idevicescreenshot.c | |||
@@ -1,113 +1,227 @@ | |||
1 | /** | 1 | /* |
2 | * idevicescreenshot -- Gets a screenshot from a connected iPhone/iPod Touch | 2 | * idevicescreenshot.c |
3 | * Gets a screenshot from a device | ||
3 | * | 4 | * |
4 | * Copyright (C) 2010 Nikias Bassen <nikias@gmx.li> | 5 | * Copyright (C) 2010 Nikias Bassen <nikias@gmx.li> |
5 | * | 6 | * |
6 | * Licensed under the GNU General Public License Version 2 | 7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public | ||
9 | * License as published by the Free Software Foundation; either | ||
10 | * version 2.1 of the License, or (at your option) any later version. | ||
7 | * | 11 | * |
8 | * This program is free software; you can redistribute it and/or modify | 12 | * This library is distributed in the hope that it will be useful, |
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 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 | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * GNU General Public License for more profile. | 15 | * Lesser General Public License for more details. |
17 | * | 16 | * |
18 | * You should have received a copy of the GNU General Public License | 17 | * You should have received a copy of the GNU Lesser General Public |
19 | * along with this program; if not, write to the Free Software | 18 | * License along with this library; if not, write to the Free Software |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 | 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
21 | * USA | ||
22 | */ | 20 | */ |
23 | 21 | ||
22 | #ifdef HAVE_CONFIG_H | ||
23 | #include <config.h> | ||
24 | #endif | ||
25 | |||
26 | #define TOOL_NAME "idevicescreenshot" | ||
27 | |||
24 | #include <stdio.h> | 28 | #include <stdio.h> |
25 | #include <string.h> | 29 | #include <string.h> |
26 | #include <stdlib.h> | 30 | #include <stdlib.h> |
31 | #include <getopt.h> | ||
27 | #include <errno.h> | 32 | #include <errno.h> |
28 | #include <time.h> | 33 | #include <time.h> |
34 | #include <unistd.h> | ||
35 | #ifndef WIN32 | ||
36 | #include <signal.h> | ||
37 | #endif | ||
29 | 38 | ||
30 | #include <libimobiledevice/libimobiledevice.h> | 39 | #include <libimobiledevice/libimobiledevice.h> |
31 | #include <libimobiledevice/lockdown.h> | 40 | #include <libimobiledevice/lockdown.h> |
32 | #include <libimobiledevice/screenshotr.h> | 41 | #include <libimobiledevice/screenshotr.h> |
33 | 42 | ||
34 | void print_usage(int argc, char **argv); | 43 | static void get_image_filename(char *imgdata, char **filename) |
44 | { | ||
45 | // If the provided filename already has an extension, use it as is. | ||
46 | if (*filename) { | ||
47 | char *last_dot = strrchr(*filename, '.'); | ||
48 | if (last_dot && !strchr(last_dot, '/')) { | ||
49 | return; | ||
50 | } | ||
51 | } | ||
52 | |||
53 | // Find the appropriate file extension for the filename. | ||
54 | const char *fileext = NULL; | ||
55 | if (memcmp(imgdata, "\x89PNG", 4) == 0) { | ||
56 | fileext = ".png"; | ||
57 | } else if (memcmp(imgdata, "MM\x00*", 4) == 0) { | ||
58 | fileext = ".tiff"; | ||
59 | } else { | ||
60 | printf("WARNING: screenshot data has unexpected image format.\n"); | ||
61 | fileext = ".dat"; | ||
62 | } | ||
63 | |||
64 | // If a filename without an extension is provided, append the extension. | ||
65 | // Otherwise, generate a filename based on the current time. | ||
66 | char *basename = NULL; | ||
67 | if (*filename) { | ||
68 | basename = (char*)malloc(strlen(*filename) + 1); | ||
69 | strcpy(basename, *filename); | ||
70 | free(*filename); | ||
71 | *filename = NULL; | ||
72 | } else { | ||
73 | time_t now = time(NULL); | ||
74 | basename = (char*)malloc(32); | ||
75 | strftime(basename, 31, "screenshot-%Y-%m-%d-%H-%M-%S", gmtime(&now)); | ||
76 | } | ||
77 | |||
78 | // Ensure the filename is unique on disk. | ||
79 | char *unique_filename = (char*)malloc(strlen(basename) + strlen(fileext) + 7); | ||
80 | sprintf(unique_filename, "%s%s", basename, fileext); | ||
81 | int i; | ||
82 | for (i = 2; i < (1 << 16); i++) { | ||
83 | if (access(unique_filename, F_OK) == -1) { | ||
84 | *filename = unique_filename; | ||
85 | break; | ||
86 | } | ||
87 | sprintf(unique_filename, "%s-%d%s", basename, i, fileext); | ||
88 | } | ||
89 | if (!*filename) { | ||
90 | free(unique_filename); | ||
91 | } | ||
92 | free(basename); | ||
93 | } | ||
94 | |||
95 | static void print_usage(int argc, char **argv, int is_error) | ||
96 | { | ||
97 | char *name = strrchr(argv[0], '/'); | ||
98 | fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [FILE]\n", (name ? name + 1: argv[0])); | ||
99 | fprintf(is_error ? stderr : stdout, | ||
100 | "\n" | ||
101 | "Gets a screenshot from a connected device.\n" | ||
102 | "\n" | ||
103 | "The image is in PNG format for iOS 9+ and otherwise in TIFF format.\n" | ||
104 | "The screenshot is saved as an image with the given FILE name.\n" | ||
105 | "If FILE has no extension, FILE will be a prefix of the saved filename.\n" | ||
106 | "If FILE is not specified, \"screenshot-DATE\", will be used as a prefix\n" | ||
107 | "of the filename, e.g.:\n" | ||
108 | " ./screenshot-2013-12-31-23-59-59.tiff\n" | ||
109 | "\n" | ||
110 | "NOTE: A mounted developer disk image is required on the device, otherwise\n" | ||
111 | "the screenshotr service is not available.\n" | ||
112 | "\n" | ||
113 | " -u, --udid UDID target specific device by UDID\n" | ||
114 | " -n, --network connect to network device\n" | ||
115 | " -d, --debug enable communication debugging\n" | ||
116 | " -h, --help prints usage information\n" | ||
117 | " -v, --version prints version information\n" | ||
118 | "\n" | ||
119 | "Homepage: <" PACKAGE_URL ">\n" | ||
120 | "Bug Reports: <" PACKAGE_BUGREPORT ">\n" | ||
121 | ); | ||
122 | } | ||
35 | 123 | ||
36 | int main(int argc, char **argv) | 124 | int main(int argc, char **argv) |
37 | { | 125 | { |
38 | idevice_t device = NULL; | 126 | idevice_t device = NULL; |
39 | lockdownd_client_t lckd = NULL; | 127 | lockdownd_client_t lckd = NULL; |
128 | lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; | ||
40 | screenshotr_client_t shotr = NULL; | 129 | screenshotr_client_t shotr = NULL; |
41 | uint16_t port = 0; | 130 | lockdownd_service_descriptor_t service = NULL; |
42 | int result = -1; | 131 | int result = -1; |
43 | int i; | 132 | const char *udid = NULL; |
44 | char *uuid = NULL; | 133 | int use_network = 0; |
134 | char *filename = NULL; | ||
135 | int c = 0; | ||
136 | const struct option longopts[] = { | ||
137 | { "debug", no_argument, NULL, 'd' }, | ||
138 | { "help", no_argument, NULL, 'h' }, | ||
139 | { "udid", required_argument, NULL, 'u' }, | ||
140 | { "network", no_argument, NULL, 'n' }, | ||
141 | { "version", no_argument, NULL, 'v' }, | ||
142 | { NULL, 0, NULL, 0} | ||
143 | }; | ||
45 | 144 | ||
145 | #ifndef WIN32 | ||
146 | signal(SIGPIPE, SIG_IGN); | ||
147 | #endif | ||
46 | /* parse cmdline args */ | 148 | /* parse cmdline args */ |
47 | for (i = 1; i < argc; i++) { | 149 | |
48 | if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { | 150 | /* parse cmdline arguments */ |
151 | while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { | ||
152 | switch (c) { | ||
153 | case 'd': | ||
49 | idevice_set_debug_level(1); | 154 | idevice_set_debug_level(1); |
50 | continue; | 155 | break; |
51 | } | 156 | case 'u': |
52 | else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) { | 157 | if (!*optarg) { |
53 | i++; | 158 | fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); |
54 | if (!argv[i] || (strlen(argv[i]) != 40)) { | 159 | print_usage(argc, argv, 1); |
55 | print_usage(argc, argv); | 160 | return 2; |
56 | return 0; | ||
57 | } | 161 | } |
58 | uuid = strdup(argv[i]); | 162 | udid = optarg; |
59 | continue; | 163 | break; |
60 | } | 164 | case 'n': |
61 | else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { | 165 | use_network = 1; |
62 | print_usage(argc, argv); | 166 | break; |
167 | case 'h': | ||
168 | print_usage(argc, argv, 0); | ||
63 | return 0; | 169 | return 0; |
64 | } | 170 | case 'v': |
65 | else { | 171 | printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); |
66 | print_usage(argc, argv); | ||
67 | return 0; | 172 | return 0; |
173 | default: | ||
174 | print_usage(argc, argv, 1); | ||
175 | return 2; | ||
68 | } | 176 | } |
69 | } | 177 | } |
178 | argc -= optind; | ||
179 | argv += optind; | ||
70 | 180 | ||
71 | if (IDEVICE_E_SUCCESS != idevice_new(&device, uuid)) { | 181 | if (argv[0]) { |
72 | printf("No device found, is it plugged in?\n"); | 182 | filename = strdup(argv[0]); |
73 | if (uuid) { | 183 | } |
74 | free(uuid); | 184 | |
185 | if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) { | ||
186 | if (udid) { | ||
187 | printf("No device found with udid %s.\n", udid); | ||
188 | } else { | ||
189 | printf("No device found.\n"); | ||
75 | } | 190 | } |
76 | return -1; | 191 | return -1; |
77 | } | 192 | } |
78 | if (uuid) { | ||
79 | free(uuid); | ||
80 | } | ||
81 | 193 | ||
82 | if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lckd, NULL)) { | 194 | if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lckd, TOOL_NAME))) { |
83 | idevice_free(device); | 195 | idevice_free(device); |
84 | printf("Exiting.\n"); | 196 | printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret); |
85 | return -1; | 197 | return -1; |
86 | } | 198 | } |
87 | 199 | ||
88 | lockdownd_start_service(lckd, "com.apple.mobile.screenshotr", &port); | 200 | lockdownd_error_t lerr = lockdownd_start_service(lckd, SCREENSHOTR_SERVICE_NAME, &service); |
89 | lockdownd_client_free(lckd); | 201 | lockdownd_client_free(lckd); |
90 | if (port > 0) { | 202 | if (lerr == LOCKDOWN_E_SUCCESS) { |
91 | if (screenshotr_client_new(device, port, &shotr) != SCREENSHOTR_E_SUCCESS) { | 203 | if (screenshotr_client_new(device, service, &shotr) != SCREENSHOTR_E_SUCCESS) { |
92 | printf("Could not connect to screenshotr!\n"); | 204 | printf("Could not connect to screenshotr!\n"); |
93 | } else { | 205 | } else { |
94 | char *imgdata = NULL; | 206 | char *imgdata = NULL; |
95 | char filename[36]; | ||
96 | uint64_t imgsize = 0; | 207 | uint64_t imgsize = 0; |
97 | time_t now = time(NULL); | ||
98 | strftime(filename, 36, "screenshot-%Y-%m-%d-%H-%M-%S.tiff", gmtime(&now)); | ||
99 | if (screenshotr_take_screenshot(shotr, &imgdata, &imgsize) == SCREENSHOTR_E_SUCCESS) { | 208 | if (screenshotr_take_screenshot(shotr, &imgdata, &imgsize) == SCREENSHOTR_E_SUCCESS) { |
100 | FILE *f = fopen(filename, "w"); | 209 | get_image_filename(imgdata, &filename); |
101 | if (f) { | 210 | if (!filename) { |
102 | if (fwrite(imgdata, 1, (size_t)imgsize, f) == (size_t)imgsize) { | 211 | printf("FATAL: Could not find a unique filename!\n"); |
103 | printf("Screenshot saved to %s\n", filename); | 212 | } else { |
104 | result = 0; | 213 | FILE *f = fopen(filename, "wb"); |
214 | if (f) { | ||
215 | if (fwrite(imgdata, 1, (size_t)imgsize, f) == (size_t)imgsize) { | ||
216 | printf("Screenshot saved to %s\n", filename); | ||
217 | result = 0; | ||
218 | } else { | ||
219 | printf("Could not save screenshot to file %s!\n", filename); | ||
220 | } | ||
221 | fclose(f); | ||
105 | } else { | 222 | } else { |
106 | printf("Could not save screenshot to file %s!\n", filename); | 223 | printf("Could not open %s for writing: %s\n", filename, strerror(errno)); |
107 | } | 224 | } |
108 | fclose(f); | ||
109 | } else { | ||
110 | printf("Could not open %s for writing: %s\n", filename, strerror(errno)); | ||
111 | } | 225 | } |
112 | } else { | 226 | } else { |
113 | printf("Could not get screenshot!\n"); | 227 | printf("Could not get screenshot!\n"); |
@@ -115,25 +229,14 @@ int main(int argc, char **argv) | |||
115 | screenshotr_client_free(shotr); | 229 | screenshotr_client_free(shotr); |
116 | } | 230 | } |
117 | } else { | 231 | } else { |
118 | printf("Could not start screenshotr service! Remember that you have to mount the Developer disk image on your device if you want to use the screenshotr service.\n"); | 232 | printf("Could not start screenshotr service: %s\nRemember that you have to mount the Developer disk image on your device if you want to use the screenshotr service.\n", lockdownd_strerror(lerr)); |
119 | } | 233 | } |
234 | |||
235 | if (service) | ||
236 | lockdownd_service_descriptor_free(service); | ||
237 | |||
120 | idevice_free(device); | 238 | idevice_free(device); |
121 | 239 | free(filename); | |
122 | return result; | ||
123 | } | ||
124 | 240 | ||
125 | void print_usage(int argc, char **argv) | 241 | return result; |
126 | { | ||
127 | char *name = NULL; | ||
128 | |||
129 | name = strrchr(argv[0], '/'); | ||
130 | printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); | ||
131 | printf("Gets a screenshot from the connected iPhone/iPod Touch.\n"); | ||
132 | printf("The screenshot is saved as a TIFF image in the current directory.\n"); | ||
133 | printf("NOTE: A mounted developer disk image is required on the device, otherwise\n"); | ||
134 | printf("the screenshotr service is not available.\n\n"); | ||
135 | printf(" -d, --debug\t\tenable communication debugging\n"); | ||
136 | printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n"); | ||
137 | printf(" -h, --help\t\tprints usage information\n"); | ||
138 | printf("\n"); | ||
139 | } | 242 | } |