diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..40f3f90e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,14 @@ +{ + "env": { + "node": true + }, + "rules": { + "comma-style": [2, "first"], + "no-extra-semi": 2, + "no-underscore-dangle": 0, + "quotes": [2, "single"], + "semi": [2, "never"], + "space-before-blocks": [2, "always"], + "strict": [0, "function"] + } +} diff --git a/.npmignore b/.npmignore index 4c8400c9..4b1edeec 100644 --- a/.npmignore +++ b/.npmignore @@ -2,6 +2,7 @@ .DS_Store /*.tgz /.bowerrc +/.dockerignore /.editorconfig /.env /.gitignore @@ -9,11 +10,12 @@ /.jscsrc /.npmignore /.npmrc +/.travis.yml +/docker /Dockerfile /bower.json /component.json /gulpfile.js -/node_modules/ /npm-debug.log /res/bower_components/ /res/test/ diff --git a/.travis.yml b/.travis.yml index ac7b3e17..a43b0bd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,61 @@ -language: node_js +language: cpp + +os: + - linux + - osx + sudo: false -node_js: - - "0.12" - - "0.10" - - "iojs" + addons: apt: + sources: + - ubuntu-toolchain-r-test packages: - - libzmq3-dev - - libprotobuf-dev - - graphicsmagick - - rethinkdb -script: - - gulp build + - libzmq3-dev + - libprotobuf-dev + - graphicsmagick + - rethinkdb + - g++-4.9 + - yasm + +env: + matrix: + - NODE_VERSION=0.12 + - NODE_VERSION=4 + +matrix: + allow_failures: + - os: osx + fast_finish: true + +before_install: + - rm -rf ~/.nvm && git clone --depth 1 https://github.com/creationix/nvm.git ~/.nvm + - source ~/.nvm/nvm.sh + - nvm install $NODE_VERSION + - node --version + - npm --version + - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then export CXX=g++-4.9; fi + - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config; fi + +install: + - npm install + - export PATH=$PWD/node_modules/.bin:$PATH + before_script: - - npm install -g bower - - bower install +# - rethinkdb --daemon + +script: + - npm test +# - ./bin/stf local + - gulp build + +after_script: +# - killall rethinkdb + cache: directories: - node_modules - res/bower_components + notifications: slack: openstf:qu01BtEgttJOrGGsRxKBJwki diff --git a/Dockerfile b/Dockerfile index 69fc8fc6..4f89d3d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,4 @@ -FROM openstf/base:v1.0.1 - -# Add a user for the app. -RUN useradd --system \ - --no-create-home \ - --shell /usr/sbin/nologin \ - --home-dir /app \ - stf +FROM openstf/base:v1.0.6 # Sneak the stf executable into $PATH. ENV PATH /app/bin:$PATH @@ -18,15 +11,31 @@ WORKDIR /app EXPOSE 3000 # Copy app source. -COPY . /app/ +COPY . /tmp/build/ -# Get the rest of the dependencies and build. -RUN export PATH=/app/node_modules/.bin:$PATH && \ - npm install && \ - bower install --allow-root && \ - gulp build +# Give permissions to our build user. +RUN mkdir -p /app && \ + chown -R stf-build:stf-build /tmp/build /app -# Switch to weak user. +# Switch over to the build user. +USER stf-build + +# Run the build. +RUN set -x && \ + cd /tmp/build && \ + export PATH=$PWD/node_modules/.bin:$PATH && \ + npm install --loglevel http && \ + npm pack && \ + tar xzf stf-*.tgz --strip-components 1 -C /app && \ + bower cache clean && \ + npm prune --production && \ + mv node_modules /app && \ + npm cache clean && \ + rm -rf ~/.node-gyp && \ + cd /app && \ + rm -rf /tmp/* + +# Switch to the app user. USER stf # Show help by default. diff --git a/README.md b/README.md index 050e5460..53c2ace0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ It is currently being used at [CyberAgent](https://www.cyberagent.co.jp/en/) to * OS support - Android - * Supports versions 2.3.3 (SDK level 10) to 5.1 (SDK level 22), plus Android M Developer Preview + * Supports versions 2.3.3 (SDK level 10) to 5.1 (SDK level 22), plus Android M Developer Preview 3 * Supports Wear 5.1 (but not 5.0 due to missing permissions) * Supports Fire OS, CyanogenMod, and other heavily Android based distributions * `root` is **not** required for any current functionality @@ -43,6 +43,7 @@ It is currently being used at [CyberAgent](https://www.cyberagent.co.jp/en/) to * Run any `adb` command locally, including shell access * [Android Studio](http://developer.android.com/tools/studio/index.html) and other IDE support, debug your app while watching the device screen on your browser * Supports [Chrome remote debug tools](https://developer.chrome.com/devtools/docs/remote-debugging) + - Experimental VNC support (work in progress) * Manage your device inventory - See which devices are connected, offline/unavailable (indicating a weak USB connection), unauthorized or unplugged - See who's using a device @@ -65,14 +66,15 @@ As the product has evolved from an internal tool running in our internal network * [GraphicsMagick](http://www.graphicsmagick.org/) (for resizing screenshots) * [ZeroMQ](http://zeromq.org/) libraries installed * [Protocol Buffers](https://github.com/google/protobuf) libraries installed +* [yasm](http://yasm.tortall.net/) installed (for compiling embedded [libjpeg-turbo](https://github.com/sorccu/node-jpeg-turbo)) * [pkg-config](http://www.freedesktop.org/wiki/Software/pkg-config/) so that Node.js can find the libraries -Note that you need these dependencies even if you've installed STF directly from [NPM](https://www.npmjs.com/), because they can't be included. +Note that you need these dependencies even if you've installed STF directly from [NPM](https://www.npmjs.com/), because they can't be included in the package. On OS X, you can use [homebrew](http://brew.sh/) to install most of the dependencies: ```bash -brew install rethinkdb graphicsmagick zeromq protobuf pkg-config +brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config ``` On Windows you're on your own. In theory you might be able to get STF installed via [Cygwin](https://www.cygwin.com/) or similar, but we've never tried. In principle we will not provide any Windows installation support, but please do send a documentation pull request if you figure out what to do. diff --git a/bower.json b/bower.json index 8827fcae..1e86fa1e 100644 --- a/bower.json +++ b/bower.json @@ -2,47 +2,46 @@ "name": "stf", "version": "0.1.0", "dependencies": { - "angular": "1.4.3", - "angular-cookies": "1.4.3", - "angular-route": "1.4.3", - "angular-sanitize": "1.4.3", - "angular-animate": "1.4.3", - "angular-touch": "1.4.3", + "angular": "~1.4.7", + "angular-cookies": "~1.4.7", + "angular-route": "~1.4.7", + "angular-sanitize": "~1.4.7", + "angular-animate": "~1.4.7", + "angular-touch": "~1.4.7", "lodash": "~3.10.1", "oboe": "~2.1.2", - "ng-table": "~0.8.1", + "ng-table": "~1.0.0-beta.7", "angular-gettext": "~2.1.0", "angular-ui-ace": "~0.2.3", - "angular-bootstrap": "~0.13.2", "angular-dialog-service": "~5.2.6", "ng-file-upload": "~2.0.5", "angular-growl-v2": "JanStevens/angular-growl-2#~0.7.3", - "underscore.string": "~3.1.1", + "underscore.string": "~3.2.2", "bootstrap": "~3.3.5", "font-lato-2-subset": "~0.4.0", - "packery": "~1.4.2", + "packery": "~1.4.3", "draggabilly": "~1.2.4", - "angular-elastic": "~2.5.0", - "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.4.5", + "angular-elastic": "~2.5.1", + "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.6.0", "angular-borderlayout": "git://github.com/filearts/angular-borderlayout.git#7c9716aebd9260763f798561ca49d6fbfd4a5c67", - "angular-ui-bootstrap": "~0.13.2", - "ng-context-menu": "~1.0.1", + "angular-ui-bootstrap": "~0.14.2", + "ng-context-menu": "AdiDahan/ng-context-menu#~1.0.5", "components-font-awesome": "~4.4.0", "epoch": "~0.6.0", "ng-epoch": "~1.0.7", - "eventEmitter": "~4.2.11", + "eventEmitter": "~4.3.0", "angular-ladda": "~0.3.1", "d3": "~3.5.6", "spin.js": "~2.3.2" }, "private": true, "devDependencies": { - "angular-mocks": "1.4.3" + "angular-mocks": "~1.4.7" }, "resolutions": { - "angular": "1.4.3", + "angular": "~1.4.7", "d3": "~3.5.5", "spin.js": "~2.3.2", - "angular-bootstrap": "~0.13.2" + "eventEmitter": "~4.3.0" } } diff --git a/doc/DEPLOYMENT.md b/doc/DEPLOYMENT.md index d79a07ce..524c9a15 100644 --- a/doc/DEPLOYMENT.md +++ b/doc/DEPLOYMENT.md @@ -56,6 +56,12 @@ The app role can contain any of the following units. You may distribute them as * [stf-triproxy-dev.service](#stf-triproxy-devservice) * [stf-websocket@.service](#stf-websocketservice) +### Database role + +The database role requires the following units, UNLESS you already have a working RethinkDB server/cluster running somewhere. In that case you simply will not have this role, and should point your [rethinkdb-proxy-28015.service](#rethinkdb-proxy-28015service) to that server instead. + +* [rethinkdb.service](#rethinkdbservice) + ### Proxy role The proxy role ties all HTTP-based units together behind a common reverse proxy. See [nginx configuration](#nginx-configuration) for more information. @@ -91,13 +97,29 @@ ExecStart=/usr/bin/docker run --rm \ ExecStop=-/usr/bin/docker stop -t 2 %p ``` -### `rethinkdb-proxy-28015.service` +### `rethinkdb.service` -You need a single instance of the `rethinkdb-proxy-28015.service` unit on each host where you have another unit that needs to access the database. Having a local proxy simplifies configuration for other units and allows the `AUTHKEY` to be specified only once. +As mentioned before, you only need this unit if you do not have an existing RethinkDB cluster. This configuration is provided as an example, and will get you going, but is not very robust or secure. + +If you need to expand your RethinkDB cluster beyond one server you may encounter problems that you'll have to solve by yourself, we're not going to help with that. There are many ways to configure the unit, this is just one possibility! Note that if you end up not using `--net host`, you will then have to give `rethinkdb` the `--canonical-address` option with the server's real IP, and expose the necessary ports somehow. + +You will also have to: + +1. Modify the `--cache-size` as you please. It limits the amount of memory RethinkDB uses and is given in megabytes, but is not an absolute limit! Real usage can be slightly higher. +2. Update the version number in `rethinkdb:2.1.1` for the latest release. We don't use `rethinkdb:latest` here because then you might occasionally have to manually rebuild your indexes after an update and not even realize it, bringing the whole system effectively down. +3. The `AUTHKEY` environment variable is only for convenience when linking. So, the first time you set things up, you will have to access http://DB_SERVER_IP:8080 after starting the unit and run the following command: + +```javascript +r.db('rethinkdb').table('cluster_config').get('auth').update({auth_key: 'newkey'}) +``` + +More information can be found [here](https://rethinkdb.com/docs/security/). You will then need to replace `YOUR_RETHINKDB_AUTH_KEY_HERE_IF_ANY` in the the rest of the units with the real authentication key. + +Here's the unit configuration itself. ```ini [Unit] -Description=RethinkDB proxy/28015 +Description=RethinkDB After=docker.service Requires=docker.service @@ -105,7 +127,39 @@ Requires=docker.service EnvironmentFile=/etc/environment TimeoutStartSec=0 Restart=always -ExecStartPre=/usr/bin/docker pull ctlc/ambassador:latest +ExecStartPre=/usr/bin/docker pull rethinkdb:2.1.1 +ExecStartPre=-/usr/bin/docker kill %p +ExecStartPre=-/usr/bin/docker rm %p +ExecStartPre=/usr/bin/mkdir -p /srv/rethinkdb +ExecStartPre=/usr/bin/chattr -R +C /srv/rethinkdb +ExecStart=/usr/bin/docker run --rm \ + --name %p \ + -v /srv/rethinkdb:/data \ + -e "AUTHKEY=YOUR_RETHINKDB_AUTH_KEY_HERE_IF_ANY" \ + --net host \ + rethinkdb:2.1.1 \ + rethinkdb --bind all \ + --cache-size 8192 +ExecStop=-/usr/bin/docker stop -t 10 %p +``` + +### `rethinkdb-proxy-28015.service` + +You need a single instance of the `rethinkdb-proxy-28015.service` unit on each host where you have another unit that needs to access the database. Having a local proxy simplifies configuration for other units and allows the `AUTHKEY` to be specified only once. + +Note that the `After` condition also specifies the [rethinkdb.service](#rethinkdbservice) unit just in case you're on a low budget and want to run the RethinkDB unit on the same server as the rest of the units, which by the way is NOT recommended at all. + +```ini +[Unit] +Description=RethinkDB proxy/28015 +After=docker.service rethinkdb.service +Requires=docker.service + +[Service] +EnvironmentFile=/etc/environment +TimeoutStartSec=0 +Restart=always +ExecStartPre=/usr/bin/docker pull openstf/ambassador:latest ExecStartPre=-/usr/bin/docker kill %p ExecStartPre=-/usr/bin/docker rm %p ExecStart=/usr/bin/docker run --rm \ @@ -113,7 +167,7 @@ ExecStart=/usr/bin/docker run --rm \ -e "AUTHKEY=YOUR_RETHINKDB_AUTH_KEY_HERE_IF_ANY" \ -p 28015 \ -e RETHINKDB_PORT_28015_TCP=tcp://rethinkdb.stf.example.org:28015 \ - ctlc/ambassador:latest + openstf/ambassador:latest ExecStop=-/usr/bin/docker stop -t 10 %p ``` @@ -146,7 +200,7 @@ ExecStart=/usr/bin/docker run --rm \ --name %p-%i \ --link rethinkdb-proxy-28015:rethinkdb \ -e "SECRET=YOUR_SESSION_SECRET_HERE" \ - -p 127.0.0.1:%i:3000 \ + -p %i:3000 \ openstf/stf:latest \ stf app --port 3000 \ --auth-url https://stf.example.org/auth/mock/ \ @@ -160,7 +214,7 @@ You may have to change the `--auth-url` depending on which authentication method You have multiple options here. STF currently provides authentication units for [OAuth 2.0](http://oauth.net/2/) and [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol), plus a mock implementation that simply asks for a name and an email address. -Since the other providers require quite a bit of configuration, we'll simply set up a mock auth unit here. If you'd rather use the real providers, see `stf auth-oauth2 --help` and `stf auth-ldap --help` for the required variables. +Since the other providers require quite a bit of configuration, we'll simply set up a mock auth unit here. If you'd rather use the real providers, see `stf auth-oauth2 --help` and `stf auth-ldap --help` for the required variables. Note that if your OAuth 2 provider uses a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`. This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-auth@3200.service` runs on port 3200). You can have multiple instances running on the same host by using different ports. @@ -180,7 +234,7 @@ ExecStartPre=-/usr/bin/docker rm %p-%i ExecStart=/usr/bin/docker run --rm \ --name %p-%i \ -e "SECRET=YOUR_SESSION_SECRET_HERE" \ - -p 127.0.0.1:%i:3000 \ + -p %i:3000 \ openstf/stf:latest \ stf auth-mock --port 3000 \ --app-url https://stf.example.org/ @@ -259,6 +313,8 @@ This is a template unit, meaning that you'll need to start it with an instance i Note that you cannot have more than one provider unit running on the same host, as they would compete over which one gets to control the devices. In the future we might add a negotiation protocol to allow for relatively seamless upgrades. +Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`. + ```ini [Unit] Description=STF provider @@ -293,7 +349,7 @@ ExecStop=-/usr/bin/docker stop -t 10 %p-%i **Requires** the `rethinkdb-proxy-28015.service` unit on the same host. -The reaper unit receives heartbeat events from device workers, and marks lost devices as absent until a heartbeat is received again. The purpose of this unit is to ensure the integrity of the present/absent flag in the database, in case a provider shuts down unexpectedly or another unexpected failure occurs. It loads the current state from the database on startup and keeps keeps patching its internal view as events are routed to it. +The reaper unit receives heartbeat events from device workers, and marks lost devices as absent until a heartbeat is received again. The purpose of this unit is to ensure the integrity of the present/absent flag in the database, in case a provider shuts down unexpectedly or another unexpected failure occurs. It loads the current state from the database on startup and keeps patching its internal view as events are routed to it. Note that it doesn't make sense to have more than one reaper running at once, as they would just duplicate the events. @@ -327,6 +383,8 @@ The APK storage plugin loads raw blobs from the main storage unit and allows add This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-storage-plugin-apk@3300.service` runs on port 3300). You can have multiple instances running on the same host by using different ports. +Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`. + ```ini [Unit] Description=STF APK storage plugin @@ -342,7 +400,7 @@ ExecStartPre=-/usr/bin/docker kill %p-%i ExecStartPre=-/usr/bin/docker rm %p-%i ExecStart=/usr/bin/docker run --rm \ --name %p-%i \ - -p 127.0.0.1:%i:3000 \ + -p %i:3000 \ openstf/stf:latest \ stf storage-plugin-apk --port 3000 \ --storage-url https://stf.example.org/ @@ -355,6 +413,8 @@ The image storage plugin loads raw blobs from the main storage unit and and allo This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-storage-plugin-image@3400.service` runs on port 3400). You can have multiple instances running on the same host by using different ports. +Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`. + ```ini [Unit] Description=STF image storage plugin @@ -370,7 +430,7 @@ ExecStartPre=-/usr/bin/docker kill %p-%i ExecStartPre=-/usr/bin/docker rm %p-%i ExecStart=/usr/bin/docker run --rm \ --name %p-%i \ - -p 127.0.0.1:%i:3000 \ + -p %i:3000 \ openstf/stf:latest \ stf storage-plugin-image --port 3000 \ --storage-url https://stf.example.org/ @@ -397,7 +457,7 @@ ExecStartPre=-/usr/bin/docker rm %p-%i ExecStart=/usr/bin/docker run --rm \ --name %p-%i \ -v /mnt/storage:/data \ - -p 127.0.0.1:%i:3000 \ + -p %i:3000 \ openstf/stf:latest \ stf storage-temp --port 3000 \ --save-dir /data @@ -476,6 +536,8 @@ The websocket unit provides the communication layer between client-side JavaScri This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-websocket@3600.service` runs on port 3600). You can have multiple instances running on the same host by using different ports. +Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`. + ```ini [Unit] Description=STF websocket @@ -493,7 +555,7 @@ ExecStart=/usr/bin/docker run --rm \ --name %p-%i \ --link rethinkdb-proxy-28015:rethinkdb \ -e "SECRET=YOUR_SESSION_SECRET_HERE" \ - -p 127.0.0.1:%i:3000 \ + -p %i:3000 \ openstf/stf:latest \ stf websocket --port 3000 \ --storage-url https://stf.example.org/ \ @@ -688,8 +750,8 @@ http { proxy_set_header X-Real-IP $remote_addr; } - location /auth/mock/ { - proxy_pass http://stf_auth/auth/mock/; + location /auth/ { + proxy_pass http://stf_auth/auth/; } location /s/image/ { diff --git a/doc/VNC.md b/doc/VNC.md new file mode 100644 index 00000000..40bcce1b --- /dev/null +++ b/doc/VNC.md @@ -0,0 +1,17 @@ +# VNC + +## Implementation details + +### Authentication + +#### According to the spec + +VNC authentication is very weak by default, and doesn't encrypt traffic in any way. It works by sending a random 16-byte challenge to the user, who then encrypts with his/her password and sends back the 16-byte result. The server then encrypts the challenge as well, and checks whether the result sent by the client matches the server's result. Passwords are required to be 8 characters long. Shorter passwords are padded with zeroes and longer passwords simply truncated. Both the server and the client have to know the password. There are no usernames. + +#### The way we do it + +Since the authentication is very weak anyway, we might as well exploit it. The problem with the spec method is that since there's no username, it's difficult to know *who* wants to connect to a device. The only place for any kind of information is the password, but without knowing the password we can't decrypt the challenge response to see the contents. While we could go through our whole user database encrypting the challenge with each user's password, that doesn't really scale in the long run, especially since we're interested in having per-device passwords as well (more on that later). + +Instead, we send over a *static* challenge, e.g. 16 zeroes, every time. Then we simply identify the user by the returned challenge response itself, which is both unique and constant for each password. This makes the authentication more susceptible to eavesdropping since responses from previous sessions can be reused, but given the already weak nature of basic VNC authentication this shouldn't be a massive downgrade, and the app should be running inside an internal network anyway. For real security, all connections should be over a secure tunnel. + +Furthermore, each password is only valid for a single device. This will enable interesting proxying and/or load balancing opportunities in the future as we should be able to expose every single device in the system via a single port if desired. diff --git a/docker/armv7hf/Dockerfile b/docker/armv7hf/Dockerfile index 926cf30f..996da601 100644 --- a/docker/armv7hf/Dockerfile +++ b/docker/armv7hf/Dockerfile @@ -24,7 +24,8 @@ COPY . /app/ RUN export PATH=/app/node_modules/.bin:$PATH && \ npm install && \ bower install --allow-root && \ - gulp build + gulp build && \ + npm prune --production # Switch to weak user. USER stf diff --git a/gulpfile.js b/gulpfile.js index 9d2a0627..1ae034cb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,16 +1,16 @@ +var path = require('path') + var gulp = require('gulp') var gutil = require('gulp-util') var jshint = require('gulp-jshint') var jsonlint = require('gulp-jsonlint') var standard = require('gulp-standard') var webpack = require('webpack') -var ngAnnotatePlugin = require('ng-annotate-webpack-plugin') var webpackConfig = require('./webpack.config').webpack var webpackStatusConfig = require('./res/common/status/webpack.config') var gettext = require('gulp-angular-gettext') var jade = require('gulp-jade') var del = require('del') -var runSequence = require('run-sequence').use(gulp) //var protractor = require('gulp-protractor') var protractor = require('./res/test/e2e/helpers/gulp-protractor-adv') var protractorConfig = './res/test/protractor.conf' @@ -22,30 +22,42 @@ var run = require('gulp-run') gulp.task('jshint', function () { return gulp.src([ - 'lib/**/*.js', 'res/app/**/*.js', 'res/auth-ldap/**/*.js', - 'res/auth-mock/**/*.js', 'res/common/**/*.js', 'res/test/**/*.js', - '*.js' - ]) + 'lib/**/*.js' + , 'res/app/**/*.js' + , 'res/auth-ldap/**/*.js' + , 'res/auth-mock/**/*.js' + , 'res/common/**/*.js' + , 'res/test/**/*.js' + , '*.js' + ]) .pipe(jshint()) .pipe(jshint.reporter('jshint-stylish')) }) gulp.task('jsonlint', function () { return gulp.src([ - '.jshintrc', 'res/.jshintrc', '.bowerrc', '.yo-rc.json', '*.json' - ]) + '.jshintrc' + , 'res/.jshintrc' + , '.bowerrc' + , '.yo-rc.json' + , '*.json' + ]) .pipe(jsonlint()) .pipe(jsonlint.reporter()) }) gulp.task('jscs', function () { return gulp.src([ - 'lib/**/*.js', 'res/app/**/*.js', 'res/auth-ldap/**/*.js', - 'res/auth-mock/**/*.js', 'res/common/**/*.js', 'res/test/**/*.js', - '*.js' - ]) + 'lib/**/*.js' + , 'res/app/**/*.js' + , 'res/auth-ldap/**/*.js' + , 'res/auth-mock/**/*.js' + , 'res/common/**/*.js' + , 'res/test/**/*.js' + , '*.js' + ]) .pipe(jscs()) -}); +}) gulp.task('standard', function () { // Check res/app for now @@ -58,27 +70,23 @@ gulp.task('standard', function () { gulp.task('lint', ['jshint', 'jsonlint']) gulp.task('test', ['lint', 'run:checkversion']) - -gulp.task('build', function (cb) { - runSequence('clean', 'webpack:build', cb) -}) +gulp.task('build', ['clean', 'webpack:build']) gulp.task('run:checkversion', function () { gutil.log('Checking STF version...') - return run('./bin/stf -V').exec() }) gulp.task('karma_ci', function (done) { karma.start({ - configFile: __dirname + karmaConfig, - singleRun: true + configFile: path.join(__dirname, karmaConfig) + , singleRun: true }, done) }) gulp.task('karma', function (done) { karma.start({ - configFile: __dirname + karmaConfig + configFile: path.join(__dirname, karmaConfig) }, done) }) @@ -88,32 +96,37 @@ if (gutil.env.multi) { protractorConfig = './res/test/protractor-appium.conf' } -gulp.task('webdriver-update', protractor.webdriver_update) -gulp.task('webdriver-standalone', protractor.webdriver_standalone) +gulp.task('webdriver-update', protractor.webdriverUpdate) +gulp.task('webdriver-standalone', protractor.webdriverStandalone) gulp.task('protractor-explorer', function (callback) { - protractor.protractor_explorer({ + protractor.protractorExplorer({ url: require(protractorConfig).config.baseUrl }, callback) }) gulp.task('protractor', ['webdriver-update'], function (callback) { - gulp.src(["./res/test/e2e/**/*.js"]) + gulp.src(['./res/test/e2e/**/*.js']) .pipe(protractor.protractor({ - configFile: protractorConfig, - debug: gutil.env.debug, - suite: gutil.env.suite + configFile: protractorConfig + , debug: gutil.env.debug + , suite: gutil.env.suite })) .on('error', function (e) { console.log(e) - }).on('end', callback) + }) + .on('end', callback) }) // For piping strings function fromString(filename, string) { - var src = stream.Readable({objectMode: true}) + /* eslint no-underscore-dangle: 0 */ + var src = new stream.Readable({objectMode: true}) src._read = function () { this.push(new gutil.File({ - cwd: '', base: '', path: filename, contents: new Buffer(string) + cwd: '' + , base: '' + , path: filename + , contents: new Buffer(string) })) this.push(null) } @@ -122,20 +135,14 @@ function fromString(filename, string) { // For production -gulp.task("webpack:build", function (callback) { +gulp.task('webpack:build', function (callback) { var myConfig = Object.create(webpackConfig) myConfig.plugins = myConfig.plugins.concat( new webpack.DefinePlugin({ - "process.env": { - "NODE_ENV": JSON.stringify('production') + 'process.env': { + 'NODE_ENV': JSON.stringify('production') } }) - //new webpack.optimize.DedupePlugin(), - //new ngAnnotatePlugin({ - // add: true, - //}) - // TODO: mangle when ngmin works - //new webpack.optimize.UglifyJsPlugin({mangle: false}) ) myConfig.devtool = false @@ -144,7 +151,7 @@ gulp.task("webpack:build", function (callback) { throw new gutil.PluginError('webpack:build', err) } - gutil.log("[webpack:build]", stats.toString({ + gutil.log('[webpack:build]', stats.toString({ colors: true })) @@ -157,17 +164,14 @@ gulp.task("webpack:build", function (callback) { }) }) -gulp.task("webpack:others", function (callback) { +gulp.task('webpack:others', function (callback) { var myConfig = Object.create(webpackStatusConfig) myConfig.plugins = myConfig.plugins.concat( new webpack.DefinePlugin({ - "process.env": { - "NODE_ENV": JSON.stringify('production') + 'process.env': { + 'NODE_ENV': JSON.stringify('production') } - }), - new webpack.optimize.DedupePlugin() -// new ngminPlugin(), -// new webpack.optimize.UglifyJsPlugin({mangle: false}) + }) ) myConfig.devtool = false @@ -176,19 +180,25 @@ gulp.task("webpack:others", function (callback) { throw new gutil.PluginError('webpack:others', err) } - gutil.log("[webpack:others]", stats.toString({ + gutil.log('[webpack:others]', stats.toString({ colors: true })) callback() }) }) -gulp.task('translate', ['translate:compile']) +gulp.task('translate', [ + 'translate:extract' +, 'translate:push' +, 'translate:pull' +, 'translate:compile' +]) -gulp.task('jade', function (cb) { +gulp.task('jade', function () { return gulp.src([ - './res/**/*.jade', '!./res/bower_components/**' - ]) + './res/**/*.jade' + , '!./res/bower_components/**' + ]) .pipe(jade({ locals: { // So res/views/docs.jade doesn't complain @@ -201,16 +211,18 @@ gulp.task('jade', function (cb) { .pipe(gulp.dest('./tmp/html/')) }) -gulp.task('translate:extract', ['jade'], function (cb) { +gulp.task('translate:extract', ['jade'], function () { return gulp.src([ - './tmp/html/**/*.html', './res/**/*.js', '!./res/bower_components/**', - '!./res/build/**' - ]) + './tmp/html/**/*.html' + , './res/**/*.js' + , '!./res/bower_components/**' + , '!./res/build/**' + ]) .pipe(gettext.extract('stf.pot')) .pipe(gulp.dest('./res/common/lang/po/')) }) -gulp.task('translate:compile', ['translate:pull'], function (cb) { +gulp.task('translate:compile', function () { return gulp.src('./res/common/lang/po/**/*.po') .pipe(gettext.compile({ format: 'json' @@ -218,21 +230,16 @@ gulp.task('translate:compile', ['translate:pull'], function (cb) { .pipe(gulp.dest('./res/common/lang/translations/')) }) -gulp.task('translate:push', ['translate:extract'], function () { +gulp.task('translate:push', function () { gutil.log('Pushing translation source to Transifex...') - return run('tx push -s').exec() }) -gulp.task('translate:pull', ['translate:push'], function () { +gulp.task('translate:pull', function () { gutil.log('Pulling translations from Transifex...') - return run('tx pull').exec() }) - - - gulp.task('clean', function (cb) { del(['./tmp', './res/build'], cb) }) diff --git a/lib/cli.js b/lib/cli.js index 5212ec0c..404ec3d4 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -15,7 +15,7 @@ program .version(pkg.version) program - .command('provider [serial..]') + .command('provider [serial...]') .description('start provider') .option('-s, --connect-sub ' , 'sub endpoint' @@ -68,12 +68,15 @@ program , 'adb connect URL pattern' , String , '${publicIp}:${publicPort}') + .option('--vnc-initial-size ' + , 'initial VNC size' + , cliutil.size + , [600, 800]) .option('--mute-master' , 'whether to mute master volume when devices are being used') - .action(function() { - var serials = cliutil.allUnknownArgs(arguments) - , options = cliutil.lastArg(arguments) - + .option('--lock-rotation' + , 'whether to lock rotation when devices are being used') + .action(function(serials, options) { if (!options.connectSub) { this.missingArgument('--connect-sub') } @@ -101,6 +104,7 @@ program , '--connect-push', options.connectPush.join(',') , '--screen-port', ports.shift() , '--connect-port', ports.shift() + , '--vnc-port', ports.shift() , '--public-ip', options.publicIp , '--group-timeout', options.groupTimeout , '--storage-url', options.storageUrl @@ -109,8 +113,10 @@ program , '--screen-ws-url-pattern', options.screenWsUrlPattern , '--connect-url-pattern', options.connectUrlPattern , '--heartbeat-interval', options.heartbeatInterval + , '--vnc-initial-size', options.vncInitialSize.join('x') ] - .concat(options.muteMaster ? ['--mute-master'] : [])) + .concat(options.muteMaster ? ['--mute-master'] : []) + .concat(options.lockRotation ? ['--lock-rotation'] : [])) } , endpoints: { sub: options.connectSub @@ -139,6 +145,13 @@ program .option('--connect-port ' , 'port allocated to adb connect' , Number) + .option('--vnc-port ' + , 'port allocated to vnc' + , Number) + .option('--vnc-initial-size ' + , 'initial VNC size' + , cliutil.size + , [600, 800]) .option('--connect-url-pattern ' , 'adb connect URL pattern' , String @@ -172,6 +185,8 @@ program , 10000) .option('--mute-master' , 'whether to mute master volume when devices are being used') + .option('--lock-rotation' + , 'whether to lock rotation when devices are being used') .action(function(serial, options) { if (!options.connectSub) { this.missingArgument('--connect-sub') @@ -188,6 +203,9 @@ program if (!options.connectPort) { this.missingArgument('--connect-port') } + if (!options.vncPort) { + this.missingArgument('--vnc-port') + } if (!options.storageUrl) { this.missingArgument('--storage-url') } @@ -208,8 +226,11 @@ program , screenPort: options.screenPort , connectUrlPattern: options.connectUrlPattern , connectPort: options.connectPort + , vncPort: options.vncPort + , vncInitialSize: options.vncInitialSize , heartbeatInterval: options.heartbeatInterval , muteMaster: options.muteMaster + , lockRotation: options.lockRotation }) }) @@ -866,7 +887,7 @@ program }) program - .command('local [serial..]') + .command('local [serial...]') .description('start everything locally') .option('--bind-app-pub ' , 'app pub endpoint' @@ -976,6 +997,10 @@ program .option('--user-profile-url ' , 'URL to external user profile page' , String) + .option('--vnc-initial-size ' + , 'initial VNC size' + , cliutil.size + , [600, 800]) .option('--mute-master' , 'whether to mute master volume when devices are being used') .option('--use-s3' @@ -991,10 +1016,10 @@ program , 's3 endpoint' , String , 's3-ap-northeast-1.amazonaws.com') - .action(function() { + .option('--lock-rotation' + , 'whether to lock rotation when devices are being used') + .action(function(serials, options) { var log = logger.createLogger('cli:local') - , args = arguments - , options = cliutil.lastArg(args) , procutil = require('./util/procutil') // Each forked process waits for signals to stop, and so we run over the @@ -1054,10 +1079,12 @@ program , util.format('http://localhost:%d/', options.poorxyPort) , '--adb-host', options.adbHost , '--adb-port', options.adbPort + , '--vnc-initial-size', options.vncInitialSize.join('x') ] .concat(options.allowRemote ? ['--allow-remote'] : []) .concat(options.muteMaster ? ['--mute-master'] : []) - .concat(cliutil.allUnknownArgs(args))) + .concat(options.lockRotation ? ['--lock-rotation'] : []) + .concat(serials)) // auth , procutil.fork(__filename, [ diff --git a/lib/db/api.js b/lib/db/api.js index adba788e..8cc0d829 100644 --- a/lib/db/api.js +++ b/lib/db/api.js @@ -100,6 +100,27 @@ dbapi.lookupUserByAdbFingerprint = function(fingerprint) { }) } +dbapi.lookupUserByVncAuthResponse = function(response, serial) { + return db.run(r.table('vncauth').getAll([response, serial], { + index: 'responsePerDevice' + }) + .eqJoin('userId', r.table('users'))('right') + .pluck('email', 'name', 'group')) + .then(function(cursor) { + return cursor.toArray() + }) + .then(function(groups) { + switch (groups.length) { + case 1: + return groups[0] + case 0: + return null + default: + throw new Error('Found multiple users with the same VNC response') + } + }) +} + dbapi.loadGroup = function(email) { return db.run(r.table('devices').getAll(email, { index: 'owner' diff --git a/lib/db/tables.js b/lib/db/tables.js index fa843c12..61679896 100644 --- a/lib/db/tables.js +++ b/lib/db/tables.js @@ -14,6 +14,17 @@ module.exports = { } } } +, vncauth: { + primaryKey: 'password' + , indexes: { + response: null + , responsePerDevice: { + indexFunction: function(row) { + return [row('response'), row('deviceId')] + } + } + } + } , devices: { primaryKey: 'serial' , indexes: { diff --git a/lib/units/app/middleware/webpack.js b/lib/units/app/middleware/webpack.js index 0910322d..f3ccce45 100644 --- a/lib/units/app/middleware/webpack.js +++ b/lib/units/app/middleware/webpack.js @@ -5,7 +5,7 @@ var webpack = require('webpack') var mime = require('mime') var Promise = require('bluebird') var _ = require('lodash') -var MemoryFileSystem = require('webpack/node_modules/memory-fs') +var MemoryFileSystem = require('memory-fs') var logger = require('../../../util/logger') var lifecycle = require('../../../util/lifecycle') diff --git a/lib/units/device/index.js b/lib/units/device/index.js index fa2a3a29..08be946b 100644 --- a/lib/units/device/index.js +++ b/lib/units/device/index.js @@ -20,6 +20,7 @@ module.exports = function(options) { .dependency(require('./plugins/solo')) .dependency(require('./plugins/screen/stream')) .dependency(require('./plugins/screen/capture')) + .dependency(require('./plugins/vnc')) .dependency(require('./plugins/service')) .dependency(require('./plugins/browser')) .dependency(require('./plugins/store')) @@ -38,6 +39,7 @@ module.exports = function(options) { .dependency(require('./plugins/ringer')) .dependency(require('./plugins/wifi')) .dependency(require('./plugins/sd')) + .dependency(require('./plugins/filesystem')) .define(function(options, heartbeat, solo) { if (process.send) { // Only if we have a parent process diff --git a/lib/units/device/plugins/filesystem.js b/lib/units/device/plugins/filesystem.js new file mode 100644 index 00000000..8825aa11 --- /dev/null +++ b/lib/units/device/plugins/filesystem.js @@ -0,0 +1,74 @@ +var syrup = require('stf-syrup') +var path = require('path') + +var logger = require('../../../util/logger') +var wire = require('../../../wire') +var wireutil = require('../../../wire/util') + +module.exports = syrup.serial() + .dependency(require('../support/adb')) + .dependency(require('../support/router')) + .dependency(require('../support/push')) + .dependency(require('../support/storage')) + .define(function(options, adb, router, push, storage) { + var log = logger.createLogger('device:plugins:filesystem') + var plugin = Object.create(null) + + plugin.retrieve = function(file) { + log.info('Retrieving file "%s"', file) + + return adb.stat(options.serial, file) + .then(function(stats) { + return adb.pull(options.serial, file) + .then(function(transfer) { + // We may have add new storage plugins for various file types + // in the future, and add proper detection for the mimetype. + // But for now, let's just use application/octet-stream for + // everything like it's 2001. + return storage.store('blob', transfer, { + filename: path.basename(file) + , contentType: 'application/octet-stream' + , knownLength: stats.size + }) + }) + }) + } + + router.on(wire.FileSystemGetMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + plugin.retrieve(message.file) + .then(function(file) { + push.send([ + channel + , reply.okay('success', file) + ]) + }) + .catch(function(err) { + log.warn('Unable to retrieve "%s"', message.file, err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + + router.on(wire.FileSystemListMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + adb.readdir(options.serial, message.dir) + .then(function(files) { + push.send([ + channel + , reply.okay('success', files) + ]) + }) + .catch(function(err) { + log.warn('Unable to list directory "%s"', message.dir, err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + + return plugin + }) diff --git a/lib/units/device/plugins/group.js b/lib/units/device/plugins/group.js index 883a8653..c69b0e78 100644 --- a/lib/units/device/plugins/group.js +++ b/lib/units/device/plugins/group.js @@ -104,6 +104,7 @@ module.exports = syrup.serial() plugin.on('leave', function() { service.pressKey('home') + service.thawRotation() service.releaseWakeLock() }) diff --git a/lib/units/device/plugins/screen/stream.js b/lib/units/device/plugins/screen/stream.js index f75069fa..f8a6f25d 100644 --- a/lib/units/device/plugins/screen/stream.js +++ b/lib/units/device/plugins/screen/stream.js @@ -25,7 +25,6 @@ module.exports = syrup.serial() .dependency(require('./options')) .define(function(options, adb, minicap, display, screenOptions) { var log = logger.createLogger('device:plugins:screen:stream') - var plugin = Object.create(null) function FrameProducer(config) { EventEmitter.call(this) @@ -443,9 +442,9 @@ module.exports = syrup.serial() return createServer() .then(function(wss) { - var broadcastSet = new BroadcastSet() var frameProducer = new FrameProducer( new FrameConfig(display.properties, display.properties)) + var broadcastSet = frameProducer.broadcastSet = new BroadcastSet() broadcastSet.on('nonempty', function() { frameProducer.start() @@ -455,37 +454,26 @@ module.exports = syrup.serial() frameProducer.stop() }) + broadcastSet.on('insert', function(id) { + // If two clients join a session in the middle, one of them + // may not release the initial size because the projection + // doesn't necessarily change, and the producer doesn't Getting + // restarted. Therefore we have to call onStart() manually + // if the producer is already up and running. + switch (frameProducer.runningState) { + case FrameProducer.STATE_STARTED: + broadcastSet.get(id).onStart(frameProducer) + break + } + }) + display.on('rotationChange', function(newRotation) { frameProducer.updateRotation(newRotation) }) frameProducer.on('start', function() { - var message = util.format( - 'start %s' - , JSON.stringify(frameProducer.banner) - ) - - broadcastSet.keys().forEach(function(id) { - var ws = broadcastSet.get(id) - switch (ws.readyState) { - case WebSocket.OPENING: - // This should never happen. - log.warn('Unable to send banner to OPENING client "%s"', id) - break - case WebSocket.OPEN: - // This is what SHOULD happen. - ws.send(message) - break - case WebSocket.CLOSING: - // Ok, a 'close' event should remove the client from the set - // soon. - break - case WebSocket.CLOSED: - // This should never happen. - log.warn('Unable to send banner to CLOSED client "%s"', id) - broadcastSet.remove(id) - break - } + broadcastSet.keys().map(function(id) { + return broadcastSet.get(id).onStart(frameProducer) }) }) @@ -493,32 +481,7 @@ module.exports = syrup.serial() var frame if ((frame = frameProducer.nextFrame())) { Promise.settle([broadcastSet.keys().map(function(id) { - return new Promise(function(resolve, reject) { - var ws = broadcastSet.get(id) - switch (ws.readyState) { - case WebSocket.OPENING: - // This should never happen. - return reject(new Error(util.format( - 'Unable to send frame to OPENING client "%s"', id))) - case WebSocket.OPEN: - // This is what SHOULD happen. - ws.send(frame, { - binary: true - }, function(err) { - return err ? reject(err) : resolve() - }) - return - case WebSocket.CLOSING: - // Ok, a 'close' event should remove the client from the set - // soon. - return - case WebSocket.CLOSED: - // This should never happen. - broadcastSet.remove(id) - return reject(new Error(util.format( - 'Unable to send frame to CLOSED client "%s"', id))) - } - }) + return broadcastSet.get(id).onFrame(frame) })]).then(next) } else { @@ -534,12 +497,74 @@ module.exports = syrup.serial() wss.on('connection', function(ws) { var id = uuid.v4() + function wsStartNotifier() { + return new Promise(function(resolve, reject) { + var message = util.format( + 'start %s' + , JSON.stringify(frameProducer.banner) + ) + + switch (ws.readyState) { + case WebSocket.OPENING: + // This should never happen. + log.warn('Unable to send banner to OPENING client "%s"', id) + break + case WebSocket.OPEN: + // This is what SHOULD happen. + ws.send(message, function(err) { + return err ? reject(err) : resolve() + }) + break + case WebSocket.CLOSING: + // Ok, a 'close' event should remove the client from the set + // soon. + break + case WebSocket.CLOSED: + // This should never happen. + log.warn('Unable to send banner to CLOSED client "%s"', id) + broadcastSet.remove(id) + break + } + }) + } + + function wsFrameNotifier(frame) { + return new Promise(function(resolve, reject) { + switch (ws.readyState) { + case WebSocket.OPENING: + // This should never happen. + return reject(new Error(util.format( + 'Unable to send frame to OPENING client "%s"', id))) + case WebSocket.OPEN: + // This is what SHOULD happen. + ws.send(frame, { + binary: true + }, function(err) { + return err ? reject(err) : resolve() + }) + return + case WebSocket.CLOSING: + // Ok, a 'close' event should remove the client from the set + // soon. + return + case WebSocket.CLOSED: + // This should never happen. + broadcastSet.remove(id) + return reject(new Error(util.format( + 'Unable to send frame to CLOSED client "%s"', id))) + } + }) + } + ws.on('message', function(data) { var match if ((match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data))) { switch (match[2] || match[1]) { case 'on': - broadcastSet.insert(id, ws) + broadcastSet.insert(id, { + onStart: wsStartNotifier + , onFrame: wsFrameNotifier + }) break case 'off': broadcastSet.remove(id) @@ -563,6 +588,7 @@ module.exports = syrup.serial() lifecycle.observe(function() { frameProducer.stop() }) + + return frameProducer }) - .return(plugin) }) diff --git a/lib/units/device/plugins/service.js b/lib/units/device/plugins/service.js index 5f8ce68f..91b3a32a 100644 --- a/lib/units/device/plugins/service.js +++ b/lib/units/device/plugins/service.js @@ -365,7 +365,7 @@ module.exports = syrup.serial() plugin.rotate = function(rotation) { return runAgentCommand( apk.wire.MessageType.SET_ROTATION - , new apk.wire.SetRotationRequest(rotation, false) + , new apk.wire.SetRotationRequest(rotation, options.lockRotation || false) ) } diff --git a/lib/units/device/plugins/vnc/index.js b/lib/units/device/plugins/vnc/index.js new file mode 100644 index 00000000..e95b1c54 --- /dev/null +++ b/lib/units/device/plugins/vnc/index.js @@ -0,0 +1,290 @@ +var net = require('net') +var util = require('util') +var os = require('os') + +var syrup = require('stf-syrup') +var Promise = require('bluebird') +var uuid = require('node-uuid') +var jpeg = require('jpeg-turbo') + +var logger = require('../../../../util/logger') +var grouputil = require('../../../../util/grouputil') +var wire = require('../../../../wire') +var wireutil = require('../../../../wire/util') +var lifecycle = require('../../../../util/lifecycle') + +var VncServer = require('./util/server') +var VncConnection = require('./util/connection') +var PointerTranslator = require('./util/pointertranslator') + +module.exports = syrup.serial() + .dependency(require('../../support/router')) + .dependency(require('../../support/push')) + .dependency(require('../screen/stream')) + .dependency(require('../touch')) + .dependency(require('../group')) + .dependency(require('../solo')) + .define(function(options, router, push, screenStream, touch, group, solo) { + var log = logger.createLogger('device:plugins:vnc') + + function vncAuthHandler(data) { + log.info( + 'VNC authentication attempt using "%s"' + , data.response.toString('hex') + ) + + var resolver = Promise.defer() + + function notify() { + group.get() + .then(function(currentGroup) { + push.send([ + solo.channel + , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage( + options.serial + , data.response.toString('hex') + , currentGroup.group + )) + ]) + }) + .catch(grouputil.NoGroupError, function() { + push.send([ + solo.channel + , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage( + options.serial + , data.response.toString('hex') + )) + ]) + }) + } + + function joinListener(newGroup, identifier) { + if (!data.response.equals(new Buffer(identifier || '', 'hex'))) { + resolver.reject(new Error('Someone else took the device')) + } + } + + function autojoinListener(identifier, joined) { + if (data.response.equals(new Buffer(identifier, 'hex'))) { + if (joined) { + resolver.resolve() + } + else { + resolver.reject(new Error('Device is already in use')) + } + } + } + + group.on('join', joinListener) + group.on('autojoin', autojoinListener) + router.on(wire.VncAuthResponsesUpdatedMessage, notify) + + notify() + + return resolver.promise + .timeout(5000) + .finally(function() { + group.removeListener('join', joinListener) + group.removeListener('autojoin', autojoinListener) + router.removeListener(wire.VncAuthResponsesUpdatedMessage, notify) + }) + } + + function createServer() { + log.info('Starting VNC server on port %d', options.vncPort) + + var opts = { + name: options.serial + , width: options.vncInitialSize[0] + , height: options.vncInitialSize[1] + , security: [{ + type: VncConnection.SECURITY_VNC + , challenge: new Buffer(16).fill(0) + , auth: vncAuthHandler + }] + } + + var vnc = new VncServer(net.createServer({ + allowHalfOpen: true + }), opts) + + var listeningListener, errorListener + return new Promise(function(resolve, reject) { + listeningListener = function() { + return resolve(vnc) + } + + errorListener = function(err) { + return reject(err) + } + + vnc.on('listening', listeningListener) + vnc.on('error', errorListener) + + vnc.listen(options.vncPort) + }) + .finally(function() { + vnc.removeListener('listening', listeningListener) + vnc.removeListener('error', errorListener) + }) + } + + return createServer() + .then(function(vnc) { + vnc.on('connection', function(conn) { + log.info('New VNC connection from %s', conn.conn.remoteAddress) + + var id = util.format('vnc-%s', uuid.v4()) + + var connState = { + lastFrame: null + , lastFrameTime: null + , frameWidth: 0 + , frameHeight: 0 + , sentFrameTime: null + , updateRequests: 0 + , frameConfig: { + format: jpeg.FORMAT_RGB + } + } + + var pointerTranslator = new PointerTranslator() + + pointerTranslator.on('touchdown', function(event) { + touch.touchDown(event) + }) + + pointerTranslator.on('touchmove', function(event) { + touch.touchMove(event) + }) + + pointerTranslator.on('touchup', function(event) { + touch.touchUp(event) + }) + + pointerTranslator.on('touchcommit', function() { + touch.touchCommit() + }) + + function maybeSendFrame() { + if (!connState.updateRequests) { + return + } + + if (!connState.lastFrame) { + return + } + + if (connState.lastFrameTime === connState.sentFrameTime) { + return + } + + var decoded = jpeg.decompressSync( + connState.lastFrame, connState.frameConfig) + + conn.writeFramebufferUpdate([ + { xPosition: 0 + , yPosition: 0 + , width: decoded.width + , height: decoded.height + , encodingType: VncConnection.ENCODING_RAW + , data: decoded.data + } + , { xPosition: 0 + , yPosition: 0 + , width: decoded.width + , height: decoded.height + , encodingType: VncConnection.ENCODING_DESKTOPSIZE + } + ]) + + connState.updateRequests = 0 + connState.sentFrameTime = connState.lastFrameTime + } + + function vncStartListener(frameProducer) { + return new Promise(function(resolve/*, reject*/) { + connState.frameWidth = frameProducer.banner.virtualWidth + connState.frameHeight = frameProducer.banner.virtualHeight + resolve() + }) + } + + function vncFrameListener(frame) { + return new Promise(function(resolve/*, reject*/) { + connState.lastFrame = frame + connState.lastFrameTime = Date.now() + maybeSendFrame() + resolve() + }) + } + + function groupLeaveListener() { + conn.end() + } + + conn.on('authenticated', function() { + screenStream.updateProjection( + options.vncInitialSize[0], options.vncInitialSize[1]) + screenStream.broadcastSet.insert(id, { + onStart: vncStartListener + , onFrame: vncFrameListener + }) + }) + + conn.on('fbupdaterequest', function() { + connState.updateRequests += 1 + maybeSendFrame() + }) + + conn.on('formatchange', function(format) { + var same = os.endianness() === 'BE' + === Boolean(format.bigEndianFlag) + switch (format.bitsPerPixel) { + case 8: + connState.frameConfig = { + format: jpeg.FORMAT_GRAY + } + break + case 24: + connState.frameConfig = { + format: ((format.redShift > format.blueShift) === same) + ? jpeg.FORMAT_BGR + : jpeg.FORMAT_RGB + } + break + case 32: + connState.frameConfig = { + format: ((format.redShift > format.blueShift) === same) + ? (format.blueShift === 0 + ? jpeg.FORMAT_BGRX + : jpeg.FORMAT_XBGR) + : (format.redShift === 0 + ? jpeg.FORMAT_RGBX + : jpeg.FORMAT_XRGB) + } + break + } + }) + + conn.on('pointer', function(event) { + pointerTranslator.push(event) + }) + + conn.on('close', function() { + screenStream.broadcastSet.remove(id) + group.removeListener('leave', groupLeaveListener) + }) + + conn.on('userActivity', function() { + group.keepalive() + }) + + group.on('leave', groupLeaveListener) + }) + + lifecycle.observe(function() { + vnc.close() + }) + }) + }) diff --git a/lib/units/device/plugins/vnc/util/connection.js b/lib/units/device/plugins/vnc/util/connection.js new file mode 100644 index 00000000..82bbef49 --- /dev/null +++ b/lib/units/device/plugins/vnc/util/connection.js @@ -0,0 +1,535 @@ +var util = require('util') +var os = require('os') +var crypto = require('crypto') + +var EventEmitter = require('eventemitter3').EventEmitter +var debug = require('debug')('vnc:connection') +var Promise = require('bluebird') + +var PixelFormat = require('./pixelformat') + +function VncConnection(conn, options) { + this.options = options + + this._bound = { + _errorListener: this._errorListener.bind(this) + , _readableListener: this._readableListener.bind(this) + , _endListener: this._endListener.bind(this) + , _closeListener: this._closeListener.bind(this) + } + + this._buffer = null + this._state = 0 + this._changeState(VncConnection.STATE_NEED_CLIENT_VERSION) + + this._serverVersion = VncConnection.V3_008 + this._serverSupportedSecurity = this.options.security + this._serverSupportedSecurityByType = + this.options.security.reduce( + function(map, method) { + map[method.type] = method + return map + } + , Object.create(null) + ) + this._serverWidth = this.options.width + this._serverHeight = this.options.height + this._serverPixelFormat = new PixelFormat({ + bitsPerPixel: 32 + , depth: 24 + , bigEndianFlag: os.endianness() === 'BE' ? 1 : 0 + , trueColorFlag: 1 + , redMax: 255 + , greenMax: 255 + , blueMax: 255 + , redShift: 16 + , greenShift: 8 + , blueShift: 0 + }) + this._serverName = this.options.name + + this._clientVersion = null + this._clientShare = false + this._clientPixelFormat = this._serverPixelFormat + this._clientEncodingCount = 0 + this._clientEncodings = [] + this._clientCutTextLength = 0 + + this._authChallenge = this.options.challenge || crypto.randomBytes(16) + + this.conn = conn + .on('error', this._bound._errorListener) + .on('readable', this._bound._readableListener) + .on('end', this._bound._endListener) + .on('close', this._bound._closeListener) + + this._blockingOps = [] + + this._writeServerVersion() + this._read() +} + +util.inherits(VncConnection, EventEmitter) + +VncConnection.V3_003 = 3003 +VncConnection.V3_007 = 3007 +VncConnection.V3_008 = 3008 + +VncConnection.SECURITY_NONE = 1 +VncConnection.SECURITY_VNC = 2 + +VncConnection.SECURITYRESULT_OK = 0 +VncConnection.SECURITYRESULT_FAIL = 1 + +VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT = 0 +VncConnection.CLIENT_MESSAGE_SETENCODINGS = 2 +VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST = 3 +VncConnection.CLIENT_MESSAGE_KEYEVENT = 4 +VncConnection.CLIENT_MESSAGE_POINTEREVENT = 5 +VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT = 6 + +VncConnection.SERVER_MESSAGE_FBUPDATE = 0 + +var StateReverse = Object.create(null), State = { + STATE_NEED_CLIENT_VERSION: 10 +, STATE_NEED_CLIENT_SECURITY: 20 +, STATE_NEED_CLIENT_INIT: 30 +, STATE_NEED_CLIENT_VNC_AUTH: 31 +, STATE_NEED_CLIENT_MESSAGE: 40 +, STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: 50 +, STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: 60 +, STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: 61 +, STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: 70 +, STATE_NEED_CLIENT_MESSAGE_KEYEVENT: 80 +, STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: 90 +, STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: 100 +, STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: 101 +} + +VncConnection.ENCODING_RAW = 0 +VncConnection.ENCODING_DESKTOPSIZE = -223 + +Object.keys(State).map(function(name) { + VncConnection[name] = State[name] + StateReverse[State[name]] = name +}) + +VncConnection.prototype.end = function() { + this.conn.end() +} + +VncConnection.prototype.writeFramebufferUpdate = function(rectangles) { + var chunk = new Buffer(4) + chunk[0] = VncConnection.SERVER_MESSAGE_FBUPDATE + chunk[1] = 0 + chunk.writeUInt16BE(rectangles.length, 2) + this._write(chunk) + + rectangles.forEach(function(rect) { + var rchunk = new Buffer(12) + rchunk.writeUInt16BE(rect.xPosition, 0) + rchunk.writeUInt16BE(rect.yPosition, 2) + rchunk.writeUInt16BE(rect.width, 4) + rchunk.writeUInt16BE(rect.height, 6) + rchunk.writeInt32BE(rect.encodingType, 8) + this._write(rchunk) + + switch (rect.encodingType) { + case VncConnection.ENCODING_RAW: + this._write(rect.data) + break + case VncConnection.ENCODING_DESKTOPSIZE: + this._serverWidth = rect.width + this._serverHeight = rect.height + break + default: + throw new Error(util.format( + 'Unsupported encoding type', rect.encodingType)) + } + }, this) +} + +VncConnection.prototype._error = function(err) { + this.emit('error', err) + this.end() +} + +VncConnection.prototype._errorListener = function(err) { + this._error(err) +} + +VncConnection.prototype._endListener = function() { + this.emit('end') +} + +VncConnection.prototype._closeListener = function() { + this.emit('close') +} + +VncConnection.prototype._writeServerVersion = function() { + // Yes, we could just format the string instead. Didn't feel like it. + switch (this._serverVersion) { + case VncConnection.V3_003: + this._write(new Buffer('RFB 003.003\n')) + break + case VncConnection.V3_007: + this._write(new Buffer('RFB 003.007\n')) + break + case VncConnection.V3_008: + this._write(new Buffer('RFB 003.008\n')) + break + } +} + +VncConnection.prototype._writeSupportedSecurity = function() { + var chunk = new Buffer(1 + this._serverSupportedSecurity.length) + + chunk[0] = this._serverSupportedSecurity.length + this._serverSupportedSecurity.forEach(function(security, i) { + chunk[1 + i] = security.type + }) + + this._write(chunk) +} + +VncConnection.prototype._writeSecurityResult = function(result, reason) { + var chunk + switch (result) { + case VncConnection.SECURITYRESULT_OK: + chunk = new Buffer(4) + chunk.writeUInt32BE(result, 0) + this._write(chunk) + break + case VncConnection.SECURITYRESULT_FAIL: + chunk = new Buffer(4 + 4 + reason.length) + chunk.writeUInt32BE(result, 0) + chunk.writeUInt32BE(reason.length, 4) + chunk.write(reason, 8, reason.length) + this._write(chunk) + break + } +} + +VncConnection.prototype._writeServerInit = function() { + debug('server pixel format', this._serverPixelFormat) + var chunk = new Buffer(2 + 2 + 16 + 4 + this._serverName.length) + chunk.writeUInt16BE(this._serverWidth, 0) + chunk.writeUInt16BE(this._serverHeight, 2) + chunk[4] = this._serverPixelFormat.bitsPerPixel + chunk[5] = this._serverPixelFormat.depth + chunk[6] = this._serverPixelFormat.bigEndianFlag + chunk[7] = this._serverPixelFormat.trueColorFlag + chunk.writeUInt16BE(this._serverPixelFormat.redMax, 8) + chunk.writeUInt16BE(this._serverPixelFormat.greenMax, 10) + chunk.writeUInt16BE(this._serverPixelFormat.blueMax, 12) + chunk[14] = this._serverPixelFormat.redShift + chunk[15] = this._serverPixelFormat.greenShift + chunk[16] = this._serverPixelFormat.blueShift + chunk[17] = 0 // padding + chunk[18] = 0 // padding + chunk[19] = 0 // padding + chunk.writeUInt32BE(this._serverName.length, 20) + chunk.write(this._serverName, 24, this._serverName.length) + this._write(chunk) +} + +VncConnection.prototype._writeVncAuthChallenge = function() { + var vncSec = this._serverSupportedSecurityByType[VncConnection.SECURITY_VNC] + debug('vnc auth challenge', vncSec.challenge) + this._write(vncSec.challenge) +} + +VncConnection.prototype._readableListener = function() { + this._read() +} + +VncConnection.prototype._read = function() { + Promise.all(this._blockingOps).bind(this) + .then(this._unguardedRead) +} + +VncConnection.prototype._auth = function(type, data) { + var security = this._serverSupportedSecurityByType[type] + this._blockingOps.push( + security.auth(data).bind(this) + .then(function() { + this._changeState(VncConnection.STATE_NEED_CLIENT_INIT) + this._writeSecurityResult(VncConnection.SECURITYRESULT_OK) + this.emit('authenticated') + this._read() + }) + .catch(function() { + this._writeSecurityResult( + VncConnection.SECURITYRESULT_FAIL, 'Authentication failure') + this.end() + }) + ) +} + +VncConnection.prototype._unguardedRead = function() { + var chunk, lo, hi + while (this._append(this.conn.read())) { + do { + debug('state', StateReverse[this._state]) + chunk = null + switch (this._state) { + case VncConnection.STATE_NEED_CLIENT_VERSION: + if ((chunk = this._consume(12))) { + if ((this._clientVersion = this._parseVersion(chunk)) === null) { + this.end() + return + } + debug('client version', this._clientVersion) + this._writeSupportedSecurity() + this._changeState(VncConnection.STATE_NEED_CLIENT_SECURITY) + } + break + case VncConnection.STATE_NEED_CLIENT_SECURITY: + if ((chunk = this._consume(1))) { + if ((this._clientSecurity = this._parseSecurity(chunk)) === null) { + this._writeSecurityResult( + VncConnection.SECURITYRESULT_FAIL, 'Unimplemented security type') + this.end() + return + } + debug('client security', this._clientSecurity) + if (!(this._clientSecurity in this._serverSupportedSecurityByType)) { + this._writeSecurityResult( + VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type') + this.end() + return + } + switch (this._clientSecurity) { + case VncConnection.SECURITY_NONE: + this._auth(VncConnection.SECURITY_NONE) + return + case VncConnection.SECURITY_VNC: + this._writeVncAuthChallenge() + this._changeState(VncConnection.STATE_NEED_CLIENT_VNC_AUTH) + break + } + } + break + case VncConnection.STATE_NEED_CLIENT_VNC_AUTH: + if ((chunk = this._consume(16))) { + this._auth(VncConnection.SECURITY_VNC, { + response: chunk + }) + return + } + break + case VncConnection.STATE_NEED_CLIENT_INIT: + if ((chunk = this._consume(1))) { + this._clientShare = chunk[0] + debug('client shareFlag', this._clientShare) + this._writeServerInit() + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE: + if ((chunk = this._consume(1))) { + switch (chunk[0]) { + case VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT: + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT) + break + case VncConnection.CLIENT_MESSAGE_SETENCODINGS: + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS) + break + case VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST: + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST) + break + case VncConnection.CLIENT_MESSAGE_KEYEVENT: + this.emit('userActivity') + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT) + break + case VncConnection.CLIENT_MESSAGE_POINTEREVENT: + this.emit('userActivity') + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT) + break + case VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT: + this.emit('userActivity') + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT) + break + default: + this._error(new Error(util.format( + 'Unsupported message type %d', chunk[0]))) + return + } + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: + if ((chunk = this._consume(19))) { + // [0b, 3b) padding + this._clientPixelFormat = new PixelFormat({ + bitsPerPixel: chunk[3] + , depth: chunk[4] + , bigEndianFlag: chunk[5] + , trueColorFlag: chunk[6] + , redMax: chunk.readUInt16BE(7, true) + , greenMax: chunk.readUInt16BE(9, true) + , blueMax: chunk.readUInt16BE(11, true) + , redShift: chunk[13] + , greenShift: chunk[14] + , blueShift: chunk[15] + }) + // [16b, 19b) padding + debug('client pixel format', this._clientPixelFormat) + this.emit('formatchange', this._clientPixelFormat) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: + if ((chunk = this._consume(3))) { + // [0b, 1b) padding + this._clientEncodingCount = chunk.readUInt16BE(1, true) + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: + lo = 0 + hi = 4 * this._clientEncodingCount + if ((chunk = this._consume(hi))) { + this._clientEncodings = [] + while (lo < hi) { + this._clientEncodings.push(chunk.readInt32BE(lo, true)) + lo += 4 + } + debug('client encodings', this._clientEncodings) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: + if ((chunk = this._consume(9))) { + this.emit('fbupdaterequest', { + incremental: chunk[0] + , xPosition: chunk.readUInt16BE(1, true) + , yPosition: chunk.readUInt16BE(3, true) + , width: chunk.readUInt16BE(5, true) + , height: chunk.readUInt16BE(7, true) + }) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT: + if ((chunk = this._consume(7))) { + // downFlag = chunk[0] + // [1b, 3b) padding + // key = chunk.readUInt32BE(3, true) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: + if ((chunk = this._consume(5))) { + this.emit('pointer', { + buttonMask: chunk[0] + , xPosition: chunk.readUInt16BE(1, true) / this._serverWidth + , yPosition: chunk.readUInt16BE(3, true) / this._serverHeight + }) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: + if ((chunk = this._consume(7))) { + // [0b, 3b) padding + this._clientCutTextLength = chunk.readUInt32BE(3) + this._changeState( + VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: + if ((chunk = this._consume(this._clientCutTextLength))) { + // value = chunk + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + default: + throw new Error(util.format('Impossible state %d', this._state)) + } + } + while (chunk) + } +} + +VncConnection.prototype._parseVersion = function(chunk) { + if (chunk.equals(new Buffer('RFB 003.008\n'))) { + return VncConnection.V3_008 + } + + if (chunk.equals(new Buffer('RFB 003.007\n'))) { + return VncConnection.V3_007 + } + + if (chunk.equals(new Buffer('RFB 003.003\n'))) { + return VncConnection.V3_003 + } + + return null +} + +VncConnection.prototype._parseSecurity = function(chunk) { + switch (chunk[0]) { + case VncConnection.SECURITY_NONE: + case VncConnection.SECURITY_VNC: + return chunk[0] + default: + return null + } +} + +VncConnection.prototype._changeState = function(state) { + this._state = state +} + +VncConnection.prototype._append = function(chunk) { + if (!chunk) { + return false + } + + debug('in', chunk) + + if (this._buffer) { + this._buffer = Buffer.concat( + [this._buffer, chunk], this._buffer.length + chunk.length) + } + else { + this._buffer = chunk + } + + return true +} + +VncConnection.prototype._consume = function(n) { + var chunk + + if (!this._buffer) { + return null + } + + if (n < this._buffer.length) { + chunk = this._buffer.slice(0, n) + this._buffer = this._buffer.slice(n) + return chunk + } + + if (n === this._buffer.length) { + chunk = this._buffer + this._buffer = null + return chunk + } + + return null +} + +VncConnection.prototype._write = function(chunk) { + debug('out', chunk) + this.conn.write(chunk) +} + +module.exports = VncConnection diff --git a/lib/units/device/plugins/vnc/util/pixelformat.js b/lib/units/device/plugins/vnc/util/pixelformat.js new file mode 100644 index 00000000..9a1c4273 --- /dev/null +++ b/lib/units/device/plugins/vnc/util/pixelformat.js @@ -0,0 +1,14 @@ +function PixelFormat(values) { + this.bitsPerPixel = values.bitsPerPixel + this.depth = values.depth + this.bigEndianFlag = values.bigEndianFlag + this.trueColorFlag = values.trueColorFlag + this.redMax = values.redMax + this.greenMax = values.greenMax + this.blueMax = values.blueMax + this.redShift = values.redShift + this.greenShift = values.greenShift + this.blueShift = values.blueShift +} + +module.exports = PixelFormat diff --git a/lib/units/device/plugins/vnc/util/pointertranslator.js b/lib/units/device/plugins/vnc/util/pointertranslator.js new file mode 100644 index 00000000..8161efa8 --- /dev/null +++ b/lib/units/device/plugins/vnc/util/pointertranslator.js @@ -0,0 +1,66 @@ +var util = require('util') + +var EventEmitter = require('eventemitter3').EventEmitter + +function PointerTranslator() { + this.previousEvent = null +} + +util.inherits(PointerTranslator, EventEmitter) + +PointerTranslator.prototype.push = function(event) { + if (event.buttonMask & 0xFE) { + // Non-primary buttons included, ignore. + return + } + + if (this.previousEvent) { + var buttonChanges = event.buttonMask ^ this.previousEvent.buttonMask + + // If the primary button changed, we have an up/down event. + if (buttonChanges & 1) { + // If it's pressed now, that's a down event. + if (event.buttonMask & 1) { + this.emit('touchdown', { + contact: 1 + , x: event.xPosition + , y: event.yPosition + }) + this.emit('touchcommit') + } + // It's not pressed, so we have an up event. + else { + this.emit('touchup', { + contact: 1 + }) + this.emit('touchcommit') + } + } + // Otherwise, if we're still holding the primary button down, + // that's a move event. + else if (event.buttonMask & 1) { + this.emit('touchmove', { + contact: 1 + , x: event.xPosition + , y: event.yPosition + }) + this.emit('touchcommit') + } + } + else { + // If it's the first event we get and the primary button's pressed, + // it's a down event. + if (event.buttonMask & 1) { + this.emit('touchdown', { + contact: 1 + , x: event.xPosition + , y: event.yPosition + }) + this.emit('touchcommit') + } + } + + this.previousEvent = event +} + +module.exports = PointerTranslator diff --git a/lib/units/device/plugins/vnc/util/server.js b/lib/units/device/plugins/vnc/util/server.js new file mode 100644 index 00000000..5a8b2382 --- /dev/null +++ b/lib/units/device/plugins/vnc/util/server.js @@ -0,0 +1,52 @@ +var util = require('util') + +var EventEmitter = require('eventemitter3').EventEmitter +var debug = require('debug')('vnc:server') + +var VncConnection = require('./connection') + +function VncServer(server, options) { + this.options = options + + this._bound = { + _listeningListener: this._listeningListener.bind(this) + , _connectionListener: this._connectionListener.bind(this) + , _closeListener: this._closeListener.bind(this) + , _errorListener: this._errorListener.bind(this) + } + + this.server = server + .on('listening', this._bound._listeningListener) + .on('connection', this._bound._connectionListener) + .on('close', this._bound._closeListener) + .on('error', this._bound._errorListener) +} + +util.inherits(VncServer, EventEmitter) + +VncServer.prototype.close = function() { + this.server.close() +} + +VncServer.prototype.listen = function() { + this.server.listen.apply(this.server, arguments) +} + +VncServer.prototype._listeningListener = function() { + this.emit('listening') +} + +VncServer.prototype._connectionListener = function(conn) { + debug('connection', conn.remoteAddress, conn.remotePort) + this.emit('connection', new VncConnection(conn, this.options)) +} + +VncServer.prototype._closeListener = function() { + this.emit('close') +} + +VncServer.prototype._errorListener = function(err) { + this.emit('error', err) +} + +module.exports = VncServer diff --git a/lib/units/device/resources/minicap.js b/lib/units/device/resources/minicap.js index 01e0a1cf..67189bf2 100644 --- a/lib/units/device/resources/minicap.js +++ b/lib/units/device/resources/minicap.js @@ -14,7 +14,7 @@ module.exports = syrup.serial() .dependency(require('../support/properties')) .dependency(require('../support/abi')) .define(function(options, adb, properties, abi) { - var log = logger.createLogger('device:resources:minicap') + logger.createLogger('device:resources:minicap') var resources = { bin: { diff --git a/lib/units/device/resources/service.js b/lib/units/device/resources/service.js index 09d89a50..657f82a8 100644 --- a/lib/units/device/resources/service.js +++ b/lib/units/device/resources/service.js @@ -17,7 +17,7 @@ module.exports = syrup.serial() pathutil.vendor('STFService/wire.proto')) var resource = { - requiredVersion: '1.0.1' + requiredVersion: '1.0.2' , pkg: 'jp.co.cyberagent.stf' , main: 'jp.co.cyberagent.stf.Agent' , apk: pathutil.vendor('STFService/STFService.apk') @@ -79,9 +79,9 @@ module.exports = syrup.serial() .then(function() { return promiseutil.periodicNotify( adb.install(options.serial, resource.apk) - , 10000 + , 20000 ) - .timeout(60000) + .timeout(65000) }) .progressed(function() { log.warn( diff --git a/lib/units/device/support/push.js b/lib/units/device/support/push.js index d8564f13..c68bf47d 100644 --- a/lib/units/device/support/push.js +++ b/lib/units/device/support/push.js @@ -1,17 +1,17 @@ var syrup = require('stf-syrup') -var zmq = require('zmq') var Promise = require('bluebird') var logger = require('../../../util/logger') var srv = require('../../../util/srv') +var zmqutil = require('../../../util/zmqutil') module.exports = syrup.serial() .define(function(options) { var log = logger.createLogger('device:support:push') // Output - var push = zmq.socket('push') + var push = zmqutil.socket('push') return Promise.map(options.endpoints.push, function(endpoint) { return srv.resolve(endpoint).then(function(records) { diff --git a/lib/units/device/support/sub.js b/lib/units/device/support/sub.js index 39a226fb..99882c53 100644 --- a/lib/units/device/support/sub.js +++ b/lib/units/device/support/sub.js @@ -1,19 +1,19 @@ var syrup = require('stf-syrup') -var zmq = require('zmq') var Promise = require('bluebird') var logger = require('../../../util/logger') var wireutil = require('../../../wire/util') var srv = require('../../../util/srv') -var lifecycle = require('../../../util/lifecycle') +require('../../../util/lifecycle') +var zmqutil = require('../../../util/zmqutil') module.exports = syrup.serial() .define(function(options) { var log = logger.createLogger('device:support:sub') // Input - var sub = zmq.socket('sub') + var sub = zmqutil.socket('sub') return Promise.map(options.endpoints.sub, function(endpoint) { return srv.resolve(endpoint).then(function(records) { diff --git a/lib/units/log/rethinkdb.js b/lib/units/log/rethinkdb.js index 94215b5c..cbb60e88 100644 --- a/lib/units/log/rethinkdb.js +++ b/lib/units/log/rethinkdb.js @@ -1,5 +1,4 @@ var Promise = require('bluebird') -var zmq = require('zmq') var logger = require('../../util/logger') var wire = require('../../wire') @@ -8,12 +7,13 @@ var wireutil = require('../../wire/util') var lifecycle = require('../../util/lifecycle') var srv = require('../../util/srv') var dbapi = require('../../db/api') +var zmqutil = require('../../util/zmqutil') module.exports = function(options) { var log = logger.createLogger('log-db') // Input - var sub = zmq.socket('sub') + var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { diff --git a/lib/units/notify/hipchat.js b/lib/units/notify/hipchat.js index 71e81d7b..c7b9127e 100644 --- a/lib/units/notify/hipchat.js +++ b/lib/units/notify/hipchat.js @@ -2,7 +2,6 @@ var util = require('util') var Hipchatter = require('hipchatter') var Promise = require('bluebird') -var zmq = require('zmq') var logger = require('../../util/logger') var wire = require('../../wire') @@ -10,6 +9,7 @@ var wirerouter = require('../../wire/router') var wireutil = require('../../wire/util') var lifecycle = require('../../util/lifecycle') var srv = require('../../util/srv') +var zmqutil = require('../../util/zmqutil') var COLORS = { 1: 'gray' @@ -28,7 +28,7 @@ module.exports = function(options) { , timer // Input - var sub = zmq.socket('sub') + var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { diff --git a/lib/units/processor/index.js b/lib/units/processor/index.js index de11f7fa..c30c9cb2 100644 --- a/lib/units/processor/index.js +++ b/lib/units/processor/index.js @@ -1,5 +1,4 @@ var Promise = require('bluebird') -var zmq = require('zmq') var logger = require('../../util/logger') var wire = require('../../wire') @@ -8,6 +7,7 @@ var wireutil = require('../../wire/util') var dbapi = require('../../db/api') var lifecycle = require('../../util/lifecycle') var srv = require('../../util/srv') +var zmqutil = require('../../util/zmqutil') module.exports = function(options) { var log = logger.createLogger('processor') @@ -17,7 +17,7 @@ module.exports = function(options) { } // App side - var appDealer = zmq.socket('dealer') + var appDealer = zmqutil.socket('dealer') Promise.map(options.endpoints.appDealer, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { @@ -37,7 +37,7 @@ module.exports = function(options) { }) // Device side - var devDealer = zmq.socket('dealer') + var devDealer = zmqutil.socket('dealer') Promise.map(options.endpoints.devDealer, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { @@ -124,12 +124,46 @@ module.exports = function(options) { }) .catch(function(err) { log.error( - 'Unable to lookup user by fingerprint "%s"' + 'Unable to lookup user by ADB fingerprint "%s"' , message.fingerprint , err.stack ) }) }) + .on(wire.JoinGroupByVncAuthResponseMessage, function(channel, message) { + dbapi.lookupUserByVncAuthResponse(message.response, message.serial) + .then(function(user) { + if (user) { + devDealer.send([ + channel + , wireutil.envelope(new wire.AutoGroupMessage( + new wire.OwnerMessage( + user.email + , user.name + , user.group + ) + , message.response + )) + ]) + } + else if (message.currentGroup) { + appDealer.send([ + message.currentGroup + , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage( + message.serial + , message.response + )) + ]) + } + }) + .catch(function(err) { + log.error( + 'Unable to lookup user by VNC auth response "%s"' + , message.response + , err.stack + ) + }) + }) .on(wire.JoinGroupMessage, function(channel, message, data) { dbapi.setDeviceOwner(message.serial, message.owner) appDealer.send([channel, data]) diff --git a/lib/units/provider/index.js b/lib/units/provider/index.js index 981875a0..77e0c14a 100644 --- a/lib/units/provider/index.js +++ b/lib/units/provider/index.js @@ -1,6 +1,5 @@ var adb = require('adbkit') var Promise = require('bluebird') -var zmq = require('zmq') var _ = require('lodash') var EventEmitter = require('eventemitter3').EventEmitter @@ -11,6 +10,7 @@ var wirerouter = require('../../wire/router') var procutil = require('../../util/procutil') var lifecycle = require('../../util/lifecycle') var srv = require('../../util/srv') +var zmqutil = require('../../util/zmqutil') module.exports = function(options) { var log = logger.createLogger('provider') @@ -70,7 +70,7 @@ module.exports = function(options) { })() // Output - var push = zmq.socket('push') + var push = zmqutil.socket('push') Promise.map(options.endpoints.push, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { @@ -86,7 +86,7 @@ module.exports = function(options) { }) // Input - var sub = zmq.socket('sub') + var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { @@ -316,7 +316,7 @@ module.exports = function(options) { // Spawn a device worker function spawn() { - var allocatedPorts = ports.splice(0, 2) + var allocatedPorts = ports.splice(0, 4) , proc = options.fork(device, allocatedPorts) , resolver = Promise.defer() diff --git a/lib/units/reaper/index.js b/lib/units/reaper/index.js index cff1dbe0..89c43a24 100644 --- a/lib/units/reaper/index.js +++ b/lib/units/reaper/index.js @@ -1,5 +1,4 @@ var Promise = require('bluebird') -var zmq = require('zmq') var logger = require('../../util/logger') var wire = require('../../wire') @@ -9,6 +8,7 @@ var dbapi = require('../../db/api') var lifecycle = require('../../util/lifecycle') var srv = require('../../util/srv') var TtlSet = require('../../util/ttlset') +var zmqutil = require('../../util/zmqutil') module.exports = function(options) { var log = logger.createLogger('reaper') @@ -19,7 +19,7 @@ module.exports = function(options) { } // Input - var sub = zmq.socket('sub') + var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { @@ -41,7 +41,7 @@ module.exports = function(options) { }) // Output - var push = zmq.socket('push') + var push = zmqutil.socket('push') Promise.map(options.endpoints.push, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { diff --git a/lib/units/storage/temp.js b/lib/units/storage/temp.js index 363ced16..58345afc 100644 --- a/lib/units/storage/temp.js +++ b/lib/units/storage/temp.js @@ -138,6 +138,10 @@ module.exports = function(options) { app.get('/s/blob/:id/:name', function(req, res) { var file = storage.retrieve(req.params.id) if (file) { + if (typeof req.query.download !== 'undefined') { + res.set('Content-Disposition', + 'attachment; filename="' + path.basename(file.name) + '"') + } res.set('Content-Type', file.type) res.sendFile(file.path) } diff --git a/lib/units/triproxy/index.js b/lib/units/triproxy/index.js index 0d0112bf..1632d181 100644 --- a/lib/units/triproxy/index.js +++ b/lib/units/triproxy/index.js @@ -1,7 +1,6 @@ -var zmq = require('zmq') - var logger = require('../../util/logger') var lifecycle = require('../../util/lifecycle') +var zmqutil = require('../../util/zmqutil') module.exports = function(options) { var log = logger.createLogger('triproxy') @@ -17,18 +16,18 @@ module.exports = function(options) { } // App/device output - var pub = zmq.socket('pub') + var pub = zmqutil.socket('pub') pub.bindSync(options.endpoints.pub) log.info('PUB socket bound on', options.endpoints.pub) // Coordinator input/output - var dealer = zmq.socket('dealer') + var dealer = zmqutil.socket('dealer') dealer.bindSync(options.endpoints.dealer) dealer.on('message', proxy(pub)) log.info('DEALER socket bound on', options.endpoints.dealer) // App/device input - var pull = zmq.socket('pull') + var pull = zmqutil.socket('pull') pull.bindSync(options.endpoints.pull) pull.on('message', proxy(dealer)) log.info('PULL socket bound on', options.endpoints.pull) diff --git a/lib/units/websocket/index.js b/lib/units/websocket/index.js index aa6a1955..f99ea509 100644 --- a/lib/units/websocket/index.js +++ b/lib/units/websocket/index.js @@ -3,7 +3,6 @@ var events = require('events') var util = require('util') var socketio = require('socket.io') -var zmq = require('zmq') var Promise = require('bluebird') var _ = require('lodash') var request = Promise.promisifyAll(require('request')) @@ -17,6 +16,7 @@ var dbapi = require('../../db/api') var datautil = require('../../util/datautil') var srv = require('../../util/srv') var lifecycle = require('../../util/lifecycle') +var zmqutil = require('../../util/zmqutil') var cookieSession = require('./middleware/cookie-session') var ip = require('./middleware/remote-ip') var auth = require('./middleware/auth') @@ -31,7 +31,7 @@ module.exports = function(options) { , channelRouter = new events.EventEmitter() // Output - var push = zmq.socket('push') + var push = zmqutil.socket('push') Promise.map(options.endpoints.push, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { @@ -47,7 +47,7 @@ module.exports = function(options) { }) // Input - var sub = zmq.socket('sub') + var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { return srv.resolve(endpoint).then(function(records) { return srv.attempt(records, function(record) { @@ -826,6 +826,26 @@ module.exports = function(options) { ) ]) }) + .on('fs.retrieve', function(channel, responseChannel, data) { + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction( + responseChannel + , new wire.FileSystemGetMessage(data) + ) + ]) + }) + .on('fs.list', function(channel, responseChannel, data){ + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction( + responseChannel + , new wire.FileSystemListMessage(data) + ) + ]) + }) }) .finally(function() { // Clean up all listeners and subscriptions diff --git a/lib/util/cliutil.js b/lib/util/cliutil.js index 12260802..12d90612 100644 --- a/lib/util/cliutil.js +++ b/lib/util/cliutil.js @@ -2,12 +2,9 @@ module.exports.list = function(val) { return val.split(/\s*,\s*/g).filter(Boolean) } -module.exports.allUnknownArgs = function(args) { - return [].slice.call(args, 0, -1).filter(Boolean) -} - -module.exports.lastArg = function(args) { - return args[args.length - 1] +module.exports.size = function(val) { + var match = /^(\d+)x(\d+)$/.exec(val) + return match ? [+match[1], +match[2]] : undefined } module.exports.range = function(from, to) { diff --git a/lib/util/vncauth.js b/lib/util/vncauth.js new file mode 100644 index 00000000..711d12df --- /dev/null +++ b/lib/util/vncauth.js @@ -0,0 +1,44 @@ +var crypto = require('crypto') + +// See http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits +function reverseByteBits(b) { + return (((b * 0x0802 & 0x22110) | + (b * 0x8020 & 0x88440)) * 0x10101 >> 16 & 0xFF) +} + +function reverseBufferByteBits(b) { + var result = new Buffer(b.length) + + for (var i = 0; i < result.length; ++i) { + result[i] = reverseByteBits(b[i]) + } + + return result +} + +function normalizePassword(password) { + var key = new Buffer(8).fill(0) + + // Make sure the key is always 8 bytes long. VNC passwords cannot be + // longer than 8 bytes. Shorter passwords are padded with zeroes. + reverseBufferByteBits(password).copy(key, 0, 0, 8) + + return key +} + +function encrypt(challenge, password) { + var key = normalizePassword(password) + , iv = new Buffer(0).fill(0) + + // Note: do not call .final(), .update() is the one that gives us the + // desired result. + return crypto.createCipheriv('des-ecb', key, iv).update(challenge) +} + +module.exports.encrypt = encrypt + +function verify(response, challenge, password) { + return encrypt(challenge, password).equals(response) +} + +module.exports.verify = verify diff --git a/lib/util/zmqutil.js b/lib/util/zmqutil.js new file mode 100644 index 00000000..8c5fc39c --- /dev/null +++ b/lib/util/zmqutil.js @@ -0,0 +1,23 @@ +// ISSUE-100 (https://github.com/openstf/stf/issues/100) + +// In some networks TCP Connection dies if kept idle for long. +// Setting TCP_KEEPALIVE option true, to all the zmq sockets +// won't let it die + +var zmq = require('zmq') + +var log = require('./logger').createLogger('util:zmqutil') + +module.exports.socket = function() { + var sock = zmq.socket.apply(zmq, arguments) + + try { + sock.setsockopt(zmq.ZMQ_TCP_KEEPALIVE, 1) + sock.setsockopt(zmq.ZMQ_TCP_KEEPALIVE_IDLE, 300000) + } + catch (err) { + log.warn('ZeroMQ library too old, no support for TCP keepalive options') + } + + return sock +} diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index e3e50b9b..868e507e 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -17,6 +17,8 @@ enum MessageType { PhysicalIdentifyMessage = 29; JoinGroupMessage = 11; JoinGroupByAdbFingerprintMessage = 69; + JoinGroupByVncAuthResponseMessage = 90; + VncAuthResponsesUpdatedMessage = 91; AutoGroupMessage = 70; AdbKeysUpdatedMessage = 71; KeyDownMessage = 12; @@ -72,6 +74,16 @@ enum MessageType { AccountRemoveMessage = 55; SdStatusMessage = 61; ReverseForwardsEvent = 72; + FileSystemListMessage = 81; + FileSystemGetMessage = 82; +} + +message FileSystemListMessage { + required string dir = 1; +} + +message FileSystemGetMessage { + required string file = 1; } message Envelope { @@ -261,9 +273,18 @@ message JoinGroupByAdbFingerprintMessage { optional string currentGroup = 4; } +message JoinGroupByVncAuthResponseMessage { + required string serial = 1; + required string response = 2; + optional string currentGroup = 4; +} + message AdbKeysUpdatedMessage { } +message VncAuthResponsesUpdatedMessage { +} + message LeaveGroupMessage { required string serial = 1; required OwnerMessage owner = 2; diff --git a/package.json b/package.json index 20bb3f4c..73931f3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stf", - "version": "1.0.9", + "version": "1.0.10", "description": "Smartphone Test Farm", "keywords": [ "adb", @@ -36,39 +36,41 @@ "aws-sdk": "^2.2.3", "bluebird": "^2.9.34", "body-parser": "^1.13.3", - "chalk": "~1.0.0", - "commander": "^2.7.1", + "chalk": "~1.1.1", + "commander": "^2.9.0", "compression": "^1.5.2", "cookie-session": "^1.2.0", "csurf": "^1.7.0", + "debug": "^2.2.0", "eventemitter3": "^0.1.6", "express": "^4.13.3", - "express-validator": "^2.14.0", + "express-validator": "^2.17.1", "formidable": "^1.0.17", "gm": "^1.17.0", "hipchatter": "^0.2.0", - "http-proxy": "^1.9.0", + "http-proxy": "^1.11.2", "in-publish": "^2.0.0", "jade": "^1.9.2", + "jpeg-turbo": "^0.3.0", "jws": "^3.1.0", "ldapjs": "git+https://github.com/mcavage/node-ldapjs.git#acc1ca8f4314fd9d67561feabc8ce4c235076a5e", "lodash": "^3.10.1", "markdown-serve": "^0.3.2", "mime": "^1.3.4", - "minimatch": "^2.0.10", + "minimatch": "^3.0.0", "my-local-ip": "^1.0.0", "node-uuid": "^1.4.3", - "passport": "^0.2.1", + "passport": "^0.3.0", "passport-oauth2": "^1.1.2", "protobufjs": "^3.8.2", "proxy-addr": "^1.0.7", - "request": "^2.60.0", + "request": "^2.65.0", "request-progress": "^0.3.1", "rethinkdb": "^2.0.2", "semver": "^5.0.1", "serve-favicon": "^2.2.0", "serve-static": "^1.9.2", - "socket.io": "1.3.6", + "socket.io": "1.3.7", "split": "^1.0.0", "stf-appstore-db": "^1.0.0", "stf-browser-db": "^1.0.2", @@ -77,68 +79,67 @@ "stf-wiki": "^1.0.0", "temp": "^0.8.1", "transliteration": "^0.1.1", - "ws": "^0.7.2", - "zmq": "^2.12.0" + "ws": "^0.8.0", + "zmq": "^2.13.0" }, "devDependencies": { - "async": "^1.4.0", "aws-sdk": "^2.1.46", - "bower": "^1.3.12", + "async": "^1.4.2", + "bower": "^1.6.3", "chai": "^3.2.0", - "css-loader": "^0.14.0", - "del": "^1.2.0", - "event-stream": "^3.3.0", + "css-loader": "^0.20.1", + "del": "^2.0.1", + "event-stream": "^3.3.2", "exports-loader": "^0.6.2", "extract-text-webpack-plugin": "^0.8.2", "file-loader": "^0.8.1", "gulp": "^3.8.11", "gulp-angular-gettext": "^2.1.0", "gulp-jade": "^1.0.0", - "gulp-jscs": "^2.0.0", + "gulp-jscs": "^3.0.0", "gulp-jshint": "^1.11.2", "gulp-jsonlint": "^1.0.2", "gulp-protractor": "^1.0.0", "gulp-run": "^1.6.10", - "gulp-standard": "^4.5.3", + "gulp-standard": "^5.1.0", "gulp-util": "^3.0.4", "html-loader": "^0.3.0", - "imports-loader": "^0.6.3", + "imports-loader": "^0.6.5", "jasmine-core": "^2.3.4", "jasmine-reporters": "^2.0.5", "jshint": "^2.6.3", "jshint-loader": "^0.8.3", "jshint-stylish": "^2.0.0", "json-loader": "^0.5.1", - "karma": "^0.13.3", + "karma": "^0.13.11", "karma-chrome-launcher": "^0.2.0", "karma-firefox-launcher": "^0.1.4", "karma-ie-launcher": "^0.2.0", "karma-jasmine": "^0.3.5", - "karma-junit-reporter": "^0.3.3", - "karma-opera-launcher": "^0.2.0", - "karma-phantomjs-launcher": "^0.2.0", + "karma-junit-reporter": "^0.3.4", + "karma-opera-launcher": "^0.3.0", + "karma-phantomjs-launcher": "^0.2.1", "karma-safari-launcher": "^0.1.1", "karma-webpack": "^1.6.0", "less": "^2.4.0", "less-loader": "^2.1.0", - "ng-annotate-webpack-plugin": "^0.1.2", + "memory-fs": "^0.2.0", "node-libs-browser": "^0.5.2", - "node-sass": "^3.2.0", - "phantomjs": "^1.9.17", - "protractor": "^2.0.0", + "node-sass": "^3.3.3", + "phantomjs": "^1.9.18", + "protractor": "^2.5.1", "protractor-html-screenshot-reporter": "0.0.21", "raw-loader": "^0.5.1", - "run-sequence": "^1.1.2", - "sass-loader": "^1.0.4", + "sass-loader": "^3.0.0", "script-loader": "^0.6.1", - "sinon": "^1.14.1", + "sinon": "^1.16.1", "sinon-chai": "^2.7.0", - "socket.io-client": "1.3.6", + "socket.io-client": "1.3.7", "style-loader": "^0.12.3", "template-html-loader": "^0.0.3", "url-loader": "^0.5.5", - "webpack": "^1.10.5", - "webpack-dev-server": "^1.7.0" + "webpack": "^1.12.2", + "webpack-dev-server": "^1.12.1" }, "engines": { "node": ">= 0.10" diff --git a/res/app/components/stf/angular-packery/angular-packery.css b/res/app/components/stf/angular-packery/angular-packery.css index 03c861c3..419795fb 100644 --- a/res/app/components/stf/angular-packery/angular-packery.css +++ b/res/app/components/stf/angular-packery/angular-packery.css @@ -40,10 +40,3 @@ div[angular-packery]:after { width: 100%; } } - -.packery-item.is-dragging, -.packery-item.is-positioning-post-drag { - /*border-color: red;*/ - /*background: #09F;*/ - /*z-index: 2;*/ -} diff --git a/res/app/components/stf/basic-mode/basic-mode-directive.js b/res/app/components/stf/basic-mode/basic-mode-directive.js index 5a258abe..192dd0af 100644 --- a/res/app/components/stf/basic-mode/basic-mode-directive.js +++ b/res/app/components/stf/basic-mode/basic-mode-directive.js @@ -2,7 +2,7 @@ module.exports = function basicModeDirective($rootScope, BrowserInfo) { return { restrict: 'AE', link: function (scope, element) { - $rootScope.basicMode = !!BrowserInfo.mobile // CHECK: use .mobile instead of .small + $rootScope.basicMode = !!BrowserInfo.mobile if ($rootScope.basicMode) { element.addClass('basic-mode') } diff --git a/res/app/components/stf/basic-mode/basic-mode.css b/res/app/components/stf/basic-mode/basic-mode.css index 2db654cd..b7f38e6c 100644 --- a/res/app/components/stf/basic-mode/basic-mode.css +++ b/res/app/components/stf/basic-mode/basic-mode.css @@ -1,7 +1,3 @@ -.basic-mode { - /*background: red;*/ -} - .basic-mode .devices-icon-view { padding: 0; } @@ -10,11 +6,6 @@ margin: 3px; } -.basic-mode .stf-vnc-bottom .btn-lg { - /*padding: 5px;*/ - /*font-size: 12px;*/ -} - .basic-mode .stf-vnc-bottom .btn-primary:hover, .basic-mode .stf-vnc-bottom .btn-primary.active { background: #007aff; @@ -27,8 +18,6 @@ .basic-mode .basic-remote-control { width: 100%; - /*width: 320px;*/ - /*height: 485px;*/ } .basic-mode .stf-device-list .device-search { diff --git a/res/app/components/stf/browser-info/browser-info-service.js b/res/app/components/stf/browser-info/browser-info-service.js index a72fc8ca..cb85b6f4 100644 --- a/res/app/components/stf/browser-info/browser-info-service.js +++ b/res/app/components/stf/browser-info/browser-info-service.js @@ -29,8 +29,6 @@ module.exports = function BrowserInfoServiceFactory() { var windowWidth = window.screen.width < window.outerWidth ? window.screen.width : window.outerWidth return windowWidth < 800 -// return !!(window.matchMedia && -// window.matchMedia('only screen and (max-width: 760px)').matches) }) addTest('mobile', function () { @@ -59,38 +57,6 @@ module.exports = function BrowserInfoServiceFactory() { addTest('ua', navigator.userAgent) - - //function hasEvent() { - // return (function (undefined) { - // function isEventSupportedInner(eventName, element) { - // var isSupported - // if (!eventName) { - // return false - // } - // if (!element || typeof element === 'string') { - // element = createElement(element || 'div') - // } - // eventName = 'on' + eventName - // isSupported = eventName in element - // return isSupported - // } - // - // return isEventSupportedInner - // })() - //} -// var domPrefixes = 'Webkit Moz O ms'.toLowerCase().split(' ') -// addTest('pointerevents', function () { -// var bool = false -// var i = domPrefixes.length -// bool = hasEvent('pointerdown') -// while (i-- && !bool) { -// if (hasEvent(domPrefixes[i] + 'pointerdown')) { -// bool = true -// } -// } -// return bool -// }) - addTest('devicemotion', 'DeviceMotionEvent' in window) addTest('deviceorientation', 'DeviceOrientationEvent' in window) diff --git a/res/app/components/stf/browser-info/browser-info-spec.js b/res/app/components/stf/browser-info/browser-info-spec.js index 8f9a8533..101d5bf2 100644 --- a/res/app/components/stf/browser-info/browser-info-spec.js +++ b/res/app/components/stf/browser-info/browser-info-spec.js @@ -1,11 +1,11 @@ describe('BrowserInfo', function() { - beforeEach(angular.mock.module(require('./').name)); + beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function(BrowserInfo) { + it('should ...', inject(function() { //expect(BrowserInfo.doSomething()).toEqual('something'); - })); + })) }) diff --git a/res/app/components/stf/common-ui/badge-icon/badge-icon.jade b/res/app/components/stf/common-ui/badge-icon/badge-icon.jade index bcf59e7e..d0b471eb 100644 --- a/res/app/components/stf/common-ui/badge-icon/badge-icon.jade +++ b/res/app/components/stf/common-ui/badge-icon/badge-icon.jade @@ -1,3 +1,2 @@ div.stf-badge-icon - //i.fa.fa-warning.stf-badge-icon-warning(popover='I appeared on mouse enter!', popover-placement='bottom', popover-trigger='mouseenter') i.fa.fa-warning.stf-badge-icon-warning(tooltip-placement='bottom', tooltip='An error has ocurred') diff --git a/res/app/components/stf/common-ui/counter/counter-directive.js b/res/app/components/stf/common-ui/counter/counter-directive.js index d46d5676..0183a026 100644 --- a/res/app/components/stf/common-ui/counter/counter-directive.js +++ b/res/app/components/stf/common-ui/counter/counter-directive.js @@ -3,8 +3,6 @@ module.exports = function counterDirective($timeout) { replace: false, scope: true, link: function (scope, element, attrs) { - // TODO: use $$rAF later - var el = element[0] var num, refreshInterval, duration, steps, step, countTo, increment diff --git a/res/app/components/stf/common-ui/help-icon/help-icon-directive.js b/res/app/components/stf/common-ui/help-icon/help-icon-directive.js index 3438521e..00f52e27 100644 --- a/res/app/components/stf/common-ui/help-icon/help-icon-directive.js +++ b/res/app/components/stf/common-ui/help-icon/help-icon-directive.js @@ -1,5 +1,3 @@ -require('./help-icon.css') - module.exports = function clearButtonDirective() { return { restrict: 'EA', diff --git a/res/app/components/stf/common-ui/index.js b/res/app/components/stf/common-ui/index.js index 1a1fc0a3..6264cf60 100644 --- a/res/app/components/stf/common-ui/index.js +++ b/res/app/components/stf/common-ui/index.js @@ -8,7 +8,6 @@ module.exports = angular.module('stf/common-ui', [ require('./notifications').name, require('./ng-enter').name, require('./tooltips').name, - //require('./tree').name, require('./modals').name, require('./include-cached').name, require('./text-focus-select').name, diff --git a/res/app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal-spec.js b/res/app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal-spec.js index f54a5135..1639a7be 100644 --- a/res/app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal-spec.js +++ b/res/app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal-spec.js @@ -2,7 +2,7 @@ describe('FatalMessageService', function() { beforeEach(angular.mock.module(require('./').name)); - it('should ...', inject(function(FatalMessageService) { + it('should ...', inject(function() { //expect(FatalMessageService.doSomething()).toEqual('something'); diff --git a/res/app/components/stf/common-ui/modals/add-adb-key-modal/index.js b/res/app/components/stf/common-ui/modals/add-adb-key-modal/index.js index 4bffd2da..45210a7d 100644 --- a/res/app/components/stf/common-ui/modals/add-adb-key-modal/index.js +++ b/res/app/components/stf/common-ui/modals/add-adb-key-modal/index.js @@ -1,5 +1,4 @@ module.exports = angular.module('stf.add-adb-key-modal', [ - require('stf/common-ui/modals/common').name, - //require('stf/keys/add-adb-key').name + require('stf/common-ui/modals/common').name ]) .factory('AddAdbKeyModalService', require('./add-adb-key-modal-service')) diff --git a/res/app/components/stf/common-ui/modals/common/index.js b/res/app/components/stf/common-ui/modals/common/index.js index 40ba4f10..3c0d7a38 100644 --- a/res/app/components/stf/common-ui/modals/common/index.js +++ b/res/app/components/stf/common-ui/modals/common/index.js @@ -1,5 +1,3 @@ -//require('angular-dialog-service/dialogs') -//require('angular-dialog-service/dialogs.css') require('./modals.css') module.exports = angular.module('stf.modals.common', [ diff --git a/res/app/components/stf/common-ui/modals/common/modals.css b/res/app/components/stf/common-ui/modals/common/modals.css index 32ba94e6..b98f8e34 100644 --- a/res/app/components/stf/common-ui/modals/common/modals.css +++ b/res/app/components/stf/common-ui/modals/common/modals.css @@ -50,7 +50,6 @@ .modal-size-80p .modal-dialog { width: 80%; height: 100%; - /*max-height: 800px;*/ } .modal-size-80p .modal-body { diff --git a/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-service.js b/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-service.js index bbc69df9..095982d3 100644 --- a/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-service.js +++ b/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-service.js @@ -19,7 +19,6 @@ module.exports = function ServiceFactory($modal, $sce) { var modalInstance = $modal.open({ template: require('./external-url-modal.jade'), controller: ModalInstanceCtrl, -// size: 'lg', windowClass: 'modal-size-80p', resolve: { title: function() { diff --git a/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-spec.js b/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-spec.js index 6ccee1a8..863a675c 100644 --- a/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-spec.js +++ b/res/app/components/stf/common-ui/modals/external-url-modal/external-url-modal-spec.js @@ -2,7 +2,7 @@ describe('ExternalUrlModalService', function() { beforeEach(angular.mock.module(require('./').name)); - it('should ...', inject(function(ExternalUrlModalService) { + it('should ...', inject(function() { //expect(FatalMessageService.doSomething()).toEqual('something'); diff --git a/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-service.js b/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-service.js index a7e2b970..39b28ac9 100644 --- a/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-service.js +++ b/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-service.js @@ -10,7 +10,6 @@ module.exports = $scope.ok = function () { $modalInstance.close(true) $route.reload() - //$location.path('/control/' + device.serial) } function update() { diff --git a/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-spec.js b/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-spec.js index f54a5135..1639a7be 100644 --- a/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-spec.js +++ b/res/app/components/stf/common-ui/modals/fatal-message/fatal-message-spec.js @@ -2,7 +2,7 @@ describe('FatalMessageService', function() { beforeEach(angular.mock.module(require('./').name)); - it('should ...', inject(function(FatalMessageService) { + it('should ...', inject(function() { //expect(FatalMessageService.doSomething()).toEqual('something'); diff --git a/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-service.js b/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-service.js index dbf40a3c..cd8c2d82 100644 --- a/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-service.js +++ b/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-service.js @@ -18,7 +18,7 @@ module.exports = function ServiceFactory($modal) { var modalInstance = $modal.open({ template: require('./lightbox-image.jade'), controller: ModalInstanceCtrl, - windowClass: 'modal-size-xl', // TODO: Make width dynamic adjusting + windowClass: 'modal-size-xl', resolve: { title: function() { return title diff --git a/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-spec.js b/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-spec.js index c0e011fd..7a2b9a72 100644 --- a/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-spec.js +++ b/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image-spec.js @@ -2,7 +2,7 @@ describe('LightboxImageService', function() { beforeEach(angular.mock.module(require('./').name)); - it('should ...', inject(function(LightboxImageService) { + it('should ...', inject(function() { //expect(XLightboxImageService.doSomething()).toEqual('something'); diff --git a/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image.jade b/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image.jade index e5f89809..167639be 100644 --- a/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image.jade +++ b/res/app/components/stf/common-ui/modals/lightbox-image/lightbox-image.jade @@ -7,4 +7,3 @@ .modal-body img(ng-if='imageUrl', ng-src='{{imageUrl}}') nothing-to-show(message='{{"No photo available"|translate}}', icon='fa-picture-o', ng-if='!imageUrl') - // TODO: replace !imageUrl here with a image-not-available='imageIsNotPresent = true' directive diff --git a/res/app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected-spec.js b/res/app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected-spec.js index 22a4a17e..0e3f29eb 100644 --- a/res/app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected-spec.js +++ b/res/app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected-spec.js @@ -2,7 +2,7 @@ describe('SocketDisconnectedService', function() { beforeEach(angular.mock.module(require('./index').name)) - it('should ...', inject(function(SocketDisconnectedService) { + it('should ...', inject(function() { //expect(SocketDisconnectedService.doSomething()).toEqual('something') diff --git a/res/app/components/stf/common-ui/modals/version-update/version-update-spec.js b/res/app/components/stf/common-ui/modals/version-update/version-update-spec.js index f8acf372..1fb2587c 100644 --- a/res/app/components/stf/common-ui/modals/version-update/version-update-spec.js +++ b/res/app/components/stf/common-ui/modals/version-update/version-update-spec.js @@ -3,7 +3,7 @@ describe('VersionUpdateService', function() { beforeEach(angular.mock.module(require('ui-bootstrap').name)); beforeEach(angular.mock.module(require('./').name)); - it('should ...', inject(function(VersionUpdateService) { + it('should ...', inject(function() { //expect(VersionUpdateService.doSomething()).toEqual('something'); diff --git a/res/app/components/stf/common-ui/native-autocomplete/index.js b/res/app/components/stf/common-ui/native-autocomplete/index.js deleted file mode 100644 index 3f6444d8..00000000 --- a/res/app/components/stf/common-ui/native-autocomplete/index.js +++ /dev/null @@ -1,6 +0,0 @@ -require('./native-autocomplete.css') - -module.exports = angular.module('stf.native-autocomplete', [ - -]) - .directive('nativeAutocomplete', require('./native-autocomplete-directive')) diff --git a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete-directive.js b/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete-directive.js deleted file mode 100644 index 57330d6e..00000000 --- a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete-directive.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = function nativeAutocompleteDirective() { - return { - restrict: 'E', - replace: true, - scope: { - - }, - template: require('./native-autocomplete.jade'), - link: function (scope, element, attrs) { - - } - } -} diff --git a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete-spec.js b/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete-spec.js deleted file mode 100644 index c6ce909b..00000000 --- a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete-spec.js +++ /dev/null @@ -1,23 +0,0 @@ -describe('nativeAutocomplete', function () { - - beforeEach(angular.mock.module(require('./').name)); - - var scope, compile; - - beforeEach(inject(function ($rootScope, $compile) { - scope = $rootScope.$new(); - compile = $compile; - })); - - it('should ...', function () { - - /* - To test your directive, you need to create some html that would use your directive, - send that through compile() then compare the results. - - var element = compile('
hi
')(scope); - expect(element.text()).toBe('hello, world'); - */ - - }); -}); diff --git a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete.css b/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete.css deleted file mode 100644 index 7e597249..00000000 --- a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete.css +++ /dev/null @@ -1,3 +0,0 @@ -.stf-native-autocomplete { - -} \ No newline at end of file diff --git a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete.jade b/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete.jade deleted file mode 100644 index b4c17dbf..00000000 --- a/res/app/components/stf/common-ui/native-autocomplete/native-autocomplete.jade +++ /dev/null @@ -1 +0,0 @@ -div.stf-native-autocomplete \ No newline at end of file diff --git a/res/app/components/stf/common-ui/native-autocomplete/sample.jade b/res/app/components/stf/common-ui/native-autocomplete/sample.jade deleted file mode 100644 index 28b1e2a8..00000000 --- a/res/app/components/stf/common-ui/native-autocomplete/sample.jade +++ /dev/null @@ -1,7 +0,0 @@ -input( - type='text', - native-autocomplete, - ng-model='text', - typeahead='["text1", "text2"]', - history='20' -) diff --git a/res/app/components/stf/common-ui/notifications/growl.css b/res/app/components/stf/common-ui/notifications/growl.css index 38c00d07..a95a9c04 100644 --- a/res/app/components/stf/common-ui/notifications/growl.css +++ b/res/app/components/stf/common-ui/notifications/growl.css @@ -3,7 +3,6 @@ top: 60px; right: 15px; float: right; - /*width: 320px;*/ z-index: 9999; } diff --git a/res/app/components/stf/common-ui/refresh-page/refresh-page-directive.js b/res/app/components/stf/common-ui/refresh-page/refresh-page-directive.js index 084091cb..34cf8a42 100644 --- a/res/app/components/stf/common-ui/refresh-page/refresh-page-directive.js +++ b/res/app/components/stf/common-ui/refresh-page/refresh-page-directive.js @@ -9,8 +9,6 @@ module.exports = function refreshPageDirective($window) { scope.reloadWindow = function () { $window.location.reload() } - - // TODO: reload with $route.reload() } } } diff --git a/res/app/components/stf/common-ui/table/index.js b/res/app/components/stf/common-ui/table/index.js index a7439cc7..eb1056f6 100644 --- a/res/app/components/stf/common-ui/table/index.js +++ b/res/app/components/stf/common-ui/table/index.js @@ -1,6 +1,5 @@ require('./table.css') require('script!ng-table/dist/ng-table') -//require('ng-table/ng-table.css') module.exports = angular.module('stf/common-ui/table', [ 'ngTable' diff --git a/res/app/components/stf/common-ui/table/table.css b/res/app/components/stf/common-ui/table/table.css index 412d67bc..727a87ee 100644 --- a/res/app/components/stf/common-ui/table/table.css +++ b/res/app/components/stf/common-ui/table/table.css @@ -1,5 +1,4 @@ .ng-table th { - /*text-align: center;*/ -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; diff --git a/res/app/components/stf/common-ui/tree/index.js b/res/app/components/stf/common-ui/tree/index.js deleted file mode 100644 index 60a130ac..00000000 --- a/res/app/components/stf/common-ui/tree/index.js +++ /dev/null @@ -1,8 +0,0 @@ -//require('angular-tree-control/css/tree-control.css') -//require('./tree.css') -//require('angular-tree-control') - -module.exports = angular.module('stf.tree', [ -// 'treeControl' -]) - .factory('TreeService', require('./tree-service')) diff --git a/res/app/components/stf/common-ui/tree/tree-service.js b/res/app/components/stf/common-ui/tree/tree-service.js deleted file mode 100644 index 9f4cdd3c..00000000 --- a/res/app/components/stf/common-ui/tree/tree-service.js +++ /dev/null @@ -1,52 +0,0 @@ -module.exports = function () { - var treeService = {} - - var tree = [ - {name: 'glossary', children: [ - {name: 'title'} - ]} - ] - - function createTreeFromJSON(tree, json) { - - - (function updateRecursive(item) { - if (item.iconSrc) { - item.iconSrcFullpath = 'some value..'; - } - _.each(item.items, updateRecursive); - })(json); - } - - - $scope.treeOptions = { - nodeChildren: 'children', - dirSelectable: true, - injectClasses: { - ul: "a1", - li: "a2", - liSelected: "a7", - iExpanded: "a3", - iCollapsed: "a4", - iLeaf: "a5", - label: "a6", - labelSelected: "a8" - } - } - - $scope.treeData = [ - { "name": "Joe", "age": "21", "children": [ - { "name": "Smith", "age": "42", "children": [] }, - { "name": "Gary", "age": "21", "children": [ - { "name": "Jenifer", "age": "23", "children": [ - { "name": "Dani", "age": "32", "children": [] }, - { "name": "Max", "age": "34", "children": [] } - ]} - ]} - ]}, - { "name": "Albert", "age": "33", "children": [] }, - { "name": "Ron", "age": "29", "children": [] } - ]; - - return treeService -} \ No newline at end of file diff --git a/res/app/components/stf/common-ui/tree/tree.css b/res/app/components/stf/common-ui/tree/tree.css deleted file mode 100644 index ebe5ac2c..00000000 --- a/res/app/components/stf/common-ui/tree/tree.css +++ /dev/null @@ -1,3 +0,0 @@ -.stf-tree { - -} \ No newline at end of file diff --git a/res/app/components/stf/control/control-service.js b/res/app/components/stf/control/control-service.js index 471dd8df..d3b23fdf 100644 --- a/res/app/components/stf/control/control-service.js +++ b/res/app/components/stf/control/control-service.js @@ -157,9 +157,10 @@ module.exports = function ControlServiceFactory( return sendTwoWay('device.reboot') } - this.rotate = function(rotation) { + this.rotate = function(rotation, lock) { return sendOneWay('display.rotate', { - rotation: rotation + rotation: rotation, + lock: lock }) } @@ -224,6 +225,18 @@ module.exports = function ControlServiceFactory( return sendTwoWay('screen.capture') } + this.fsretrieve = function(file){ + return sendTwoWay('fs.retrieve', { + file: file, + }) + } + + this.fslist = function(dir){ + return sendTwoWay('fs.list', { + dir: dir, + }) + } + this.checkAccount = function(type, account) { return sendTwoWay('account.check', { type: type diff --git a/res/app/components/stf/filter-string/filter-string-spec.js b/res/app/components/stf/filter-string/filter-string-spec.js index 150cde7c..b0848f9c 100644 --- a/res/app/components/stf/filter-string/filter-string-spec.js +++ b/res/app/components/stf/filter-string/filter-string-spec.js @@ -1,11 +1,11 @@ describe('FilterStringService', function() { - beforeEach(angular.mock.module(require('./').name)); + beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function(FilterStringService) { + it('should ...', inject(function() { - //expect(FilterStringService.doSomething()).toEqual('something'); + //expect(FilterStringService.doSomething()).toEqual('something') - })); + })) }) diff --git a/res/app/components/stf/install/install-spec.js b/res/app/components/stf/install/install-spec.js index c11280fd..704432ee 100644 --- a/res/app/components/stf/install/install-spec.js +++ b/res/app/components/stf/install/install-spec.js @@ -2,9 +2,9 @@ describe('install', function() { beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function($filter) { + it('should ...', inject(function() { - var filter = $filter('installError') + //var filter = $filter('installError') //expect(filter('input')).toEqual('output') diff --git a/res/app/components/stf/logcat/logcat-spec.js b/res/app/components/stf/logcat/logcat-spec.js index df9fc51b..7d386944 100644 --- a/res/app/components/stf/logcat/logcat-spec.js +++ b/res/app/components/stf/logcat/logcat-spec.js @@ -1,11 +1,11 @@ describe('LogcatService', function() { - beforeEach(angular.mock.module(require('./').name)); + beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function(LogcatService) { + it('should ...', inject(function() { - //expect(LogcatService.doSomething()).toEqual('something'); + //expect(LogcatService.doSomething()).toEqual('something') - })); + })) }) diff --git a/res/app/components/stf/native-url/native-url-spec.js b/res/app/components/stf/native-url/native-url-spec.js index 39b7baf1..fd1db94a 100644 --- a/res/app/components/stf/native-url/native-url-spec.js +++ b/res/app/components/stf/native-url/native-url-spec.js @@ -2,7 +2,7 @@ describe('NativeUrlService', function() { beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function(NativeUrlService) { + it('should ...', inject(function() { //expect(NativeUrlService.doSomething()).toEqual('something') diff --git a/res/app/components/stf/port-forwarding/port-forwarding-spec.js b/res/app/components/stf/port-forwarding/port-forwarding-spec.js index 84b3df71..2574bde2 100644 --- a/res/app/components/stf/port-forwarding/port-forwarding-spec.js +++ b/res/app/components/stf/port-forwarding/port-forwarding-spec.js @@ -1,11 +1,10 @@ describe('PortForwardingService', function() { - beforeEach(angular.mock.module(require('./').name)); + beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function(PortForwardingService) { - expect(1).toBe(1) - //expect(PortForwardingService.doSomething()).toEqual('something'); + it('should ...', inject(function() { + //expect(PortForwardingService.doSomething()).toEqual('something') - })); + })) }) diff --git a/res/app/components/stf/scoped-hotkeys/scoped-hotkeys-spec.js b/res/app/components/stf/scoped-hotkeys/scoped-hotkeys-spec.js index c22ba96d..deef34f6 100644 --- a/res/app/components/stf/scoped-hotkeys/scoped-hotkeys-spec.js +++ b/res/app/components/stf/scoped-hotkeys/scoped-hotkeys-spec.js @@ -1,11 +1,11 @@ describe('ScopedHotkeysService', function() { - beforeEach(angular.mock.module(require('./').name)); + beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function(ScopedHotkeysService) { + it('should ...', inject(function() { - //expect(ScopedHotkeysService.doSomething()).toEqual('something'); + //expect(ScopedHotkeysService.doSomething()).toEqual('something') - })); + })) }) diff --git a/res/app/components/stf/screen/fast-image-render/test/performance_test.js b/res/app/components/stf/screen/fast-image-render/test/performance_test.js index ebba7712..f9f3afa6 100644 --- a/res/app/components/stf/screen/fast-image-render/test/performance_test.js +++ b/res/app/components/stf/screen/fast-image-render/test/performance_test.js @@ -7,6 +7,10 @@ var frame = { current: 0 } +function FastImageRender () { + +} + var imageRender = new FastImageRender( canvasElement , { diff --git a/res/app/components/stf/screen/screen-keyboard/screen-keyboard-directive.js b/res/app/components/stf/screen/screen-keyboard/screen-keyboard-directive.js index 1b3449b2..fd85ff98 100644 --- a/res/app/components/stf/screen/screen-keyboard/screen-keyboard-directive.js +++ b/res/app/components/stf/screen/screen-keyboard/screen-keyboard-directive.js @@ -3,8 +3,7 @@ module.exports = function screenKeyboardDirective() { restrict: 'E', template: require('./screen-keyboard.jade'), link: function (scope, element) { - var input = element.find('input') - + element.find('input') } } diff --git a/res/app/components/stf/screen/screen-touch/screen-touch-directive.js b/res/app/components/stf/screen/screen-touch/screen-touch-directive.js index f4291fc8..272ab552 100644 --- a/res/app/components/stf/screen/screen-touch/screen-touch-directive.js +++ b/res/app/components/stf/screen/screen-touch/screen-touch-directive.js @@ -1,7 +1,7 @@ module.exports = function screenTouchDirective() { return { restrict: 'A', - link: function (scope, element, attrs) { + link: function () { } } diff --git a/res/app/components/stf/text-history/text-history-directive.js b/res/app/components/stf/text-history/text-history-directive.js index cba78b07..0714889b 100644 --- a/res/app/components/stf/text-history/text-history-directive.js +++ b/res/app/components/stf/text-history/text-history-directive.js @@ -5,7 +5,7 @@ module.exports = function textHistoryDirective() { return { restrict: 'A', template: '', - link: function (scope, element, attrs) { + link: function () { } } diff --git a/res/app/components/stf/timeline/timeline-spec.js b/res/app/components/stf/timeline/timeline-spec.js index d1e22933..6bc2050f 100644 --- a/res/app/components/stf/timeline/timeline-spec.js +++ b/res/app/components/stf/timeline/timeline-spec.js @@ -2,7 +2,7 @@ describe('TimelineService', function() { beforeEach(angular.mock.module(require('./').name)); - it('should ...', inject(function(TimelineService) { + it('should ...', inject(function() { //expect(TimelineService.doSomething()).toEqual('something'); diff --git a/res/app/components/stf/upload/upload-spec.js b/res/app/components/stf/upload/upload-spec.js index a7ed030c..a2abb45f 100644 --- a/res/app/components/stf/upload/upload-spec.js +++ b/res/app/components/stf/upload/upload-spec.js @@ -2,9 +2,9 @@ describe('upload', function() { beforeEach(angular.mock.module(require('./').name)) - it('should ...', inject(function($filter) { + it('should ...', inject(function() { - var filter = $filter('uploadError') + //var filter = $filter('uploadError') //expect(filter('input')).toEqual('output') diff --git a/res/app/control-panes/activity/activity-controller.js b/res/app/control-panes/activity/activity-controller.js index b8e49c89..665deb46 100644 --- a/res/app/control-panes/activity/activity-controller.js +++ b/res/app/control-panes/activity/activity-controller.js @@ -25,13 +25,6 @@ module.exports = function ActivityCtrl($scope, gettext, TimelineService) { serial: $scope.device.serial }) -// $scope.timeline.push({ -// title: title, -// message: message, -// serial: angular.copy($scope.device.serial), -// time: Date.now() -// }) - } diff --git a/res/app/control-panes/activity/activity.jade b/res/app/control-panes/activity/activity.jade index 19b8007b..b3eb7603 100644 --- a/res/app/control-panes/activity/activity.jade +++ b/res/app/control-panes/activity/activity.jade @@ -1,7 +1,4 @@ .widget-container.scrollableX.messages.stf-activity(ng-controller='ActivityCtrl') - //.heading - i.fa - span(translate) Activity .widget-content.padded ul(ng-repeat='line in timeline.lines') @@ -24,27 +21,3 @@ div refresh-page - - //a(href='/') - .status.unread - //i.fa.fa-exclamation-triangle.fa-2x.activity-icon - h2.activity-title WebSocket Disconnected - span.activity-date 2014/04/30 18:33:22 - - p.pull-left Socket connection was lost, try again reloading the page. - .activity-buttons.pull-right - refresh-page - .clearfix - //li.list-group-item - .reviewer-info - i.fa.fa-mobile.fa-2x.activity-icon - h5.activity-title 'Nexus 5' Disconnected - em.activity-date.pull-right 2014/04/30 15:33:22 - .review-text - p.pull-left Device was disconnected because it timed out. - .activity-buttons.pull-right - button.btn.btn-sm.btn-primary-outline(ng-click='') - i.fa.fa-refresh - span(translate) Reconnect device - - diff --git a/res/app/control-panes/advanced/advanced.jade b/res/app/control-panes/advanced/advanced.jade index 3b369fd8..d31ae11f 100644 --- a/res/app/control-panes/advanced/advanced.jade +++ b/res/app/control-panes/advanced/advanced.jade @@ -5,5 +5,9 @@ div(ng-include='"control-panes/advanced/input/input.jade"') .col-md-6 div(ng-include='"control-panes/advanced/port-forwarding/port-forwarding.jade"') +.row + .col-md-6 + div(ng-include='"control-panes/advanced/vnc/vnc.jade"') + .col-md-6 div(ng-include='"control-panes/advanced/maintenance/maintenance.jade"') diff --git a/res/app/control-panes/advanced/index.js b/res/app/control-panes/advanced/index.js index 1c535435..0c8fa1ca 100644 --- a/res/app/control-panes/advanced/index.js +++ b/res/app/control-panes/advanced/index.js @@ -4,6 +4,7 @@ module.exports = angular.module('stf.advanced', [ require('./input').name, // require('./run-js').name, // require('./usb').name, + require('./vnc').name, require('./port-forwarding').name, require('./maintenance').name ]) diff --git a/res/app/control-panes/advanced/input/input.jade b/res/app/control-panes/advanced/input/input.jade index 5d987da9..2a1ffb91 100644 --- a/res/app/control-panes/advanced/input/input.jade +++ b/res/app/control-panes/advanced/input/input.jade @@ -6,13 +6,13 @@ div h6(translate) Special Keys div.special-keys-buttons - button(tooltip='{{ "Power" | translate }}', ng-click='press("power")').btn.btn-danger + button(tooltip='{{ "Power" | translate }}', ng-click='press("power")').btn.btn-danger.btn-xs i.fa.fa-power-off - button(tooltip='{{ "Camera" | translate }}', ng-click='press("camera")').btn.btn-primary + button(tooltip='{{ "Camera" | translate }}', ng-click='press("camera")').btn.btn-primary.btn-xs i.fa.fa-camera - button(tooltip='{{ "Switch Charset" | translate }}', ng-click='press("switch_charset")').btn.btn-primary.btn-info + button(tooltip='{{ "Switch Charset" | translate }}', ng-click='press("switch_charset")').btn.btn-primary.btn-info.btn-xs i.fa Aa - button(tooltip='{{ "Search" | translate }}', ng-click='press("search")').btn.btn-primary + button(tooltip='{{ "Search" | translate }}', ng-click='press("search")').btn.btn-primary.btn-xs i.fa.fa-search h6(translate) Volume @@ -38,58 +38,8 @@ i.fa.fa-step-forward button(tooltip='{{ "Fast Forward" | translate }}', ng-click='press("media_fast_forward")').btn.btn-primary.btn-xs i.fa.fa-fast-forward - //h6 Physical Media - //.btn-group - button(tooltip='{{ "Play" | translate }}', ng-click='press("KEYCODE_MEDIA_PLAY")').btn.btn-primary.btn-xs - i.fa.fa-play - button(tooltip='{{ "Pause" | translate }}', ng-click='press("KEYCODE_MEDIA_PAUSE")').btn.btn-primary.btn-xs - i.fa.fa-pause - button(tooltip='{{ "Close" | translate }}', ng-click='press("KEYCODE_MEDIA_CLOSE")').btn.btn-primary.btn-xs - i.fa.fa-sign-out - button(tooltip='{{ "Eject" | translate }}', ng-click='press("KEYCODE_MEDIA_EJECT")').btn.btn-primary.btn-xs - i.fa.fa-eject - button(tooltip='{{ "Record" | translate }}', ng-click='press("KEYCODE_MEDIA_RECORD")').btn.btn-primary.btn-xs - i.fa.fa-circle - //h6(translate) Other Keys - //div.special-other-keys-buttons - button(ng-click='press("KEYCODE_APP_SWITCH")').btn.btn-default.btn-xs - i.fa App Switch - button(ng-click='press("KEYCODE_MANNER_MODE")').btn.btn-default.btn-xs - i.fa Manner Mode - button(ng-click='press("KEYCODE_3D_MODE")').btn.btn-default.btn-xs - i.fa 3D Mode - button(ng-click='press("KEYCODE_CONTACTS")').btn.btn-default.btn-xs - i.fa Contacts - button(ng-click='press("KEYCODE_CALENDAR")').btn.btn-default.btn-xs - i.fa Calendar - button(ng-click='press("KEYCODE_MUSIC")').btn.btn-default.btn-xs - i.fa Music - button(ng-click='press("KEYCODE_CALCULATOR")').btn.btn-default.btn-xs - i.fa Calculator - button(ng-click='press("KEYCODE_ZENKAKU_HANKAKU")').btn.btn-default.btn-xs - i.fa 全角/半角 - button(ng-click='press("KEYCODE_EISU")').btn.btn-default.btn-xs - i.fa 英数 - button(ng-click='press("KEYCODE_MUHENKAN")').btn.btn-default.btn-xs - i.fa 無変換 - button(ng-click='press("KEYCODE_HENKAN")').btn.btn-default.btn-xs - i.fa 変換 - button(ng-click='press("KEYCODE_KATAKANA_HIRAGANA")').btn.btn-default.btn-xs - i.fa カタかナ/ひらがな - button(ng-click='press("KEYCODE_YEN")').btn.btn-default.btn-xs - i.fa ¥ - button(ng-click='press("KEYCODE_RO")').btn.btn-default.btn-xs - i.fa RO - button(ng-click='press("KEYCODE_KANA")').btn.btn-default.btn-xs - i.fa かな - button(ng-click='press("KEYCODE_ASSIST")').btn.btn-default.btn-xs - i.fa Assist - //button(tooltip='{{ "Switch Charset" | translate }}', ng-click='press(80)').btn.btn-primary - i.fa TST - //button(ng-click='press("KEYCODE_CLEAR")').btn.btn-primary.btn-sm - i.fa Clear - h6 D-pad - table.special-keys-dpad-buttons + //h6 D-pad + //table.special-keys-dpad-buttons tr td td diff --git a/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade b/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade index 79369207..b4165ba7 100644 --- a/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade +++ b/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade @@ -1,7 +1,7 @@ .widget-container.fluid-height.stf-port-forwarding(ng-controller='PortForwardingCtrl') .heading span - stacked-icon(icon='fa-random', color='color-darkgreen') + stacked-icon(icon='fa-random', color='color-orange') span(translate, ng-click='isCollapsed = !isCollapsed').pointer Port Forwarding button.btn.pull-right.btn-sm.btn-primary-outline( @@ -44,7 +44,3 @@ td button.btn.btn-sm.btn-danger-outline(ng-click='removeRow(forward)') i.fa.fa-trash-o - //.checkbox - label - input(type='checkbox', value='') - span(translate) Always forward on connect diff --git a/res/app/control-panes/advanced/run-js/run-js-controller.js b/res/app/control-panes/advanced/run-js/run-js-controller.js index 7c940582..cf42a383 100644 --- a/res/app/control-panes/advanced/run-js/run-js-controller.js +++ b/res/app/control-panes/advanced/run-js/run-js-controller.js @@ -1,3 +1,3 @@ -module.exports = function RunJsCtrl($scope) { - +module.exports = function RunJsCtrl() { + } diff --git a/res/app/control-panes/advanced/run-js/run-js.jade b/res/app/control-panes/advanced/run-js/run-js.jade index 4b1b272a..e2f12bd7 100644 --- a/res/app/control-panes/advanced/run-js/run-js.jade +++ b/res/app/control-panes/advanced/run-js/run-js.jade @@ -3,11 +3,7 @@ .heading i.fa.fa-code span(translate) Run JavaScript - //form.form-inline .btn-group - //button(ng-disabled='true').btn.btn-sm.btn-default-outline - i.fa.fa-upload - | Load File... script(type='text/ng-template', id='saveSnippetModal.html') .modal-header h2 Save snippet @@ -29,24 +25,6 @@ li.divider li a(ng-click='clearSnippets()', type='button', translate).btn-link Clear - //span.form-inline.form-group.unselectable - .checkbox - label - input(type='checkbox', ng-model='snippet.safe') - span(tooltip='Execute code in a safe way') Safe - span - .checkbox - label - input(type='checkbox', nxg-model='snippet.evaluate') - span(tooltip='Evaluate code') Evaluate - .checkbox - label - input(type='checkbox', ng-model='snippet.async') - span(tooltip='Execute code in an async way') Async - .checkbox - label - input(type='checkbox', ng-model='snippet.scriptTag', ng-disabled='true') - span(tooltip='{{scriptTagPopover}}') Script tag .btn-group.pull-right button.btn.btn-sm.btn-primary-outline(ng-click='injectJS()', ng-disabled='!snippet.editorText') i.fa.fa-play @@ -64,14 +42,12 @@ span {{ result.deviceName }} td(width='30%', title="'Returns'", sortable='prettyValue') div(ng-show='result.isObject') - //ace-json-viewer(ng-model='result.prettyValue') - //div(ui-ace="miniAceOptions", ng-model='result.prettyValue').stf-mini-ace-viewer code.value-next-to-progress {{ result.prettyValue }} div(ng-hide='result.isObject') .value-next-to-progress {{ result.value }} td(width='40%', ng-show='result.isSpecialValue') div(ng-show='result.isNumber') - //progressbar.table-progress(value='result.percentage', max='100') + progressbar.table-progress(value='result.percentage', max='100') div(ng-show='result.isObject') div.label.label-info Object div(ng-show='result.isFunction') @@ -84,7 +60,6 @@ div.label(style='width=100%', ng-class="{'label-success': result.value, 'label-important': !result.value}") i.fa(ng-class="{'fa-check': result.value, 'fa-times-circle': !result.value }") span {{ result.value.toString() }} - //span {{ result.value.toString() | capitalize }} tab(heading='Raw') pre.selectable {{results | json}} clear-button(ng-click='clear()', ng-disabled='!results.length') diff --git a/res/app/control-panes/advanced/usb/usb-controller.js b/res/app/control-panes/advanced/usb/usb-controller.js index 09ee2aed..e7ad2488 100644 --- a/res/app/control-panes/advanced/usb/usb-controller.js +++ b/res/app/control-panes/advanced/usb/usb-controller.js @@ -1,3 +1,3 @@ -module.exports = function UsbCtrl($scope) { - +module.exports = function UsbCtrl() { + } diff --git a/res/app/control-panes/advanced/vnc/index.js b/res/app/control-panes/advanced/vnc/index.js new file mode 100644 index 00000000..da5b4f2b --- /dev/null +++ b/res/app/control-panes/advanced/vnc/index.js @@ -0,0 +1,12 @@ +require('./vnc.css') + +module.exports = angular.module('stf.vnc', [ + require('gettext').name +]) + .run(["$templateCache", function ($templateCache) { + $templateCache.put( + 'control-panes/advanced/vnc/vnc.jade', + require('./vnc.jade') + ) + }]) + .controller('VNCCtrl', require('./vnc-controller')) diff --git a/res/app/control-panes/advanced/vnc/vnc-controller.js b/res/app/control-panes/advanced/vnc/vnc-controller.js new file mode 100644 index 00000000..e0235bee --- /dev/null +++ b/res/app/control-panes/advanced/vnc/vnc-controller.js @@ -0,0 +1,11 @@ +module.exports = function RemoteDebugCtrl($scope) { + $scope.vnc = {} + + $scope.generateVNCLogin = function () { + $scope.vnc = { + serverHost: 'localhost' + , serverPort: '7042' + , serverPassword: '12345678' + } + } +} diff --git a/res/app/control-panes/advanced/vnc/vnc-spec.js b/res/app/control-panes/advanced/vnc/vnc-spec.js new file mode 100644 index 00000000..c9b6d2fe --- /dev/null +++ b/res/app/control-panes/advanced/vnc/vnc-spec.js @@ -0,0 +1,17 @@ +describe('VNCCtrl', function () { + + beforeEach(angular.mock.module(require('./').name)); + + var scope, ctrl; + + beforeEach(inject(function ($rootScope, $controller) { + scope = $rootScope.$new(); + ctrl = $controller('VNCCtrl', {$scope: scope}); + })); + + it('should ...', inject(function () { + expect(1).toEqual(1); + + })); + +}); diff --git a/res/app/control-panes/advanced/vnc/vnc.css b/res/app/control-panes/advanced/vnc/vnc.css new file mode 100644 index 00000000..7f2a668c --- /dev/null +++ b/res/app/control-panes/advanced/vnc/vnc.css @@ -0,0 +1,4 @@ +.stf-vnc { + +} + diff --git a/res/app/control-panes/advanced/vnc/vnc.jade b/res/app/control-panes/advanced/vnc/vnc.jade new file mode 100644 index 00000000..4a6e65ff --- /dev/null +++ b/res/app/control-panes/advanced/vnc/vnc.jade @@ -0,0 +1,28 @@ +.widget-container.fluid-height.stf-vnc(ng-controller='VNCCtrl') + .heading + stacked-icon(icon='fa-eye', color='color-darkgreen') + span(translate) VNC + + button.btn.pull-right.btn-sm.btn-primary-outline( + ng-click='generateVNCLogin()', tooltip='{{"Generate Login for VNC"|translate}}') + i.fa.fa-plus.fa-fw + + .widget-content.padded + form(name='vncloginform', ng-show='vnc.serverHost') + table.table.table-condensed + thead + tr + th(colspan='1') + span(translate) Server + th(colspan='1') + span(translate) Port + th(colspan='1') + span(translate) Password + tbody + tr + td(width='50%') + input.form-control.input-sm(type='text', ng-model='vnc.serverHost', readonly, text-focus-select).vnc-server-host + td(width='20%') + input.form-control.input-sm(type='text', ng-model='vnc.serverPort', readonly, text-focus-select).vnc-server-port + td(width='30%') + input.form-control.input-sm(type='text', ng-model='vnc.serverPassword', readonly, text-focus-select).vnc-server-password diff --git a/res/app/control-panes/automation/device-settings/device-settings.jade b/res/app/control-panes/automation/device-settings/device-settings.jade index 375ccfd3..4f2b33fa 100644 --- a/res/app/control-panes/automation/device-settings/device-settings.jade +++ b/res/app/control-panes/automation/device-settings/device-settings.jade @@ -3,16 +3,28 @@ stacked-icon(icon='fa-gears', color='color-darkgray') span(translate) Device Settings .widget-content.padded + .row + .col-md-6 + h6(translate) Manner Mode + .btn-group + label.btn.btn-sm.btn-primary-outline(ng-model='ringerMode', btn-radio='"SILENT"') + i.fa.fa-volume-off.fa-fw(tooltip='{{"Silent Mode" | translate}}') + label.btn.btn-sm.btn-primary-outline(ng-model='ringerMode', btn-radio='"VIBRATE"') + i.fa.fa-mobile.fa-fw(tooltip='{{"Vibrate Mode" | translate}}') + label.btn.btn-sm.btn-primary-outline(ng-model='ringerMode', btn-radio='"NORMAL"') + i.fa.fa-volume-up.fa-fw(tooltip='{{"Normal Mode" | translate}}') - button.btn.btn-sm.btn-primary-outline(ng-click='toggleWifi()', - ng-model='wifiEnabled', btn-checkbox) - i.fa.fa-signal.fa-fw(ng-show='wifiEnabled', tooltip='{{"Disable WiFi" | translate}}') - i.fa.fa-signal.fa-fw(ng-hide='wifiEnabled', tooltip='{{"Enable WiFi" | translate}}') + .col-md-6 + h6(translate) WiFi + button.btn.btn-sm.btn-primary-outline(ng-click='toggleWifi()', + ng-model='wifiEnabled', btn-checkbox) + i.fa.fa-wifi.fa-fw(ng-show='wifiEnabled', tooltip='{{"Disable WiFi" | translate}}') + i.fa.fa-wifi.fa-fw(ng-hide='wifiEnabled', tooltip='{{"Enable WiFi" | translate}}') + //.row + .col-md-12 + h6(translate) Lock Rotation - .btn-group - label.btn.btn-sm.btn-primary-outline(ng-model='ringerMode', btn-radio='"SILENT"') - i.fa.fa-volume-off.fa-fw(tooltip='{{"Silent Mode" | translate}}') - label.btn.btn-sm.btn-primary-outline(ng-model='ringerMode', btn-radio='"VIBRATE"') - i.fa.fa-mobile.fa-fw(tooltip='{{"Vibrate Mode" | translate}}') - label.btn.btn-sm.btn-primary-outline(ng-model='ringerMode', btn-radio='"NORMAL"') - i.fa.fa-volume-up.fa-fw(tooltip='{{"Normal Mode" | translate}}') + button.btn.btn-sm.btn-primary-outline(ng-click='toggleLockRotation()', + ng-model='lockRotation', btn-checkbox) + i.fa.fa-repeat.fa-fw(ng-show='lockRotation', tooltip='{{"Unlock Rotation" | translate}}') + i.fa.fa-repeat.fa-fw(ng-hide='lockRotation', tooltip='{{"Lock Rotation" | translate}}') diff --git a/res/app/control-panes/automation/store-account/store-account.jade b/res/app/control-panes/automation/store-account/store-account.jade index 9ff59791..8f544dcc 100644 --- a/res/app/control-panes/automation/store-account/store-account.jade +++ b/res/app/control-panes/automation/store-account/store-account.jade @@ -7,9 +7,7 @@ tooltip='{{"App Store" | translate}}', tooltip-placement="bottom") i.fa.fa-shopping-cart .widget-content.padded - div - form(name='storeLogin', novalidate, enable-autofill) .form-group .input-group @@ -55,5 +53,4 @@ td button.btn.btn-xs.btn-danger-outline(ng-click='removeAccount(account)') i.fa.fa-sign-out - //i.fa.fa-trash-o span(translate) Sign Out diff --git a/res/app/control-panes/control-panes-controller.js b/res/app/control-panes/control-panes-controller.js index faf503da..ea936d18 100644 --- a/res/app/control-panes/control-panes-controller.js +++ b/res/app/control-panes/control-panes-controller.js @@ -22,6 +22,12 @@ module.exports = templateUrl: 'control-panes/advanced/advanced.jade', filters: ['native', 'web'] }, + { + title: gettext('File Explorer'), + icon: 'fa-folder-open color-blue', + templateUrl: 'control-panes/explorer/explorer.jade', + filters: ['native', 'web'] + }, { title: gettext('Info'), icon: 'fa-info color-orange', diff --git a/res/app/control-panes/control-panes-hotkeys-controller.js b/res/app/control-panes/control-panes-hotkeys-controller.js index 08c99b54..b31f551b 100644 --- a/res/app/control-panes/control-panes-hotkeys-controller.js +++ b/res/app/control-panes/control-panes-hotkeys-controller.js @@ -10,12 +10,6 @@ module.exports = }, nextDevice: function () { console.log('next') - // console.log('$scope.groupTracker.devices', $scope.groupTracker.devices) - // console.log('$scope.groupTracker.devices', $scope.groupDevices) - - // groupDevice in $scope.groupTracker.devices - // groupDevice.serial === device.serial - // $location.path('/control/' + device.serial) }, deviceList: function () { $location.path('/devices/') diff --git a/res/app/control-panes/control-panes.jade b/res/app/control-panes/control-panes.jade index ae95b9c4..45ee602d 100644 --- a/res/app/control-panes/control-panes.jade +++ b/res/app/control-panes/control-panes.jade @@ -8,7 +8,6 @@ div(ng-controller='ControlPanesHotKeysCtrl').fill-height div(fa-pane, pane-id='control-device', pane-anchor='west', pane-size='{{remotePaneSize}}', pane-min='200px', pane-max='100% + 2px', pane-handle='4', pane-no-toggle='false') .remote-control - //include control-screen div(ng-include='"control-panes/device-control/device-control.jade"').fill-height div(fa-pane, pane-id='control-bottom-tabs', pane-anchor='south', pane-size='30% + 2px', pane-handle='4').pane-bottom-p diff --git a/res/app/control-panes/cpu/cpu.jade b/res/app/control-panes/cpu/cpu.jade index 1bc5d4cc..4b7be671 100644 --- a/res/app/control-panes/cpu/cpu.jade +++ b/res/app/control-panes/cpu/cpu.jade @@ -1,10 +1,9 @@ .widget-container.fluid-height.stf-cpu(ng-controller='CpuCtrl') .widget-content.padded - //nothing-to-show(show='!data', message='No CPU info', icon='fa-bar-chart-o') div.overflow-x ul li(ng-repeat="(id, stats) in data") span {{ stats.deviceName }} ul li(ng-repeat="(cpu, load) in stats.loads") - span {{cpu}}: {{load.user + load.nice + load.system}}% \ No newline at end of file + span {{cpu}}: {{load.user + load.nice + load.system}}% diff --git a/res/app/control-panes/dashboard/apps/apps-controller.js b/res/app/control-panes/dashboard/apps/apps-controller.js index ff6d7e28..2fd1bc82 100644 --- a/res/app/control-panes/dashboard/apps/apps-controller.js +++ b/res/app/control-panes/dashboard/apps/apps-controller.js @@ -15,7 +15,6 @@ module.exports = function ShellCtrl($scope) { // TODO: Move this to server side // TODO: Android 2.x doesn't support openSetting(), account for that on the UI - function openSetting(activity) { run('am start -a android.intent.action.MAIN -n com.android.settings/.Settings\\$' + activity) @@ -59,11 +58,6 @@ module.exports = function ShellCtrl($scope) { openSetting('DevelopmentSettingsActivity') } - - //'am start -n com.android.settings/.Settings\$PowerUsageSummaryActivity' - //'am start -a android.intent.action.POWER_USAGE_SUMMARY' - - $scope.clear = function () { $scope.command = '' $scope.data = '' diff --git a/res/app/control-panes/dashboard/apps/apps.css b/res/app/control-panes/dashboard/apps/apps.css index 8e0655eb..f866a1bc 100644 --- a/res/app/control-panes/dashboard/apps/apps.css +++ b/res/app/control-panes/dashboard/apps/apps.css @@ -12,6 +12,10 @@ margin-top: -8px; } +.stf-apps .icon-app:hover .fa { + color: #fff; +} + .stf-apps .heading .icon-app .fa { font-size: 19px !important; line-height: 14px !important; diff --git a/res/app/control-panes/dashboard/apps/apps.jade b/res/app/control-panes/dashboard/apps/apps.jade index 64d59cf3..211e8775 100644 --- a/res/app/control-panes/dashboard/apps/apps.jade +++ b/res/app/control-panes/dashboard/apps/apps.jade @@ -13,7 +13,7 @@ .widget-content.padded button.btn.btn-primary-outline.icon-app.pull-right(ng-click='openWiFiSettings()') - i.fa.fa-signal.fa-lg.color-skyblue + i.fa.fa-wifi.fa-lg.color-skyblue .icon-title {{"WiFi" | translate}} button.btn.btn-primary-outline.icon-app.pull-right(ng-click='openManageApps()') diff --git a/res/app/control-panes/dashboard/dashboard.jade b/res/app/control-panes/dashboard/dashboard.jade index 2713ea2a..ae38fd2f 100644 --- a/res/app/control-panes/dashboard/dashboard.jade +++ b/res/app/control-panes/dashboard/dashboard.jade @@ -1,10 +1,6 @@ .row .col-md-6 div(ng-include='"control-panes/dashboard/navigation/navigation.jade"') - //.col-md-6(ng-if='$root.platform == "web"') - div(ng-include='"control-panes/dashboard/browser/browser.jade"') - //.col-md-6 - div(ng-include='"control-panes/dashboard/input/input.jade"') .col-md-6 div(ng-include='"control-panes/dashboard/clipboard/clipboard.jade"') diff --git a/res/app/control-panes/dashboard/install/install.css b/res/app/control-panes/dashboard/install/install.css index d0adb227..0bb05dfd 100644 --- a/res/app/control-panes/dashboard/install/install.css +++ b/res/app/control-panes/dashboard/install/install.css @@ -24,7 +24,6 @@ padding-top: 10px; border: 2px transparent dashed; border-radius: 2px; - /*background-color: #f6f6f6;*/ cursor: pointer; } @@ -41,13 +40,6 @@ } .stf-upload .drop-area-text { - /*border-top: 1px solid #e2e2e2;*/ - /*opacity: 0;*/ font-size: 14px; font-weight: 300; - /*transition: opacity 0.25s ease-in-out;*/ -} - -.stf-upload .drop-area:hover .drop-area-text { - /*opacity: 1;*/ } diff --git a/res/app/control-panes/dashboard/install/install.jade b/res/app/control-panes/dashboard/install/install.jade index 6cb91543..914c38b1 100644 --- a/res/app/control-panes/dashboard/install/install.jade +++ b/res/app/control-panes/dashboard/install/install.jade @@ -58,8 +58,6 @@ span(ng-if='!showManifest') Show Manifest pre.manifest-text(ng-if='showManifest') {{ installation.manifest | json }} - //error-message(message='{{ installation.error | installError | translate }} ({{ installation.error }})') - // TODO alert(type='danger', close='clear()', ng-if='installation.error') strong(translate) Oops! | diff --git a/res/app/control-panes/dashboard/shell/shell.jade b/res/app/control-panes/dashboard/shell/shell.jade index 8727de2a..874ea2f8 100644 --- a/res/app/control-panes/dashboard/shell/shell.jade +++ b/res/app/control-panes/dashboard/shell/shell.jade @@ -7,22 +7,14 @@ .widget-content.padded - // TODO: autofill doesn't work here + // NOTE: autofill doesn't work here form(method='post', enable-autofill, ng-submit='run(command)') .input-group.form-inline input(type=text, ng-model='command', Xtext-focus-select, autocapitalize='off', spellcheck='false', tabindex='30', accesskey='S', autocomplete='on').form-control.shell-input span.input-group-btn - // , tooltip='{{"Run Command"|translate}}' button.btn.btn-primary-outline(ng-click='run(command)', ng-disabled='!command') - //, tooltip='{{ "Run command" | translate }}') i.fa.fa-play pre.shell-results.selectable(ng-show='data') {{data}} pre.shell-results.selectable.shell-results-empty(ng-show='result.settled && !data') No output - - - // table - tr(ng-repeat='result in results track by result.device.serial') - td {{ result.device.serial }} - td {{ result.data }} diff --git a/res/app/control-panes/device-control/device-control.css b/res/app/control-panes/device-control/device-control.css index 336c4ccf..d746eb8f 100644 --- a/res/app/control-panes/device-control/device-control.css +++ b/res/app/control-panes/device-control/device-control.css @@ -202,7 +202,6 @@ device-screen input { /* VNC buttons */ .stf-vnc-bottom .btn-primary:hover { background: rgba(255, 255, 255, 1.0); - /*border-color: rgba(255, 255, 255, 1.0);*/ border: none; } @@ -210,12 +209,10 @@ device-screen input { border-radius: 0; } -/*.stf-vnc-bottom .btn-primary:active,*/ .stf-vnc-bottom .btn-primary:active { background: rgba(250, 250, 250, 0.75); border: none; color: #0d3fa4; - /*border-color: rgba(255, 255, 255, 0.7);*/ } .stf-vnc-navbar-buttons { @@ -229,8 +226,6 @@ device-screen input { } .stf-device-control .stf-vnc-device-name { - /*padding: 6px 2px 6px 15px;*/ - /*float: left;*/ font-size: 16px; line-height: 20px; text-overflow: ellipsis; @@ -240,7 +235,6 @@ device-screen input { } .stf-device-control .device-name-container { - overflow: hidden; text-overflow: ellipsis; } diff --git a/res/app/control-panes/device-control/device-control.jade b/res/app/control-panes/device-control/device-control.jade index 3d7531e8..1ef2aff2 100644 --- a/res/app/control-panes/device-control/device-control.jade +++ b/res/app/control-panes/device-control/device-control.jade @@ -11,7 +11,6 @@ ng-model='currentRotation', btn-radio='"landscape"').pointer i.fa.fa-mobile.fa-rotate-90(tooltip='{{ "Landscape" | translate }} (Current rotation: {{ device.display.rotation }}°)', tooltip-placement='bottom') .button-spacer - // NOTE: ui-bootstrap bug: tooltip breaks btn-checkbox so don't put in the same button button(type='button', ng-model='showScreen', btn-checkbox).btn.btn-sm.btn-danger i(ng-show='showScreen', tooltip-html-unsafe='{{"Hide Screen"|translate}}', tooltip-placement='bottom').fa.fa-eye i(ng-show='!showScreen', tooltip-html-unsafe='{{"Show Screen"|translate}}', tooltip-placement='bottom').fa.fa-eye-slash @@ -23,7 +22,6 @@ img(ng-src='/static/app/devices/icon/x24/{{ device.image || "E30HT.jpg" }}') span.device-name-text {{ device.enhancedName }} span.caret(ng-show='groupDevices.length > 0') - //span(ng-show='device && !device.present', translate) (Absent) ul.dropdown-menu(role='menu', data-toggle='dropdown', ng-show='groupDevices.length > 0').pointer.unselectable li(ng-repeat='groupDevice in groupDevices') diff --git a/res/app/control-panes/explorer/explorer-controller.js b/res/app/control-panes/explorer/explorer-controller.js new file mode 100644 index 00000000..478023e5 --- /dev/null +++ b/res/app/control-panes/explorer/explorer-controller.js @@ -0,0 +1,69 @@ +module.exports = function ExplorerCtrl($scope) { + $scope.explorer = { + search: '', + files: [], + paths: [] + } + + $scope.getAbsolutePath = function () { + return ('/' + $scope.explorer.paths.join('/')).replace(/\/\/+/g, '/') + } + + function resetPaths(path) { + $scope.explorer.paths = path.split('/') + } + + var listDir = function listDir() { + var path = $scope.getAbsolutePath() + $scope.explorer.search = path + + $scope.control.fslist(path) + .then(function (result) { + $scope.explorer.files = result.body; + $scope.$digest(); + }) + .catch(function (err) { + alert(err.message) + }) + } + + $scope.dirEnterLocation = function () { + if ($scope.explorer.search) { + resetPaths($scope.explorer.search) + listDir() + $scope.explorer.search = $scope.getAbsolutePath() + } + } + + $scope.dirEnter = function (name) { + if (name) { + $scope.explorer.paths.push(name) + } + listDir() + $scope.explorer.search = $scope.getAbsolutePath() + } + + $scope.dirUp = function () { + if ($scope.explorer.paths.length !== 0) { + $scope.explorer.paths.pop() + } + listDir() + $scope.explorer.search = $scope.getAbsolutePath() + } + + $scope.getFile = function (file) { + var path = $scope.getAbsolutePath() + '/' + file + $scope.control.fsretrieve(path) + .then(function (result) { + if (result.body) { + location.href = result.body.href + "?download" + } + }) + .catch(function (err) { + alert(err.message) + }) + } + + // Initialize + listDir($scope.dir) +} diff --git a/res/app/control-panes/explorer/explorer-spec.js b/res/app/control-panes/explorer/explorer-spec.js new file mode 100644 index 00000000..52ebf1af --- /dev/null +++ b/res/app/control-panes/explorer/explorer-spec.js @@ -0,0 +1,17 @@ +describe('FsCtrl', function () { + + beforeEach(angular.mock.module(require('./').name)) + + var scope, ctrl + + beforeEach(inject(function ($rootScope, $controller) { + scope = $rootScope.$new() + ctrl = $controller('ExplorerCtrl', {$scope: scope}) + })) + + it('should ...', inject(function () { + expect(1).toEqual(1) + + })) + +}) diff --git a/res/app/components/stf/common-ui/help-icon/help-icon.css b/res/app/control-panes/explorer/explorer.css similarity index 100% rename from res/app/components/stf/common-ui/help-icon/help-icon.css rename to res/app/control-panes/explorer/explorer.css diff --git a/res/app/control-panes/explorer/explorer.jade b/res/app/control-panes/explorer/explorer.jade new file mode 100644 index 00000000..40b420f2 --- /dev/null +++ b/res/app/control-panes/explorer/explorer.jade @@ -0,0 +1,47 @@ +.widget-container.fluid-height(ng-controller='ExplorerCtrl').stf-explorer + .heading + + form.input-group.form-inline(name='explorerForm', ng-submit='dirEnterLocation()') + span.input-group-btn + button.btn.btn-primary-outline(ng-click='dirUp()') + i.fa.fa-level-up + input(type='text', ng-model='explorer.search', + ng-enter='dirEnterLocation()' + autocorrect='off', autocapitalize='off', spellcheck='false').form-control + span.input-group-btn + button.btn.btn-primary-outline(type='submit') + i.fa.fa-play + + .widget-content.padded.selectable + table.table.table-hover.table-condensed.dataTable.ng-table + thead + tr + th + div(translate) Name + th + div(translate) Size + th + div(translate) Date + th + div(translate) Permissions + tbody + tr.header(ng-repeat='f in explorer.files | filter:search | orderBy: ["-mode|fileIsDir", "+name"]') + td + button.btn.btn-sm.btn-primary-outline( + ng-click='dirEnter(f.name)', ng-show='f.mode|fileIsDir') + span + i.fa.fa-folder-open + span {{f.name}} + + button.btn.btn-sm.btn-primary-outline( + ng-click='getFile(f.name)', ng-hide='f.mode|fileIsDir') + span + i.fa.fa-file-o + span {{f.name}} + td + span(ng-show='f.mode|fileIsDir') - + span(ng-hide='f.mode|fileIsDir') {{f.size|formatFileSize}} + td + span {{f.mtime|formatFileDate}} + td + i {{f.mode|formatPermissionMode}} diff --git a/res/app/control-panes/explorer/index.js b/res/app/control-panes/explorer/index.js new file mode 100644 index 00000000..db6d88aa --- /dev/null +++ b/res/app/control-panes/explorer/index.js @@ -0,0 +1,59 @@ +require('./explorer.css') + +module.exports = angular.module('stf.explorer', []) + .run(["$templateCache", function ($templateCache) { + $templateCache.put('control-panes/explorer/explorer.jade', + require('./explorer.jade') + ) + }]) + .filter('formatPermissionMode', function () { + return function (mode) { + if (mode !== null) { + var res = []; + var s = ['x', 'w', 'r']; + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + if ((mode >> (i * 3 + j)) & 1 !== 0) { + res.unshift(s[j]) + } else { + res.unshift('-') + } + } + } + res.unshift(mode & 040000 ? 'd' : '-'); + return res.join(''); + } + } + }) + .filter('fileIsDir', function () { + return function (mode) { + if (mode !== null) { + mode = parseInt(mode, 10) + mode = mode - (mode & 0777) + return (mode == 040000) || (mode == 0120000) + } + } + }) + .filter('formatFileSize', function () { + return function (size) { + var formattedSize + if (size < 1024) { + formattedSize = size + ' B' + } else if (size >= 1024 && size < 1024 * 1024) { + formattedSize = Math.round(size / 1024, 1) + ' Kb' + } else { + formattedSize = Math.round(size / (1024 * 1024), 1) + ' Mb' + } + return formattedSize + } + }) + .filter('formatFileDate', function () { + return function (inputString) { + var input = new Date(inputString) + return input instanceof Date ? + input.toISOString().substring(0, 19).replace('T', ' ') : + (input.toLocaleString || input.toString).apply(input) + } + }) + + .controller('ExplorerCtrl', require('./explorer-controller')) diff --git a/res/app/control-panes/index.js b/res/app/control-panes/index.js index b802d6e7..1e35aeb6 100644 --- a/res/app/control-panes/index.js +++ b/res/app/control-panes/index.js @@ -13,6 +13,7 @@ module.exports = angular.module('control-panes', [ require('./logs').name, //require('./resources').name, require('./screenshots').name, + require('./explorer').name, require('./info').name ]) .config(['$routeProvider', function ($routeProvider) { diff --git a/res/app/control-panes/info/info.css b/res/app/control-panes/info/info.css index bd31152c..2cb3ba14 100644 --- a/res/app/control-panes/info/info.css +++ b/res/app/control-panes/info/info.css @@ -11,15 +11,10 @@ .stf-info .table-infocard tbody > tr > td:first-child { text-align: right; margin-right: 20px; - /*color: #111111;*/ font-weight: bold; white-space: nowrap; } -.stf-info .table-infocard tbody > tr > td:last-child { - /*color: #555;*/ -} - .stf-info .progress { margin-bottom: 0; height: 15px; diff --git a/res/app/control-panes/inspect/inspect.jade b/res/app/control-panes/inspect/inspect.jade index 73f58a2f..17077672 100644 --- a/res/app/control-panes/inspect/inspect.jade +++ b/res/app/control-panes/inspect/inspect.jade @@ -1,10 +1,6 @@ .stf-inspect(ng-controller='InspectCtrl') nothing-to-show(ng-if='$root.browser != "webview"', message='{{"Inspecting is currently only supported in WebView"|translate}}', icon='fa-search-plus') .something-white(ng-if='$root.browser == "webview"') - //.btn-group.padded - button.btn.btn-sm.btn-primary(ng-click='inspect()') - i.fa.fa-bug - span(translate) Inspect Current Page nothing-to-show(ng-if='!results.length', message='{{"Nothing to inspect"|translate}}', icon='fa-search-plus') table.table.table-striped(ng-table='tableParams', ng-show='results.length').selectable @@ -16,7 +12,5 @@ button(ng-click='setUrl(result.value.clientUrl)').btn.btn-primary i.fa.fa-search-plus span(translate) Inspect Device - //a.btn.btn-primary(href='{{ result.value.clientUrl}}', target='_blank') - i.fa.fa-share-alt Launch .weinre-window(ng-show='urlToShow') - iframe.weinre-content(type='text/html', width='100%', height='100%', ng-src='{{trustSrc(urlToShow)}}', frameborder='0') \ No newline at end of file + iframe.weinre-content(type='text/html', width='100%', height='100%', ng-src='{{trustSrc(urlToShow)}}', frameborder='0') diff --git a/res/app/control-panes/logs/logs-controller.js b/res/app/control-panes/logs/logs-controller.js index 1e2028ed..ee6069e9 100644 --- a/res/app/control-panes/logs/logs-controller.js +++ b/res/app/control-panes/logs/logs-controller.js @@ -50,10 +50,4 @@ module.exports = function LogsCtrl($scope, LogcatService) { 'tag', 'priority' ]) - -// $scope.$watchCollection('filters', function (newValue, oldValue) { -// console.log(newValue) -// }); - - } diff --git a/res/app/control-panes/logs/logs.jade b/res/app/control-panes/logs/logs.jade index 1991b13c..3db51322 100644 --- a/res/app/control-panes/logs/logs.jade +++ b/res/app/control-panes/logs/logs.jade @@ -1,45 +1,13 @@ .stf-logs(ng-controller='LogsCtrl') .widget-container.fluid-height - //.heading - .form-inline - button(ng-click='toggle()', ng-class="{active: started}", title='Start/Stop Logging').btn.btn-sm.btn-primary-outline - i.fa.fa-list-alt - span {{ started ? 'Stop Logging' : 'Start Logging' }} - - clear-button(ng-click='clear()', ng-disabled='!hasLines') - - label.checkbox.pull-right - input(type='checkbox', ng-model='autoScroll') - span Auto Scroll - .widget-content - //nothing-to-show(ng-show='!hasLines', message='No logs captured', icon='fa-list-alt') - - //table.table.table-condensed(ng-show='filters.show') - - // - table.table.table-condensed.logcat-filters-table(ng-show='true') - - //tr - th(width='4%')//Line - th(width='10%') Device - th(width='8%') Level - th(width='8%') Time - th(width='4%') PID - th(width='4%') TID - th(width='10%') App - th(width='16%') Tag - th(width='46%') Text tr td(width='1%') button(ng-model='started', btn-checkbox, title='{{"Start/Stop Logging"|translate}}').btn.btn-xs.btn-primary-outline i.fa.fa-list-alt span(ng-if='started') {{"Stop"|translate}} span(ng-if='!started') {{"Get"|translate}} - //td(width='10%') - select(ng-model='filters.deviceName', ng-options='d for d in filters.devicesList') - option(value='') {{"Device"|translate}} td(width='6%') select(ng-model='filters.priority', ng-options='l.name for l in filters.levelNumbers') option(value='', disabled, selected) {{"Level"|translate}} @@ -49,8 +17,6 @@ input(ng-model='filters.pid', type='text', placeholder='{{"PID"|translate}}').input-sm.form-control td(width='8%', ng-if='$root.platform == "native"') input(ng-model='filters.tid', type='text', placeholder='{{"TID"|translate}}').input-sm.form-control - //td(width='14%', ng-if='$root.platform == "native"') - input(ng-model='filters.app', type='text', placeholder='{{"App"|translate}}').input-sm.form-control td(width='14%', ng-if='$root.platform == "native"') input(ng-model='filters.tag', type='text', placeholder='{{"Tag"|translate}}').input-sm.form-control td(width='40%') @@ -61,19 +27,3 @@ span(translate) Clear logcat-table(add-row='lastEntry') - //#logcatParent.force-gpu - table#logcatTable.console-message-text.tableX.table-condensedX.selectableX - //thead - tr - th(width='4%') - th(width='10%') - th(width='8%') - th(width='8%') - th(width='4%') - th(width='4%') - th(width='10%') - th(width='16%') - th(width='46%') - tbody#logcatBody - pre {{ logEntries | json }} - br diff --git a/res/app/control-panes/logs/logs.less b/res/app/control-panes/logs/logs.less index d990cf8e..de10b63b 100644 --- a/res/app/control-panes/logs/logs.less +++ b/res/app/control-panes/logs/logs.less @@ -1,5 +1,4 @@ .stf-logs { - .logcat-filters-table { margin-bottom: 0; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1); @@ -17,17 +16,4 @@ .logcat-filters-table tfoot > tr > td { border-top: none; } - - } - -/*#logcatMax {*/ - -/*overflow-y: auto;*/ -/*position: absolute;*/ -/*top: 180px;*/ -/*left: 20px;*/ -/*bottom: 0;*/ -/*bottom: 0;*/ -/*background: hotpink;*/ -/*}*/ diff --git a/res/app/control-panes/performance/cpu/cpu-controller.js b/res/app/control-panes/performance/cpu/cpu-controller.js index 48db4162..bc4038e8 100644 --- a/res/app/control-panes/performance/cpu/cpu-controller.js +++ b/res/app/control-panes/performance/cpu/cpu-controller.js @@ -1,79 +1,3 @@ module.exports = function CpuCtrl() { - - //var RealTimeData = function (layers) { - // this.layers = layers; - // this.timestamp = ((new Date()).getTime() / 1000) | 0; - //}; - // - //RealTimeData.prototype.rand = function () { - // return parseInt(Math.random() * 100) + 50; - //}; - // - //RealTimeData.prototype.history = function (entries) { - // if (typeof(entries) != 'number' || !entries) { - // entries = 60; - // } - // - // var history = []; - // for (var k = 0; k < this.layers; k++) { - // history.push({values: []}); - // } - // - // for (var i = 0; i < entries; i++) { - // for (var j = 0; j < this.layers; j++) { - // history[j].values.push({time: this.timestamp, y: this.rand()}); - // } - // this.timestamp++; - // } - // - // return history; - //}; - // - //RealTimeData.prototype.next = function () { - // var entry = []; - // for (var i = 0; i < this.layers; i++) { - // entry.push({time: this.timestamp, y: this.rand()}); - // } - // this.timestamp++; - // return entry; - //} - // - //window.RealTimeData = RealTimeData; - // - // - //var liveAreaData = new RealTimeData(4) - // - // - //function generateAreaData() { - // var values = []; - // var data = [ - // {label: 'Sqrt', values: []}, - // {label: 'Cbrt', values: []}, - // {label: '4th', values: []} - // ]; - // for (var i = 0; i <= 128; i++) { - // var x2 = 20 * (i / 128); - // data[0].values.push({x: x2, y: Math.sqrt(x2)}); - // data[1].values.push({x: x2, y: Math.pow(x2, (1 / 3))}); - // data[2].values.push({x: x2, y: Math.pow(x2, (1 / 4))}); - // } - // return data; - //} - // - //$scope.areaData = generateAreaData() - // - //$scope.realtimeArea = liveAreaData.history(); - //$scope.realtimeAreaFeed = liveAreaData.next(); - //$scope.getNextLiveArea = function () { - // $scope.realtimeAreaFeed = liveAreaData.next(); - // $timeout($scope.getNextLiveArea, 1000); - //} - //$timeout($scope.getNextLiveArea, 1000); - // - // - //$scope.areaAxes = ['left', 'right', 'bottom']; - //$scope.lineAxes = ['right', 'bottom']; - //$scope.scatterAxes = ['left', 'right', 'top', 'bottom']; - } diff --git a/res/app/control-panes/performance/cpu/cpu.jade b/res/app/control-panes/performance/cpu/cpu.jade index f3303d27..a012d85e 100644 --- a/res/app/control-panes/performance/cpu/cpu.jade +++ b/res/app/control-panes/performance/cpu/cpu.jade @@ -4,7 +4,3 @@ span(translate) CPU .widget-content.padded div - - //epoch-live-area(chart-class='category10', chart-height='200', - chart-data='realtimeArea', chart-stream='realtimeAreaFeed', - chart-axes='areaAxes') diff --git a/res/app/control-panes/resources/resources.jade b/res/app/control-panes/resources/resources.jade index 1b6811ac..feb1caf0 100644 --- a/res/app/control-panes/resources/resources.jade +++ b/res/app/control-panes/resources/resources.jade @@ -35,31 +35,8 @@ input(select-on-click, ng-model='newCookie.domain', placeholder='http') td(width='10%', data-title="'Path'|translate") input(select-on-click, ng-model='newCookie.path', placeholder='/') - //td(width='5%', data-title="'Expires'") - span(contenteditable="true") {{ newCookie.expiry }} td(width='5%', data-title="'Secure'|translate") input(type='checkbox', indeterminate='true', ng-model='newCookie.secure') td(title="'Add'|translate") button.btn.btn-default(ng-click='addCookie()') i.fa.fa-plus-circle - //| Add - - //table.table.table-hover.table-condensed(ng-table='tableParams') - tbody(ng-repeat='group in $groups') - tr.ng-table-group - td(colspan='{{$columns.length}}').unselectable.pointer - a(ng-click='group.$hideRows = !group.$hideRows') - img(ng-src='{{ group.data[0].deviceImage }}').device-icon-smallest - //i.fa(ng-class='{"fa-chevron-right": group.$hideRows, "fa-chevron-down": !group.$hideRows}') - strong {{group.value}} - tr(ng-hide='group.$hideRows', ng-repeat='cookie in group.data') - td(sortable='name', data-title="'Name'|translate") - strong {{cookie.name}} - td(width='50%', sortable='value', data-title="'Value'|translate") - span {{cookie.value}} - td(width='15%', sortable='domain', data-title="'Domain'|translate") - span {{cookie.domain}} - td(idth='10%', sortable='path', data-title="'Path'|translate") - span {{cookie.path}} - td(idth='5%', sortable='secure', data-title="'Secure'|translate") - span {{cookie.secure ? 'Yes' : 'No'}} diff --git a/res/app/control-panes/screenshots/screenshots-controller.js b/res/app/control-panes/screenshots/screenshots-controller.js index 7afe1249..994d197f 100644 --- a/res/app/control-panes/screenshots/screenshots-controller.js +++ b/res/app/control-panes/screenshots/screenshots-controller.js @@ -6,11 +6,6 @@ module.exports = function ScreenshotsCtrl($scope) { $scope.screenshots = [] } - //SettingsService.bind($scope, { - // target: 'screenShotSize', - // defaultValue: 200 - //}) - $scope.shotSizeParameter = function (maxSize, multiplier) { var finalSize = $scope.screenShotSize * multiplier var finalMaxSize = maxSize * multiplier @@ -36,5 +31,4 @@ module.exports = function ScreenshotsCtrl($scope) { } $scope.screenShotSize = newValue } - } diff --git a/res/app/control-panes/screenshots/screenshots.css b/res/app/control-panes/screenshots/screenshots.css index 6a52e94a..22d209da 100644 --- a/res/app/control-panes/screenshots/screenshots.css +++ b/res/app/control-panes/screenshots/screenshots.css @@ -9,8 +9,6 @@ .stf-screenshots .screenshot-image { -webkit-transition: 1000ms; transition: 1000ms; - /*-webkit-transition: width 2s; */ - /*transition: width 2s;*/ } .stf-screenshots .screenshot-image.ng-image-not-loaded { diff --git a/res/app/device-list/column/device-column-service.js b/res/app/device-list/column/device-column-service.js index 6c26feb8..4f183df6 100644 --- a/res/app/device-list/column/device-column-service.js +++ b/res/app/device-list/column/device-column-service.js @@ -36,7 +36,7 @@ module.exports = function DeviceColumnService($filter, gettext) { , name: DeviceNameCell({ title: gettext('Product') , value: function(device) { - return device.name || '' + return device.name || device.model || device.serial } }) , operator: TextCell({ diff --git a/res/app/device-list/device-list.css b/res/app/device-list/device-list.css index ebd29bae..d378b2f8 100644 --- a/res/app/device-list/device-list.css +++ b/res/app/device-list/device-list.css @@ -3,15 +3,12 @@ } .stf-device-list .filtering-buttons { - /*margin-top: -32px;*/ - position: absolute; top: 10px; right: 20px; } .stf-device-list .device-not-usable { - /*background: rgb(245, 245, 245);*/ opacity: 0.8; } @@ -26,7 +23,6 @@ .stf-device-list .line { height: 1px; width: 100%; - /*padding: 10px 15px 12px 2px;*/ border-bottom: 1px solid #e2e2e2; margin: 0; } @@ -61,12 +57,10 @@ img.device-icon-smallest { .device-list-active-tabs.ng-enter { -webkit-transition: 250ms; transition: 250ms; - /*margin-top: -100%;*/ opacity: 0; } .device-list-active-tabs.ng-enter-active { - /*margin-top: 0;*/ opacity: 1; } @@ -77,17 +71,3 @@ img.device-icon-smallest { .stf-device-list .device-product-name-using { border-bottom: 1px solid; /* leaving out the color inherits text color */ } - - -.device-status .using { - -} - -/*using: 'state-using btn-primary',*/ -/*busy: 'state-busy btn-warning',*/ -/*available: 'state-available btn-primary-outline',*/ -/*ready: 'state-ready btn-primary-outline',*/ -/*present: 'state-present btn-primary-outline',*/ -/*preparing: 'state-preparing btn-primary-outline btn-success-outline',*/ -/*unauthorized: 'state-unauthorized btn-danger-outline',*/ -/*offline: 'state-offline btn-warning-outline'*/ diff --git a/res/app/device-list/icons/device-list-icons-directive.js b/res/app/device-list/icons/device-list-icons-directive.js index 5e2aa8fe..d148fe9d 100644 --- a/res/app/device-list/icons/device-list-icons-directive.js +++ b/res/app/device-list/icons/device-list-icons-directive.js @@ -1,6 +1,6 @@ var patchArray = require('./../util/patch-array') -module.exports = function DeviceListDetailsDirective( +module.exports = function DeviceListIconsDirective( $filter , gettext , DeviceColumnService @@ -132,12 +132,6 @@ module.exports = function DeviceListDetailsDirective( element.on('click', function (e) { - // TODO: find a way to find the correct parent - //if (e.shiftKey) { - // console.log(e) - // //StandaloneService.open() - // e.preventDefault() - //} var id if (e.target.classList.contains('thumbnail')) { @@ -146,6 +140,8 @@ module.exports = function DeviceListDetailsDirective( e.target.classList.contains('device-photo-small') || e.target.classList.contains('device-name')) { id = e.target.parentNode.parentNode.id + } else if (e.target.parentNode.classList.contains('device-photo-small')) { + id = e.target.parentNode.parentNode.parentNode.id } if (id) { diff --git a/res/app/device-list/search/device-list-search.css b/res/app/device-list/search/device-list-search.css index 73aa7435..98f6fb03 100644 --- a/res/app/device-list/search/device-list-search.css +++ b/res/app/device-list/search/device-list-search.css @@ -7,7 +7,3 @@ .stf-device-list .filter-out { display: none; } - -.stf-device-list .device-search:focus { - /*width: 25em;*/ -} diff --git a/res/app/device-list/stats/device-list-stats.css b/res/app/device-list/stats/device-list-stats.css index f1cb4829..2ede24b5 100644 --- a/res/app/device-list/stats/device-list-stats.css +++ b/res/app/device-list/stats/device-list-stats.css @@ -1,5 +1,3 @@ -/* TODO: REFORMAT device-stats */ - .device-stats { min-height: 100px; height: 100px; diff --git a/res/app/docs/docs-controller.js b/res/app/docs/docs-controller.js index cad87190..babedf6c 100644 --- a/res/app/docs/docs-controller.js +++ b/res/app/docs/docs-controller.js @@ -2,7 +2,6 @@ module.exports = function DocsCtrl($rootScope, $scope, $window, $location) { function hasHistory() { - // TODO: watch this return $window.history.length > 1 } diff --git a/res/app/docs/index.js b/res/app/docs/index.js index a915095d..bd3066e5 100644 --- a/res/app/docs/index.js +++ b/res/app/docs/index.js @@ -18,6 +18,7 @@ module.exports = angular.module('stf.help.docs', [ .when('/docs/:document*', { templateUrl: function (params) { var lang = languageProvider.$get().selectedLanguage + lang = 'en' // Only English for now var document = params.document.replace('.md', '') return '/static/wiki/[' + lang + ']-' + document } @@ -25,6 +26,7 @@ module.exports = angular.module('stf.help.docs', [ .when('/help', { templateUrl: function () { var lang = languageProvider.$get().selectedLanguage + lang = 'en' // Only English for now return '/static/wiki/[' + lang + ']-' + 'Help' } }) diff --git a/res/app/layout/small.css b/res/app/layout/small.css index af8a0a95..0fe4ee2d 100644 --- a/res/app/layout/small.css +++ b/res/app/layout/small.css @@ -1,11 +1,3 @@ -/*.row {*/ -/*margin: 0 !important;*/ -/*}*/ - -/* Fix Bootstrap */ - -/* Make tabs smaller */ - .row { margin: 0 5px; } @@ -23,7 +15,6 @@ margin-top: 0; } -/*@media (max-width: 1200px) {*/ [class*="col-sm"], [class*="col-md"], [class*="col-lg"], @@ -31,7 +22,6 @@ margin-bottom: 10px; } -/*}*/ .row [class^="col-"] { padding: 0 5px; @@ -41,10 +31,6 @@ font-size: 12px; } -.btn .btn-sm span { - /*font-size: 12px;*/ -} - .btn i+span, .btn span+span{ margin-left: 5px; @@ -65,29 +51,3 @@ a tab-heading i + span { table.table .btn { margin: 0; } - -/* Widget Container */ -/*.widget-container .heading-for-tabs {*/ -/*background: rgba(255, 255, 255, 0.94);*/ -/*height: 50px;*/ -/*padding: 15px 15px;*/ -/*color: #007aff;*/ -/*font-size: 15px;*/ -/*width: 100%;*/ -/*font-weight: 400;*/ -/*margin: 0;*/ -/*height: 44px;*/ -/*}*/ - -/*.widget-container .heading-for-tabs [class^="fa"],*/ -/*.widget-container .heading-for-tabs [class*="fa"] {*/ -/*margin-right: 5px;*/ -/*}*/ - -/*.widget-container .heading-for-tabs [class^="fa"].pull-right,*/ -/*.widget-container .heading-for-tabs [class*="fa"].pull-right {*/ -/*margin-right: 0;*/ -/*margin-left: 15px;*/ -/*opacity: 0.35;*/ -/*font-size: 1.1em;*/ -/*}*/ \ No newline at end of file diff --git a/res/app/layout/stf-styles.css b/res/app/layout/stf-styles.css index 1c002904..c2abdc25 100644 --- a/res/app/layout/stf-styles.css +++ b/res/app/layout/stf-styles.css @@ -1,8 +1,3 @@ -body { - /*font-size: 14px;*/ - /*background: #eee;*/ -} - .page-container { padding: 0 30px; margin: 0 auto; @@ -95,10 +90,6 @@ Colors for awesome fonts height: 150px; } -.stf-mini-ace-viewer { - /*min-height: 10px;*/ -} - .ace_editor_wrapper { position: relative; height: 180px; @@ -215,7 +206,6 @@ svg { /* Movement Area */ .movement-area-container { - /*background: #555;*/ width: 100%; height: 100%; } @@ -322,18 +312,13 @@ svg { text-align: right; } -/* For se7en bootstrap */ +/* For nine-bootstrap */ .btn [class^="fa"], .btn [class*="fa"] { margin-right: 0 !important; } -/*.btn [class^="fa"]+span i,*/ -/*.btn [class*="fa"]+span {*/ -/*margin-right: 15px;*/ -/*}*/ - .interact-control .navbar { height: auto !important; } @@ -432,11 +417,3 @@ fieldset[disabled] input[type="text"].form-control cursor: text; } -/* - Fix scrolling behaviour change of overflow: auto; introduced in Chrome 41. - It prevented the y-scrolling of the parent while the mouse is positioned on - the child element. -*/ - - - diff --git a/res/app/menu/menu.css b/res/app/menu/menu.css index 999cc318..539700a0 100644 --- a/res/app/menu/menu.css +++ b/res/app/menu/menu.css @@ -11,7 +11,6 @@ .stf-menu .stf-top-bar { height: 44px; - /*border-bottom: 1px solid #e6e6e6;*/ padding: 0 10px 0 20px; width: 100%; float: left; @@ -42,7 +41,6 @@ font-size: 15px; line-height: 44px; color: #777777; - /*border-bottom: 1px solid #eee;*/ font-weight: 400; height: 44px; position: relative; @@ -61,8 +59,6 @@ .stf-menu .stf-nav > li > a.current { color: #007aff; - /*border-bottom: 1px solid #eee;*/ - /*border-bottom-color: #007aff;*/ } .stf-menu.navbar { diff --git a/res/app/menu/menu.jade b/res/app/menu/menu.jade index a9abcaef..19b73576 100644 --- a/res/app/menu/menu.jade +++ b/res/app/menu/menu.jade @@ -18,32 +18,6 @@ button(type='button', ng-model='$root.platform', btn-radio="'web'", translate).btn.btn-sm.btn-default-outline Web button(type='button', ng-model='$root.platform', btn-radio="'native'", translate).btn.btn-sm.btn-default-outline Native - //li - a - badge-icon(type='warning', message='There was an error') - - //button.btn.btn-sm.btn-primary-outline(type='button', dropdown-toggle) - i.fa.fa-columns - span(ng-bind='"Customize"|translate') - - //ul.dropdown-menu(role='menu').pointer.stf-device-details-customize - - //li.dropdown.notifications.hidden-xs(dropdown, style='z-index: 11000;') - a(href='#', dropdown-toggle) - span.se7en-flag - .sr-only Notifications - p.counter 4 - ul.dropdown-menu(role='menu') - li - a(href='') - .notifications.label.label-info New - p New device added: Nexus 6 - li - a(href='#') - .notifications.label.label-info Bug fix - p Devices now don't turn off - - li(ng-show='!$root.basicMode') a(ng-href='/#!/help', accesskey='6') i.fa.fa-question-circle.fa-fw diff --git a/res/app/settings/general/local/local-settings-controller.js b/res/app/settings/general/local/local-settings-controller.js index 84c08e1a..9ea3a129 100644 --- a/res/app/settings/general/local/local-settings-controller.js +++ b/res/app/settings/general/local/local-settings-controller.js @@ -4,20 +4,4 @@ module.exports = function ($scope, SettingsService) { console.log('Settings cleared') } -// $scope.resetSettings = function () { -// var title = 'Reset Settings'; -// var msg = 'Are you sure you want to revert all settings to ' + -// 'their default values?'; -// var btns = [ -// {result: 'cancel', label: 'Cancel'}, -// {result: 'ok', label: 'OK', cssClass: 'btn-primary'} -// ]; -// $dialog.messageBox(title, msg, btns) -// .open() -// .then(function (result) { -// if (result === 'ok') { -// //SettingsService.clearAll(); -// } -// }); -// }; } diff --git a/res/app/settings/keys/access-tokens/access-tokens.jade b/res/app/settings/keys/access-tokens/access-tokens.jade index af6ff1b6..098177e4 100644 --- a/res/app/settings/keys/access-tokens/access-tokens.jade +++ b/res/app/settings/keys/access-tokens/access-tokens.jade @@ -13,13 +13,9 @@ .widget-content.padded - //add-adb-key(show-clipboard='true', show-add='showAdd') - nothing-to-show(icon='fa-key', message='{{"No access tokens" | translate}}', ng-if='!adbKeys.length && !showAdd') - - ul.list-group.key-list li.list-group-item(ng-repeat='key in adbKeys').animate-repeat a diff --git a/res/app/settings/keys/adb-keys/adb-keys-controller.js b/res/app/settings/keys/adb-keys/adb-keys-controller.js index 19b69b2a..2f0dc637 100644 --- a/res/app/settings/keys/adb-keys/adb-keys-controller.js +++ b/res/app/settings/keys/adb-keys/adb-keys-controller.js @@ -12,17 +12,4 @@ module.exports = $scope.$on('user.keys.adb.updated', updateKeys) updateKeys() - - //AddAdbKeyModalService.open({ - // fingerprint: '9a:12:5b:14:e3:3e:c9:d3:59:be:4f:16:0d:4d:cd:26', - // title: 'a12907@PC-5954.local' - //}) - - //FatalMessageService.open({ - // enhancedName: 'dev', - // enhancedStatePassive: 'online', - // likelyLeaveReason: 'Not good enough' - //}) - - } diff --git a/res/app/settings/keys/adb-keys/adb-keys.jade b/res/app/settings/keys/adb-keys/adb-keys.jade index 6c0dfc5c..c335cd76 100644 --- a/res/app/settings/keys/adb-keys/adb-keys.jade +++ b/res/app/settings/keys/adb-keys/adb-keys.jade @@ -3,12 +3,9 @@ i.fa.fa-android span(translate) ADB Keys - - button.btn.pull-right.btn-sm( ng-click='showAdd = !showAdd', ng-class='{ "btn-primary-outline": !showAdd, "btn-primary": showAdd }') - //(tooltip='{{ "Add ADB Key" | translate }}') i.fa.fa-plus.fa-fw a(ng-href='/#!/docs/ADB-Keys').pull-right.btn.btn-sm @@ -21,86 +18,6 @@ add-adb-key(show-clipboard='true', show-add='showAdd') - - //accordion(ng-if='showAdd').pointer - accordion-group(is-open='showAdd') - accordion-heading.pointer - i.fa.fa-fw.fa-key - span(translateX) Add ADB Key - - form.form-horizontal(name='adbkeyform', ng-submit='addKey(key)') - - .alert.alert-info.selectable Tip: Run this command to copy the key to your clipboard - a(ng-href='/#!/docs/adb-keys').pull-right - i.fa.fa-question-circle(tooltip='{{"More about ADB Keys" | translate}}', tooltip-placement='left') - textarea(readonly, rows='1', text-focus-select, - ).form-control.remote-debug-textarea pbcopy < ~/.android/adbkey.pub - //pbcopy < ~/.android/adbkey.pub - - br - - .form-group - label.control-label.col-md-1 - i.fa.fa-key.fa-fw - span(translate) Key - .col-md-11 - textarea(rows='4', ng-model='key', ng-required='true', - autocorrect='off', autocapitalize='off', spellcheck='false', - focus-element='focusAddKey').form-control - - .form-group - label.control-label.col-md-1 - i.fa.fa-laptop.fa-fw - span(translate) Device - .col-md-11 - input(type='text', ng-model='title', ng-required='true', - text-focus-select, focus-element='focusAddTitle').form-control - - button.btn.btn-primary-outline.btn-sm.pull-right(type='submit') - i.fa.fa-plus.fa-fw - span(translate) Add Key - - //.panel.panel-default(ng-show='showAdd') - .panel-heading - h3.panel-title(translate) Add ADB Key - .panel-body - form.form-horizontal(name='adbkeyform', ng-submit='addKey(key)') - - .alert.alert-info.selectable Tip: Run this command to copy the key to your clipboard - a(ng-href='/#!/docs/adb-keys').pull-right - i.fa.fa-question-circle(tooltip='{{"More about ADB Keys" | translate}}', tooltip-placement='left') - textarea(readonly, rows='1', text-focus-select, ng-copy='focusAddKey = true' - ).form-control.remote-debug-textarea pbcopy < ~/.android/adbkey.pub - //pbcopy < ~/.android/adbkey.pub - - br - - .form-group - label.control-label.col-md-1 - i.fa.fa-key.fa-fw - span(translate) Key - .col-md-11 - textarea(rows='4', ng-model='key', ng-required='true', - autocorrect='off', autocapitalize='off', spellcheck='false', - focus-element='focusAddKey', ng-paste='focusAddTitle = true').form-control - - .form-group - label.control-label.col-md-1 - i.fa.fa-laptop.fa-fw - span(translate) Device - .col-md-11 - input(type='text', ng-model='title', ng-required='true', - text-focus-select, focus-element='focusAddTitle').form-control - - - - button.btn.btn-primary-outline.btn-sm.pull-right(type='submit') - i.fa.fa-plus.fa-fw - span(translate) Add Key - - //error-message(message='{{error}}') - - ul.list-group.key-list li.list-group-item(ng-repeat='key in adbKeys').animate-repeat a diff --git a/res/app/settings/settings-controller.js b/res/app/settings/settings-controller.js index e5df7707..7dd306e9 100644 --- a/res/app/settings/settings-controller.js +++ b/res/app/settings/settings-controller.js @@ -1,11 +1,6 @@ module.exports = function SettingsCtrl($scope, gettext) { $scope.settingTabs = [ - //{ - // title: gettext('Keys'), - // icon: 'fa-key fa-fw', - // templateUrl: 'settings/keys/keys.jade' - //}, { title: gettext('General'), icon: 'fa-gears fa-fw', diff --git a/res/app/settings/settings.jade b/res/app/settings/settings.jade index 60d1d42b..23e8971a 100644 --- a/res/app/settings/settings.jade +++ b/res/app/settings/settings.jade @@ -1,4 +1,3 @@ div(pane='center', ng-controller='SettingsCtrl') - //br .widget-container.fluid-height nice-tabs(key='SettingsTabs', tabs='settingTabs', filter='') diff --git a/res/app/terminal/terminal.css b/res/app/terminal/terminal.css index 514139e0..54a3316e 100644 --- a/res/app/terminal/terminal.css +++ b/res/app/terminal/terminal.css @@ -1,7 +1,6 @@ html, body { padding: 0; margin: 0; - /*height: 100%;*/ cursor: text; background: #000; } @@ -15,5 +14,4 @@ html, body { -webkit-font-smoothing: antialiased; font-smoothing: antialiased; cursor: text; - /*min-height: 100%;*/ } diff --git a/res/app/views/index.jade b/res/app/views/index.jade index 77468a40..71a565d0 100644 --- a/res/app/views/index.jade +++ b/res/app/views/index.jade @@ -21,14 +21,6 @@ html(ng-app='app') pane-handle='') div(ng-include='"menu.jade"') - //.pane-alert(fa-pane, pane-id='toptop', pane-anchor='north', pane-size='100px', ng-show='true') - .row - .col-md-12 - .widget-container.fluid-height - .widget-content.padded - div(style='text-align: center; color: #007aff;') - i.fa.fa-newspaper-o.fa-3x - h4(ng-bind='"ただいま、システムメンテナンス中です。"') .pane-center(fa-pane, pane-id='main', pane-anchor='center').fill-height socket-state div(growl) diff --git a/res/common/lang/langs.json b/res/common/lang/langs.json index a398c14e..91429137 100644 --- a/res/common/lang/langs.json +++ b/res/common/lang/langs.json @@ -2,5 +2,7 @@ "en": "English", "es": "Español", "ja": "日本語", + "zh": "中文", + "ko_KR": "한국어", "ru_RU": "Русский" } diff --git a/res/common/lang/po/stf.es.po b/res/common/lang/po/stf.es.po index c5f29d13..d3dee400 100644 --- a/res/common/lang/po/stf.es.po +++ b/res/common/lang/po/stf.es.po @@ -2,12 +2,13 @@ # Translators: # Gunther Brunner, 2015 # Gunther Brunner, 2015 +# takeshimiya , 2015 msgid "" msgstr "" "Project-Id-Version: STF\n" -"PO-Revision-Date: 2015-07-28 09:05+0000\n" +"PO-Revision-Date: 2015-09-10 06:06+0000\n" "Last-Translator: takeshimiya \n" -"Language-Team: Spanish (http://www.transifex.com/projects/p/stf/language/es/)\n" +"Language-Team: Spanish (http://www.transifex.com/openstf/stf/language/es/)\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" @@ -57,11 +58,11 @@ msgstr "Cuenta" #: app/control-panes/dashboard/install/activities/activities.html:1 msgid "Action" -msgstr "" +msgstr "Acción" #: app/control-panes/automation/store-account/store-account.html:1 msgid "Actions" -msgstr "" +msgstr "Acciones" #: app/control-panes/dashboard/install/activities/activities.html:1 msgid "Activity" @@ -73,17 +74,16 @@ msgstr "" #: app/control-panes/resources/resources.html:1 msgid "Add" -msgstr "" +msgstr "Añadir" #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Add ADB Key" -msgstr "" +msgstr "Añadir Llave de ADB" #: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 msgid "Add Key" -msgstr "" +msgstr "Añadir Llave" #: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 msgid "Add the following ADB Key to STF?" @@ -99,7 +99,7 @@ msgstr "" #: app/control-panes/control-panes-controller.js:20 msgid "Advanced" -msgstr "" +msgstr "Avanzado" #: app/control-panes/advanced/input/input.html:1 msgid "Advanced Input" @@ -109,10 +109,6 @@ msgstr "" msgid "Airplane Mode" msgstr "" -#: app/control-panes/logs/logs.html:20 -msgid "App" -msgstr "" - #: app/control-panes/automation/store-account/store-account.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "App Store" @@ -132,16 +128,16 @@ msgstr "" #: app/control-panes/control-panes-controller.js:14 msgid "Automation" -msgstr "" +msgstr "Automatización" #: app/components/stf/device/device-info-filter/index.js:28 msgid "Available" -msgstr "" +msgstr "Disponible" #: app/components/stf/device-context-menu/device-context-menu.html:1 #: app/control-panes/device-control/device-control.html:1 msgid "Back" -msgstr "" +msgstr "Atrás" #: app/control-panes/info/info.html:1 msgid "Battery" @@ -190,7 +186,7 @@ msgstr "" #: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 msgid "Cancel" -msgstr "" +msgstr "Cancelar" #: app/components/stf/upload/upload-error-filter.js:6 msgid "Cannot access specified URL" @@ -209,18 +205,14 @@ msgstr "" msgid "Charging" msgstr "" -#: app/menu/menu.html:3 -msgid "Chat" -msgstr "" - #: auth/ldap/scripts/signin/signin.html:1 #: auth/mock/scripts/signin/signin.html:1 msgid "Check errors below" msgstr "" #: app/components/stf/common-ui/clear-button/clear-button.html:1 -#: app/control-panes/advanced/run-js/run-js.html:2 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/advanced/run-js/run-js.html:1 +#: app/control-panes/logs/logs.html:1 msgid "Clear" msgstr "" @@ -228,10 +220,6 @@ msgstr "" msgid "Clipboard" msgstr "Portapapeles" -#: app/control-panes/advanced/input/input.html:5 -msgid "Close" -msgstr "" - #: app/components/stf/device/device-info-filter/index.js:46 msgid "Cold" msgstr "" @@ -248,7 +236,7 @@ msgstr "" #: app/menu/menu.html:1 msgid "Control" -msgstr "" +msgstr "Control" #: app/control-panes/resources/resources.html:1 msgid "Cookies" @@ -267,29 +255,29 @@ msgstr "" msgid "Customize" msgstr "Personalizar" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Center" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Down" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Left" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Right" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Up" msgstr "" #: app/control-panes/control-panes-controller.js:35 msgid "Dashboard" -msgstr "" +msgstr "Tablero" #: app/control-panes/dashboard/install/activities/activities.html:1 msgid "Data" @@ -318,10 +306,9 @@ msgstr "" #: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 #: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 -#: app/control-panes/inspect/inspect.html:3 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/inspect/inspect.html:1 msgid "Device" -msgstr "" +msgstr "Dispositivo" #: app/device-list/details/device-list-details-directive.js:36 #: app/device-list/icons/device-list-icons-directive.js:123 @@ -386,10 +373,6 @@ msgstr "" msgid "Dummy" msgstr "" -#: app/control-panes/advanced/input/input.html:7 -msgid "Eject" -msgstr "" - #: app/settings/notifications/notifications.html:1 msgid "Enable notifications" msgstr "" @@ -454,11 +437,11 @@ msgstr "" msgid "Full" msgstr "" -#: app/settings/settings-controller.js:10 +#: app/settings/settings-controller.js:5 msgid "General" msgstr "" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 #: app/control-panes/resources/resources.html:1 msgid "Get" msgstr "" @@ -476,7 +459,7 @@ msgid "Go Forward" msgstr "" #: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:95 +#: app/control-panes/control-panes-hotkeys-controller.js:89 msgid "Go to Device List" msgstr "" @@ -496,7 +479,7 @@ msgstr "" msgid "Height" msgstr "" -#: app/menu/menu.html:22 +#: app/menu/menu.html:1 msgid "Help" msgstr "" @@ -536,9 +519,9 @@ msgstr "" #: app/control-panes/control-panes-controller.js:26 msgid "Info" -msgstr "" +msgstr "Información" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspect Device" msgstr "" @@ -546,7 +529,7 @@ msgstr "" msgid "Inspecting is currently only supported in WebView" msgstr "" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspector" msgstr "" @@ -574,7 +557,7 @@ msgstr "" msgid "Key" msgstr "" -#: app/settings/settings-controller.js:15 +#: app/settings/settings-controller.js:10 msgid "Keys" msgstr "" @@ -594,7 +577,7 @@ msgstr "" msgid "Launching activity..." msgstr "" -#: app/control-panes/info/info.html:1 app/control-panes/logs/logs.html:20 +#: app/control-panes/info/info.html:1 app/control-panes/logs/logs.html:1 msgid "Level" msgstr "" @@ -606,6 +589,10 @@ msgstr "" msgid "Location" msgstr "" +#: app/control-panes/automation/device-settings/device-settings.html:7 +msgid "Lock Rotation" +msgstr "" + #: app/control-panes/control-panes-controller.js:44 msgid "Logs" msgstr "" @@ -618,6 +605,10 @@ msgstr "" msgid "Manage Apps" msgstr "" +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Manner Mode" +msgstr "" + #: app/control-panes/info/info.html:1 #: app/device-list/column/device-column-service.js:165 msgid "Manufacturer" @@ -665,8 +656,6 @@ msgid "More about Access Tokens" msgstr "" #: app/settings/keys/adb-keys/adb-keys.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:10 -#: app/settings/keys/adb-keys/adb-keys.html:44 msgid "More about ADB Keys" msgstr "" @@ -748,7 +737,7 @@ msgstr "" msgid "Notes" msgstr "" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Nothing to inspect" msgstr "" @@ -803,10 +792,6 @@ msgstr "" msgid "Path" msgstr "" -#: app/control-panes/advanced/input/input.html:3 -msgid "Pause" -msgstr "" - #: app/device-list/column/device-column-service.js:184 msgid "Phone" msgstr "" @@ -823,7 +808,7 @@ msgstr "" msgid "Physical Device" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "PID" msgstr "" @@ -835,10 +820,6 @@ msgstr "" msgid "Platform" msgstr "" -#: app/control-panes/advanced/input/input.html:1 -msgid "Play" -msgstr "" - #: app/control-panes/advanced/input/input.html:1 msgid "Play/Pause" msgstr "" @@ -896,15 +877,15 @@ msgstr "" msgid "Preparing" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:110 +#: app/control-panes/control-panes-hotkeys-controller.js:104 msgid "Press Back button" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:109 +#: app/control-panes/control-panes-hotkeys-controller.js:103 msgid "Press Home button" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:108 +#: app/control-panes/control-panes-hotkeys-controller.js:102 msgid "Press Menu button" msgstr "" @@ -938,10 +919,6 @@ msgstr "" msgid "Reconnected successfully." msgstr "" -#: app/control-panes/advanced/input/input.html:9 -msgid "Record" -msgstr "" - #: app/components/stf/common-ui/refresh-page/refresh-page.html:1 msgid "Refresh" msgstr "" @@ -961,7 +938,7 @@ msgid "Remote debug" msgstr "Conexión remota" #: app/settings/keys/access-tokens/access-tokens.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:74 +#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Remove" msgstr "" @@ -1003,27 +980,19 @@ msgid "ROM" msgstr "" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:98 +#: app/control-panes/control-panes-hotkeys-controller.js:92 msgid "Rotate Left" msgstr "" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:99 +#: app/control-panes/control-panes-hotkeys-controller.js:93 msgid "Rotate Right" msgstr "" -#: app/control-panes/advanced/run-js/run-js.html:18 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run" msgstr "" -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run command" -msgstr "" - -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run Command" -msgstr "" - #: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run JavaScript" msgstr "" @@ -1046,7 +1015,7 @@ msgstr "" msgid "Save ScreenShot" msgstr "" -#: app/control-panes/advanced/run-js/run-js.html:2 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Save..." msgstr "" @@ -1060,7 +1029,7 @@ msgstr "" #: app/control-panes/control-panes-controller.js:8 msgid "Screenshots" -msgstr "" +msgstr "Capturas de Pantalla" #: app/control-panes/info/info.html:1 msgid "SD Card Mounted" @@ -1079,7 +1048,7 @@ msgstr "" msgid "Secure" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:97 +#: app/control-panes/control-panes-hotkeys-controller.js:91 msgid "Selects Next IME" msgstr "" @@ -1107,7 +1076,7 @@ msgstr "Configuración" #: app/control-panes/dashboard/shell/shell.html:1 msgid "Shell" -msgstr "" +msgstr "Línea de Comandos" #: app/control-panes/device-control/device-control.html:1 msgid "Show Screen" @@ -1145,7 +1114,7 @@ msgstr "" msgid "Special Keys" msgstr "" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Start/Stop Logging" msgstr "" @@ -1155,7 +1124,7 @@ msgid "Status" msgstr "" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Stop" msgstr "" @@ -1173,11 +1142,10 @@ msgid "Sub Type" msgstr "" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/advanced/input/input.html:41 msgid "Switch Charset" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Tag" msgstr "" @@ -1193,7 +1161,7 @@ msgstr "" msgid "Temperature" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Text" msgstr "" @@ -1373,11 +1341,11 @@ msgstr "" msgid "The URI passed in is invalid." msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "TID" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Time" msgstr "" @@ -1385,7 +1353,7 @@ msgstr "" msgid "Tip:" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:113 +#: app/control-panes/control-panes-hotkeys-controller.js:107 msgid "Toggle Web/Native" msgstr "" @@ -1420,6 +1388,10 @@ msgstr "" msgid "Unknown reason." msgstr "" +#: app/control-panes/automation/device-settings/device-settings.html:6 +msgid "Unlock Rotation" +msgstr "" + #: app/components/stf/device/device-info-filter/index.js:51 msgid "Unspecified Failure" msgstr "" @@ -1518,6 +1490,7 @@ msgstr "" #: app/components/stf/device/device-info-filter/index.js:105 #: app/components/stf/device/device-info-filter/index.js:97 +#: app/control-panes/automation/device-settings/device-settings.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "WiFi" msgstr "" diff --git a/res/common/lang/po/stf.ja.po b/res/common/lang/po/stf.ja.po index 02b7f810..0ac9e269 100644 --- a/res/common/lang/po/stf.ja.po +++ b/res/common/lang/po/stf.ja.po @@ -4,9 +4,9 @@ msgid "" msgstr "" "Project-Id-Version: STF\n" -"PO-Revision-Date: 2015-07-28 09:07+0000\n" -"Last-Translator: Gunther Brunner\n" -"Language-Team: Japanese (http://www.transifex.com/projects/p/stf/language/ja/)\n" +"PO-Revision-Date: 2015-09-10 06:06+0000\n" +"Last-Translator: takeshimiya \n" +"Language-Team: Japanese (http://www.transifex.com/openstf/stf/language/ja/)\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ja\n" @@ -75,7 +75,6 @@ msgid "Add" msgstr "追加" #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Add ADB Key" msgstr "ADBキーを追加" @@ -108,10 +107,6 @@ msgstr "高度な入力" msgid "Airplane Mode" msgstr "機内モード" -#: app/control-panes/logs/logs.html:20 -msgid "App" -msgstr "アプリ" - #: app/control-panes/automation/store-account/store-account.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "App Store" @@ -208,18 +203,14 @@ msgstr "カテゴリー" msgid "Charging" msgstr "充電中" -#: app/menu/menu.html:3 -msgid "Chat" -msgstr "チャット" - #: auth/ldap/scripts/signin/signin.html:1 #: auth/mock/scripts/signin/signin.html:1 msgid "Check errors below" msgstr "下記エラーがありました" #: app/components/stf/common-ui/clear-button/clear-button.html:1 -#: app/control-panes/advanced/run-js/run-js.html:2 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/advanced/run-js/run-js.html:1 +#: app/control-panes/logs/logs.html:1 msgid "Clear" msgstr "クリア" @@ -227,10 +218,6 @@ msgstr "クリア" msgid "Clipboard" msgstr "クリップボード" -#: app/control-panes/advanced/input/input.html:5 -msgid "Close" -msgstr "閉じる" - #: app/components/stf/device/device-info-filter/index.js:46 msgid "Cold" msgstr "コールド" @@ -266,23 +253,23 @@ msgstr "CPU" msgid "Customize" msgstr "カスタマイズ" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Center" msgstr "D-padセンター" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Down" msgstr "D-pad下" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Left" msgstr "D-pad左" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Right" msgstr "D-pad右" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Up" msgstr "D-pad上" @@ -317,8 +304,7 @@ msgstr "開発者" #: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 #: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 -#: app/control-panes/inspect/inspect.html:3 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/inspect/inspect.html:1 msgid "Device" msgstr "デバイス" @@ -385,10 +371,6 @@ msgstr "ここにファイルをドロップ" msgid "Dummy" msgstr "ダミー" -#: app/control-panes/advanced/input/input.html:7 -msgid "Eject" -msgstr "排出" - #: app/settings/notifications/notifications.html:1 msgid "Enable notifications" msgstr "通知を有効にする" @@ -453,11 +435,11 @@ msgstr "クロック" msgid "Full" msgstr "フル" -#: app/settings/settings-controller.js:10 +#: app/settings/settings-controller.js:5 msgid "General" msgstr "一般" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 #: app/control-panes/resources/resources.html:1 msgid "Get" msgstr "取得" @@ -475,7 +457,7 @@ msgid "Go Forward" msgstr "進む" #: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:95 +#: app/control-panes/control-panes-hotkeys-controller.js:89 msgid "Go to Device List" msgstr "端末リストへ" @@ -495,7 +477,7 @@ msgstr "健康状態" msgid "Height" msgstr "高さ" -#: app/menu/menu.html:22 +#: app/menu/menu.html:1 msgid "Help" msgstr "ヘルプ" @@ -537,7 +519,7 @@ msgstr "不正ログイン情報" msgid "Info" msgstr "情報" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspect Device" msgstr "端末の要素検証" @@ -545,7 +527,7 @@ msgstr "端末の要素検証" msgid "Inspecting is currently only supported in WebView" msgstr "要素の検証機能は、現在WebViewのみ対応" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspector" msgstr "要素の検証" @@ -573,7 +555,7 @@ msgstr "アプリをインストール中..." msgid "Key" msgstr "キー" -#: app/settings/settings-controller.js:15 +#: app/settings/settings-controller.js:10 msgid "Keys" msgstr "認証キー" @@ -593,7 +575,7 @@ msgstr "アクティビティを起動する" msgid "Launching activity..." msgstr "アクティビティを起動中..." -#: app/control-panes/info/info.html:1 app/control-panes/logs/logs.html:20 +#: app/control-panes/info/info.html:1 app/control-panes/logs/logs.html:1 msgid "Level" msgstr "レベル" @@ -605,6 +587,10 @@ msgstr "ローカル設定" msgid "Location" msgstr "場所" +#: app/control-panes/automation/device-settings/device-settings.html:7 +msgid "Lock Rotation" +msgstr "" + #: app/control-panes/control-panes-controller.js:44 msgid "Logs" msgstr "ログ" @@ -617,6 +603,10 @@ msgstr "メンテナンス" msgid "Manage Apps" msgstr "アプリ管理" +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Manner Mode" +msgstr "" + #: app/control-panes/info/info.html:1 #: app/device-list/column/device-column-service.js:165 msgid "Manufacturer" @@ -664,8 +654,6 @@ msgid "More about Access Tokens" msgstr "アクセストークンについて" #: app/settings/keys/adb-keys/adb-keys.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:10 -#: app/settings/keys/adb-keys/adb-keys.html:44 msgid "More about ADB Keys" msgstr "ADBキーについて" @@ -747,7 +735,7 @@ msgstr "充電されていない" msgid "Notes" msgstr "注釈" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Nothing to inspect" msgstr "要素の検証対象はありません" @@ -802,10 +790,6 @@ msgstr "パスワード" msgid "Path" msgstr "パス" -#: app/control-panes/advanced/input/input.html:3 -msgid "Pause" -msgstr "停止" - #: app/device-list/column/device-column-service.js:184 msgid "Phone" msgstr "電話番号" @@ -822,7 +806,7 @@ msgstr "携帯IMEI" msgid "Physical Device" msgstr "物理デバイス" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "PID" msgstr "PID" @@ -834,10 +818,6 @@ msgstr "場所" msgid "Platform" msgstr "プラットホーム" -#: app/control-panes/advanced/input/input.html:1 -msgid "Play" -msgstr "再生" - #: app/control-panes/advanced/input/input.html:1 msgid "Play/Pause" msgstr "再生/停止" @@ -895,15 +875,15 @@ msgstr "電力源" msgid "Preparing" msgstr "準備中" -#: app/control-panes/control-panes-hotkeys-controller.js:110 +#: app/control-panes/control-panes-hotkeys-controller.js:104 msgid "Press Back button" msgstr "戻るボタンを押す" -#: app/control-panes/control-panes-hotkeys-controller.js:109 +#: app/control-panes/control-panes-hotkeys-controller.js:103 msgid "Press Home button" msgstr "ホームボタンを押す" -#: app/control-panes/control-panes-hotkeys-controller.js:108 +#: app/control-panes/control-panes-hotkeys-controller.js:102 msgid "Press Menu button" msgstr "メニューボタンを押す" @@ -937,10 +917,6 @@ msgstr "利用可能" msgid "Reconnected successfully." msgstr "正常に再接続しました。" -#: app/control-panes/advanced/input/input.html:9 -msgid "Record" -msgstr "記録する" - #: app/components/stf/common-ui/refresh-page/refresh-page.html:1 msgid "Refresh" msgstr "更新" @@ -960,7 +936,7 @@ msgid "Remote debug" msgstr "リモートデバッグ" #: app/settings/keys/access-tokens/access-tokens.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:74 +#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Remove" msgstr "削除" @@ -1002,27 +978,19 @@ msgid "ROM" msgstr "ROM" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:98 +#: app/control-panes/control-panes-hotkeys-controller.js:92 msgid "Rotate Left" msgstr "左回りに回転" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:99 +#: app/control-panes/control-panes-hotkeys-controller.js:93 msgid "Rotate Right" msgstr "右回りに回転" -#: app/control-panes/advanced/run-js/run-js.html:18 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run" msgstr "実行" -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run command" -msgstr "コマンドを実行する" - -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run Command" -msgstr "コマンドを実行" - #: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run JavaScript" msgstr "JavaScript注入" @@ -1045,7 +1013,7 @@ msgstr "次のコマンドを実行しますと、キーがコピーされます msgid "Save ScreenShot" msgstr "スクリーンショットを保存する" -#: app/control-panes/advanced/run-js/run-js.html:2 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Save..." msgstr "保存する..." @@ -1078,7 +1046,7 @@ msgstr "検索" msgid "Secure" msgstr "セキュア" -#: app/control-panes/control-panes-hotkeys-controller.js:97 +#: app/control-panes/control-panes-hotkeys-controller.js:91 msgid "Selects Next IME" msgstr "入力モードの切り替え" @@ -1144,7 +1112,7 @@ msgstr "誰かはデバイスを盗みました。" msgid "Special Keys" msgstr "特別なキー" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Start/Stop Logging" msgstr "ログ取得の開始/停止" @@ -1154,7 +1122,7 @@ msgid "Status" msgstr "ステータス" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Stop" msgstr "停止" @@ -1172,11 +1140,10 @@ msgid "Sub Type" msgstr "サブタイプ" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/advanced/input/input.html:41 msgid "Switch Charset" msgstr "文字入力の切り替え" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Tag" msgstr "タグ" @@ -1192,7 +1159,7 @@ msgstr "スクリーンショットを撮る" msgid "Temperature" msgstr "温度" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Text" msgstr "テキスト" @@ -1372,11 +1339,11 @@ msgstr "" msgid "The URI passed in is invalid." msgstr "渡されたURIは無効です。" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "TID" msgstr "TID" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Time" msgstr "時刻" @@ -1384,7 +1351,7 @@ msgstr "時刻" msgid "Tip:" msgstr "ヒント:" -#: app/control-panes/control-panes-hotkeys-controller.js:113 +#: app/control-panes/control-panes-hotkeys-controller.js:107 msgid "Toggle Web/Native" msgstr "ウェブ/ネイティブを選択" @@ -1419,6 +1386,10 @@ msgstr "未知" msgid "Unknown reason." msgstr "未知。" +#: app/control-panes/automation/device-settings/device-settings.html:6 +msgid "Unlock Rotation" +msgstr "" + #: app/components/stf/device/device-info-filter/index.js:51 msgid "Unspecified Failure" msgstr "未定義の失敗" @@ -1517,6 +1488,7 @@ msgstr "幅" #: app/components/stf/device/device-info-filter/index.js:105 #: app/components/stf/device/device-info-filter/index.js:97 +#: app/control-panes/automation/device-settings/device-settings.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "WiFi" msgstr "無線LAN" diff --git a/res/common/lang/po/stf.ko_KR.po b/res/common/lang/po/stf.ko_KR.po new file mode 100644 index 00000000..6527bc34 --- /dev/null +++ b/res/common/lang/po/stf.ko_KR.po @@ -0,0 +1,1518 @@ +# +# Translators: +# JINYOUNGYOO , 2015 +msgid "" +msgstr "" +"Project-Id-Version: STF\n" +"PO-Revision-Date: 2015-10-02 05:23+0000\n" +"Last-Translator: JINYOUNGYOO \n" +"Language-Team: Korean (Korea) (http://www.transifex.com/openstf/stf/language/ko_KR/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ko_KR\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: app/components/stf/device/device-info-filter/index.js:119 +#: app/components/stf/device/device-info-filter/index.js:52 +#: app/components/stf/device/device-info-filter/index.js:61 +#: app/components/stf/device/device-info-filter/index.js:71 +msgid "-" +msgstr "" + +#: app/components/stf/common-ui/modals/version-update/version-update.html:1 +msgid "A new version of STF is available" +msgstr "새 버전의 STF를 사용 가능 합니다" + +#: app/components/stf/install/install-error-filter.js:26 +msgid "A package is already installed with the same name." +msgstr "동일한 패키지가 설치되어 있습니다" + +#: app/components/stf/install/install-error-filter.js:30 +msgid "" +"A previously installed package of the same name has a different signature " +"than the new package (and the old package's data was not removed)." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:50 +msgid "A secure container mount point couldn't be accessed on external media." +msgstr "" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:178 +msgid "ABI" +msgstr "ABI" + +#: app/components/stf/device/device-info-filter/index.js:58 +msgid "AC" +msgstr "AC" + +#: app/settings/keys/access-tokens/access-tokens.html:1 +msgid "Access Tokens" +msgstr "액세스 토큰" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Account" +msgstr "계정" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Action" +msgstr "동작" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Actions" +msgstr "동작" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Activity" +msgstr "액티비티" + +#: app/settings/keys/adb-keys/adb-keys.html:1 +msgid "ADB Keys" +msgstr "ADB 키" + +#: app/control-panes/resources/resources.html:1 +msgid "Add" +msgstr "추가" + +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Add ADB Key" +msgstr "ADB 키 추가" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Add Key" +msgstr "키 추가" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +msgid "Add the following ADB Key to STF?" +msgstr "" + +#: app/layout/layout-controller.js:7 +msgid "Admin mode has been disabled." +msgstr "" + +#: app/layout/layout-controller.js:6 +msgid "Admin mode has been enabled." +msgstr "" + +#: app/control-panes/control-panes-controller.js:20 +msgid "Advanced" +msgstr "고급" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Advanced Input" +msgstr "고급 입력" + +#: app/control-panes/info/info.html:1 +msgid "Airplane Mode" +msgstr "비행기 모드" + +#: app/control-panes/automation/store-account/store-account.html:1 +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "App Store" +msgstr "앱 스토어" + +#: app/control-panes/dashboard/install/install.html:1 +msgid "App Upload" +msgstr "앱 업로드" + +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "Apps" +msgstr "앱" + +#: app/control-panes/advanced/maintenance/maintenance-controller.js:9 +msgid "Are you sure you want to reboot this device?" +msgstr "해당 단말기를 재시작 하시겠습니까?" + +#: app/control-panes/control-panes-controller.js:14 +msgid "Automation" +msgstr "자동화" + +#: app/components/stf/device/device-info-filter/index.js:28 +msgid "Available" +msgstr "사용 가능" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +#: app/control-panes/device-control/device-control.html:1 +msgid "Back" +msgstr "뒤로 가기" + +#: app/control-panes/info/info.html:1 +msgid "Battery" +msgstr "배터리" + +#: app/device-list/column/device-column-service.js:202 +msgid "Battery Health" +msgstr "베터리 상태" + +#: app/device-list/column/device-column-service.js:226 +msgid "Battery Level" +msgstr "베터리 수준" + +#: app/device-list/column/device-column-service.js:210 +msgid "Battery Source" +msgstr "베터리 종류" + +#: app/device-list/column/device-column-service.js:218 +msgid "Battery Status" +msgstr "배터리 충전 상태" + +#: app/device-list/column/device-column-service.js:239 +msgid "Battery Temp" +msgstr "배터리 온도" + +#: app/components/stf/device/device-info-filter/index.js:89 +msgid "Bluetooth" +msgstr "블루투스" + +#: app/device-list/column/device-column-service.js:153 +msgid "Browser" +msgstr "브라우저" + +#: app/components/stf/device/device-info-filter/index.js:12 +#: app/components/stf/device/device-info-filter/index.js:27 +msgid "Busy" +msgstr "점유중" + +#: app/device-list/stats/device-list-stats.html:1 +msgid "Busy Devices" +msgstr "점유중인 단말기" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Camera" +msgstr "카메라" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +msgid "Cancel" +msgstr "취소" + +#: app/components/stf/upload/upload-error-filter.js:6 +msgid "Cannot access specified URL" +msgstr "지정한 URL에 접근 할 수 없습니다" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:43 +msgid "Carrier" +msgstr "이동통신사" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Category" +msgstr "범주" + +#: app/components/stf/device/device-info-filter/index.js:67 +msgid "Charging" +msgstr "충전중" + +#: auth/ldap/scripts/signin/signin.html:1 +#: auth/mock/scripts/signin/signin.html:1 +msgid "Check errors below" +msgstr "아래 오류를 확인 하세요" + +#: app/components/stf/common-ui/clear-button/clear-button.html:1 +#: app/control-panes/advanced/run-js/run-js.html:1 +#: app/control-panes/logs/logs.html:1 +msgid "Clear" +msgstr "지우기" + +#: app/control-panes/dashboard/clipboard/clipboard.html:1 +msgid "Clipboard" +msgstr "클립보드" + +#: app/components/stf/device/device-info-filter/index.js:46 +msgid "Cold" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:21 +#: app/components/stf/device/device-info-filter/index.js:6 +#: app/control-panes/info/info.html:1 +msgid "Connected" +msgstr "연결됨" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:20 +msgid "Connected successfully." +msgstr "잘 연결했습니다" + +#: app/menu/menu.html:1 +msgid "Control" +msgstr "컨트롤" + +#: app/control-panes/resources/resources.html:1 +msgid "Cookies" +msgstr "쿠키" + +#: app/control-panes/info/info.html:1 +msgid "Cores" +msgstr "코어" + +#: app/control-panes/info/info.html:1 +#: app/control-panes/performance/cpu/cpu.html:1 +msgid "CPU" +msgstr "CPU" + +#: app/device-list/device-list.html:1 +msgid "Customize" +msgstr "사용자 지정" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Center" +msgstr "D-pad 가운데" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Down" +msgstr "D-pad 아래쪽" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Left" +msgstr "D-pad 왼쪽" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Right" +msgstr "D-pad 오른쪽" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Up" +msgstr "D-pad 위쪽" + +#: app/control-panes/control-panes-controller.js:35 +msgid "Dashboard" +msgstr "대시보드" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Data" +msgstr "데이터" + +#: app/components/stf/device/device-info-filter/index.js:48 +msgid "Dead" +msgstr "정지" + +#: app/control-panes/resources/resources.html:1 +msgid "Delete" +msgstr "삭제" + +#: app/control-panes/info/info.html:1 +msgid "Density" +msgstr "밀도" + +#: app/device-list/device-list.html:1 +msgid "Details" +msgstr "세부 정보" + +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "Developer" +msgstr "개발자" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +#: app/control-panes/inspect/inspect.html:1 +msgid "Device" +msgstr "단말기" + +#: app/device-list/details/device-list-details-directive.js:36 +#: app/device-list/icons/device-list-icons-directive.js:123 +msgid "Device cannot get kicked from the group" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:38 +msgid "Device is not present anymore for some reason." +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:39 +msgid "Device is present but offline." +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Device Photo" +msgstr "" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Device Settings" +msgstr "단말기 설정" + +#: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 +msgid "Device was disconnected" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:37 +msgid "Device was kicked by automatic timeout." +msgstr "" + +#: app/device-list/device-list.html:1 app/menu/menu.html:1 +msgid "Devices" +msgstr "단말기" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Disable WiFi" +msgstr "WiFi 비활성화" + +#: app/components/stf/device/device-info-filter/index.js:68 +msgid "Discharging" +msgstr "충전중이 아님" + +#: app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected.html:1 +#: app/components/stf/device/device-info-filter/index.js:20 +#: app/components/stf/device/device-info-filter/index.js:5 +msgid "Disconnected" +msgstr "연결 끊김" + +#: app/control-panes/info/info.html:1 +msgid "Display" +msgstr "화면" + +#: app/control-panes/resources/resources.html:1 +msgid "Domain" +msgstr "도메인" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Drop file to upload" +msgstr "업로드 할 파일을 올려놓으세요" + +#: app/components/stf/device/device-info-filter/index.js:90 +msgid "Dummy" +msgstr "더미" + +#: app/settings/notifications/notifications.html:1 +msgid "Enable notifications" +msgstr "알림 사용" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Enable WiFi" +msgstr "WiFi 활성화" + +#: app/control-panes/info/info.html:1 +msgid "Encrypted" +msgstr "암호화된" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:31 +msgid "Error" +msgstr "오류" + +#: app/components/stf/control/control-service.js:129 +msgid "Error while getting data" +msgstr "데이터를 얻어오는데 실패했습니다" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:35 +msgid "Error while reconnecting" +msgstr "재연결이 실패 했습니다" + +#: app/components/stf/device/device-info-filter/index.js:91 +msgid "Ethernet" +msgstr "이더넷" + +#: app/control-panes/dashboard/shell/shell.html:1 +msgid "Executes remote shell commands" +msgstr "원격 쉘 명령을 실행합니다" + +#: app/components/stf/upload/upload-error-filter.js:5 +msgid "Failed to download file" +msgstr "" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Fast Forward" +msgstr "빨리 감기" + +#: app/components/stf/common-ui/filter-button/filter-button.html:1 +msgid "Filter" +msgstr "필터" + +#: app/control-panes/info/info.html:1 +msgid "Find Device" +msgstr "장치 찾기" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +msgid "Fingerprint" +msgstr "지문" + +#: app/control-panes/info/info.html:1 +msgid "FPS" +msgstr "FPS" + +#: app/control-panes/info/info.html:1 +msgid "Frequency" +msgstr "주파수" + +#: app/components/stf/device/device-info-filter/index.js:69 +msgid "Full" +msgstr "전체" + +#: app/settings/settings-controller.js:5 +msgid "General" +msgstr "일반" + +#: app/control-panes/logs/logs.html:1 +#: app/control-panes/resources/resources.html:1 +msgid "Get" +msgstr "" + +#: app/control-panes/dashboard/clipboard/clipboard.html:1 +msgid "Get clipboard contents" +msgstr "" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Go Back" +msgstr "뒤로 이동" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Go Forward" +msgstr "앞으로 이동" + +#: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 +#: app/control-panes/control-panes-hotkeys-controller.js:89 +msgid "Go to Device List" +msgstr "단말기 목록으로 이동" + +#: app/components/stf/device/device-info-filter/index.js:47 +msgid "Good" +msgstr "양호" + +#: app/control-panes/info/info.html:1 +msgid "Hardware" +msgstr "하드웨어" + +#: app/control-panes/info/info.html:1 +msgid "Health" +msgstr "상태" + +#: app/control-panes/info/info.html:1 +msgid "Height" +msgstr "높이이" + +#: app/menu/menu.html:1 +msgid "Help" +msgstr "도움말" + +#: app/control-panes/device-control/device-control.html:1 +msgid "Hide Screen" +msgstr "화면 숨김" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +#: app/control-panes/device-control/device-control.html:1 +msgid "Home" +msgstr "홈" + +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +msgid "Host" +msgstr "호스트" + +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +msgid "Hostname" +msgstr "호스트이름" + +#: app/control-panes/info/info.html:1 +msgid "ICCID" +msgstr "ICCID" + +#: app/control-panes/info/info.html:1 +msgid "ID" +msgstr "ID" + +#: app/control-panes/info/info.html:1 +msgid "IMEI" +msgstr "IMEI" + +#: auth/ldap/scripts/signin/signin.html:1 +#: auth/mock/scripts/signin/signin.html:1 +msgid "Incorrect login details" +msgstr "" + +#: app/control-panes/control-panes-controller.js:26 +msgid "Info" +msgstr "정보" + +#: app/control-panes/inspect/inspect.html:1 +msgid "Inspect Device" +msgstr "단말기 검사" + +#: app/control-panes/inspect/inspect.html:1 +msgid "Inspecting is currently only supported in WebView" +msgstr "" + +#: app/control-panes/inspect/inspect.html:1 +msgid "Inspector" +msgstr "검사기" + +#: app/components/stf/install/install-error-filter.js:13 +msgid "Installation canceled by user." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:9 +msgid "Installation failed due to an unknown error." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:7 +msgid "Installation succeeded." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:11 +msgid "Installation timed out." +msgstr "" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Installing app..." +msgstr "" + +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Key" +msgstr "키" + +#: app/settings/settings-controller.js:10 +msgid "Keys" +msgstr "키" + +#: app/control-panes/device-control/device-control.html:1 +msgid "Landscape" +msgstr "가로" + +#: app/settings/general/language/language.html:1 +msgid "Language" +msgstr "언어" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Launch Activity" +msgstr "" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Launching activity..." +msgstr "" + +#: app/control-panes/info/info.html:1 app/control-panes/logs/logs.html:1 +msgid "Level" +msgstr "수준" + +#: app/settings/general/local/local-settings.html:1 +msgid "Local Settings" +msgstr "로컬 설정" + +#: app/device-list/column/device-column-service.js:250 +msgid "Location" +msgstr "위치" + +#: app/control-panes/automation/device-settings/device-settings.html:7 +msgid "Lock Rotation" +msgstr "화면 잠금" + +#: app/control-panes/control-panes-controller.js:44 +msgid "Logs" +msgstr "로그" + +#: app/control-panes/advanced/maintenance/maintenance.html:1 +msgid "Maintenance" +msgstr "유지 관리" + +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "Manage Apps" +msgstr "앱 관리" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Manner Mode" +msgstr "매너 모드" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:165 +msgid "Manufacturer" +msgstr "제조사" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Media" +msgstr "미디어" + +#: app/control-panes/info/info.html:1 +msgid "Memory" +msgstr "메모리" + +#: app/control-panes/device-control/device-control.html:1 +msgid "Menu" +msgstr "메뉴" + +#: app/components/stf/device/device-info-filter/index.js:92 +msgid "Mobile" +msgstr "모바일" + +#: app/components/stf/device/device-info-filter/index.js:93 +msgid "Mobile DUN" +msgstr "모바일 DUN" + +#: app/components/stf/device/device-info-filter/index.js:94 +msgid "Mobile High Priority" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:95 +msgid "Mobile MMS" +msgstr "모바일 MMS" + +#: app/components/stf/device/device-info-filter/index.js:96 +msgid "Mobile SUPL" +msgstr "모바일 SUPL" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:31 +msgid "Model" +msgstr "모델" + +#: app/settings/keys/access-tokens/access-tokens.html:1 +msgid "More about Access Tokens" +msgstr "" + +#: app/settings/keys/adb-keys/adb-keys.html:1 +msgid "More about ADB Keys" +msgstr "" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Mute" +msgstr "음소거" + +#: app/control-panes/info/info.html:1 +#: app/control-panes/resources/resources.html:1 +msgid "Name" +msgstr "이름" + +#: app/menu/menu.html:1 +msgid "Native" +msgstr "네이티브" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Navigation" +msgstr "탐색" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:129 +msgid "Network" +msgstr "네트워크" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Next" +msgstr "다음" + +#: app/components/stf/device/device-info-filter/index.js:116 +msgid "No" +msgstr "아니오" + +#: app/settings/keys/access-tokens/access-tokens.html:1 +msgid "No access tokens" +msgstr "" + +#: app/settings/keys/adb-keys/adb-keys.html:1 +msgid "No ADB keys" +msgstr "" + +#: app/components/stf/control/control-service.js:126 +msgid "No clipboard data" +msgstr "" + +#: app/control-panes/resources/resources.html:1 +msgid "No cookies to show" +msgstr "" + +#: app/components/stf/screen/screen.html:1 +msgid "No device screen" +msgstr "" + +#: app/device-list/empty/device-list-empty.html:1 +msgid "No devices connected" +msgstr "" + +#: app/components/stf/common-ui/modals/lightbox-image/lightbox-image.html:1 +msgid "No photo available" +msgstr "" + +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +msgid "No Ports Forwarded" +msgstr "" + +#: app/control-panes/screenshots/screenshots.html:5 +msgid "No screenshots taken" +msgstr "" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Normal Mode" +msgstr "표준 모드" + +#: app/components/stf/device/device-info-filter/index.js:70 +msgid "Not Charging" +msgstr "충전 안함" + +#: app/device-list/column/device-column-service.js:256 +msgid "Notes" +msgstr "메모" + +#: app/control-panes/inspect/inspect.html:1 +msgid "Nothing to inspect" +msgstr "" + +#: app/settings/notifications/notifications.html:1 +msgid "Notifications" +msgstr "알림" + +#: app/control-panes/info/info.html:1 +msgid "Number" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:22 +#: app/components/stf/device/device-info-filter/index.js:7 +msgid "Offline" +msgstr "오프라인" + +#: app/components/stf/common-ui/error-message/error-message.html:1 +#: app/control-panes/dashboard/install/install.html:7 +msgid "Oops!" +msgstr "웁스!" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Open" +msgstr "열기" + +#: app/control-panes/info/info.html:1 +msgid "Orientation" +msgstr "방향" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:55 +msgid "OS" +msgstr "OS" + +#: app/components/stf/device/device-info-filter/index.js:49 +msgid "Over Voltage" +msgstr "과전압" + +#: app/components/stf/device/device-info-filter/index.js:50 +msgid "Overheat" +msgstr "" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Package" +msgstr "패키지" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Password" +msgstr "비밀번호" + +#: app/control-panes/resources/resources.html:1 +msgid "Path" +msgstr "경로" + +#: app/device-list/column/device-column-service.js:184 +msgid "Phone" +msgstr "휴대폰" + +#: app/device-list/column/device-column-service.js:196 +msgid "Phone ICCID" +msgstr "휴대폰 ICCID" + +#: app/device-list/column/device-column-service.js:190 +msgid "Phone IMEI" +msgstr "휴대폰 IMEI" + +#: app/control-panes/info/info.html:1 +msgid "Physical Device" +msgstr "물리 단말기" + +#: app/control-panes/logs/logs.html:1 +msgid "PID" +msgstr "PID" + +#: app/control-panes/info/info.html:1 +msgid "Place" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Platform" +msgstr "플랫폼" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Play/Pause" +msgstr "재생/일시 중지" + +#: auth/mock/scripts/signin/signin.html:1 +msgid "Please enter a valid email" +msgstr "" + +#: auth/mock/scripts/signin/signin.html:1 +msgid "Please enter your email" +msgstr "" + +#: auth/ldap/scripts/signin/signin.html:1 +msgid "Please enter your LDAP username" +msgstr "" + +#: auth/mock/scripts/signin/signin.html:1 +msgid "Please enter your name" +msgstr "" + +#: auth/ldap/scripts/signin/signin.html:1 +msgid "Please enter your password" +msgstr "" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Please enter your Store password" +msgstr "" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Please enter your Store username" +msgstr "" + +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +msgid "Port" +msgstr "포트" + +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +msgid "Port Forwarding" +msgstr "포트 포워딩" + +#: app/control-panes/device-control/device-control.html:1 +msgid "Portrait" +msgstr "세로" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Power" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Power Source" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:24 +#: app/components/stf/device/device-info-filter/index.js:9 +msgid "Preparing" +msgstr "준비중" + +#: app/control-panes/control-panes-hotkeys-controller.js:104 +msgid "Press Back button" +msgstr "뒤로가기 버튼을 누르세요" + +#: app/control-panes/control-panes-hotkeys-controller.js:103 +msgid "Press Home button" +msgstr "홈 버튼을 누르세요" + +#: app/control-panes/control-panes-hotkeys-controller.js:102 +msgid "Press Menu button" +msgstr "메뉴 버튼을 누르세요" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Previous" +msgstr "" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Processing..." +msgstr "" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:37 +msgid "Product" +msgstr "" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Pushing app..." +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "RAM" +msgstr "RAM" + +#: app/components/stf/device/device-info-filter/index.js:10 +#: app/components/stf/device/device-info-filter/index.js:25 +msgid "Ready" +msgstr "" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:39 +msgid "Reconnected successfully." +msgstr "" + +#: app/components/stf/common-ui/refresh-page/refresh-page.html:1 +msgid "Refresh" +msgstr "" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:49 +msgid "Released" +msgstr "" + +#: app/components/stf/common-ui/modals/version-update/version-update.html:1 +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Reload" +msgstr "" + +#: app/control-panes/dashboard/remote-debug/remote-debug.html:1 +msgid "Remote debug" +msgstr "" + +#: app/settings/keys/access-tokens/access-tokens.html:1 +#: app/settings/keys/adb-keys/adb-keys.html:1 +msgid "Remove" +msgstr "" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +#: app/device-list/device-list.html:1 +msgid "Reset" +msgstr "" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Reset all browser settings" +msgstr "" + +#: app/settings/general/local/local-settings.html:1 +msgid "Reset Settings" +msgstr "" + +#: app/control-panes/advanced/maintenance/maintenance.html:1 +msgid "Restart Device" +msgstr "" + +#: app/components/stf/screen/screen.html:1 +msgid "Retrieving the device screen has timed out." +msgstr "" + +#: app/components/stf/screen/screen.html:1 +msgid "Retry" +msgstr "재시도" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Rewind" +msgstr "되감기" + +#: app/control-panes/info/info.html:1 +msgid "Roaming" +msgstr "로밍" + +#: app/control-panes/info/info.html:1 +msgid "ROM" +msgstr "ROM" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +#: app/control-panes/control-panes-hotkeys-controller.js:92 +msgid "Rotate Left" +msgstr "왼쪽으로 회전" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +#: app/control-panes/control-panes-hotkeys-controller.js:93 +msgid "Rotate Right" +msgstr "오른쪽으로 회전" + +#: app/control-panes/advanced/run-js/run-js.html:1 +msgid "Run" +msgstr "실행" + +#: app/control-panes/advanced/run-js/run-js.html:1 +msgid "Run JavaScript" +msgstr "자바스크립트 실행" + +#: app/control-panes/dashboard/remote-debug/remote-debug-controller.js:31 +msgid "" +"Run the following on your command line to debug the device from your Browser" +msgstr "" + +#: app/control-panes/dashboard/remote-debug/remote-debug-controller.js:28 +msgid "" +"Run the following on your command line to debug the device from your IDE" +msgstr "" + +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Run this command to copy the key to your clipboard" +msgstr "" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +msgid "Save ScreenShot" +msgstr "스크린 샷 저장" + +#: app/control-panes/advanced/run-js/run-js.html:1 +msgid "Save..." +msgstr "저장..." + +#: app/device-list/column/device-column-service.js:135 +msgid "Screen" +msgstr "화면" + +#: app/control-panes/screenshots/screenshots.html:1 +msgid "Screenshot" +msgstr "스크린 샷" + +#: app/control-panes/control-panes-controller.js:8 +msgid "Screenshots" +msgstr "스크린 샷" + +#: app/control-panes/info/info.html:1 +msgid "SD Card Mounted" +msgstr "SD카드가 탑재되었습니다" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:171 +msgid "SDK" +msgstr "SDK" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Search" +msgstr "검색" + +#: app/control-panes/resources/resources.html:1 +msgid "Secure" +msgstr "보안" + +#: app/control-panes/control-panes-hotkeys-controller.js:91 +msgid "Selects Next IME" +msgstr "" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:159 +msgid "Serial" +msgstr "일련 번호" + +#: auth/ldap/scripts/signin/signin.html:1 +#: auth/mock/scripts/signin/signin.html:1 +msgid "Server error. Check log output." +msgstr "서버 에러. 로그를 확인하세요" + +#: app/control-panes/resources/resources.html:1 +msgid "Set" +msgstr "Set" + +#: app/control-panes/resources/resources.html:1 +msgid "Set Cookie" +msgstr "쿠키 설정" + +#: app/control-panes/dashboard/apps/apps.html:1 app/menu/menu.html:1 +msgid "Settings" +msgstr "설정" + +#: app/control-panes/dashboard/shell/shell.html:1 +msgid "Shell" +msgstr "셸" + +#: app/control-panes/device-control/device-control.html:1 +msgid "Show Screen" +msgstr "화면 표시" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Sign In" +msgstr "로그인" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Sign Out" +msgstr "로그아웃" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Silent Mode" +msgstr "음소거" + +#: app/control-panes/info/info.html:1 +msgid "SIM" +msgstr "SIM" + +#: app/control-panes/info/info.html:1 +msgid "Size" +msgstr "크기" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:26 +msgid "Socket connection was lost" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:36 +msgid "Someone stole your device." +msgstr "" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Special Keys" +msgstr "특수 키" + +#: app/control-panes/logs/logs.html:1 +msgid "Start/Stop Logging" +msgstr "시작/종료 로깅" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:25 +msgid "Status" +msgstr "상태" + +#: app/control-panes/advanced/input/input.html:1 +#: app/control-panes/logs/logs.html:1 +msgid "Stop" +msgstr "정지" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +#: app/components/stf/device/device-info-filter/index.js:11 +msgid "Stop Using" +msgstr "사용 종료" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Store Account" +msgstr "저장소 계정" + +#: app/control-panes/info/info.html:1 +msgid "Sub Type" +msgstr "하위 유형" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Switch Charset" +msgstr "문자 집합 변경" + +#: app/control-panes/logs/logs.html:1 +msgid "Tag" +msgstr "태그" + +#: app/control-panes/screenshots/screenshots.html:1 +msgid "Take Pageshot (Needs WebView running)" +msgstr "" + +#: app/control-panes/screenshots/screenshots.html:1 +msgid "Take Screenshot" +msgstr "스크린샷 캡처" + +#: app/control-panes/info/info.html:1 +msgid "Temperature" +msgstr "온도" + +#: app/control-panes/logs/logs.html:1 +msgid "Text" +msgstr "텍스트" + +#: app/components/stf/screen/screen.html:1 +msgid "The current view is marked secure and cannot be viewed remotely." +msgstr "" + +#: app/control-panes/advanced/maintenance/maintenance-controller.js:10 +msgid "The device will be unavailable for a moment." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:34 +msgid "The existing package could not be deleted." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:58 +msgid "" +"The new package couldn't be installed because the verification did not " +"succeed." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:56 +msgid "" +"The new package couldn't be installed because the verification timed out." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:54 +msgid "" +"The new package couldn't be installed in the specified install location " +"because the media is not available." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:52 +msgid "" +"The new package couldn't be installed in the specified install location." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:40 +msgid "" +"The new package failed because it contains a content provider with thesame " +"authority as a provider already installed in the system." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:44 +msgid "" +"The new package failed because it has specified that it is a test-only " +"package and the caller has not supplied the INSTALL_ALLOW_TEST flag." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:42 +msgid "" +"The new package failed because the current SDK version is newer than that " +"required by the package." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:38 +msgid "" +"The new package failed because the current SDK version is older than that " +"required by the package." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:36 +msgid "" +"The new package failed while optimizing and validating its dex files, either" +" because there was not enough storage or the validation failed." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:64 +msgid "" +"The new package has an older version code than the currently installed " +"package." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:62 +msgid "The new package is assigned a different UID than it previously held." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:48 +msgid "The new package uses a feature that is not available." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:32 +msgid "The new package uses a shared library that is not available." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:20 +msgid "The package archive file is invalid." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:46 +msgid "" +"The package being installed contains native code, but none that is " +"compatible with the device's CPU_ABI." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:60 +msgid "The package changed from what the calling program expected." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:18 +msgid "The package is already installed." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:24 +msgid "" +"The package manager service found that the device didn't have enough storage" +" space to install the app." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:84 +msgid "" +"The parser did not find any actionable tags (instrumentation or application)" +" in the manifest." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:72 +msgid "The parser did not find any certificates in the .apk." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:78 +msgid "The parser encountered a bad or missing package name in the manifest." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:80 +msgid "The parser encountered a bad shared user id name in the manifest." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:76 +msgid "" +"The parser encountered a CertificateEncodingException in one of the files in" +" the .apk." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:70 +msgid "The parser encountered an unexpected exception." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:82 +msgid "The parser encountered some structural problem in the manifest." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:74 +msgid "The parser found inconsistent certificates on the files in the .apk." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:66 +msgid "" +"The parser was given a path that is not a file, or does not end with the " +"expected '.apk' extension." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:68 +msgid "The parser was unable to retrieve the AndroidManifest.xml file." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:28 +msgid "The requested shared user does not exist." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:90 +msgid "" +"The system failed to install the package because its packaged native code " +"did not match any of the ABIs supported by the system." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:86 +msgid "The system failed to install the package because of system issues." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:88 +msgid "" +"The system failed to install the package because the user is restricted from" +" installing apps." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:22 +msgid "The URI passed in is invalid." +msgstr "" + +#: app/control-panes/logs/logs.html:1 +msgid "TID" +msgstr "TID" + +#: app/control-panes/logs/logs.html:1 +msgid "Time" +msgstr "시간" + +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Tip:" +msgstr "팁" + +#: app/control-panes/control-panes-hotkeys-controller.js:107 +msgid "Toggle Web/Native" +msgstr "웹/네이티브 전환" + +#: app/device-list/stats/device-list-stats.html:1 +msgid "Total Devices" +msgstr "총 단말기 수" + +#: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 +#: app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected.html:1 +msgid "Try to reconnect" +msgstr "다시 연결" + +#: app/control-panes/info/info.html:1 +msgid "Type" +msgstr "유형" + +#: app/components/stf/device/device-info-filter/index.js:23 +#: app/components/stf/device/device-info-filter/index.js:8 +msgid "Unauthorized" +msgstr "미인증" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Uninstall" +msgstr "설치 제거" + +#: app/components/stf/device/device-info-filter/index.js:14 +#: app/components/stf/device/device-info-filter/index.js:29 +msgid "Unknown" +msgstr "알 수 없음" + +#: app/components/stf/device/device-info-filter/index.js:40 +msgid "Unknown reason." +msgstr "알 수 없는 이유" + +#: app/control-panes/automation/device-settings/device-settings.html:6 +msgid "Unlock Rotation" +msgstr "회전 잠금 해제" + +#: app/components/stf/device/device-info-filter/index.js:51 +msgid "Unspecified Failure" +msgstr "지정되지 않은 오류" + +#: app/components/stf/upload/upload-error-filter.js:7 +msgid "Upload failed" +msgstr "업로드 실패" + +#: app/control-panes/dashboard/install/install.html:5 +msgid "Upload From Link" +msgstr "링크로 업로드" + +#: app/components/stf/upload/upload-error-filter.js:8 +msgid "Upload unknown error" +msgstr "" + +#: app/components/stf/upload/upload-error-filter.js:4 +msgid "Uploaded file is not valid" +msgstr "업로드된 파일이 유효하지 않습니다" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Uploading..." +msgstr "업로드중..." + +#: app/device-list/stats/device-list-stats.html:1 +msgid "Usable Devices" +msgstr "사용 가능한 단말기" + +#: app/components/stf/device/device-info-filter/index.js:59 +msgid "USB" +msgstr "USB" + +#: app/control-panes/advanced/usb/usb.html:1 +msgid "Usb speed" +msgstr "Usb 속도" + +#: app/components/stf/device/device-info-filter/index.js:13 +msgid "Use" +msgstr "사용" + +#: app/device-list/column/device-column-service.js:262 +msgid "User" +msgstr "사용자" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Username" +msgstr "사용자 이름" + +#: app/components/stf/device/device-info-filter/index.js:26 +msgid "Using" +msgstr "사용중" + +#: app/control-panes/info/info.html:1 +msgid "Using Fallback" +msgstr "대체 사용" + +#: app/control-panes/resources/resources.html:1 +msgid "Value" +msgstr "값" + +#: app/control-panes/info/info.html:1 +msgid "Version" +msgstr "버전" + +#: app/components/stf/common-ui/modals/version-update/version-update.html:1 +msgid "Version Update" +msgstr "버전 업데이트" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Vibrate Mode" +msgstr "진동" + +#: app/control-panes/info/info.html:1 +msgid "Voltage" +msgstr "전압" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Volume" +msgstr "음량" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Volume Down" +msgstr "음량 줄이기" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Volume Up" +msgstr "음량 올리기" + +#: app/menu/menu.html:1 +msgid "Web" +msgstr "웹" + +#: app/control-panes/info/info.html:1 +msgid "Width" +msgstr "너비" + +#: app/components/stf/device/device-info-filter/index.js:105 +#: app/components/stf/device/device-info-filter/index.js:97 +#: app/control-panes/automation/device-settings/device-settings.html:1 +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "WiFi" +msgstr "WiFi" + +#: app/components/stf/device/device-info-filter/index.js:98 +msgid "WiMAX" +msgstr "WiMAX" + +#: app/components/stf/device/device-info-filter/index.js:60 +msgid "Wireless" +msgstr "무선" + +#: app/control-panes/info/info.html:1 +msgid "X DPI" +msgstr "X DPI" + +#: app/control-panes/info/info.html:1 +msgid "Y DPI" +msgstr "Y DPI" + +#: app/components/stf/device/device-info-filter/index.js:113 +msgid "Yes" +msgstr "네" + +#: app/components/stf/device/device-info-filter/index.js:35 +msgid "You (or someone else) kicked the device." +msgstr "" diff --git a/res/common/lang/po/stf.pot b/res/common/lang/po/stf.pot index 69a61606..af908b2f 100644 --- a/res/common/lang/po/stf.pot +++ b/res/common/lang/po/stf.pot @@ -65,7 +65,6 @@ msgid "Add" msgstr "" #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Add ADB Key" msgstr "" @@ -98,10 +97,6 @@ msgstr "" msgid "Airplane Mode" msgstr "" -#: app/control-panes/logs/logs.html:20 -msgid "App" -msgstr "" - #: app/control-panes/automation/store-account/store-account.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "App Store" @@ -198,18 +193,14 @@ msgstr "" msgid "Charging" msgstr "" -#: app/menu/menu.html:3 -msgid "Chat" -msgstr "" - #: auth/ldap/scripts/signin/signin.html:1 #: auth/mock/scripts/signin/signin.html:1 msgid "Check errors below" msgstr "" #: app/components/stf/common-ui/clear-button/clear-button.html:1 -#: app/control-panes/advanced/run-js/run-js.html:2 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/advanced/run-js/run-js.html:1 +#: app/control-panes/logs/logs.html:1 msgid "Clear" msgstr "" @@ -217,10 +208,6 @@ msgstr "" msgid "Clipboard" msgstr "" -#: app/control-panes/advanced/input/input.html:5 -msgid "Close" -msgstr "" - #: app/components/stf/device/device-info-filter/index.js:46 msgid "Cold" msgstr "" @@ -256,27 +243,27 @@ msgstr "" msgid "Customize" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Center" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Down" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Left" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Right" msgstr "" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Up" msgstr "" -#: app/control-panes/control-panes-controller.js:35 +#: app/control-panes/control-panes-controller.js:41 msgid "Dashboard" msgstr "" @@ -284,6 +271,10 @@ msgstr "" msgid "Data" msgstr "" +#: app/control-panes/explorer/explorer.html:1 +msgid "Date" +msgstr "" + #: app/components/stf/device/device-info-filter/index.js:48 msgid "Dead" msgstr "" @@ -307,8 +298,7 @@ msgstr "" #: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 #: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 -#: app/control-panes/inspect/inspect.html:3 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/inspect/inspect.html:1 msgid "Device" msgstr "" @@ -364,10 +354,6 @@ msgstr "" msgid "Display" msgstr "" -#: app/control-panes/resources/resources.html:1 -msgid "Domain" -msgstr "" - #: app/control-panes/dashboard/install/install.html:7 msgid "Drop file to upload" msgstr "" @@ -376,10 +362,6 @@ msgstr "" msgid "Dummy" msgstr "" -#: app/control-panes/advanced/input/input.html:7 -msgid "Eject" -msgstr "" - #: app/settings/notifications/notifications.html:1 msgid "Enable notifications" msgstr "" @@ -420,6 +402,10 @@ msgstr "" msgid "Fast Forward" msgstr "" +#: app/control-panes/control-panes-controller.js:26 +msgid "File Explorer" +msgstr "" + #: app/components/stf/common-ui/filter-button/filter-button.html:1 msgid "Filter" msgstr "" @@ -444,11 +430,11 @@ msgstr "" msgid "Full" msgstr "" -#: app/settings/settings-controller.js:10 +#: app/settings/settings-controller.js:5 msgid "General" msgstr "" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 #: app/control-panes/resources/resources.html:1 msgid "Get" msgstr "" @@ -466,7 +452,7 @@ msgid "Go Forward" msgstr "" #: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:95 +#: app/control-panes/control-panes-hotkeys-controller.js:89 msgid "Go to Device List" msgstr "" @@ -486,7 +472,7 @@ msgstr "" msgid "Height" msgstr "" -#: app/menu/menu.html:22 +#: app/menu/menu.html:1 msgid "Help" msgstr "" @@ -524,11 +510,11 @@ msgstr "" msgid "Incorrect login details" msgstr "" -#: app/control-panes/control-panes-controller.js:26 +#: app/control-panes/control-panes-controller.js:32 msgid "Info" msgstr "" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspect Device" msgstr "" @@ -536,7 +522,7 @@ msgstr "" msgid "Inspecting is currently only supported in WebView" msgstr "" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspector" msgstr "" @@ -564,7 +550,7 @@ msgstr "" msgid "Key" msgstr "" -#: app/settings/settings-controller.js:15 +#: app/settings/settings-controller.js:10 msgid "Keys" msgstr "" @@ -585,7 +571,7 @@ msgid "Launching activity..." msgstr "" #: app/control-panes/info/info.html:1 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Level" msgstr "" @@ -597,7 +583,11 @@ msgstr "" msgid "Location" msgstr "" -#: app/control-panes/control-panes-controller.js:44 +#: app/control-panes/automation/device-settings/device-settings.html:7 +msgid "Lock Rotation" +msgstr "" + +#: app/control-panes/control-panes-controller.js:50 msgid "Logs" msgstr "" @@ -609,6 +599,10 @@ msgstr "" msgid "Manage Apps" msgstr "" +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Manner Mode" +msgstr "" + #: app/control-panes/info/info.html:1 #: app/device-list/column/device-column-service.js:165 msgid "Manufacturer" @@ -656,8 +650,6 @@ msgid "More about Access Tokens" msgstr "" #: app/settings/keys/adb-keys/adb-keys.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:10 -#: app/settings/keys/adb-keys/adb-keys.html:44 msgid "More about ADB Keys" msgstr "" @@ -665,8 +657,8 @@ msgstr "" msgid "Mute" msgstr "" +#: app/control-panes/explorer/explorer.html:1 #: app/control-panes/info/info.html:1 -#: app/control-panes/resources/resources.html:1 msgid "Name" msgstr "" @@ -739,7 +731,7 @@ msgstr "" msgid "Notes" msgstr "" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Nothing to inspect" msgstr "" @@ -790,12 +782,8 @@ msgstr "" msgid "Password" msgstr "" -#: app/control-panes/resources/resources.html:1 -msgid "Path" -msgstr "" - -#: app/control-panes/advanced/input/input.html:3 -msgid "Pause" +#: app/control-panes/explorer/explorer.html:1 +msgid "Permissions" msgstr "" #: app/device-list/column/device-column-service.js:184 @@ -814,7 +802,7 @@ msgstr "" msgid "Physical Device" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "PID" msgstr "" @@ -826,10 +814,6 @@ msgstr "" msgid "Platform" msgstr "" -#: app/control-panes/advanced/input/input.html:1 -msgid "Play" -msgstr "" - #: app/control-panes/advanced/input/input.html:1 msgid "Play/Pause" msgstr "" @@ -887,15 +871,15 @@ msgstr "" msgid "Preparing" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:110 +#: app/control-panes/control-panes-hotkeys-controller.js:104 msgid "Press Back button" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:109 +#: app/control-panes/control-panes-hotkeys-controller.js:103 msgid "Press Home button" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:108 +#: app/control-panes/control-panes-hotkeys-controller.js:102 msgid "Press Menu button" msgstr "" @@ -929,10 +913,6 @@ msgstr "" msgid "Reconnected successfully." msgstr "" -#: app/control-panes/advanced/input/input.html:9 -msgid "Record" -msgstr "" - #: app/components/stf/common-ui/refresh-page/refresh-page.html:1 msgid "Refresh" msgstr "" @@ -952,7 +932,7 @@ msgid "Remote debug" msgstr "" #: app/settings/keys/access-tokens/access-tokens.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:74 +#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Remove" msgstr "" @@ -994,27 +974,19 @@ msgid "ROM" msgstr "" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:98 +#: app/control-panes/control-panes-hotkeys-controller.js:92 msgid "Rotate Left" msgstr "" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:99 +#: app/control-panes/control-panes-hotkeys-controller.js:93 msgid "Rotate Right" msgstr "" -#: app/control-panes/advanced/run-js/run-js.html:18 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run" msgstr "" -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run command" -msgstr "" - -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run Command" -msgstr "" - #: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run JavaScript" msgstr "" @@ -1035,7 +1007,7 @@ msgstr "" msgid "Save ScreenShot" msgstr "" -#: app/control-panes/advanced/run-js/run-js.html:2 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Save..." msgstr "" @@ -1064,11 +1036,7 @@ msgstr "" msgid "Search" msgstr "" -#: app/control-panes/resources/resources.html:1 -msgid "Secure" -msgstr "" - -#: app/control-panes/control-panes-hotkeys-controller.js:97 +#: app/control-panes/control-panes-hotkeys-controller.js:91 msgid "Selects Next IME" msgstr "" @@ -1119,6 +1087,7 @@ msgstr "" msgid "SIM" msgstr "" +#: app/control-panes/explorer/explorer.html:1 #: app/control-panes/info/info.html:1 msgid "Size" msgstr "" @@ -1135,7 +1104,7 @@ msgstr "" msgid "Special Keys" msgstr "" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Start/Stop Logging" msgstr "" @@ -1145,7 +1114,7 @@ msgid "Status" msgstr "" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Stop" msgstr "" @@ -1163,11 +1132,10 @@ msgid "Sub Type" msgstr "" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/advanced/input/input.html:41 msgid "Switch Charset" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Tag" msgstr "" @@ -1183,7 +1151,7 @@ msgstr "" msgid "Temperature" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Text" msgstr "" @@ -1331,11 +1299,11 @@ msgstr "" msgid "The URI passed in is invalid." msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "TID" msgstr "" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Time" msgstr "" @@ -1343,7 +1311,7 @@ msgstr "" msgid "Tip:" msgstr "" -#: app/control-panes/control-panes-hotkeys-controller.js:113 +#: app/control-panes/control-panes-hotkeys-controller.js:107 msgid "Toggle Web/Native" msgstr "" @@ -1351,6 +1319,11 @@ msgstr "" msgid "Total Devices" msgstr "" +#: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 +#: app/control-panes/resources/resources.html:1 +msgid "translate" +msgstr "" + #: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 #: app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected.html:1 msgid "Try to reconnect" @@ -1378,6 +1351,10 @@ msgstr "" msgid "Unknown reason." msgstr "" +#: app/control-panes/automation/device-settings/device-settings.html:6 +msgid "Unlock Rotation" +msgstr "" + #: app/components/stf/device/device-info-filter/index.js:51 msgid "Unspecified Failure" msgstr "" @@ -1434,10 +1411,6 @@ msgstr "" msgid "Using Fallback" msgstr "" -#: app/control-panes/resources/resources.html:1 -msgid "Value" -msgstr "" - #: app/control-panes/info/info.html:1 msgid "Version" msgstr "" @@ -1476,6 +1449,7 @@ msgstr "" #: app/components/stf/device/device-info-filter/index.js:105 #: app/components/stf/device/device-info-filter/index.js:97 +#: app/control-panes/automation/device-settings/device-settings.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "WiFi" msgstr "" diff --git a/res/common/lang/po/stf.ru_RU.po b/res/common/lang/po/stf.ru_RU.po index b293b98a..5622c264 100644 --- a/res/common/lang/po/stf.ru_RU.po +++ b/res/common/lang/po/stf.ru_RU.po @@ -4,9 +4,9 @@ msgid "" msgstr "" "Project-Id-Version: STF\n" -"PO-Revision-Date: 2015-07-28 09:05+0000\n" +"PO-Revision-Date: 2015-09-10 06:06+0000\n" "Last-Translator: takeshimiya \n" -"Language-Team: Russian (Russia) (http://www.transifex.com/projects/p/stf/language/ru_RU/)\n" +"Language-Team: Russian (Russia) (http://www.transifex.com/openstf/stf/language/ru_RU/)\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru_RU\n" @@ -75,7 +75,6 @@ msgid "Add" msgstr "Добавить" #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Add ADB Key" msgstr "Добавить ADB ключ" @@ -108,10 +107,6 @@ msgstr "Расширенный ввод" msgid "Airplane Mode" msgstr "Режим В самолёте" -#: app/control-panes/logs/logs.html:20 -msgid "App" -msgstr "Приложение" - #: app/control-panes/automation/store-account/store-account.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "App Store" @@ -208,18 +203,14 @@ msgstr "Категория" msgid "Charging" msgstr "Заряжается" -#: app/menu/menu.html:3 -msgid "Chat" -msgstr "Чат" - #: auth/ldap/scripts/signin/signin.html:1 #: auth/mock/scripts/signin/signin.html:1 msgid "Check errors below" msgstr "Проверьте сообщения об ошибках ниже" #: app/components/stf/common-ui/clear-button/clear-button.html:1 -#: app/control-panes/advanced/run-js/run-js.html:2 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/advanced/run-js/run-js.html:1 +#: app/control-panes/logs/logs.html:1 msgid "Clear" msgstr "Очистить" @@ -227,10 +218,6 @@ msgstr "Очистить" msgid "Clipboard" msgstr "Буфер обмена" -#: app/control-panes/advanced/input/input.html:5 -msgid "Close" -msgstr "Закрыть" - #: app/components/stf/device/device-info-filter/index.js:46 msgid "Cold" msgstr "Холодно" @@ -266,23 +253,23 @@ msgstr "Процессор" msgid "Customize" msgstr "Настроить" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Center" msgstr "Центральная кнопка D-pad" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Down" msgstr "Вниз" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Left" msgstr "Влево" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Right" msgstr "Вправо" -#: app/control-panes/advanced/input/input.html:41 +#: app/control-panes/advanced/input/input.html:1 msgid "D-pad Up" msgstr "Вверх" @@ -317,8 +304,7 @@ msgstr "Разработчик" #: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 #: app/components/stf/keys/add-adb-key/add-adb-key.html:1 #: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 -#: app/control-panes/inspect/inspect.html:3 -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/inspect/inspect.html:1 msgid "Device" msgstr "Устройство" @@ -385,10 +371,6 @@ msgstr "Перетащите файл для загрузки" msgid "Dummy" msgstr "Макет" -#: app/control-panes/advanced/input/input.html:7 -msgid "Eject" -msgstr "Извлечь" - #: app/settings/notifications/notifications.html:1 msgid "Enable notifications" msgstr "Включить уведомления" @@ -453,11 +435,11 @@ msgstr "Частота" msgid "Full" msgstr "Полный" -#: app/settings/settings-controller.js:10 +#: app/settings/settings-controller.js:5 msgid "General" msgstr "Общие" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 #: app/control-panes/resources/resources.html:1 msgid "Get" msgstr "Получить" @@ -475,7 +457,7 @@ msgid "Go Forward" msgstr "Вперёд" #: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:95 +#: app/control-panes/control-panes-hotkeys-controller.js:89 msgid "Go to Device List" msgstr "Открыть список устройств" @@ -495,7 +477,7 @@ msgstr "Состояние" msgid "Height" msgstr "Высота" -#: app/menu/menu.html:22 +#: app/menu/menu.html:1 msgid "Help" msgstr "Помощь" @@ -537,7 +519,7 @@ msgstr "Некорректные логин или пароль" msgid "Info" msgstr "Инфо" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspect Device" msgstr "Инспектировать устройство" @@ -545,7 +527,7 @@ msgstr "Инспектировать устройство" msgid "Inspecting is currently only supported in WebView" msgstr "Инспектирование пока поддерживается в WebView" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Inspector" msgstr "Инспекто" @@ -573,7 +555,7 @@ msgstr "Устанавливаем приложение..." msgid "Key" msgstr "Ключ" -#: app/settings/settings-controller.js:15 +#: app/settings/settings-controller.js:10 msgid "Keys" msgstr "Ключи" @@ -593,7 +575,7 @@ msgstr "Запустить приложение" msgid "Launching activity..." msgstr "Приложение запускается..." -#: app/control-panes/info/info.html:1 app/control-panes/logs/logs.html:20 +#: app/control-panes/info/info.html:1 app/control-panes/logs/logs.html:1 msgid "Level" msgstr "Уровень" @@ -605,6 +587,10 @@ msgstr "Локальные настройки" msgid "Location" msgstr "Местоположение" +#: app/control-panes/automation/device-settings/device-settings.html:7 +msgid "Lock Rotation" +msgstr "" + #: app/control-panes/control-panes-controller.js:44 msgid "Logs" msgstr "Журнал" @@ -617,6 +603,10 @@ msgstr "Обслуживание" msgid "Manage Apps" msgstr "Управление приложениями" +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Manner Mode" +msgstr "" + #: app/control-panes/info/info.html:1 #: app/device-list/column/device-column-service.js:165 msgid "Manufacturer" @@ -664,8 +654,6 @@ msgid "More about Access Tokens" msgstr "Подробнее о ключах доступа" #: app/settings/keys/adb-keys/adb-keys.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:10 -#: app/settings/keys/adb-keys/adb-keys.html:44 msgid "More about ADB Keys" msgstr "Подробнее о ADB ключах" @@ -747,7 +735,7 @@ msgstr "Не заряжается" msgid "Notes" msgstr "Записи" -#: app/control-panes/inspect/inspect.html:3 +#: app/control-panes/inspect/inspect.html:1 msgid "Nothing to inspect" msgstr "" @@ -802,10 +790,6 @@ msgstr "Пароль" msgid "Path" msgstr "Путь" -#: app/control-panes/advanced/input/input.html:3 -msgid "Pause" -msgstr "Пауза" - #: app/device-list/column/device-column-service.js:184 msgid "Phone" msgstr "Телефон" @@ -822,7 +806,7 @@ msgstr "" msgid "Physical Device" msgstr "Физическое устройство" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "PID" msgstr "" @@ -834,10 +818,6 @@ msgstr "Место" msgid "Platform" msgstr "Платформа" -#: app/control-panes/advanced/input/input.html:1 -msgid "Play" -msgstr "Играть" - #: app/control-panes/advanced/input/input.html:1 msgid "Play/Pause" msgstr "Играть/Пауза" @@ -895,15 +875,15 @@ msgstr "Источник энергии" msgid "Preparing" msgstr "Подготовка" -#: app/control-panes/control-panes-hotkeys-controller.js:110 +#: app/control-panes/control-panes-hotkeys-controller.js:104 msgid "Press Back button" msgstr "Нажмите кнопку Назад" -#: app/control-panes/control-panes-hotkeys-controller.js:109 +#: app/control-panes/control-panes-hotkeys-controller.js:103 msgid "Press Home button" msgstr "Нажмите кнопку Домой" -#: app/control-panes/control-panes-hotkeys-controller.js:108 +#: app/control-panes/control-panes-hotkeys-controller.js:102 msgid "Press Menu button" msgstr "Нажмите кнопку Меню" @@ -937,10 +917,6 @@ msgstr "Готово" msgid "Reconnected successfully." msgstr "Переподключился успешно." -#: app/control-panes/advanced/input/input.html:9 -msgid "Record" -msgstr "Запись" - #: app/components/stf/common-ui/refresh-page/refresh-page.html:1 msgid "Refresh" msgstr "Обновить" @@ -960,7 +936,7 @@ msgid "Remote debug" msgstr "Удалённая отладка" #: app/settings/keys/access-tokens/access-tokens.html:1 -#: app/settings/keys/adb-keys/adb-keys.html:74 +#: app/settings/keys/adb-keys/adb-keys.html:1 msgid "Remove" msgstr "Удалить" @@ -1002,27 +978,19 @@ msgid "ROM" msgstr "" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:98 +#: app/control-panes/control-panes-hotkeys-controller.js:92 msgid "Rotate Left" msgstr "Повернуть влево" #: app/components/stf/device-context-menu/device-context-menu.html:1 -#: app/control-panes/control-panes-hotkeys-controller.js:99 +#: app/control-panes/control-panes-hotkeys-controller.js:93 msgid "Rotate Right" msgstr "Повернуть вправо" -#: app/control-panes/advanced/run-js/run-js.html:18 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run" msgstr "Выполнить" -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run command" -msgstr "Выполнить команду" - -#: app/control-panes/dashboard/shell/shell.html:1 -msgid "Run Command" -msgstr "Выполнить команду" - #: app/control-panes/advanced/run-js/run-js.html:1 msgid "Run JavaScript" msgstr "Выполнить JavaScript" @@ -1045,7 +1013,7 @@ msgstr "Выполните эту команду, чтобы скопирова msgid "Save ScreenShot" msgstr "Сохранить скриншот" -#: app/control-panes/advanced/run-js/run-js.html:2 +#: app/control-panes/advanced/run-js/run-js.html:1 msgid "Save..." msgstr "Сохранить.." @@ -1078,7 +1046,7 @@ msgstr "Поиск" msgid "Secure" msgstr "Безопасно" -#: app/control-panes/control-panes-hotkeys-controller.js:97 +#: app/control-panes/control-panes-hotkeys-controller.js:91 msgid "Selects Next IME" msgstr "" @@ -1144,7 +1112,7 @@ msgstr "Кто-то утащил ваше устройство" msgid "Special Keys" msgstr "Специальные кнопки" -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Start/Stop Logging" msgstr "Начать/Остановить журналирование" @@ -1154,7 +1122,7 @@ msgid "Status" msgstr "Статус" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/logs/logs.html:19 +#: app/control-panes/logs/logs.html:1 msgid "Stop" msgstr "Стоп" @@ -1172,11 +1140,10 @@ msgid "Sub Type" msgstr "Подтип" #: app/control-panes/advanced/input/input.html:1 -#: app/control-panes/advanced/input/input.html:41 msgid "Switch Charset" msgstr "Переключить кодировку" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Tag" msgstr "" @@ -1192,7 +1159,7 @@ msgstr "Сделать снимок экрана" msgid "Temperature" msgstr "Температура" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Text" msgstr "Текст" @@ -1372,11 +1339,11 @@ msgstr "Не удалось установить пакет, так как да msgid "The URI passed in is invalid." msgstr "Невалидный URI." -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "TID" msgstr "TID" -#: app/control-panes/logs/logs.html:20 +#: app/control-panes/logs/logs.html:1 msgid "Time" msgstr "Время" @@ -1384,7 +1351,7 @@ msgstr "Время" msgid "Tip:" msgstr "Подсказка:" -#: app/control-panes/control-panes-hotkeys-controller.js:113 +#: app/control-panes/control-panes-hotkeys-controller.js:107 msgid "Toggle Web/Native" msgstr "Переключить Web/Native" @@ -1419,6 +1386,10 @@ msgstr "Неизвестный" msgid "Unknown reason." msgstr "Неизвестная причина." +#: app/control-panes/automation/device-settings/device-settings.html:6 +msgid "Unlock Rotation" +msgstr "" + #: app/components/stf/device/device-info-filter/index.js:51 msgid "Unspecified Failure" msgstr "" @@ -1517,6 +1488,7 @@ msgstr "Ширина" #: app/components/stf/device/device-info-filter/index.js:105 #: app/components/stf/device/device-info-filter/index.js:97 +#: app/control-panes/automation/device-settings/device-settings.html:1 #: app/control-panes/dashboard/apps/apps.html:1 msgid "WiFi" msgstr "WiFi" diff --git a/res/common/lang/po/stf.zh.po b/res/common/lang/po/stf.zh.po new file mode 100644 index 00000000..865e3356 --- /dev/null +++ b/res/common/lang/po/stf.zh.po @@ -0,0 +1,1559 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: zh\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.8.4\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: app/components/stf/device/device-info-filter/index.js:119 +#: app/components/stf/device/device-info-filter/index.js:52 +#: app/components/stf/device/device-info-filter/index.js:61 +#: app/components/stf/device/device-info-filter/index.js:71 +msgid "-" +msgstr "" + +#: app/components/stf/common-ui/modals/version-update/version-update.html:1 +msgid "A new version of STF is available" +msgstr "" + +#: app/components/stf/install/install-error-filter.js:26 +msgid "A package is already installed with the same name." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:30 +msgid "" +"A previously installed package of the same name has a different signature " +"than the new package (and the old package's data was not removed)." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:50 +msgid "A secure container mount point couldn't be accessed on external media." +msgstr "" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:178 +msgid "ABI" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:58 +msgid "AC" +msgstr "" + +#: app/settings/keys/adb-keys/adb-keys.html:1 +msgid "ADB Keys" +msgstr "ADB密钥" + +#: app/settings/keys/access-tokens/access-tokens.html:1 +msgid "Access Tokens" +msgstr "访问令牌" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Account" +msgstr "帐户" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Action" +msgstr "" + +#: app/control-panes/automation/store-account/store-account.html:1 +msgid "Actions" +msgstr "" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Activity" +msgstr "" + +#: app/control-panes/resources/resources.html:1 +msgid "Add" +msgstr "添加" + +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Add ADB Key" +msgstr "增加ADB密钥" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Add Key" +msgstr "增加密钥" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +msgid "Add the following ADB Key to STF?" +msgstr "" + +#: app/layout/layout-controller.js:7 +msgid "Admin mode has been disabled." +msgstr "" + +#: app/layout/layout-controller.js:6 +msgid "Admin mode has been enabled." +msgstr "" + +#: app/control-panes/control-panes-controller.js:20 +msgid "Advanced" +msgstr "高级" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Advanced Input" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Airplane Mode" +msgstr "飞行模式" + +#: app/control-panes/dashboard/h12/h12.html:1 +msgid "App Info" +msgstr "应用信息" + +#: app/control-panes/automation/store-account/store-account.html:1 +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "App Store" +msgstr "应用商店" + +#: app/control-panes/dashboard/install/install.html:1 +msgid "App Upload" +msgstr "应用安装" + +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "Apps" +msgstr "" + +#: app/control-panes/advanced/maintenance/maintenance-controller.js:9 +msgid "Are you sure you want to reboot this device?" +msgstr "确定需要重启该设备吗?" + +#: app/control-panes/control-panes-controller.js:14 +msgid "Automation" +msgstr "自动化" + +#: app/components/stf/device/device-info-filter/index.js:28 +msgid "Available" +msgstr "可用" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +#: app/control-panes/device-control/device-control.html:1 +msgid "Back" +msgstr "返回" + +#: app/control-panes/info/info.html:1 +msgid "Battery" +msgstr "电池" + +#: app/device-list/column/device-column-service.js:202 +msgid "Battery Health" +msgstr "电池健康状态" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:226 +msgid "Battery Level" +msgstr "电池电量" + +#: app/device-list/column/device-column-service.js:210 +msgid "Battery Source" +msgstr "供电" + +#: app/device-list/column/device-column-service.js:218 +msgid "Battery Status" +msgstr "电池状态" + +#: app/device-list/column/device-column-service.js:239 +msgid "Battery Temp" +msgstr "电池温度" + +#: app/control-panes/info/info.html:1 +msgid "Bettery Status" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:89 +msgid "Bluetooth" +msgstr "蓝牙" + +#: app/device-list/column/device-column-service.js:153 +msgid "Browser" +msgstr "浏览器" + +#: app/components/stf/device/device-info-filter/index.js:12 +#: app/components/stf/device/device-info-filter/index.js:27 +msgid "Busy" +msgstr "使用中" + +#: app/device-list/stats/device-list-stats.html:1 +msgid "Busy Devices" +msgstr "使用中的设备" + +#: app/control-panes/info/info.html:1 +#: app/control-panes/performance/cpu/cpu.html:1 +msgid "CPU" +msgstr "" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Camera" +msgstr "摄像机" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +msgid "Cancel" +msgstr "取消" + +#: app/components/stf/upload/upload-error-filter.js:6 +msgid "Cannot access specified URL" +msgstr "" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:43 +msgid "Carrier" +msgstr "运营商" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Category" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:67 +msgid "Charging" +msgstr "充电中" + +#: auth/ldap/scripts/signin/signin.html:1 +#: auth/mock/scripts/signin/signin.html:1 +msgid "Check errors below" +msgstr "" + +#: app/components/stf/common-ui/clear-button/clear-button.html:1 +#: app/control-panes/advanced/run-js/run-js.html:1 +#: app/control-panes/logs/logs.html:1 +msgid "Clear" +msgstr "清空" + +#: app/control-panes/dashboard/clipboard/clipboard.html:1 +msgid "Clipboard" +msgstr "剪贴板" + +#: app/components/stf/device/device-info-filter/index.js:46 +msgid "Cold" +msgstr "低温" + +#: app/components/stf/device/device-info-filter/index.js:21 +#: app/components/stf/device/device-info-filter/index.js:6 +#: app/control-panes/info/info.html:1 +msgid "Connected" +msgstr "已连接" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:20 +msgid "Connected successfully." +msgstr "" + +#: app/menu/menu.html:1 +msgid "Control" +msgstr "控制面板" + +#: app/control-panes/resources/resources.html:1 +msgid "Cookies" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Cores" +msgstr "核心数" + +#: app/device-list/device-list.html:1 +msgid "Customize" +msgstr "选择列" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Center" +msgstr "确定" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Down" +msgstr "下" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Left" +msgstr "左" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Right" +msgstr "右" + +#: app/control-panes/advanced/input/input.html:1 +msgid "D-pad Up" +msgstr "上" + +#: app/control-panes/control-panes-controller.js:35 +msgid "Dashboard" +msgstr "控制面板" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Data" +msgstr "数据" + +#: app/components/stf/device/device-info-filter/index.js:48 +msgid "Dead" +msgstr "损坏" + +#: app/control-panes/resources/resources.html:1 +msgid "Delete" +msgstr "删除" + +#: app/control-panes/info/info.html:1 +msgid "Density" +msgstr "像素密度" + +#: app/device-list/device-list.html:1 +msgid "Details" +msgstr "详情" + +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "Developer" +msgstr "开发者" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +#: app/control-panes/inspect/inspect.html:1 +msgid "Device" +msgstr "设备" + +#: app/control-panes/info/info.html:1 +msgid "Device Photo" +msgstr "" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Device Settings" +msgstr "设备设置" + +#: app/device-list/details/device-list-details-directive.js:36 +#: app/device-list/icons/device-list-icons-directive.js:123 +msgid "Device cannot get kicked from the group" +msgstr "设备无法停止使用" + +#: app/components/stf/device/device-info-filter/index.js:38 +msgid "Device is not present anymore for some reason." +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:39 +msgid "Device is present but offline." +msgstr "设备离线" + +#: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 +msgid "Device was disconnected" +msgstr "设备已断开" + +#: app/components/stf/device/device-info-filter/index.js:37 +msgid "Device was kicked by automatic timeout." +msgstr "" + +#: app/device-list/device-list.html:1 app/menu/menu.html:1 +msgid "Devices" +msgstr "设备" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Disable WiFi" +msgstr "关闭WiFi" + +#: app/components/stf/device/device-info-filter/index.js:68 +msgid "Discharging" +msgstr "" + +#: app/components/stf/common-ui/modals/socket-disconnected/socket-disconnected.html:1 +#: app/components/stf/device/device-info-filter/index.js:20 +#: app/components/stf/device/device-info-filter/index.js:5 +msgid "Disconnected" +msgstr "未连接" + +#: app/control-panes/info/info.html:1 +msgid "Display" +msgstr "显示" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Drop file to upload" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:90 +msgid "Dummy" +msgstr "" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Enable WiFi" +msgstr "打开WiFi" + +#: app/settings/notifications/notifications.html:1 +msgid "Enable notifications" +msgstr "打开通知" + +#: app/control-panes/info/info.html:1 +msgid "Encrypted" +msgstr "加密" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:31 +msgid "Error" +msgstr "" + +#: app/components/stf/control/control-service.js:129 +msgid "Error while getting data" +msgstr "" + +#: app/components/stf/socket/socket-state/socket-state-directive.js:35 +msgid "Error while reconnecting" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:91 +msgid "Ethernet" +msgstr "" + +#: app/control-panes/dashboard/h12/h12.html:1 +#: app/control-panes/dashboard/shell/shell.html:1 +msgid "Executes remote shell commands" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "FPS" +msgstr "" + +#: app/components/stf/upload/upload-error-filter.js:5 +msgid "Failed to download file" +msgstr "" + +#: app/control-panes/advanced/input/input.html:1 +msgid "Fast Forward" +msgstr "" + +#: app/components/stf/common-ui/filter-button/filter-button.html:1 +msgid "Filter" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Find Device" +msgstr "搜寻设备" + +#: app/components/stf/common-ui/modals/add-adb-key-modal/add-adb-key-modal.html:1 +msgid "Fingerprint" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Frequency" +msgstr "主频" + +#: app/components/stf/device/device-info-filter/index.js:69 +msgid "Full" +msgstr "满" + +#: app/settings/settings-controller.js:5 +msgid "General" +msgstr "通用" + +#: app/control-panes/logs/logs.html:1 +#: app/control-panes/resources/resources.html:1 +msgid "Get" +msgstr "" + +#: app/control-panes/dashboard/clipboard/clipboard.html:1 +msgid "Get clipboard contents" +msgstr "" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Go Back" +msgstr "" + +#: app/control-panes/dashboard/navigation/navigation.html:1 +msgid "Go Forward" +msgstr "" + +#: app/components/stf/common-ui/modals/fatal-message/fatal-message.html:1 +#: app/control-panes/control-panes-hotkeys-controller.js:89 +msgid "Go to Device List" +msgstr "" + +#: app/components/stf/device/device-info-filter/index.js:47 +msgid "Good" +msgstr "良好" + +#: app/control-panes/dashboard/h12/h12.html:1 +msgid "H12" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "Hardware" +msgstr "硬件" + +#: app/control-panes/info/info.html:1 +msgid "Health" +msgstr "健康状态" + +#: app/control-panes/info/info.html:1 +msgid "Height" +msgstr "高" + +#: app/menu/menu.html:1 +msgid "Help" +msgstr "帮助" + +#: app/control-panes/device-control/device-control.html:1 +msgid "Hide Screen" +msgstr "隐藏屏幕" + +#: app/components/stf/device-context-menu/device-context-menu.html:1 +#: app/control-panes/device-control/device-control.html:1 +msgid "Home" +msgstr "首页" + +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +msgid "Host" +msgstr "主机" + +#: app/control-panes/advanced/port-forwarding/port-forwarding.html:1 +msgid "Hostname" +msgstr "主机名" + +#: app/control-panes/info/info.html:1 +msgid "ICCID" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "ID" +msgstr "" + +#: app/control-panes/info/info.html:1 +msgid "IMEI" +msgstr "" + +#: auth/ldap/scripts/signin/signin.html:1 +#: auth/mock/scripts/signin/signin.html:1 +msgid "Incorrect login details" +msgstr "" + +#: app/control-panes/control-panes-controller.js:26 +msgid "Info" +msgstr "信息" + +#: app/control-panes/inspect/inspect.html:1 +msgid "Inspect Device" +msgstr "" + +#: app/control-panes/inspect/inspect.html:1 +msgid "Inspecting is currently only supported in WebView" +msgstr "" + +#: app/control-panes/inspect/inspect.html:1 +msgid "Inspector" +msgstr "" + +#: app/components/stf/install/install-error-filter.js:13 +msgid "Installation canceled by user." +msgstr "安装被用户中断" + +#: app/components/stf/install/install-error-filter.js:9 +msgid "Installation failed due to an unknown error." +msgstr "" + +#: app/components/stf/install/install-error-filter.js:7 +msgid "Installation succeeded." +msgstr "安装成功" + +#: app/components/stf/install/install-error-filter.js:11 +msgid "Installation timed out." +msgstr "安装过程超时" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Installing app..." +msgstr "安装应用中..." + +#: app/components/stf/keys/add-adb-key/add-adb-key.html:1 +msgid "Key" +msgstr "" + +#: app/settings/settings-controller.js:10 +msgid "Keys" +msgstr "密钥" + +#: app/control-panes/dashboard/h12/h12.html:1 +msgid "Kill App" +msgstr "杀掉应用" + +#: app/control-panes/device-control/device-control.html:1 +msgid "Landscape" +msgstr "" + +#: app/settings/general/language/language.html:1 +msgid "Language" +msgstr "语言" + +#: app/control-panes/dashboard/install/activities/activities.html:1 +msgid "Launch Activity" +msgstr "启动应用" + +#: app/control-panes/dashboard/install/install.html:7 +msgid "Launching activity..." +msgstr "启动应用中..." + +#: app/control-panes/logs/logs.html:1 +msgid "Level" +msgstr "" + +#: app/settings/general/local/local-settings.html:1 +msgid "Local Settings" +msgstr "本地设置" + +#: app/device-list/column/device-column-service.js:250 +msgid "Location" +msgstr "位置" + +#: app/control-panes/automation/device-settings/device-settings.html:7 +msgid "Lock Rotation" +msgstr "方向锁定" + +#: app/control-panes/control-panes-controller.js:41 +#: app/control-panes/control-panes-controller.js:50 +msgid "Logs" +msgstr "logcat日志" + +#: app/control-panes/advanced/maintenance/maintenance.html:1 +msgid "Maintenance" +msgstr "维护" + +#: app/control-panes/dashboard/apps/apps.html:1 +msgid "Manage Apps" +msgstr "管理应用" + +#: app/control-panes/automation/device-settings/device-settings.html:1 +msgid "Manner Mode" +msgstr "静音模式" + +#: app/control-panes/info/info.html:1 +#: app/device-list/column/device-column-service.js:165 +msgid "Manufacturer" +msgstr "制造商" + +#: app/control-panes/screenshots/screenshots.html:5 +msgid "" +"Max number of captured images"|translate}\" class=\"input-sm\"/>