├── .dockerignore
├── .editorconfig
├── .eslintrc.yml
├── .gitattributes
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE.md
├── .gitignore
├── .gitmodules
├── .gitsecret
├── keys
│ ├── pubring.kbx
│ ├── pubring.kbx~
│ └── trustdb.gpg
└── paths
│ └── mapping.cfg
├── .nvmrc
├── CHANGELOG.md
├── FAQ.md
├── LICENSE.md
├── Logo.svg
├── Makefile
├── README.md
├── SUPPORT.md
├── TODO.md
├── after-install.tpl
├── afterPack.js
├── afterSignHook.js
├── assets
├── dmg
│ ├── background.png
│ ├── background.tiff
│ └── background@2x.png
├── entitlements.mac.plist
├── icon.icns
├── icon.ico
├── iconset
│ ├── 1024x1024.png
│ ├── 128x128.png
│ ├── 144x144.png
│ ├── 16x16.png
│ ├── 24x24.png
│ ├── 256x256.png
│ ├── 32x32.png
│ ├── 48x48.png
│ ├── 512x512.png
│ ├── 64x64.png
│ └── 96x96.png
└── install-spinner.gif
├── build.sh
├── build_linux.sh
├── dev-app-update.yml
├── dictionary.txt
├── docs
├── ARCHITECTURE.md
├── COMMIT-GUIDELINES.md
├── CONTRIBUTING.md
├── FAQ.md
├── MAINTAINERS.md
├── MANUAL-TESTING.md
├── PUBLISHING.md
├── SUPPORT.md
└── USER-DOCUMENTATION.md
├── electron-builder.json
├── icon.png
├── lib
├── gui
│ ├── app
│ │ ├── app.ts
│ │ ├── components
│ │ │ ├── drive-selector
│ │ │ │ └── drive-selector.tsx
│ │ │ ├── drive-status-warning-modal
│ │ │ │ └── drive-status-warning-modal.tsx
│ │ │ ├── finish
│ │ │ │ └── finish.tsx
│ │ │ ├── flash-another
│ │ │ │ └── flash-another.tsx
│ │ │ ├── flash-results
│ │ │ │ └── flash-results.tsx
│ │ │ ├── progress-button
│ │ │ │ └── progress-button.tsx
│ │ │ ├── reduced-flashing-infos
│ │ │ │ └── reduced-flashing-infos.tsx
│ │ │ ├── safe-webview
│ │ │ │ └── safe-webview.tsx
│ │ │ ├── settings
│ │ │ │ └── settings.tsx
│ │ │ ├── source-selector
│ │ │ │ └── source-selector.tsx
│ │ │ ├── svg-icon
│ │ │ │ └── svg-icon.tsx
│ │ │ └── target-selector
│ │ │ │ ├── target-selector-button.tsx
│ │ │ │ └── target-selector.tsx
│ │ ├── css
│ │ │ ├── fonts
│ │ │ │ ├── SourceSansPro-Regular.ttf
│ │ │ │ └── SourceSansPro-SemiBold.ttf
│ │ │ └── main.css
│ │ ├── i18n.ts
│ │ ├── i18n
│ │ │ ├── README.md
│ │ │ ├── en.ts
│ │ │ ├── zh-CN.ts
│ │ │ └── zh-TW.ts
│ │ ├── index.css
│ │ ├── index.dev.html
│ │ ├── index.html
│ │ ├── models
│ │ │ ├── available-drives.ts
│ │ │ ├── flash-state.ts
│ │ │ ├── leds.ts
│ │ │ ├── selection-state.ts
│ │ │ ├── settings.ts
│ │ │ └── store.ts
│ │ ├── modules
│ │ │ ├── analytics.ts
│ │ │ ├── drive-scanner.ts
│ │ │ ├── exception-reporter.ts
│ │ │ ├── image-writer.ts
│ │ │ └── progress-status.ts
│ │ ├── os
│ │ │ ├── dialog.ts
│ │ │ ├── notification.ts
│ │ │ ├── open-external
│ │ │ │ └── services
│ │ │ │ │ └── open-external.ts
│ │ │ ├── open-internal-remote
│ │ │ │ └── services
│ │ │ │ │ └── open-internal-remote.ts
│ │ │ ├── open-internal
│ │ │ │ └── services
│ │ │ │ │ └── open-internal.ts
│ │ │ ├── window-progress.ts
│ │ │ └── windows-network-drives.ts
│ │ ├── pages
│ │ │ └── main
│ │ │ │ ├── Flash.tsx
│ │ │ │ └── MainPage.tsx
│ │ ├── renderer.ts
│ │ ├── styled-components.tsx
│ │ ├── theme.ts
│ │ └── utils
│ │ │ ├── etcher-pro-specific.ts
│ │ │ └── middle-ellipsis.ts
│ ├── assets
│ │ ├── balena.svg
│ │ ├── drive.svg
│ │ ├── etcher.svg
│ │ ├── flash.svg
│ │ ├── icon.ico
│ │ ├── icon48.png
│ │ ├── icon64.png
│ │ ├── image.svg
│ │ ├── loading.svg
│ │ ├── love.svg
│ │ ├── raspberrypi.svg
│ │ ├── resin.svg
│ │ ├── src.svg
│ │ ├── tgt.svg
│ │ └── warning.svg
│ ├── etcher.ts
│ ├── menu.ts
│ └── modules
│ │ └── child-writer.ts
└── shared
│ ├── catalina-sudo
│ ├── sudo-askpass.osascript-en.js
│ ├── sudo-askpass.osascript-zh.js
│ └── sudo.ts
│ ├── drive-constraints.ts
│ ├── errors.ts
│ ├── exit-codes.ts
│ ├── messages.ts
│ ├── permissions.ts
│ ├── supported-formats.ts
│ ├── units.ts
│ └── utils.ts
├── npm-shrinkwrap.json
├── package.json
├── promo.png
├── repo.yml
├── requirements.txt
├── scripts
├── ci
│ └── ensure-all-file-extensions-in-gitattributes.sh
└── clean-shrinkwrap.ts
├── secrets
├── APPLE_SIGNING.p12.secret
├── APPLE_SIGNING_PASSWORD.txt.secret
├── WINDOWS_SIGNING.pfx.secret
├── WINDOWS_SIGNING_PASSWORD.txt.secret
└── XCODE_APP_LOADER_PASSWORD.txt.secret
├── tests
├── .eslintrc.yml
├── data
│ └── wmic-output.txt
├── gui
│ ├── allow-renderer-process-reuse.ts
│ ├── models
│ │ ├── available-drives.spec.ts
│ │ ├── flash-state.spec.ts
│ │ ├── selection-state.spec.ts
│ │ └── settings.spec.ts
│ ├── modules
│ │ ├── child-writer.spec.ts
│ │ ├── image-writer.spec.ts
│ │ └── progress-status.spec.ts
│ ├── os
│ │ ├── window-progress.spec.ts
│ │ └── windows-network-drives.spec.ts
│ ├── utils
│ │ └── middle-ellipsis.spec.ts
│ └── window-config.json
├── shared
│ ├── drive-constraints.spec.ts
│ ├── errors.spec.ts
│ ├── messages.spec.ts
│ ├── permissions.spec.ts
│ ├── supported-formats.spec.ts
│ ├── units.spec.ts
│ └── utils.spec.ts
└── spectron
│ └── runner.spec.ts
├── tsconfig.json
├── tsconfig.webpack.json
├── tslint.json
├── typings
├── omit-deep-lodash
│ └── index.d.ts
├── path-is-inside
│ └── index.d.ts
├── pnp-webpack-plugin
│ └── index.d.ts
├── resin-corvus
│ └── index.d.ts
├── simple-progress-webpack-plugin
│ └── index.d.ts
├── sudo-prompt
│ └── index.d.ts
└── svg
│ └── index.d.ts
├── webpack.config.ts
├── webpack.dev.config.bak
└── webpack.dev.config.ts
/.dockerignore:
--------------------------------------------------------------------------------
1 | *
2 | !requirements.txt
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [Makefile]
15 | indent_style = tab
16 |
17 | [*.ts]
18 | indent_style = tab
19 |
20 | [*.tsx]
21 | indent_style = tab
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # default
2 | * text
3 |
4 | # Javascript files must retain LF line-endings (to keep eslint happy)
5 | *.js text eol=lf
6 | *.jsx text eol=lf
7 | *.ts text eol=lf
8 | *.tsx text eol=lf
9 | # CSS and SCSS files must retain LF line-endings (to keep ensure-staged-sass.sh happy)
10 | *.css text eol=lf
11 | *.scss text eol=lf
12 |
13 | # Text files
14 | Dockerfile* text
15 | .dockerignore text
16 | .editorconfig text
17 | etcher text
18 | .git* text
19 | *.html text
20 | *.json text eol=lf
21 | *.cpp text
22 | *.h text
23 | *.gyp text
24 | LICENSE text
25 | Makefile text
26 | *.md text
27 | *.sh text
28 | *.bat text
29 | *.svg text
30 | *.yml text
31 | *.patch text
32 | *.txt text
33 | *.tpl text
34 | CODEOWNERS text
35 | *.plist text
36 |
37 | # Binary files (no line-ending conversions)
38 | *.bz2 binary diff=hex
39 | *.gz binary diff=hex
40 | *.icns binary diff=hex
41 | *.ico binary diff=hex
42 | *.tiff binary diff=hex
43 | *.img binary diff=hex
44 | *.iso binary diff=hex
45 | *.png binary diff=hex
46 | *.bin binary diff=hex
47 | *.elf binary diff=hex
48 | *.xz binary diff=hex
49 | *.zip binary diff=hex
50 | *.dtb binary diff=hex
51 | *.dtbo binary diff=hex
52 | *.dat binary diff=hex
53 | *.bin binary diff=hex
54 | *.dmg binary diff=hex
55 | *.rpi-sdcard binary diff=hex
56 | *.wic binary diff=hex
57 | *.foo binary diff=hex
58 | *.eot binary diff=hex
59 | *.otf binary diff=hex
60 | *.woff binary diff=hex
61 | *.woff2 binary diff=hex
62 | *.ttf binary diff=hex
63 | xz-without-extension binary diff=hex
64 | wmic-output.txt binary diff=hex
65 |
66 | # gitsecret
67 | *.secret binary
68 | .gitsecret/** binary
69 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: ['Alex313031']
4 | custom: ['https://paypal.me/alex313031?country.x=US&locale.x=en_US']
5 | patreon: ThoriumDeveloper
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | - **Etcher version:**
2 | - **Operating system and architecture:**
3 | - **Image flashed:**
4 | - **What do you think should have happened:**
5 | - **What happened:**
6 | - **Do you see any meaningful error information in the DevTools?**
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | /logs
3 | *.log
4 | npm-debug.log*
5 | .eslintcache
6 | Thumbs.db
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | /lib-cov
15 |
16 | # Image stream output directory
17 | /tests/image-stream/output
18 |
19 | # Coverage directory used by tools like istanbul
20 | /coverage
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | /build
30 | /release/
31 |
32 | # Electron binaries
33 | /electron/
34 |
35 | # Generated files
36 | /generated
37 |
38 | # Dependency directory
39 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
40 | node_modules
41 |
42 | # TypeScript v1 declaration files
43 | typings/
44 | !/typings/
45 | !/lib/shared/typings
46 |
47 | # Compiled Etcher releases
48 | /dist
49 |
50 | #local development
51 | .yalc
52 | yarn.lock
53 | package-lock.json
54 |
55 | # Certificates
56 | *.spc
57 | *.pvk
58 | *.p12
59 | *.cer
60 | *.crt
61 | *.pem
62 |
63 | # OSX files
64 |
65 | .DS_Store
66 |
67 | # VSCode files
68 |
69 | .vscode
70 | .gitsecret/keys/random_seed
71 | !*.secret
72 | secrets/APPLE_SIGNING_PASSWORD.txt
73 | secrets/WINDOWS_SIGNING_PASSWORD.txt
74 | secrets/XCODE_APP_LOADER_PASSWORD.txt
75 | secrets/WINDOWS_SIGNING.pfx
76 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "scripts/resin"]
2 | path = scripts/resin
3 | url = https://github.com/balena-io/scripts.git
4 | branch = master
5 |
--------------------------------------------------------------------------------
/.gitsecret/keys/pubring.kbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/.gitsecret/keys/pubring.kbx
--------------------------------------------------------------------------------
/.gitsecret/keys/pubring.kbx~:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/.gitsecret/keys/pubring.kbx~
--------------------------------------------------------------------------------
/.gitsecret/keys/trustdb.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/.gitsecret/keys/trustdb.gpg
--------------------------------------------------------------------------------
/.gitsecret/paths/mapping.cfg:
--------------------------------------------------------------------------------
1 | secrets/APPLE_SIGNING_PASSWORD.txt:5c9cfeb1ea5142b547bc842cc6e0b4a932641ae9811ee47abe2c3953f2a4de5d
2 | secrets/WINDOWS_SIGNING_PASSWORD.txt:852e431628494f2559793c39cf09c34e9406dd79bb15b90c9f88194020470568
3 | secrets/XCODE_APP_LOADER_PASSWORD.txt:005eb9a3c7035c77232973c9355468fc396b94e62783fb8e6dce16bce95b94a1
4 | secrets/WINDOWS_SIGNING.pfx:929f401db38733ffc41572539de7c0d938023af51ed06c205a72a71c1f815714
5 | secrets/APPLE_SIGNING.p12:61abf7b4ff2eec76ce889d71bcdd568b99a6a719b4947ac20f03966265b0946a
6 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/gallium
2 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | ## Why is my drive not bootable?
2 |
3 | Etcher copies images to drives byte by byte, without doing any transformation to the final device, which means images that require special treatment to be made bootable, like Windows images, will not work out of the box. In these cases, the general advice is to use software specific to those kind of images, usually available from the image publishers themselves. You can find more information [here](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#why-is-my-drive-not-bootable).
4 |
5 | ## How can I configure persistent storage?
6 |
7 | Some programs, usually oriented at making GNU/Linux live USB drives, include an option to set persistent storage. This is currently not supported by Etcher, so if you require this functionality, we advise to fallback to [UNetbootin](https://unetbootin.github.io/).
8 |
9 | ## How do I flash Ubuntu ISOs
10 |
11 | Ubuntu images (and potentially some other related GNU/Linux distributions) have a peculiar format that allows the image to boot without any further modification from both CDs and USB drives.
12 | A consequence of this enhancement is that some programs, like parted get confused about the drive's format and partition table, printing warnings such as:
13 |
14 | > /dev/xxx contains GPT signatures, indicating that it has a GPT table. However, it does not have a valid fake msdos partition table, as it should. Perhaps it was corrupted -- possibly by a program that doesn't understand GPT partition tables. Or perhaps you deleted the GPT table, and are now using an msdos partition table. Is this a GPT partition table? Both the primary and backup GPT tables are corrupt. Try making a fresh table, and using Parted's rescue feature to recover partitions.
15 |
16 | > Warning: The driver descriptor says the physical block size is 2048 bytes, but Linux says it is 512 bytes.
17 |
18 | All these warnings are safe to ignore, and your drive should be able to boot without any problems.
19 | Refer to [the following message from Ubuntu's mailing list](https://lists.ubuntu.com/archives/ubuntu-devel/2011-June/033495.html) if you want to learn more.
20 |
21 | ## How do I run Etcher on Wayland?
22 |
23 | The XWayland Server provides backwards compatibility to run any X client on Wayland, including Etcher.
24 | This usually works out of the box on mainstream GNU/Linux distributions that properly support Wayland. If it doesn't, make sure the xwayland.so module is being loaded by declaring it in your [weston.ini](http://manpages.ubuntu.com/manpages/wily/man5/weston.ini.5.html):
25 |
26 | ```
27 | [core]
28 | modules=xwayland.so
29 | ```
30 |
31 | ## What are the runtime GNU/LINUX dependencies?
32 |
33 | [This entry](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#runtime-gnulinux-dependencies) aims to provide an up to date list of runtime dependencies needed to run Etcher on a GNU/Linux system.
34 |
35 | ## How can I recover the broken drive?
36 |
37 | Sometimes, things might go wrong, and you end up with a half-flashed drive that is unusable by your operating systems, and common graphical tools might even refuse to get it back to a normal state.
38 | To solve these kinds of problems, we've collected [a list of fail-proof methods](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#recovering-broken-drives) to completely erase your drive in major operating systems.
39 |
40 | ## I receive "No polkit authentication agent found" error in GNU/Linux
41 |
42 | Etcher requires an available [polkit authentication agent](https://wiki.archlinux.org/index.php/Polkit#Authentication_agents) in your system in order to show a secure password prompt dialog to perform elevation. Make sure you have one installed for the desktop environment of your choice.
43 |
44 | ## May I run Etcher in older macOS versions?
45 |
46 | Etcher GUI is based on the [Electron](http://electron.atom.io/) framework, [which only supports macOS 10.10 and newer versions](https://github.com/electron/electron/blob/master/docs/tutorial/support.md#supported-platforms).
47 |
--------------------------------------------------------------------------------
/Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # ---------------------------------------------------------------------
2 | # Build configuration
3 | # ---------------------------------------------------------------------
4 |
5 | RESIN_SCRIPTS ?= ./scripts/resin
6 | export NPM_VERSION ?= 8.19.4
7 | S3_BUCKET = artifacts.ci.balena-cloud.com
8 |
9 | # This directory will be completely deleted by the `clean` rule
10 | BUILD_DIRECTORY ?= dist
11 |
12 | BUILD_TEMPORARY_DIRECTORY = $(BUILD_DIRECTORY)/.tmp
13 |
14 | $(BUILD_DIRECTORY):
15 | mkdir $@
16 |
17 | $(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
18 | mkdir $@
19 |
20 | SHELL := /bin/bash
21 |
22 | # ---------------------------------------------------------------------
23 | # Operating system and architecture detection
24 | # ---------------------------------------------------------------------
25 |
26 | # http://stackoverflow.com/a/12099167
27 | ifeq ($(OS),Windows_NT)
28 | PLATFORM = win32
29 |
30 | ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
31 | HOST_ARCH = x64
32 | else
33 | ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
34 | HOST_ARCH = x64
35 | endif
36 | ifeq ($(PROCESSOR_ARCHITECTURE),x86)
37 | HOST_ARCH = x86
38 | endif
39 | endif
40 | else
41 | ifeq ($(shell uname -s),Linux)
42 | PLATFORM = linux
43 |
44 | ifeq ($(shell uname -m),x86_64)
45 | HOST_ARCH = x64
46 | endif
47 | ifneq ($(filter %86,$(shell uname -m)),)
48 | HOST_ARCH = x86
49 | endif
50 | ifeq ($(shell uname -m),armv7l)
51 | HOST_ARCH = armv7hf
52 | endif
53 | ifeq ($(shell uname -m),aarch64)
54 | HOST_ARCH = aarch64
55 | endif
56 | ifeq ($(shell uname -m),armv8)
57 | HOST_ARCH = aarch64
58 | endif
59 | ifeq ($(shell uname -m),arm64)
60 | HOST_ARCH = aarch64
61 | endif
62 | endif
63 | ifeq ($(shell uname -s),Darwin)
64 | PLATFORM = darwin
65 |
66 | ifeq ($(shell uname -m),x86_64)
67 | HOST_ARCH = x64
68 | endif
69 | ifeq ($(shell uname -m),arm64)
70 | HOST_ARCH = aarch64
71 | endif
72 | endif
73 | endif
74 |
75 | ifndef PLATFORM
76 | $(error We could not detect your host platform)
77 | endif
78 | ifndef HOST_ARCH
79 | $(error We could not detect your host architecture)
80 | endif
81 |
82 | # Default to host architecture. You can override by doing:
83 | #
84 | # make TARGET_ARCH=
85 | #
86 | TARGET_ARCH ?= $(HOST_ARCH)
87 |
88 | # ---------------------------------------------------------------------
89 | # Electron
90 | # ---------------------------------------------------------------------
91 | electron-develop:
92 | git submodule update --init && \
93 | npm ci && \
94 | npm run webpack
95 |
96 | electron-test:
97 | $(RESIN_SCRIPTS)/electron/test.sh \
98 | -b $(shell pwd) \
99 | -s $(PLATFORM)
100 |
101 | assets/dmg/background.tiff: assets/dmg/background.png assets/dmg/background@2x.png
102 | tiffutil -cathidpicheck $^ -out $@
103 |
104 | electron-build: assets/dmg/background.tiff | $(BUILD_TEMPORARY_DIRECTORY)
105 | $(RESIN_SCRIPTS)/electron/build.sh \
106 | -b $(shell pwd) \
107 | -r $(TARGET_ARCH) \
108 | -s $(PLATFORM) \
109 | -v production \
110 | -n $(BUILD_TEMPORARY_DIRECTORY)/npm
111 |
112 | # ---------------------------------------------------------------------
113 | # Phony targets
114 | # ---------------------------------------------------------------------
115 |
116 | TARGETS = \
117 | help \
118 | info \
119 | lint \
120 | test \
121 | clean \
122 | distclean \
123 | electron-develop \
124 | electron-test \
125 | electron-build
126 |
127 | .PHONY: $(TARGETS)
128 |
129 | lint:
130 | npm run lint
131 |
132 | test:
133 | npm run test
134 |
135 | help:
136 | @echo "Available targets: $(TARGETS)"
137 |
138 | info:
139 | @echo "Platform : $(PLATFORM)"
140 | @echo "Host arch : $(HOST_ARCH)"
141 | @echo "Target arch : $(TARGET_ARCH)"
142 |
143 | clean:
144 | rm -rf $(BUILD_DIRECTORY)
145 |
146 | distclean: clean
147 | rm -rf node_modules
148 | rm -rf dist
149 | rm -rf generated
150 | rm -rf $(BUILD_TEMPORARY_DIRECTORY)
151 |
152 | .DEFAULT_GOAL = help
153 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Etcher
2 |
3 | > Flash OS images to SD cards & USB drives, safely and easily.
4 |
5 | Etcher is a powerful OS image flasher built with web technologies to ensure
6 | flashing an SDCard or USB drive is a pleasant and safe experience. It protects
7 | you from accidentally writing to your hard-drives, ensures every byte of data
8 | was written correctly, and much more. It can also directly flash Raspberry Pi devices that support [USB device boot mode](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#usb-device-boot-mode).
9 |
10 | [](https://balena.io/etcher)
11 | [](https://github.com/balena-io/etcher/blob/master/LICENSE)
12 | [](https://forums.balena.io/c/etcher)
13 |
14 | ---
15 |
16 | [**Download**][etcher] | [**Support**][support] | [**Documentation**][user-documentation] | [**Contributing**][contributing] | [**Roadmap**][milestones]
17 |
18 | ## Supported Operating Systems
19 |
20 | - Linux (most distros)
21 | - macOS 10.10 (Yosemite) and later
22 | - Microsoft Windows 7 and later
23 |
24 | **Note**: Etcher will run on any platform officially supported by
25 | [Electron][electron]. Read more in their
26 | [documentation][electron-supported-platforms].
27 |
28 | ## Installers
29 |
30 | Refer to the [downloads page][etcher] for the latest pre-made
31 | installers for all supported operating systems.
32 |
33 | ## Packages
34 |
35 | #### Debian and Ubuntu based Package Repository (GNU/Linux x86/x64)
36 |
37 | Package for Debian and Ubuntu can be downloaded from the [Github release page](https://github.com/balena-io/etcher/releases/)
38 |
39 | ##### Install .deb file using apt
40 |
41 | ```sh
42 | sudo apt install ./balena-etcher_******_amd64.deb
43 | ```
44 |
45 | ##### Uninstall
46 |
47 | ```sh
48 | sudo apt remove balena-etcher
49 | ```
50 |
51 | #### Redhat (RHEL) and Fedora-based Package Repository (GNU/Linux x86/x64)
52 |
53 | ##### Yum
54 |
55 | Package for Fedora-based and Redhat can be downloaded from the [Github release page](https://github.com/balena-io/etcher/releases/)
56 |
57 | 1. Install using yum
58 |
59 | ```sh
60 | sudo yum localinstall balena-etcher-***.x86_64.rpm
61 | ```
62 |
63 | #### Arch/Manjaro Linux (GNU/Linux x64)
64 |
65 | Etcher is offered through the Arch User Repository and can be installed on both Manjaro and Arch systems. You can compile it from the source code in this repository using [`balena-etcher`](https://aur.archlinux.org/packages/balena-etcher/). The following example uses a common AUR helper to install the latest release:
66 |
67 | ```sh
68 | yay -S balena-etcher
69 | ```
70 |
71 | ##### Uninstall
72 |
73 | ```sh
74 | yay -R balena-etcher
75 | ```
76 |
77 | #### WinGet (Windows)
78 |
79 | This package is updated by [gh-action](https://github.com/vedantmgoyal2009/winget-releaser), and is kept up to date automatically.
80 |
81 | ```sh
82 | winget install balenaEtcher #or Balena.Etcher
83 | ```
84 |
85 | ##### Uninstall
86 |
87 | ```sh
88 | winget uninstall balenaEtcher
89 | ```
90 |
91 | #### Chocolatey (Windows)
92 |
93 | This package is maintained by [@majkinetor](https://github.com/majkinetor), and
94 | is kept up to date automatically.
95 |
96 | ```sh
97 | choco install etcher
98 | ```
99 |
100 | ##### Uninstall
101 |
102 | ```sh
103 | choco uninstall etcher
104 | ```
105 |
106 | ## Support
107 |
108 | If you're having any problem, please [raise an issue][newissue] on GitHub, and
109 | the balena.io team will be happy to help.
110 |
111 | ## License
112 |
113 | Etcher is free software and may be redistributed under the terms specified in
114 | the [license].
115 |
116 | [etcher]: https://balena.io/etcher
117 | [electron]: https://electronjs.org/
118 | [electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
119 | [support]: https://github.com/balena-io/etcher/blob/master/docs/SUPPORT.md
120 | [contributing]: https://github.com/balena-io/etcher/blob/master/docs/CONTRIBUTING.md
121 | [user-documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
122 | [milestones]: https://github.com/balena-io/etcher/milestones
123 | [newissue]: https://github.com/balena-io/etcher/issues/new
124 | [license]: https://github.com/balena-io/etcher/blob/master/LICENSE
125 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | Getting help with BalenaEtcher
2 | ===============================
3 |
4 | There are various ways to get support for Etcher if you experience an issue or
5 | have an idea you'd like to share with us.
6 |
7 | Documentation
8 | ------
9 |
10 | We have answers to a variety of frequently asked questions in the [user
11 | documentation][documentation] and also in the [FAQs][faq] on the Etcher website.
12 |
13 |
14 | Forums
15 | ------
16 |
17 | We have a [Discourse forum][discourse] which is open to everyone, so please
18 | come join us :). Drop us a line there and the balena.io staff and community
19 | users will be happy to assist. Your question might already be answered, so take
20 | a look at the existing threads before opening a new one!
21 |
22 | Make sure to mention the following information to help us provide better
23 | support:
24 |
25 | - The BalenaEtcher version you're running.
26 |
27 | - The operating system you're running Etcher in.
28 |
29 | - Relevant logging output, if any, from DevTools, which you can open by
30 | pressing `Ctrl+Shift+I` or `Cmd+Alt+I` depending on your platform.
31 |
32 | GitHub
33 | ------
34 |
35 | If you encounter an issue or have a suggestion, head on over to BalenaEtcher's [issue
36 | tracker][issues] and if there isn't a ticket covering it, [create
37 | one][new-issue].
38 |
39 | [discourse]: https://forums.balena.io/c/etcher
40 | [issues]: https://github.com/balena-io/etcher/issues
41 | [new-issue]: https://github.com/balena-io/etcher/issues/new
42 | [documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
43 | [faq]: https://etcher.io
44 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | - Make openInternal work for everything
2 | - Use builtin logger instead of electron-log, and make it output to console for everything
3 |
--------------------------------------------------------------------------------
/after-install.tpl:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Link to the binary
4 | # Must hardcode Etcher-ng directory; no variable available
5 | ln -sf '/opt/Etcher-ng/${executable}' '/usr/bin/${executable}'
6 |
7 | # SUID chrome-sandbox for Electron 5+
8 | chmod 4755 '/opt/Etcher-ng/chrome-sandbox' || true
9 |
10 | update-mime-database /usr/share/mime || true
11 | update-desktop-database /usr/share/applications || true
12 |
--------------------------------------------------------------------------------
/afterPack.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const cp = require('child_process')
4 | const fs = require('fs')
5 | const outdent = require('outdent')
6 | const path = require('path')
7 |
8 | exports.default = function(context) {
9 | if (context.packager.platform.name !== 'linux') {
10 | return
11 | }
12 | const scriptPath = path.join(context.appOutDir, context.packager.executableName)
13 | const binPath = scriptPath + '.bin'
14 | cp.execFileSync('mv', [scriptPath, binPath])
15 | fs.writeFileSync(
16 | scriptPath,
17 | outdent({trimTrailingNewline: false})`
18 | #!/bin/bash
19 |
20 | # Resolve symlinks. Warning, readlink -f doesn't work on MacOS/BSD
21 | script_dir="$(dirname "$(readlink -f "\${BASH_SOURCE[0]}")")"
22 |
23 | if [[ $EUID -ne 0 ]] || [[ $ELECTRON_RUN_AS_NODE ]]; then
24 | "\${script_dir}"/${context.packager.executableName}.bin "$@"
25 | else
26 | "\${script_dir}"/${context.packager.executableName}.bin "$@" --no-sandbox
27 | fi
28 | `
29 | )
30 | cp.execFileSync('chmod', ['+x', scriptPath])
31 | }
32 |
--------------------------------------------------------------------------------
/afterSignHook.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { notarize } = require('electron-notarize')
4 | const { ELECTRON_SKIP_NOTARIZATION } = process.env
5 |
6 | async function main(context) {
7 | const { electronPlatformName, appOutDir } = context
8 | if (electronPlatformName !== 'darwin' || ELECTRON_SKIP_NOTARIZATION === 'true') {
9 | return
10 | }
11 |
12 | const appName = context.packager.appInfo.productFilename
13 | const appleId = process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io'
14 | const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD
15 |
16 | // https://github.com/electron/notarize/blob/main/README.md
17 | await notarize({
18 | appBundleId: 'com.alex313031.etcher-ng',
19 | appPath: `${appOutDir}/${appName}.app`,
20 | appleId,
21 | appleIdPassword
22 | })
23 | }
24 |
25 | exports.default = main
26 |
--------------------------------------------------------------------------------
/assets/dmg/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/dmg/background.png
--------------------------------------------------------------------------------
/assets/dmg/background.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/dmg/background.tiff
--------------------------------------------------------------------------------
/assets/dmg/background@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/dmg/background@2x.png
--------------------------------------------------------------------------------
/assets/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-jit
6 |
7 | com.apple.security.cs.allow-unsigned-executable-memory
8 |
9 | com.apple.security.cs.allow-dyld-environment-variables
10 |
11 | com.apple.security.device.usb
12 |
13 | com.apple.security.files.user-selected.read-only
14 |
15 | com.apple.security.network.client
16 |
17 | com.apple.security.cs.disable-library-validation
18 |
19 | com.apple.security.get-task-allow
20 |
21 | com.apple.security.cs.disable-executable-page-protection
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/assets/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/icon.icns
--------------------------------------------------------------------------------
/assets/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/icon.ico
--------------------------------------------------------------------------------
/assets/iconset/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/1024x1024.png
--------------------------------------------------------------------------------
/assets/iconset/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/128x128.png
--------------------------------------------------------------------------------
/assets/iconset/144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/144x144.png
--------------------------------------------------------------------------------
/assets/iconset/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/16x16.png
--------------------------------------------------------------------------------
/assets/iconset/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/24x24.png
--------------------------------------------------------------------------------
/assets/iconset/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/256x256.png
--------------------------------------------------------------------------------
/assets/iconset/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/32x32.png
--------------------------------------------------------------------------------
/assets/iconset/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/48x48.png
--------------------------------------------------------------------------------
/assets/iconset/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/512x512.png
--------------------------------------------------------------------------------
/assets/iconset/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/64x64.png
--------------------------------------------------------------------------------
/assets/iconset/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/iconset/96x96.png
--------------------------------------------------------------------------------
/assets/install-spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/assets/install-spinner.gif
--------------------------------------------------------------------------------
/dev-app-update.yml:
--------------------------------------------------------------------------------
1 | owner: Alex313031
2 | repo: etcher-ng-win7
3 | provider: github
4 | updaterCacheDirName: etcher-ng-updater
5 |
--------------------------------------------------------------------------------
/dictionary.txt:
--------------------------------------------------------------------------------
1 | boolen->boolean
2 | aknowledge->acknowledge
3 | seleted->selected
4 | reming->remind
5 | locl->local
6 | subsribe->subscribe
7 | unsubsribe->unsubscribe
8 | calcluate->calculate
9 | dictionaty->dictionary
10 |
--------------------------------------------------------------------------------
/docs/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | Etcher Architecture
2 | ===================
3 |
4 | This document aims to serve as a high-level overview of how Etcher works,
5 | specially oriented for contributors who want to understand the big picture.
6 |
7 | Technologies
8 | ------------
9 |
10 | This is a non exhaustive list of the major frameworks, libraries, and other
11 | technologies used in Etcher that you should become familiar with:
12 |
13 | - [Electron][electron]
14 | - [NodeJS][nodejs]
15 | - [Redux][redux]
16 | - [ImmutableJS][immutablejs]
17 | - [Sass][sass]
18 | - [Mocha][mocha]
19 | - [JSDoc][jsdoc]
20 |
21 | Module architecture
22 | -------------------
23 |
24 | Instead of embedding all the functionality required to create a full-featured
25 | image writer as a monolithic project, we try to hard to follow the ["lego block
26 | approach"][lego-blocks].
27 |
28 | This has the advantage of allowing other applications to re-use logic we
29 | implemented for Etcher in their own project, even for things we didn't expect,
30 | which leads to users benefitting from what we've built, and we benefitting from
31 | user's bug reports, suggestions, etc, as an indirect way to make Etcher better.
32 |
33 | The fact that low-level details are scattered around many different modules can
34 | make it challenging for a new contributor to wrap their heads around the
35 | project as a whole, and get a clear high level view of how things work or where
36 | to submit their work or bug reports.
37 |
38 | These are the main Etcher components, in a nutshell:
39 |
40 | - [Drivelist](https://github.com/balena-io-modules/drivelist)
41 |
42 | As the name implies, this module's duty is to detect the connected drives
43 | uniformly in all major operating systems, along with valuable metadata, like if
44 | a drive is removable or not, to prevent users from trying to write an image to
45 | a system drive.
46 |
47 | - [Etcher](https://github.com/balena-io/etcher)
48 |
49 | This is the *"main repository"*, from which you're reading this from, which is
50 | basically the front-end and glue for all previously listed projects.
51 |
52 | Summary
53 | -------
54 |
55 | We always welcome contributions to Etcher as well as our documentation. If you
56 | want to give back, but feel that your knowledge on how Etcher works is not
57 | enough to tackle a bug report or feature request, use that as your advantage,
58 | since fresh eyes could help unveil things that we take for granted, but should
59 | be documented instead!
60 |
61 | [lego-blocks]: https://github.com/sindresorhus/ama/issues/10#issuecomment-117766328
62 | [exit-codes]: https://github.com/balena-io/etcher/blob/master/lib/shared/exit-codes.js
63 | [gui-dir]: https://github.com/balena-io/etcher/tree/master/lib/gui
64 | [electron]: http://electron.atom.io
65 | [nodejs]: https://nodejs.org
66 | [redux]: http://redux.js.org
67 | [immutablejs]: http://facebook.github.io/immutable-js/
68 | [sass]: http://sass-lang.com
69 | [mocha]: http://mochajs.org
70 | [jsdoc]: http://usejsdoc.org
71 |
--------------------------------------------------------------------------------
/docs/COMMIT-GUIDELINES.md:
--------------------------------------------------------------------------------
1 | Commit Guidelines
2 | =================
3 |
4 | We enforce certain rules on commits with the following goals in mind:
5 |
6 | - Be able to reliably auto-generate the `CHANGELOG.md` *without* any human
7 | intervention.
8 | - Be able to automatically and correctly increment the semver version number
9 | based on what was done since the last release.
10 | - Be able to get a quick overview of what happened to the project by glancing
11 | over the commit history.
12 | - Be able to automatically reference relevant changes from a dependency
13 | upgrade.
14 |
15 |
16 | Commit structure
17 | ----------------
18 |
19 | Each commit message needs to specify the semver-type. Which can be `patch|minor|major`.
20 | See the [Semantic Versioning][semver] specification for a more detailed explanation of the meaning of these types.
21 | See balena commit guidelines for more info about the whole commit structure.
22 |
23 | ```
24 | :
25 | ```
26 | or
27 | ```
28 |
29 |
30 |
31 |
32 | Change-Type:
33 | ```
34 |
35 | The subject should not contain more than 70 characters, including the type and
36 | scope, and the body should be wrapped at 72 characters.
37 |
38 | Tags
39 | ----
40 |
41 | ### `See: `/`Link: `
42 |
43 | This tag can be used to reference a resource that is relevant to the commit,
44 | and can be repeated multiple times in the same commit.
45 |
46 | Resource examples include:
47 |
48 | - A link to pull requests.
49 | - A link to a GitHub issue.
50 | - A link to a website providing useful information.
51 | - A commit hash.
52 |
53 | Its recommended that you avoid relative URLs, and that you include the whole
54 | commit hash to avoid any potential ambiguity issues in the future.
55 |
56 | If the commit type equals `upgrade`, this tag should be present, and should
57 | link to the CHANGELOG section of the dependency describing the changes
58 | introduced from the previously used version.
59 |
60 | Examples:
61 |
62 | ```
63 | See: https://github.com/xxx/yyy/
64 | See: 49d89b4acebd80838303b011d30517cd6229fdbe
65 | Link: https://github.com/xxx/yyy/issues/zzz
66 | ```
67 |
68 | ### `Closes: `/`Fixes: `
69 |
70 | This tag is used to make GitHub close the referenced issue automatically when
71 | the commit is merged.
72 |
73 | Its recommended that you provide the absolute URL to the GitHub issue rather
74 | than simply writing the ID prefixed by a hash tag for convenience when browsing
75 | the commit history outside the GitHub web interface.
76 |
77 | A commit can include multiple instances of this tag.
78 |
79 | Examples:
80 |
81 | ```
82 | Closes: https://github.com/balena-io/etcher/issues/XXX
83 | Fixes: https://github.com/balena-io/etcher/issues/XXX
84 | ```
85 |
86 | [semver]: http://semver.org
87 |
--------------------------------------------------------------------------------
/docs/FAQ.md:
--------------------------------------------------------------------------------
1 | ## Why is my drive not bootable?
2 |
3 | Etcher copies images to drives byte by byte, without doing any transformation to the final device, which means images that require special treatment to be made bootable, like Windows images, will not work out of the box. In these cases, the general advice is to use software specific to those kind of images, usually available from the image publishers themselves. You can find more information [here](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#why-is-my-drive-not-bootable).
4 |
5 | ## How can I configure persistent storage?
6 |
7 | Some programs, usually oriented at making GNU/Linux live USB drives, include an option to set persistent storage. This is currently not supported by Etcher, so if you require this functionality, we advise to fallback to [UNetbootin](https://unetbootin.github.io/).
8 |
9 | ## How do I flash Ubuntu ISOs
10 |
11 | Ubuntu images (and potentially some other related GNU/Linux distributions) have a peculiar format that allows the image to boot without any further modification from both CDs and USB drives.
12 | A consequence of this enhancement is that some programs, like parted get confused about the drive's format and partition table, printing warnings such as:
13 |
14 | > /dev/xxx contains GPT signatures, indicating that it has a GPT table. However, it does not have a valid fake msdos partition table, as it should. Perhaps it was corrupted -- possibly by a program that doesn't understand GPT partition tables. Or perhaps you deleted the GPT table, and are now using an msdos partition table. Is this a GPT partition table? Both the primary and backup GPT tables are corrupt. Try making a fresh table, and using Parted's rescue feature to recover partitions.
15 |
16 | > Warning: The driver descriptor says the physical block size is 2048 bytes, but Linux says it is 512 bytes.
17 |
18 | All these warnings are safe to ignore, and your drive should be able to boot without any problems.
19 | Refer to [the following message from Ubuntu's mailing list](https://lists.ubuntu.com/archives/ubuntu-devel/2011-June/033495.html) if you want to learn more.
20 |
21 | ## How do I run Etcher on Wayland?
22 |
23 | The XWayland Server provides backwards compatibility to run any X client on Wayland, including Etcher.
24 | This usually works out of the box on mainstream GNU/Linux distributions that properly support Wayland. If it doesn't, make sure the xwayland.so module is being loaded by declaring it in your [weston.ini](http://manpages.ubuntu.com/manpages/wily/man5/weston.ini.5.html):
25 |
26 | ```
27 | [core]
28 | modules=xwayland.so
29 | ```
30 |
31 | ## What are the runtime GNU/LINUX dependencies?
32 |
33 | [This entry](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#runtime-gnulinux-dependencies) aims to provide an up to date list of runtime dependencies needed to run Etcher on a GNU/Linux system.
34 |
35 | ## How can I recover the broken drive?
36 |
37 | Sometimes, things might go wrong, and you end up with a half-flashed drive that is unusable by your operating systems, and common graphical tools might even refuse to get it back to a normal state.
38 | To solve these kinds of problems, we've collected [a list of fail-proof methods](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#recovering-broken-drives) to completely erase your drive in major operating systems.
39 |
40 | ## I receive "No polkit authentication agent found" error in GNU/Linux
41 |
42 | Etcher requires an available [polkit authentication agent](https://wiki.archlinux.org/index.php/Polkit#Authentication_agents) in your system in order to show a secure password prompt dialog to perform elevation. Make sure you have one installed for the desktop environment of your choice.
43 |
44 | ## May I run Etcher in older macOS versions?
45 |
46 | Etcher GUI is based on the [Electron](http://electron.atom.io/) framework, [which only supports macOS 10.10 and newer versions](https://github.com/electron/electron/blob/master/docs/tutorial/support.md#supported-platforms).
47 |
48 | ## Can I use the Flash With Etcher button on my site?
49 |
50 | You can use the Flash with Etcher button on your site or blog, if you have an OS that you want your users to be able to easily flash using Etcher, add the following code where you want to button to be:
51 |
52 | `
`
--------------------------------------------------------------------------------
/docs/MAINTAINERS.md:
--------------------------------------------------------------------------------
1 | Maintaining Etcher
2 | ==================
3 |
4 | This document is meant to serve as a guide for maintainers to perform common tasks.
5 |
6 | Releasing
7 | ---------
8 |
9 | ### Release Types
10 |
11 | - **draft**: A continues snapshot of current master, made by the CI services
12 | - **pre-release** (default): A continues snapshot of current master, made by the CI services
13 | - **release**: Full releases
14 |
15 | Draft release is created from each PR, tagged with the branch name.
16 | All merged PR will generate a new tag/version as a *pre-release*.
17 | Mark the pre-release as final when it is necessary, then distribute the packages in alternative channels as necessary.
18 |
19 |
20 | #### Preparation
21 |
22 | - [Prepare the new version](#preparing-a-new-version)
23 | - [Generate build artifacts](#generating-binaries) (binaries, archives, etc.)
24 | - [Draft a release on GitHub](https://github.com/balena-io/etcher/releases)
25 | - Upload build artifacts to GitHub release draft
26 |
27 | #### Testing
28 |
29 | - Test the prepared release and build artifacts properly on **all supported operating systems** to prevent regressions that went uncaught by the CI tests (see [MANUAL-TESTING.md](MANUAL-TESTING.md))
30 | - If regressions or other issues arise, create issues on the repository for each one, and decide whether to fix them in this release (meaning repeating the process up until this point), or to follow up with a patch release
31 |
32 | #### Publishing
33 |
34 | - [Publish release draft on GitHub](https://github.com/balena-io/etcher/releases)
35 | - [Post release note to forums](https://forums.balena.io/c/etcher)
36 | - [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
37 | - [Update the website](https://github.com/balena-io/etcher-homepage)
38 | - Wait 2-3 hours for analytics (Sentry, Amplitude) to trickle in and check for elevated error rates, or regressions
39 | - If regressions arise; pull the release, and release a patched version, else:
40 | - [Upload deb & rpm packages to Cloudfront](#uploading-packages-to-cloudfront)
41 | - Post changelog with `#release-notes` tag on internal chat
42 | - If this release packs noteworthy major changes:
43 | - Write a blog post about it, and / or
44 | - Write about it to the Etcher mailing list
45 |
46 | ### Generating binaries
47 |
48 | **Environment**
49 |
50 | Make sure to set the analytics tokens when generating production release binaries:
51 |
52 | ```bash
53 | export ANALYTICS_SENTRY_TOKEN="xxxxxx"
54 | export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
55 | ```
56 |
57 | #### Linux
58 |
59 | ##### Clean dist folder
60 |
61 | **NOTE:** Make sure to adjust the path as necessary (here the Etcher repository has been cloned to `/home/$USER/code/etcher`)
62 |
63 | ##### Generating artifacts
64 |
65 | The artifacts are generated by the CI and published as draft-release or pre-release.
66 | `electron-builder` is used to create the packaged application.
67 |
68 | #### Mac OS
69 |
70 | **ATTENTION:** For production releases you'll need the code-signing key,
71 | and set `CSC_NAME` to generate signed binaries on Mac OS.
72 |
73 | #### Windows
74 |
75 | **ATTENTION:** For production releases you'll need the code-signing key,
76 | and set `CSC_LINK`, and `CSC_KEY_PASSWORD` to generate signed binaries on Windows.
77 |
78 | **NOTE:**
79 | - Keep in mind to also generate artifacts for x86, with `TARGET_ARCH=x86`.
80 |
81 |
82 | ### Uploading packages to Cloudfront
83 |
84 | Log in to cloudfront and upload the `rpm` and `deb` files.
85 |
86 | ### Dealing with a Problematic Release
87 |
88 | There can be times where a release is accidentally plagued with bugs. If you
89 | released a new version and notice the error rates are higher than normal, then
90 | revert the problematic release as soon as possible, until the bugs are fixed.
91 |
92 | You can revert a version by deleting its builds from the S3 bucket and Bintray.
93 | Refer to the `Makefile` for the up to date information about the S3 bucket
94 | where we push builds to, and get in touch with the balena.io operations team to
95 | get write access to it.
96 |
97 | The Etcher update notifier dialog and the website only show the a certain
98 | version if all the expected files have been uploaded to it, so deleting a
99 | single package or two is enough to bring down the whole version.
100 |
101 | Use the following command to delete files from S3:
102 |
103 | ```bash
104 | aws s3api delete-object --bucket --key
105 | ```
106 |
107 | The Bintray dashboard provides an easy way to delete a version's files.
108 |
109 |
110 | ### Submitting binaries to Symantec
111 |
112 | - [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/)
113 | - Fill out form:
114 | - **Select Submission Type:** "Provide a direct download URL"
115 | - **Name of the software being detected:** Etcher
116 | - **Name of detection given by Symantec product:** WS.Reputation.1
117 | - **Contact name:** Balena.io Ltd
118 | - **E-mail address:** hello@etcher.io
119 | - **Are you the creator or distributor of the software in question?** Yes
120 |
--------------------------------------------------------------------------------
/docs/MANUAL-TESTING.md:
--------------------------------------------------------------------------------
1 | Manual Testing
2 | ==============
3 |
4 | This document describes a high-level script of manual tests to check for. We
5 | should aim to replace items on this list with automated Spectron test cases.
6 |
7 | Image Selection
8 | ---------------
9 |
10 | - [ ] Cancel image selection dialog
11 | - [ ] Select an unbootable image (without a partition table), and expect a
12 | sensible warning
13 | - [ ] Attempt to select a ZIP archive with more than one image
14 | - [ ] Attempt to select a tar archive (with any compression method)
15 | - [ ] Change image selection
16 | - [ ] Select a Windows image, and expect a sensible warning
17 |
18 | Drive Selection
19 | ---------------
20 |
21 | - [ ] Open the drive selection modal
22 | - [ ] Switch drive selection
23 | - [ ] Insert a single drive, and expect auto-selection
24 | - [ ] Insert more than one drive, and don't expect auto-selection
25 | - [ ] Insert a locked SD Card and expect a warning
26 | - [ ] Insert a too small drive and expect a warning
27 | - [ ] Put an image into a drive and attempt to flash the image to the drive
28 | that contains it
29 | - [ ] Attempt to flash a compressed image (for which we can get the
30 | uncompressed size) into a drive that is big enough to hold the compressed
31 | image, but not big enough to hold the uncompressed version
32 | - [ ] Enable "Unsafe Mode" and attempt to select a system drive
33 | - [ ] Enable "Unsafe Mode", and if there is only one system drive (and no
34 | removable ones), don't expect autoselection
35 |
36 | Image Support
37 | -------------
38 |
39 | Run the following tests with and without validation enabled:
40 |
41 | - [ ] Flash an uncompressed image
42 | - [ ] Flash a Bzip2 image
43 | - [ ] Flash a XZ image
44 | - [ ] Flash a ZIP image
45 | - [ ] Flash a GZ image
46 | - [ ] Flash a DMG image
47 | - [ ] Flash an image whose size is not a multiple of 512 bytes
48 | - [ ] Flash a compressed image whose size is not a multiple of 512 bytes
49 | - [ ] Flash an archive whose image size is not a multiple of 512 bytes
50 | - [ ] Flash an archive image containing a logo
51 | - [ ] Flash an archive image containing a blockmap file
52 | - [ ] Flash an archive image containing a manifest metadata file
53 |
54 | Flashing Process
55 | ----------------
56 |
57 | - [ ] Unplug the drive during flash or validation
58 | - [ ] Click "Flash", cancel elevation dialog, and click "Flash" again
59 | - [ ] Start flashing an image, try to close Etcher, cancel the application
60 | close warning dialog, and check that Etcher continues to flash the image
61 |
62 | ### Child Writer
63 |
64 | - [ ] Kill the child writer process (i.e. with `SIGINT` or `SIGKILL`), and
65 | check that the UI reacts appropriately
66 | - [ ] Close the application while flashing using the window manager close icon
67 | - [ ] Close the application while flashing using the OS keyboard shortcut
68 | - [ ] Close the application from the terminal using Ctrl-C while flashing
69 | - [ ] Force kill the application (using a process monitor tool, etc)
70 |
71 | In all these cases, the child writer process should not remain alive. Note that
72 | in some systems you need to open your process monitor tool of choice with extra
73 | permissions to see the elevated child writer process.
74 |
75 | GUI
76 | ----
77 |
78 | - [ ] Close application from the terminal using Ctrl-C while the application is
79 | idle
80 | - [ ] Click footer links that take you to an external website
81 | - [ ] Attempt to change image or drive selection while flashing
82 | - [ ] Go to the settings page while flashing and come back
83 | - [ ] Flash consecutive images without closing the application
84 | - [ ] Remove the selected drive right before clicking "Flash"
85 | - [ ] Minimize the application
86 | - [ ] Start the application given no internet connection
87 |
88 | Success Banner
89 | --------------
90 |
91 | - [ ] Click an external link on the success banner (with and without internet
92 | connection)
93 |
94 | Elevation Prompt
95 | ----------------
96 |
97 | - [ ] Flash an image as `root`/administrator
98 | - [ ] Reject elevation prompt
99 | - [ ] Put incorrect elevation prompt password
100 | - [ ] Unplug the drive during elevation
101 |
102 | Unmounting
103 | ----------
104 |
105 | - [ ] Disable unmounting and flash an image
106 | - [ ] Flash an image with a file system that is readable by the host OS, and
107 | check that is unmounted correctly
108 |
109 | Analytics
110 | ---------
111 |
112 | - [ ] Disable analytics, open DevTools Network pane or a packet sniffer, and
113 | check that no request is sent
114 | - [ ] **Disable analytics, refresh application from DevTools (using Cmd-R or
115 | F5), and check that initial events are not sent to Amplitude**
116 |
--------------------------------------------------------------------------------
/docs/PUBLISHING.md:
--------------------------------------------------------------------------------
1 | Publishing Etcher
2 | =================
3 |
4 | This is a small guide to package and publish Etcher to all supported operating
5 | systems.
6 |
7 | Release Types
8 | -------------
9 |
10 | Etcher supports **pre-release** and **final** release types as does Github. Each is
11 | published to Github releases.
12 | The release version is generated automatically from the commit messasges.
13 |
14 | Signing
15 | -------
16 |
17 | ### OS X
18 |
19 | 1. Get our Apple Developer ID certificate for signing applications distributed
20 | outside the Mac App Store from the balena.io Apple account.
21 |
22 | 2. Install the Developer ID certificate to your Mac's Keychain by double
23 | clicking on the certificate file.
24 |
25 | The application will be signed automatically using this certificate when
26 | packaging for OS X.
27 |
28 | ### Windows
29 |
30 | 1. Get access to our code signing certificate and decryption key as a balena.io
31 | employee by asking for it from the relevant people.
32 |
33 | 2. Place the certificate in the root of the Etcher repository naming it
34 | `certificate.p12`.
35 |
36 | Packaging
37 | ---------
38 |
39 | The resulting installers will be saved to `dist/out`.
40 |
41 | Run the following commands on all platforms with the right arguments:
42 |
43 | ```sh
44 | ./node_modules/electron-builder build <...>
45 | ```
46 |
47 |
48 | Publishing to Cloudfront
49 | ---------------------
50 |
51 | We publish GNU/Linux Debian packages to [Cloudfront][cloudfront].
52 |
53 | Log in to cloudfront and upload the `rpm` and `deb` files.
54 |
55 | Publishing to Homebrew Cask
56 | ---------------------------
57 |
58 | 1. Update [`Casks/etcher.rb`][etcher-cask-file] with the new version and
59 | `sha256`
60 |
61 | 2. Send a PR with the changes above to
62 | [`caskroom/homebrew-cask`][homebrew-cask]
63 |
64 | Announcing
65 | ----------
66 |
67 | Post messages to the [Etcher forum][balena-forum-etcher] announcing the new version
68 | of Etcher, and including the relevant section of the Changelog.
69 |
70 | [aws-cli]: https://aws.amazon.com/cli
71 | [cloudfront]: https://cloudfront.com
72 | [etcher-cask-file]: https://github.com/caskroom/homebrew-cask/blob/master/Casks/balenaetcher.rb
73 | [homebrew-cask]: https://github.com/caskroom/homebrew-cask
74 | [balena-forum-etcher]: https://forums.balena.io/c/etcher
75 | [github-releases]: https://github.com/balena-io/etcher/releases
76 |
77 | Updating EFP / Success-Banner
78 | -----------------------------
79 | Etcher Featured Project is automatically run based on an algorithm which promoted projects from the balena marketplace which have been contributed by the community, the algorithm prioritises projects which give uses the best experience. Editing both EFP and the Etcher Success-Banner can only be done by someone from balena, instruction are on the [Etcher-EFP repo (private)](https://github.com/balena-io/etcher-efp)
80 |
--------------------------------------------------------------------------------
/docs/SUPPORT.md:
--------------------------------------------------------------------------------
1 | Getting help with BalenaEtcher
2 | ===============================
3 |
4 | There are various ways to get support for Etcher if you experience an issue or
5 | have an idea you'd like to share with us.
6 |
7 | Documentation
8 | ------
9 |
10 | We have answers to a variety of frequently asked questions in the [user
11 | documentation][documentation] and also in the [FAQs][faq] on the Etcher website.
12 |
13 |
14 | Forums
15 | ------
16 |
17 | We have a [Discourse forum][discourse] which is open to everyone, so please
18 | come join us :). Drop us a line there and the balena.io staff and community
19 | users will be happy to assist. Your question might already be answered, so take
20 | a look at the existing threads before opening a new one!
21 |
22 | Make sure to mention the following information to help us provide better
23 | support:
24 |
25 | - The BalenaEtcher version you're running.
26 |
27 | - The operating system you're running Etcher in.
28 |
29 | - Relevant logging output, if any, from DevTools, which you can open by
30 | pressing `Ctrl+Shift+I` or `Cmd+Alt+I` depending on your platform.
31 |
32 | GitHub
33 | ------
34 |
35 | If you encounter an issue or have a suggestion, head on over to BalenaEtcher's [issue
36 | tracker][issues] and if there isn't a ticket covering it, [create
37 | one][new-issue].
38 |
39 | [discourse]: https://forums.balena.io/c/etcher
40 | [issues]: https://github.com/balena-io/etcher/issues
41 | [new-issue]: https://github.com/balena-io/etcher/issues/new
42 | [documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
43 | [faq]: https://etcher.io
44 |
--------------------------------------------------------------------------------
/electron-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "electronVersion": "22.3.27",
3 | "asar": false,
4 | "npmRebuild": true,
5 | "nodeGypRebuild": false,
6 | "afterPack": "./afterPack.js",
7 | "afterSign": "./afterSignHook.js",
8 | "productName": "Etcher-ng",
9 | "appId": "com.alex313031.etcher-ng",
10 | "copyright": "Copyright © 2024 Alex313031",
11 | "generateUpdatesFilesForAllChannels": false,
12 | "directories": {
13 | "app": ".",
14 | "buildResources": "./assets",
15 | "output": "dist"
16 | },
17 | "files": [
18 | "./generated",
19 | "./lib/shared/catalina-sudo/sudo-askpass.osascript.js"
20 | ],
21 | "linux": {
22 | "target": [
23 | "zip",
24 | "deb",
25 | "rpm",
26 | "appimage"
27 | ],
28 | "icon": "./assets/iconset",
29 | "maintainer": "Alex313031",
30 | "vendor": "Alex313031",
31 | "synopsis": "Etcher-ng is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
32 | "category": "Utility;",
33 | "executableName": "etcher-ng",
34 | "artifactName": "etcher-ng_${version}_${arch}.${ext}",
35 | "desktop": {
36 | "Name": "Etcher-ng",
37 | "StartupWMClass": "etcher-ng",
38 | "Keywords": "etcher;flash;utils;",
39 | "Icon": "etcher-ng"
40 | }
41 | },
42 | "deb": {
43 | "priority": "optional",
44 | "compression": "bzip2",
45 | "afterInstall": "./after-install.tpl",
46 | "depends": [
47 | "gconf-service",
48 | "gconf2",
49 | "libasound2",
50 | "libatk1.0-0",
51 | "libc6",
52 | "libcairo2",
53 | "libcups2",
54 | "libdbus-1-3",
55 | "libexpat1",
56 | "libfontconfig1",
57 | "libfreetype6",
58 | "libgbm1",
59 | "libgcc1",
60 | "libgconf-2-4",
61 | "libgdk-pixbuf2.0-0",
62 | "libglib2.0-0",
63 | "libgtk-3-0",
64 | "libkrb5-3",
65 | "liblzma5",
66 | "libnotify4",
67 | "libnspr4",
68 | "libnss3",
69 | "libpango1.0-0 | libpango-1.0-0",
70 | "libstdc++6",
71 | "libx11-6",
72 | "libxcomposite1",
73 | "libxcursor1",
74 | "libxdamage1",
75 | "libxext6",
76 | "libxfixes3",
77 | "libxi6",
78 | "libxrandr2",
79 | "libxrender1",
80 | "libxss1",
81 | "libxtst6",
82 | "polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1"
83 | ]
84 | },
85 | "appImage": {
86 | "category": "Utility;"
87 | },
88 | "rpm": {
89 | "afterInstall": "./after-install.tpl",
90 | "depends": [ "util-linux" ]
91 | },
92 | "win": {
93 | "icon": "./assets/icon.ico",
94 | "target": [
95 | "zip",
96 | "portable",
97 | "nsis"
98 | ],
99 | "publisherName": "Alex313031",
100 | "executableName": "etcher_ng",
101 | "artifactName": "etcher-ng_${version}_${arch}.${ext}"
102 | },
103 | "nsis": {
104 | "shortcutName": "Etcher-ng",
105 | "artifactName": "etcher-ng_setup_${version}_${arch}.${ext}",
106 | "uninstallDisplayName": "Etcher-ng ${version}",
107 | "deleteAppDataOnUninstall": true,
108 | "oneClick": true,
109 | "license": "LICENSE.md"
110 | },
111 | "portable": {
112 | "artifactName": "etcher-ng_portable_${version}_${arch}.${ext}"
113 | },
114 | "mac": {
115 | "category": "public.app-category.developer-tools",
116 | "entitlements": "assets/entitlements.mac.plist",
117 | "entitlementsInherit": "assets/entitlements.mac.plist",
118 | "icon": "./assets/icon.icns",
119 | "darkModeSupport": true,
120 | "minimumSystemVersion": "10.12",
121 | "artifactName": "etcher-ng_macos_${version}_${arch}.${ext}",
122 | "target": [
123 | "dmg",
124 | "zip"
125 | ]
126 | },
127 | "dmg": {
128 | "background": "./assets/dmg/background.tiff",
129 | "icon": "./assets/icon.icns",
130 | "iconSize": "110",
131 | "window": {
132 | "width": 540,
133 | "height": 405
134 | },
135 | "contents": [
136 | { "x": 140, "y": 225 },
137 | {
138 | "x": 415, "y": 225,
139 | "type": "link",
140 | "path": "/Applications"
141 | }
142 | ],
143 | "artifactName": "etcher-ng_macos_${version}_${arch}.${ext}",
144 | },
145 | "protocols": {
146 | "name": "etcher",
147 | "schemes": [ "etcher" ]
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/icon.png
--------------------------------------------------------------------------------
/lib/gui/app/components/drive-status-warning-modal/drive-status-warning-modal.tsx:
--------------------------------------------------------------------------------
1 | import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
2 | import * as _ from 'lodash';
3 | import * as React from 'react';
4 | import { Badge, Flex, Txt, ModalProps } from 'rendition';
5 | import { Modal, ScrollableFlex } from '../../styled-components';
6 | import { middleEllipsis } from '../../utils/middle-ellipsis';
7 |
8 | import * as prettyBytes from 'pretty-bytes';
9 | import { DriveWithWarnings } from '../../pages/main/Flash';
10 | import * as i18next from 'i18next';
11 |
12 | const DriveStatusWarningModal = ({
13 | done,
14 | cancel,
15 | isSystem,
16 | drivesWithWarnings,
17 | }: ModalProps & {
18 | isSystem: boolean;
19 | drivesWithWarnings: DriveWithWarnings[];
20 | }) => {
21 | let warningSubtitle = i18next.t('drives.largeDriveWarning');
22 | let warningCta = i18next.t('drives.largeDriveWarningMsg');
23 |
24 | if (isSystem) {
25 | warningSubtitle = i18next.t('drives.systemDriveWarning');
26 | warningCta = i18next.t('drives.systemDriveWarningMsg');
27 | }
28 | return (
29 |
45 |
51 |
52 |
53 |
54 | {i18next.t('warning')}
55 |
56 |
57 | {warningSubtitle}
58 |
66 | {drivesWithWarnings.map((drive, i, array) => (
67 | <>
68 |
69 | {middleEllipsis(drive.description, 28)}{' '}
70 | {drive.size && prettyBytes(drive.size) + ' '}
71 | {drive.statuses[0].message}
72 |
73 | {i !== array.length - 1 ?
: null}
74 | >
75 | ))}
76 |
77 | {warningCta}
78 |
79 |
80 | );
81 | };
82 |
83 | export default DriveStatusWarningModal;
84 |
--------------------------------------------------------------------------------
/lib/gui/app/components/finish/finish.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as React from 'react';
18 | import { Flex } from 'rendition';
19 | import { v4 as uuidV4 } from 'uuid';
20 |
21 | import * as flashState from '../../models/flash-state';
22 | import * as selectionState from '../../models/selection-state';
23 | import * as settings from '../../models/settings';
24 | import { Actions, store } from '../../models/store';
25 | import * as analytics from '../../modules/analytics';
26 | import { FlashAnother } from '../flash-another/flash-another';
27 | import { FlashResults, FlashError } from '../flash-results/flash-results';
28 | import { SafeWebview } from '../safe-webview/safe-webview';
29 |
30 | function restart(goToMain: () => void) {
31 | selectionState.deselectAllDrives();
32 | analytics.logEvent('Restart');
33 |
34 | // Reset the flashing workflow uuid
35 | store.dispatch({
36 | type: Actions.SET_FLASHING_WORKFLOW_UUID,
37 | data: uuidV4(),
38 | });
39 |
40 | goToMain();
41 | }
42 |
43 | async function getSuccessBannerURL() {
44 | return (
45 | (await settings.get('successBannerURL')) ??
46 | 'https://thorium.rocks/etcher-ng/win7'
47 | );
48 | }
49 |
50 | function FinishPage({ goToMain }: { goToMain: () => void }) {
51 | const [webviewShowing, setWebviewShowing] = React.useState(false);
52 | const [successBannerURL, setSuccessBannerURL] = React.useState('');
53 | (async () => {
54 | setSuccessBannerURL(await getSuccessBannerURL());
55 | })();
56 | const flashResults = flashState.getFlashResults();
57 | const errors: FlashError[] = (
58 | store.getState().toJS().failedDeviceErrors || []
59 | ).map(([, error]: [string, FlashError]) => ({
60 | ...error,
61 | }));
62 | const { averageSpeed, blockmappedSize, bytesWritten, failed, size } =
63 | flashState.getFlashState();
64 | const {
65 | skip,
66 | results = {
67 | bytesWritten,
68 | sourceMetadata: {
69 | size,
70 | blockmappedSize,
71 | },
72 | averageFlashingSpeed: averageSpeed,
73 | devices: { failed, successful: 0 },
74 | },
75 | } = flashResults;
76 | return (
77 |
78 |
91 |
99 |
100 | {
102 | restart(goToMain);
103 | }}
104 | />
105 |
106 | {successBannerURL.length && (
107 |
119 | )}
120 |
121 | );
122 | }
123 |
124 | export default FinishPage;
125 |
--------------------------------------------------------------------------------
/lib/gui/app/components/flash-another/flash-another.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as React from 'react';
18 |
19 | import { BaseButton } from '../../styled-components';
20 | import * as i18next from 'i18next';
21 |
22 | export interface FlashAnotherProps {
23 | onClick: () => void;
24 | }
25 |
26 | export const FlashAnother = (props: FlashAnotherProps) => {
27 | return (
28 |
29 | {i18next.t('flash.another')}
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/lib/gui/app/components/progress-button/progress-button.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as React from 'react';
18 | import { Flex, Button, ProgressBar, Txt } from 'rendition';
19 | import { default as styled } from 'styled-components';
20 |
21 | import { fromFlashState } from '../../modules/progress-status';
22 | import { StepButton } from '../../styled-components';
23 | import * as i18next from 'i18next';
24 |
25 | const FlashProgressBar = styled(ProgressBar)`
26 | > div {
27 | width: 100%;
28 | height: 12px;
29 | color: white !important;
30 | text-shadow: none !important;
31 | transition-duration: 0s;
32 |
33 | > div {
34 | transition-duration: 0s;
35 | }
36 | }
37 |
38 | width: 100%;
39 | height: 12px;
40 | margin-bottom: 6px;
41 | border-radius: 14px;
42 | font-size: 16px;
43 | line-height: 48px;
44 |
45 | background: #2f3033;
46 | `;
47 |
48 | interface ProgressButtonProps {
49 | type: 'decompressing' | 'flashing' | 'verifying';
50 | active: boolean;
51 | percentage: number;
52 | position: number;
53 | disabled: boolean;
54 | cancel: (type: string) => void;
55 | callback: () => void;
56 | warning?: boolean;
57 | }
58 |
59 | const colors = {
60 | decompressing: '#00aeef',
61 | flashing: '#da60ff',
62 | verifying: '#1ac135',
63 | } as const;
64 |
65 | const CancelButton = styled(({ type, onClick, ...props }) => {
66 | const status = type === 'verifying' ? i18next.t('skip') : i18next.t('cancel');
67 | return (
68 |
71 | );
72 | })`
73 | font-weight: 600;
74 |
75 | &&& {
76 | width: auto;
77 | height: auto;
78 | font-size: 14px;
79 | }
80 | `;
81 |
82 | export class ProgressButton extends React.PureComponent {
83 | public render() {
84 | const percentage = this.props.percentage;
85 | const warning = this.props.warning;
86 | const { status, position } = fromFlashState({
87 | type: this.props.type,
88 | percentage,
89 | position: this.props.position,
90 | });
91 | const type = this.props.type || 'default';
92 | if (this.props.active) {
93 | return (
94 | <>
95 |
106 |
107 | {status}
108 | {position}
109 |
110 | {type && (
111 |
116 | )}
117 |
118 |
119 | >
120 | );
121 | }
122 | return (
123 |
133 | {i18next.t('flash.flashNow')}
134 |
135 | );
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as React from 'react';
18 | import { Flex, Txt } from 'rendition';
19 |
20 | import DriveSvg from '../../../assets/drive.svg';
21 | import ImageSvg from '../../../assets/image.svg';
22 | import { SVGIcon } from '../svg-icon/svg-icon';
23 | import { middleEllipsis } from '../../utils/middle-ellipsis';
24 |
25 | interface ReducedFlashingInfosProps {
26 | imageLogo?: string;
27 | imageName?: string;
28 | imageSize: string;
29 | driveTitle: string;
30 | driveLabel: string;
31 | style?: React.CSSProperties;
32 | }
33 |
34 | export class ReducedFlashingInfos extends React.Component {
35 | constructor(props: ReducedFlashingInfosProps) {
36 | super(props);
37 | this.state = {};
38 | }
39 |
40 | public render() {
41 | const { imageName = '' } = this.props;
42 | return (
43 |
47 |
48 |
56 |
60 | {middleEllipsis(imageName, 16)}
61 |
62 | {this.props.imageSize}
63 |
64 |
65 |
66 |
67 |
68 | {middleEllipsis(this.props.driveTitle, 16)}
69 |
70 |
71 |
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/gui/app/components/settings/settings.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import GithubSvg from '@fortawesome/fontawesome-free/svgs/brands/github.svg';
18 | import * as _ from 'lodash';
19 | import * as React from 'react';
20 | import { Box, Checkbox, Divider, Flex, TextWithCopy, Txt } from 'rendition';
21 |
22 | import { version, packageType } from '../../../../../package.json';
23 | import * as settings from '../../models/settings';
24 | import * as analytics from '../../modules/analytics';
25 | import { open as openInternalRemote } from '../../os/open-internal-remote/services/open-internal-remote';
26 | import { open as openExternal } from '../../os/open-external/services/open-external';
27 | import { Modal } from '../../styled-components';
28 | import * as i18next from 'i18next';
29 | import { etcherProInfo } from '../../utils/etcher-pro-specific';
30 |
31 | interface Setting {
32 | name: string;
33 | label: string | JSX.Element;
34 | }
35 |
36 | async function getSettingsList(): Promise {
37 | const list: Setting[] = [
38 | {
39 | name: 'verify',
40 | label: i18next.t('settings.verify'),
41 | tooltip: i18next.t('settings.verifyDesc'),
42 | },
43 | {
44 | name: 'autoBlockmapping',
45 | label: i18next.t('settings.trimExtPartitions'),
46 | tooltip: i18next.t('settings.trimExtPartitions'),
47 | },
48 | {
49 | name: 'decompressFirst',
50 | label: i18next.t('settings.decompressFirst'),
51 | tooltip: i18next.t('settings.decompressFirst'),
52 | },
53 | ];
54 | return list;
55 | }
56 |
57 | interface SettingsModalProps {
58 | toggleModal: (value: boolean) => void;
59 | }
60 |
61 | const EPInfo = etcherProInfo();
62 |
63 | const InfoBox = (props: any) => (
64 |
65 | {props.label}
66 |
67 |
68 | );
69 |
70 | export function SettingsModal({ toggleModal }: SettingsModalProps) {
71 | const [settingsList, setCurrentSettingsList] = React.useState([]);
72 | React.useEffect(() => {
73 | (async () => {
74 | if (settingsList.length === 0) {
75 | setCurrentSettingsList(await getSettingsList());
76 | }
77 | })();
78 | });
79 | const [currentSettings, setCurrentSettings] = React.useState<
80 | _.Dictionary
81 | >({});
82 | React.useEffect(() => {
83 | (async () => {
84 | if (_.isEmpty(currentSettings)) {
85 | setCurrentSettings(await settings.getAll());
86 | }
87 | })();
88 | });
89 |
90 | const toggleSetting = async (setting: string) => {
91 | const value = currentSettings[setting];
92 | analytics.logEvent('Toggle setting', { setting, value });
93 | await settings.set(setting, !value);
94 | setCurrentSettings({
95 | ...currentSettings,
96 | [setting]: !value,
97 | });
98 | };
99 |
100 | return (
101 |
104 | {i18next.t('settings.settings')}
105 |
106 | }
107 | done={() => toggleModal(false)}
108 | >
109 |
110 | {settingsList.map((setting: Setting, i: number) => {
111 | return (
112 |
113 | toggleSetting(setting.name)}
120 | />
121 |
122 | );
123 | })}
124 | {EPInfo !== undefined && (
125 |
126 | {i18next.t('settings.systemInformation')}
127 | {EPInfo.get_serial() === undefined ? (
128 |
129 | ) : (
130 |
131 | )}
132 |
133 | )}
134 |
135 |
146 | openInternalRemote(
147 | 'https://github.com/Alex313031/etcher-ng-win7/blob/main/CHANGELOG.md',
148 | )
149 | }
150 | >
151 |
156 | {version}
157 |
158 |
159 |
160 | );
161 | }
162 |
--------------------------------------------------------------------------------
/lib/gui/app/components/svg-icon/svg-icon.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as React from 'react';
18 |
19 | const domParser = new window.DOMParser();
20 |
21 | const DEFAULT_SIZE = '40px';
22 |
23 | /**
24 | * @summary Try to parse SVG contents and return it data encoded
25 | *
26 | */
27 | function tryParseSVGContents(contents?: string): string | undefined {
28 | if (contents === undefined) {
29 | return;
30 | }
31 | const doc = domParser.parseFromString(contents, 'image/svg+xml');
32 | const parserError = doc.querySelector('parsererror');
33 | const svg = doc.querySelector('svg');
34 | if (!parserError && svg) {
35 | return `data:image/svg+xml,${encodeURIComponent(svg.outerHTML)}`;
36 | }
37 | }
38 |
39 | interface SVGIconProps {
40 | // Optional string representing the SVG contents to be tried
41 | contents?: string;
42 | // Fallback SVG element to show if `contents` is invalid/undefined
43 | fallback: React.FunctionComponent>;
44 | // SVG image width unit
45 | width?: string;
46 | // SVG image height unit
47 | height?: string;
48 | // Should the element visually appear grayed out and disabled?
49 | disabled?: boolean;
50 | style?: React.CSSProperties;
51 | }
52 |
53 | /**
54 | * @summary SVG element that takes file contents
55 | */
56 | export class SVGIcon extends React.PureComponent {
57 | public render() {
58 | const svgData = tryParseSVGContents(this.props.contents);
59 | const { width, height, style = {} } = this.props;
60 | style.width = width || DEFAULT_SIZE;
61 | style.height = height || DEFAULT_SIZE;
62 | if (svgData !== undefined) {
63 | return (
64 |
69 | );
70 | }
71 | const { fallback: FallbackSVG } = this.props;
72 | return ;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/gui/app/components/target-selector/target-selector-button.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
18 | import * as React from 'react';
19 | import { Flex, FlexProps, Txt } from 'rendition';
20 |
21 | import {
22 | getDriveImageCompatibilityStatuses,
23 | DriveStatus,
24 | } from '../../../../shared/drive-constraints';
25 | import { compatibility, warning } from '../../../../shared/messages';
26 | import * as prettyBytes from 'pretty-bytes';
27 | import { getImage, getSelectedDrives } from '../../models/selection-state';
28 | import {
29 | ChangeButton,
30 | DetailsText,
31 | StepButton,
32 | StepNameButton,
33 | } from '../../styled-components';
34 | import { middleEllipsis } from '../../utils/middle-ellipsis';
35 | import * as i18next from 'i18next';
36 |
37 | interface TargetSelectorProps {
38 | targets: any[];
39 | disabled: boolean;
40 | openDriveSelector: () => void;
41 | reselectDrive: () => void;
42 | flashing: boolean;
43 | show: boolean;
44 | tooltip: string;
45 | }
46 |
47 | function getDriveWarning(status: DriveStatus) {
48 | switch (status.message) {
49 | case compatibility.containsImage():
50 | return warning.sourceDrive();
51 | case compatibility.largeDrive():
52 | return warning.largeDriveSize();
53 | case compatibility.system():
54 | return warning.systemDrive();
55 | default:
56 | return '';
57 | }
58 | }
59 |
60 | const DriveCompatibilityWarning = ({
61 | warnings,
62 | ...props
63 | }: {
64 | warnings: string[];
65 | } & FlexProps) => {
66 | const systemDrive = warnings.find(
67 | (message) => message === warning.systemDrive(),
68 | );
69 | return (
70 |
71 |
75 |
76 | );
77 | };
78 |
79 | export function TargetSelectorButton(props: TargetSelectorProps) {
80 | const targets = getSelectedDrives();
81 |
82 | if (targets.length === 1) {
83 | const target = targets[0];
84 | const warnings = getDriveImageCompatibilityStatuses(
85 | target,
86 | getImage(),
87 | true,
88 | ).map(getDriveWarning);
89 | return (
90 | <>
91 |
92 | {warnings.length > 0 && (
93 |
94 | )}
95 | {middleEllipsis(target.description, 20)}
96 |
97 | {!props.flashing && (
98 |
99 | {i18next.t('target.change')}
100 |
101 | )}
102 | {target.size != null && (
103 | {prettyBytes(target.size)}
104 | )}
105 | >
106 | );
107 | }
108 |
109 | if (targets.length > 1) {
110 | const targetsTemplate = [];
111 | for (const target of targets) {
112 | const warnings = getDriveImageCompatibilityStatuses(
113 | target,
114 | getImage(),
115 | true,
116 | ).map(getDriveWarning);
117 | targetsTemplate.push(
118 |
125 | {warnings.length > 0 ? (
126 |
127 | ) : null}
128 | {middleEllipsis(target.description, 14)}
129 | {target.size != null && {prettyBytes(target.size)}}
130 | ,
131 | );
132 | }
133 | return (
134 | <>
135 |
136 | {targets.length} {i18next.t('target.targets')}
137 |
138 | {!props.flashing && (
139 |
140 | {i18next.t('target.change')}
141 |
142 | )}
143 | {targetsTemplate}
144 | >
145 | );
146 | }
147 |
148 | return (
149 | 0 ? -1 : 2}
152 | disabled={props.disabled}
153 | tooltip="Choose target drive(s)"
154 | onClick={props.openDriveSelector}
155 | >
156 | {i18next.t('target.selectTarget')}
157 |
158 | );
159 | }
160 |
--------------------------------------------------------------------------------
/lib/gui/app/css/fonts/SourceSansPro-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/lib/gui/app/css/fonts/SourceSansPro-Regular.ttf
--------------------------------------------------------------------------------
/lib/gui/app/css/fonts/SourceSansPro-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alex313031/etcher-ng-win7/0623959b965cf4a2c23e8cb74b2f57adcf24b5db/lib/gui/app/css/fonts/SourceSansPro-SemiBold.ttf
--------------------------------------------------------------------------------
/lib/gui/app/css/main.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @font-face {
18 | font-family: "SourceSansPro";
19 | src: url("./fonts/SourceSansPro-Regular.ttf") format("truetype");
20 | font-weight: 500;
21 | font-style: normal;
22 | }
23 |
24 | @font-face {
25 | font-family: "SourceSansPro";
26 | src: url("./fonts/SourceSansPro-SemiBold.ttf") format("truetype");
27 | font-weight: 600;
28 | font-style: normal;
29 | }
30 |
31 | html,
32 | body {
33 | margin: 0;
34 | overflow: hidden;
35 |
36 | /* Prevent white flash when running application */
37 | background-color: #4d5057;
38 |
39 | /* Prevent WebView bounce effect in OS X */
40 | height: 100%;
41 | width: 100%;
42 | }
43 |
44 | /* Prevent text selection */
45 | body {
46 | -webkit-user-select: none;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | /* Prevent blue outline */
51 | a:focus,
52 | input:focus,
53 | button:focus,
54 | [tabindex]:focus,
55 | input[type="checkbox"] + div {
56 | outline: none !important;
57 | box-shadow: none !important;
58 | }
59 |
60 | .disabled {
61 | opacity: 0.4;
62 | }
63 |
64 | #rendition-tooltip-root > div {
65 | font-family: "SourceSansPro", sans-serif;
66 | }
67 |
--------------------------------------------------------------------------------
/lib/gui/app/i18n.ts:
--------------------------------------------------------------------------------
1 | import * as i18next from 'i18next';
2 | import { initReactI18next } from 'react-i18next';
3 | import zh_CN_translation from './i18n/zh-CN';
4 | import zh_TW_translation from './i18n/zh-TW';
5 | import en_translation from './i18n/en';
6 |
7 | export function langParser() {
8 | if (process.env.LANG !== undefined) {
9 | // Bypass mocha, where lang-detect don't works
10 | return 'en';
11 | }
12 |
13 | const lang = Intl.DateTimeFormat().resolvedOptions().locale;
14 |
15 | switch (lang.substr(0, 2)) {
16 | case 'zh':
17 | if (lang === 'zh-CN' || lang === 'zh-SG') {
18 | return 'zh-CN';
19 | } // Simplified Chinese
20 | else {
21 | return 'zh-TW';
22 | } // Traditional Chinese
23 | default:
24 | return lang.substr(0, 2);
25 | }
26 | }
27 |
28 | i18next.use(initReactI18next).init({
29 | lng: langParser(),
30 | fallbackLng: 'en',
31 | nonExplicitSupportedLngs: true,
32 | interpolation: {
33 | escapeValue: false,
34 | },
35 | resources: {
36 | 'zh-CN': zh_CN_translation,
37 | 'zh-TW': zh_TW_translation,
38 | en: en_translation,
39 | },
40 | });
41 |
42 | export const supportedLocales = ['en', 'zh'];
43 |
44 | export default i18next;
45 |
--------------------------------------------------------------------------------
/lib/gui/app/i18n/README.md:
--------------------------------------------------------------------------------
1 | # i18n
2 |
3 | ## How it was done
4 |
5 | Using the open-source lib [i18next](https://www.i18next.com/).
6 |
7 | ## How to add your own language
8 |
9 | 1. Go to `lib/gui/app/i18n` and add a file named `xx.ts` (use the codes mentioned
10 | in [the link](https://www.science.co.il/language/Locale-codes.php), and we support styles as `fr`, `de`, `es-ES`
11 | and `pt-BR`)
12 | .
13 | 2. Copy the content from an existing translation and start to translate.
14 | 3. Once done, go to `lib/gui/app/i18n.ts` and add a line of `import xx_translation from './i18n/xx'` after the
15 | already-added imports and add `xx: xx_translation` in the `resources` section of `i18next.init()` function.
16 | 4. Now go to `lib/shared/catalina-sudo/` and copy the `sudo-askpass.osascript-en.js`, change it to
17 | be `sudo-askpass.osascript-xx.js` and edit
18 | the `'Etcher-ng needs privileged access in order to flash disks.\n\nType your password to allow this.'` line and
19 | those `Ok`s and `Cancel`s to your own language.
20 | 5. If, your language has several variations when they are used in several countries/regions, such as `zh-CN` and `zh-TW`
21 | , or `pt-BR` and `pt-PT`, edit
22 | the `langParser()` in the `lib/gui/app/i18n.ts` file to meet your need.
23 | 6. Make a commit, and then a pull request on GitHub.
24 |
--------------------------------------------------------------------------------
/lib/gui/app/i18n/zh-CN.ts:
--------------------------------------------------------------------------------
1 | const translation = {
2 | translation: {
3 | ok: '好',
4 | cancel: '取消',
5 | continue: '继续',
6 | skip: '跳过',
7 | sure: '我确定',
8 | warning: '请注意!',
9 | attention: '请注意',
10 | failed: '失败',
11 | completed: '完毕',
12 | yesExit: '是的,可以退出',
13 | reallyExit: '真的要现在退出 Etcher-ng 吗?',
14 | yesContinue: '是的,继续',
15 | progress: {
16 | starting: '正在启动……',
17 | decompressing: '正在解压……',
18 | flashing: '正在烧录……',
19 | finishing: '正在结束……',
20 | verifying: '正在验证……',
21 | failing: '失败……',
22 | },
23 | message: {
24 | sizeNotRecommended: '大小不推荐',
25 | tooSmall: '空间太小',
26 | locked: '被锁定',
27 | system: '系统盘',
28 | containsImage: '存放源镜像',
29 | largeDrive: '很大的磁盘',
30 | sourceLarger: '所选的镜像比目标盘大了 {{byte}} 比特。',
31 | flashSucceed_one: '烧录成功',
32 | flashSucceed_other: '烧录成功',
33 | flashFail_one: '烧录失败',
34 | flashFail_other: '烧录失败',
35 | toDrive: '到 {{description}} ({{name}})',
36 | toTarget_one: '到 {{num}} 个目标',
37 | toTarget_other: '到 {{num}} 个目标',
38 | andFailTarget_one: '并烧录失败了 {{num}} 个目标',
39 | andFailTarget_other: '并烧录失败了 {{num}} 个目标',
40 | succeedTo: '{{name}} 被成功烧录 {{target}}',
41 | exitWhileFlashing:
42 | '您当前正在刷机。 关闭 Etcher-ng 可能会导致您的磁盘无法使用。',
43 | looksLikeWindowsImage:
44 | '看起来您正在尝试刻录 Windows 镜像。\n\n与其他镜像不同,Windows 镜像需要特殊处理才能使其可启动。 我们建议您使用专门为此目的设计的工具,例如 Rufus (Windows)、WoeUSB (Linux) 或 Boot Camp 助理 (macOS)。',
45 | image: '镜像',
46 | drive: '磁盘',
47 | missingPartitionTable:
48 | '看起来这不是一个可启动的{{type}}。\n\n这个{{type}}似乎不包含分区表,因此您的设备可能无法识别或无法正确启动。',
49 | largeDriveSize: '这是个很大的磁盘!请检查并确认它不包含对您很重要的信息',
50 | systemDrive: '选择系统盘很危险,因为这将会删除你的系统',
51 | sourceDrive: '源镜像位于这个分区中',
52 | noSpace: '磁盘空间不足。 请插入另一个较大的磁盘并重试。',
53 | genericFlashError:
54 | '出了点问题。如果源镜像曾被压缩过,请检查它是否已损坏。\n{{error}}',
55 | validation:
56 | '写入已成功完成,但 Etcher-ng 在从磁盘读取镜像时检测到潜在的损坏问题。 \n\n请考虑将镜像写入其他磁盘。',
57 | openError: '打开 {{source}} 时出错。\n\n错误信息: {{error}}',
58 | flashError: '烧录 {{image}} {{targets}} 失败。',
59 | unplug:
60 | '看起来 Etcher-ng 失去了对磁盘的连接。 它是不是被意外拔掉了?\n\n有时这个错误是因为读卡器出了故障。',
61 | cannotWrite:
62 | '看起来 Etcher-ng 无法写入磁盘的这个位置。 此错误通常是由故障的磁盘、读取器或端口引起的。 \n\n请使用其他磁盘、读卡器或端口重试。',
63 | childWriterDied:
64 | '写入进程意外崩溃。请再试一次,如果问题仍然存在,请联系 Etcher-ng 团队。',
65 | badProtocol: '仅支持 http:// 和 https:// 开头的网址。',
66 | },
67 | target: {
68 | selectTarget: '选择目标磁盘',
69 | plugTarget: '请插入目标磁盘',
70 | targets: '个目标',
71 | change: '更改',
72 | },
73 | menu: {
74 | edit: '编辑',
75 | view: '视图',
76 | devTool: '打开开发者工具',
77 | window: '窗口',
78 | help: '帮助',
79 | pro: 'Etcher 专业版',
80 | website: 'Etcher 的官网',
81 | issue: '提交一个 Issue',
82 | devmenu: '开发商',
83 | electrondevtools: '打开 Electron 开发工具',
84 | testwindow: '打开测试窗口',
85 | config: '编辑配置文件',
86 | restart: '重新启动应用程序',
87 | gpu: '打开 chrome://gpu',
88 | procinternals: '打开 chrome://process-internals',
89 | goback: '回去',
90 | goforward: '前进',
91 | about: '关于 Etcher-ng',
92 | hide: '隐藏 Etcher-ng',
93 | hideOthers: '隐藏其它窗口',
94 | unhide: '取消隐藏',
95 | quit: '退出 Etcher-ng',
96 | },
97 | source: {
98 | useSourceURL: '使用镜像网络地址',
99 | auth: '验证',
100 | username: '输入用户名',
101 | password: '输入密码',
102 | unsupportedProtocol: '不支持的协议',
103 | windowsImage: '这可能是 Windows 系统镜像',
104 | partitionTable: '找不到分区表',
105 | errorOpen: '打开源镜像时出错',
106 | fromFile: '从文件烧录',
107 | fromURL: '从在线地址烧录',
108 | clone: '克隆磁盘',
109 | image: '镜像信息',
110 | name: '名称:',
111 | path: '路径:',
112 | selectSource: '选择源',
113 | plugSource: '请插入源磁盘',
114 | osImages: '系统镜像格式',
115 | allFiles: '任何文件格式',
116 | enterValidURL: '请输入一个正确的地址',
117 | },
118 | drives: {
119 | name: '名称',
120 | size: '大小',
121 | location: '位置',
122 | find: '找到 {{length}} 个',
123 | select: '选定 {{select}}',
124 | showHidden: '显示 {{num}} 个隐藏的磁盘',
125 | systemDriveDanger: '选择系统盘很危险,因为这将会删除你的系统!',
126 | openInBrowser: 'Etcher-ng 会在浏览器中打开 {{link}}',
127 | changeTarget: '改变目标',
128 | largeDriveWarning: '您即将擦除一个非常大的磁盘',
129 | largeDriveWarningMsg: '您确定所选磁盘不是存储磁盘吗?',
130 | systemDriveWarning: '您将要擦除系统盘',
131 | systemDriveWarningMsg: '您确定要烧录到系统盘吗?',
132 | },
133 | flash: {
134 | another: '烧录另一目标',
135 | target: '目标',
136 | location: '位置',
137 | error: '错误',
138 | flash: '烧录',
139 | flashNow: '现在烧录!',
140 | skip: '跳过了验证',
141 | moreInfo: '更多信息',
142 | speedTip:
143 | '通过将镜像大小除以烧录时间来计算速度。\n由于我们能够跳过未使用的部分,因此具有EXT分区的磁盘镜像烧录速度更快。',
144 | speed: '速度:{{speed}} MB/秒',
145 | speedShort: '{{speed}} MB/秒',
146 | eta: '预计还需要:{{eta}}',
147 | failedTarget: '失败的烧录目标',
148 | failedRetry: '重试烧录失败目标',
149 | flashFailed: '烧录失败。',
150 | flashCompleted: '烧录成功!',
151 | },
152 | settings: {
153 | errorReporting: '匿名地向 balena.io 报告运行错误和使用统计',
154 | verify: '刷机后自动验证',
155 | verifyDesc: '验证驱动器写入是否正确',
156 | autoUpdate: '自动更新',
157 | settings: '软件设置',
158 | systemInformation: '系统信息',
159 | trimExtPartitions: '修剪原始图像上未分配的空间(在 ext 类型分区中)',
160 | decompressFirst: '在刷新之前解压缩压缩图像(即 .tar.xz)',
161 | },
162 | },
163 | };
164 |
165 | export default translation;
166 |
--------------------------------------------------------------------------------
/lib/gui/app/i18n/zh-TW.ts:
--------------------------------------------------------------------------------
1 | const translation = {
2 | translation: {
3 | continue: '繼續',
4 | ok: '好',
5 | cancel: '取消',
6 | skip: '跳過',
7 | sure: '我確定',
8 | warning: '請注意!',
9 | attention: '請注意',
10 | failed: '失敗',
11 | completed: '完成',
12 | yesContinue: '是的,繼續',
13 | reallyExit: '真的要現在結束 Etcher-ng 嗎?',
14 | yesExit: '是的,可以結束',
15 | progress: {
16 | starting: '正在啟動……',
17 | decompressing: '正在解壓縮……',
18 | flashing: '正在燒錄……',
19 | finishing: '正在結束……',
20 | verifying: '正在驗證……',
21 | failing: '失敗……',
22 | },
23 | message: {
24 | sizeNotRecommended: '大小不建議',
25 | tooSmall: '空間太小',
26 | locked: '被鎖定',
27 | system: '系統',
28 | containsImage: '存放來源映像檔',
29 | largeDrive: '很大的磁碟',
30 | sourceLarger: '所選的映像檔比目標磁碟大了 {{byte}} 位元組。',
31 | flashSucceed_one: '燒錄成功',
32 | flashSucceed_other: '燒錄成功',
33 | flashFail_one: '燒錄失敗',
34 | flashFail_other: '燒錄失敗',
35 | toDrive: '到 {{description}} ({{name}})',
36 | toTarget_one: '到 {{num}} 個目標',
37 | toTarget_other: '到 {{num}} 個目標',
38 | andFailTarget_one: '並燒錄失敗了 {{num}} 個目標',
39 | andFailTarget_other: '並燒錄失敗了 {{num}} 個目標',
40 | succeedTo: '{{name}} 被成功燒錄 {{target}}',
41 | exitWhileFlashing:
42 | '您目前正在刷寫。關閉 Etcher-ng 可能會導致您的磁碟無法使用。',
43 | looksLikeWindowsImage:
44 | '看起來您正在嘗試燒錄 Windows 映像檔。\n\n與其他映像檔不同,Windows 映像檔需要特殊處理才能使其可啟動。我們建議您使用專門為此目的設計的工具,例如 Rufus (Windows)、WoeUSB (Linux) 或 Boot Camp 助理 (macOS)。',
45 | image: '映像檔',
46 | drive: '磁碟',
47 | missingPartitionTable:
48 | '看起來這不是一個可啟動的{{type}}。\n\n這個{{type}}似乎不包含分割表,因此您的設備可能無法識別或無法正確啟動。',
49 | largeDriveSize:
50 | '這是個很大容量的磁碟!請檢查並確認它不包含對您來說存放很重要的資料',
51 | systemDrive: '選擇系統分割區很危險,因為這將會刪除你的系統',
52 | sourceDrive: '來源映像檔位於這個分割區中',
53 | noSpace: '磁碟空間不足。請插入另一個較大的磁碟並重試。',
54 | genericFlashError:
55 | '出了點問題。如果來源映像檔曾被壓縮過,請檢查它是否已損壞。\n{{error}}',
56 | validation:
57 | '寫入已成功完成,但 Etcher-ng 在從磁碟讀取映像檔時檢測到潛在的損壞問題。\n\n請考慮將映像檔寫入其他磁碟。',
58 | openError: '打開 {{source}} 時發生錯誤。\n\n錯誤訊息: {{error}}',
59 | flashError: '燒錄 {{image}} {{targets}} 失敗。',
60 | unplug:
61 | '看起來 Etcher-ng 失去了對磁碟的連接。是不是被意外拔掉了?\n\n有時這個錯誤是因為讀卡器出了故障。',
62 | cannotWrite:
63 | '看起來 Etcher-ng 無法寫入磁碟的這個位置。此錯誤通常是由故障的磁碟、讀取器或連接埠引起的。\n\n請使用其他磁碟、讀卡器或連接埠重試。',
64 | childWriterDied:
65 | '寫入處理程序意外崩潰。請再試一次,如果問題仍然存在,請聯絡 Etcher-ng 團隊。',
66 | badProtocol: '僅支援 http:// 和 https:// 開頭的網址。',
67 | },
68 | target: {
69 | selectTarget: '選擇目標磁碟',
70 | plugTarget: '請插入目標磁碟',
71 | targets: '個目標',
72 | change: '更改',
73 | },
74 | source: {
75 | useSourceURL: '使用映像檔網址',
76 | auth: '驗證',
77 | username: '輸入使用者名稱',
78 | password: '輸入密碼',
79 | unsupportedProtocol: '不支持的通訊協定',
80 | windowsImage: '這可能是 Windows 系統映像檔',
81 | partitionTable: '找不到分割表',
82 | errorOpen: '打開來源映像檔時出錯',
83 | fromFile: '從檔案燒錄',
84 | fromURL: '從網址燒錄',
85 | clone: '再製磁碟',
86 | image: '映像檔訊息',
87 | name: '名稱:',
88 | path: '路徑:',
89 | selectSource: '選擇來源',
90 | plugSource: '請插入來源磁碟',
91 | osImages: '系統映像檔格式',
92 | allFiles: '任何檔案格式',
93 | enterValidURL: '請輸入正確的網址',
94 | },
95 | drives: {
96 | name: '名稱',
97 | size: '大小',
98 | location: '位置',
99 | find: '找到 {{length}} 個',
100 | select: '選取 {{select}}',
101 | showHidden: '顯示 {{num}} 個隱藏的磁碟',
102 | systemDriveDanger: '選擇系統分割區很危險,因為這將會刪除你的系統!',
103 | openInBrowser: 'Etcher-ng 會在瀏覽器中打開 {{link}}',
104 | changeTarget: '更改目標',
105 | largeDriveWarning: '您即將格式化一個非常大的磁碟',
106 | largeDriveWarningMsg: '您確定所選磁碟不是儲存資料的磁碟嗎?',
107 | systemDriveWarning: '您將要格式化系統分割區',
108 | systemDriveWarningMsg: '您確定要燒錄到系統分割區嗎?',
109 | },
110 | flash: {
111 | another: '燒錄另一目標',
112 | target: '目標',
113 | location: '位置',
114 | error: '錯誤',
115 | flash: '燒錄',
116 | flashNow: '現在燒錄!',
117 | skip: '跳過了驗證',
118 | moreInfo: '更多資訊',
119 | speedTip:
120 | '透過將映像檔大小除以燒錄時間來計算速度。\n由於我們能夠跳過未使用的部分,因此具有 ext 分割區的磁碟映像檔燒錄速度更快。',
121 | speed: '速度:{{speed}} MB/秒',
122 | speedShort: '{{speed}} MB/秒',
123 | eta: '預計還需要:{{eta}}',
124 | failedTarget: '目標燒錄失敗',
125 | failedRetry: '重試燒錄失敗的目標',
126 | flashFailed: '燒錄失敗。',
127 | flashCompleted: '燒錄成功!',
128 | },
129 | settings: {
130 | errorReporting: '匿名向 balena.io 回報程式錯誤和使用統計資料',
131 | verify: '刷機後自動驗證',
132 | verifyDesc: '驗證驅動器寫入是否正確',
133 | autoUpdate: '自動更新',
134 | settings: '軟體設定',
135 | systemInformation: '系統資訊',
136 | trimExtPartitions: '修改原始映像檔上未分配的空間(在 ext 類型分割區中)',
137 | decompressFirst: '在刷新之前解壓縮壓縮影像(即 .tar.xz)',
138 | },
139 | menu: {
140 | edit: '編輯',
141 | view: '預覽',
142 | devTool: '打開開發者工具',
143 | window: '視窗',
144 | help: '協助',
145 | pro: 'Etcher 專業版',
146 | website: 'Etcher 的官網',
147 | issue: '提交 Issue',
148 | devmenu: '開發商',
149 | electrondevtools: '開啟 Electron 開發工具',
150 | testwindow: '開啟測試視窗',
151 | config: '編輯設定檔',
152 | restart: '重新啟動應用程式',
153 | gpu: '打開 chrome://gpu',
154 | procinternals: '打開 chrome://process-internals',
155 | goback: '回去',
156 | goforward: '前進',
157 | about: '關於 Etcher-ng',
158 | hide: '隱藏 Etcher-ng',
159 | hideOthers: '隱藏其它視窗',
160 | unhide: '取消隱藏',
161 | quit: '結束 Etcher-ng',
162 | },
163 | },
164 | };
165 |
166 | export default translation;
167 |
--------------------------------------------------------------------------------
/lib/gui/app/index.css:
--------------------------------------------------------------------------------
1 | /* Prefer border-box */
2 | * {
3 | box-sizing: border-box;
4 | }
5 |
--------------------------------------------------------------------------------
/lib/gui/app/index.dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Etcher-ng
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/gui/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Etcher-ng
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/gui/app/models/available-drives.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { DrivelistDrive } from '../../../shared/drive-constraints';
18 | import { Actions, store } from './store';
19 |
20 | export function hasAvailableDrives() {
21 | return getDrives().length > 0;
22 | }
23 |
24 | export function setDrives(drives: any[]) {
25 | store.dispatch({
26 | type: Actions.SET_AVAILABLE_TARGETS,
27 | data: drives,
28 | });
29 | }
30 |
31 | export function getDrives(): DrivelistDrive[] {
32 | return store.getState().toJS().availableDrives;
33 | }
34 |
--------------------------------------------------------------------------------
/lib/gui/app/models/flash-state.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as electron from 'electron';
18 | import * as sdk from 'etcher-sdk';
19 | import * as _ from 'lodash';
20 | import { DrivelistDrive } from '../../../shared/drive-constraints';
21 | import { bytesToMegabytes } from '../../../shared/units';
22 | import { Actions, store } from './store';
23 |
24 | /**
25 | * @summary Reset flash state
26 | */
27 | export function resetState() {
28 | store.dispatch({
29 | type: Actions.RESET_FLASH_STATE,
30 | data: {},
31 | });
32 | }
33 |
34 | /**
35 | * @summary Check if currently flashing
36 | */
37 | export function isFlashing(): boolean {
38 | return store.getState().toJS().isFlashing;
39 | }
40 |
41 | /**
42 | * @summary Set the flashing flag
43 | *
44 | * @description
45 | * The flag is used to signify that we're going to
46 | * start a flash process.
47 | */
48 | export function setFlashingFlag() {
49 | // see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
50 | try {
51 | electron.ipcRenderer.send('disable-screensaver');
52 | } catch (error) {
53 | console.log(
54 | "Can't disable-screensaver, we're probably not running on a balena-electron env",
55 | );
56 | }
57 | store.dispatch({
58 | type: Actions.SET_FLASHING_FLAG,
59 | data: {},
60 | });
61 | }
62 |
63 | /**
64 | * @summary Unset the flashing flag
65 | *
66 | * @description
67 | * The flag is used to signify that the write process ended.
68 | */
69 | export function unsetFlashingFlag(results: {
70 | cancelled?: boolean;
71 | sourceChecksum?: string;
72 | errorCode?: string | number;
73 | }) {
74 | store.dispatch({
75 | type: Actions.UNSET_FLASHING_FLAG,
76 | data: results,
77 | });
78 | // see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
79 | electron.ipcRenderer.send('enable-screensaver');
80 | }
81 |
82 | export function setDevicePaths(devicePaths: string[]) {
83 | store.dispatch({
84 | type: Actions.SET_DEVICE_PATHS,
85 | data: devicePaths,
86 | });
87 | }
88 |
89 | export function addFailedDeviceError({
90 | device,
91 | error,
92 | }: {
93 | device: DrivelistDrive;
94 | error: Error;
95 | }) {
96 | const failedDeviceErrorsMap = new Map(
97 | store.getState().toJS().failedDeviceErrors,
98 | );
99 | if (failedDeviceErrorsMap.has(device.device)) {
100 | // Only store the first error
101 | return;
102 | }
103 | failedDeviceErrorsMap.set(device.device, {
104 | description: device.description,
105 | device: device.device,
106 | devicePath: device.devicePath,
107 | ...error,
108 | });
109 | store.dispatch({
110 | type: Actions.SET_FAILED_DEVICE_ERRORS,
111 | data: Array.from(failedDeviceErrorsMap),
112 | });
113 | }
114 |
115 | /**
116 | * @summary Set the flashing state
117 | */
118 | export function setProgressState(
119 | state: sdk.multiWrite.MultiDestinationProgress,
120 | ) {
121 | // Preserve only one decimal place
122 | const PRECISION = 1;
123 | const data = {
124 | ...state,
125 | percentage:
126 | state.percentage !== undefined && _.isFinite(state.percentage)
127 | ? Math.floor(state.percentage)
128 | : undefined,
129 |
130 | speed: _.attempt(() => {
131 | if (_.isFinite(state.speed)) {
132 | return _.round(bytesToMegabytes(state.speed), PRECISION);
133 | }
134 |
135 | return null;
136 | }),
137 | };
138 |
139 | store.dispatch({
140 | type: Actions.SET_FLASH_STATE,
141 | data,
142 | });
143 | }
144 |
145 | export function getFlashResults() {
146 | return store.getState().toJS().flashResults;
147 | }
148 |
149 | export function getFlashState() {
150 | return store.getState().get('flashState').toJS();
151 | }
152 |
153 | export function wasLastFlashCancelled() {
154 | return _.get(getFlashResults(), ['cancelled'], false);
155 | }
156 |
157 | export function getLastFlashSourceChecksum(): string {
158 | return getFlashResults().sourceChecksum;
159 | }
160 |
161 | export function getLastFlashErrorCode() {
162 | return getFlashResults().errorCode;
163 | }
164 |
165 | export function getFlashUuid() {
166 | return store.getState().toJS().flashUuid;
167 | }
168 |
--------------------------------------------------------------------------------
/lib/gui/app/models/selection-state.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { DrivelistDrive } from '../../../shared/drive-constraints';
18 | import { SourceMetadata } from '../components/source-selector/source-selector';
19 |
20 | import * as availableDrives from './available-drives';
21 | import { Actions, store } from './store';
22 |
23 | /**
24 | * @summary Select a drive by its device path
25 | */
26 | export function selectDrive(driveDevice: string) {
27 | store.dispatch({
28 | type: Actions.SELECT_TARGET,
29 | data: driveDevice,
30 | });
31 | }
32 |
33 | /**
34 | * @summary Toggle drive selection
35 | */
36 | export function toggleDrive(driveDevice: string) {
37 | if (isDriveSelected(driveDevice)) {
38 | deselectDrive(driveDevice);
39 | } else {
40 | selectDrive(driveDevice);
41 | }
42 | }
43 |
44 | export function selectSource(source: SourceMetadata) {
45 | store.dispatch({
46 | type: Actions.SELECT_SOURCE,
47 | data: source,
48 | });
49 | }
50 |
51 | /**
52 | * @summary Get all selected drives' devices
53 | */
54 | export function getSelectedDevices(): string[] {
55 | return store.getState().getIn(['selection', 'devices']).toJS();
56 | }
57 |
58 | /**
59 | * @summary Get all selected drive objects
60 | */
61 | export function getSelectedDrives(): DrivelistDrive[] {
62 | const selectedDevices = getSelectedDevices();
63 | return availableDrives
64 | .getDrives()
65 | .filter((drive) => selectedDevices.includes(drive.device));
66 | }
67 |
68 | /**
69 | * @summary Get the selected image
70 | */
71 | export function getImage(): SourceMetadata | undefined {
72 | return store.getState().toJS().selection.image;
73 | }
74 |
75 | /**
76 | * @summary Check if there is a selected drive
77 | */
78 | export function hasDrive(): boolean {
79 | return Boolean(getSelectedDevices().length);
80 | }
81 |
82 | /**
83 | * @summary Check if there is a selected image
84 | */
85 | export function hasImage(): boolean {
86 | return getImage() !== undefined;
87 | }
88 |
89 | /**
90 | * @summary Remove drive from selection
91 | */
92 | export function deselectDrive(driveDevice: string) {
93 | store.dispatch({
94 | type: Actions.DESELECT_TARGET,
95 | data: driveDevice,
96 | });
97 | }
98 |
99 | export function deselectImage() {
100 | store.dispatch({
101 | type: Actions.DESELECT_SOURCE,
102 | data: {},
103 | });
104 | }
105 |
106 | export function deselectAllDrives() {
107 | getSelectedDevices().forEach(deselectDrive);
108 | }
109 |
110 | /**
111 | * @summary Clear selections
112 | */
113 | export function clear() {
114 | deselectImage();
115 | deselectAllDrives();
116 | }
117 |
118 | /**
119 | * @summary Check whether a given device is selected.
120 | */
121 | export function isDriveSelected(driveDevice: string) {
122 | if (!driveDevice) {
123 | return false;
124 | }
125 |
126 | const selectedDriveDevices = getSelectedDevices();
127 | return selectedDriveDevices.includes(driveDevice);
128 | }
129 |
--------------------------------------------------------------------------------
/lib/gui/app/models/settings.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as _debug from 'debug';
18 | import * as electron from 'electron';
19 | import * as _ from 'lodash';
20 | import { promises as fs } from 'fs';
21 | import { join } from 'path';
22 |
23 | const debug = _debug('etcher:models:settings');
24 |
25 | const JSON_INDENT = 2;
26 |
27 | export const DEFAULT_WIDTH = 800;
28 | export const DEFAULT_HEIGHT = 480;
29 |
30 | /**
31 | * @summary Userdata directory path
32 | * @description
33 | * Defaults to the following:
34 | * - `%APPDATA%/etcher` on Windows
35 | * - `$XDG_CONFIG_HOME/etcher` or `~/.config/etcher` on Linux
36 | * - `~/Library/Application Support/etcher` on macOS
37 | * See https://electronjs.org/docs/api/app#appgetpathname
38 | *
39 | * NOTE: We use the remote property when this module
40 | * is loaded in the Electron's renderer process
41 | */
42 | function getConfigPath() {
43 | const app = electron.app || require('@electron/remote').app;
44 | return join(app.getPath('userData'), 'config.json');
45 | }
46 |
47 | async function readConfigFile(filename: string): Promise<_.Dictionary> {
48 | let contents = '{}';
49 | try {
50 | contents = await fs.readFile(filename, { encoding: 'utf8' });
51 | } catch (error: any) {
52 | // noop
53 | }
54 | try {
55 | return JSON.parse(contents);
56 | } catch (parseError) {
57 | console.error(parseError);
58 | return {};
59 | }
60 | }
61 |
62 | // exported for tests
63 | export async function readAll() {
64 | return await readConfigFile(getConfigPath());
65 | }
66 |
67 | // exported for tests
68 | export async function writeConfigFile(
69 | filename: string,
70 | data: _.Dictionary,
71 | ): Promise {
72 | await fs.writeFile(filename, JSON.stringify(data, null, JSON_INDENT));
73 | }
74 |
75 | const DEFAULT_SETTINGS: _.Dictionary = {
76 | errorReporting: false,
77 | updatesEnabled: false,
78 | desktopNotifications: true,
79 | verify: false,
80 | autoBlockmapping: true,
81 | decompressFirst: true,
82 | };
83 |
84 | const settings = _.cloneDeep(DEFAULT_SETTINGS);
85 |
86 | async function load(): Promise {
87 | debug('load');
88 | const loadedSettings = await readAll();
89 | _.assign(settings, loadedSettings);
90 | }
91 |
92 | const loaded = load();
93 |
94 | export async function set(
95 | key: string,
96 | value: any,
97 | writeConfigFileFn = writeConfigFile,
98 | ): Promise {
99 | debug('set', key, value);
100 | await loaded;
101 | const previousValue = settings[key];
102 | settings[key] = value;
103 | try {
104 | await writeConfigFileFn(getConfigPath(), settings);
105 | } catch (error: any) {
106 | // Revert to previous value if persisting settings failed
107 | settings[key] = previousValue;
108 | throw error;
109 | }
110 | }
111 |
112 | export async function get(key: string): Promise {
113 | await loaded;
114 | return getSync(key);
115 | }
116 |
117 | export function getSync(key: string): any {
118 | return _.cloneDeep(settings[key]);
119 | }
120 |
121 | export async function getAll() {
122 | debug('getAll');
123 | await loaded;
124 | return _.cloneDeep(settings);
125 | }
126 |
--------------------------------------------------------------------------------
/lib/gui/app/modules/drive-scanner.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as sdk from 'etcher-sdk';
18 | import {
19 | Adapter,
20 | BlockDeviceAdapter,
21 | UsbbootDeviceAdapter,
22 | } from 'etcher-sdk/build/scanner/adapters';
23 | import { geteuid, platform } from 'process';
24 |
25 | const adapters: Adapter[] = [
26 | new BlockDeviceAdapter({
27 | includeSystemDrives: () => true,
28 | }),
29 | ];
30 |
31 | // Can't use permissions.isElevated() here as it returns a promise and we need to set
32 | // module.exports = scanner right now.
33 | if (platform !== 'linux' || geteuid() === 0) {
34 | adapters.push(new UsbbootDeviceAdapter());
35 | }
36 |
37 | if (platform === 'win32') {
38 | const {
39 | DriverlessDeviceAdapter: driverless,
40 | // tslint:disable-next-line:no-var-requires
41 | } = require('etcher-sdk/build/scanner/adapters/driverless');
42 | adapters.push(new driverless());
43 | }
44 |
45 | export const scanner = new sdk.scanner.Scanner(adapters);
46 |
--------------------------------------------------------------------------------
/lib/gui/app/modules/exception-reporter.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { logException } from '../modules/analytics';
18 | import { showError } from '../os/dialog';
19 |
20 | /**
21 | * @summary Report an exception
22 | */
23 | export function report(exception?: Error) {
24 | if (exception === undefined) {
25 | return;
26 | }
27 | showError(exception);
28 | logException(exception);
29 | }
30 |
--------------------------------------------------------------------------------
/lib/gui/app/modules/progress-status.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License")
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as prettyBytes from 'pretty-bytes';
18 | import * as i18next from 'i18next';
19 |
20 | export interface FlashState {
21 | active: number;
22 | failed: number;
23 | percentage?: number;
24 | speed: number;
25 | position: number;
26 | type?: 'decompressing' | 'flashing' | 'verifying';
27 | }
28 |
29 | export function fromFlashState({
30 | type,
31 | percentage,
32 | position,
33 | }: Pick): {
34 | status: string;
35 | position?: string;
36 | } {
37 | if (type === undefined) {
38 | return { status: i18next.t('progress.starting') };
39 | } else if (type === 'decompressing') {
40 | if (percentage == null) {
41 | return { status: i18next.t('progress.decompressing') };
42 | } else {
43 | return {
44 | position: `${percentage}%`,
45 | status: i18next.t('progress.decompressing'),
46 | };
47 | }
48 | } else if (type === 'flashing') {
49 | if (percentage != null) {
50 | if (percentage < 100) {
51 | return {
52 | position: `${percentage}%`,
53 | status: i18next.t('progress.flashing'),
54 | };
55 | } else {
56 | return { status: i18next.t('progress.finishing') };
57 | }
58 | } else {
59 | return {
60 | status: i18next.t('progress.flashing'),
61 | position: `${position ? prettyBytes(position) : ''}`,
62 | };
63 | }
64 | } else if (type === 'verifying') {
65 | if (percentage == null) {
66 | return { status: i18next.t('progress.verifying') };
67 | } else if (percentage < 100) {
68 | return {
69 | position: `${percentage}%`,
70 | status: i18next.t('progress.verifying'),
71 | };
72 | } else {
73 | return { status: i18next.t('progress.finishing') };
74 | }
75 | }
76 | return { status: i18next.t('progress.failing') };
77 | }
78 |
79 | export function titleFromFlashState(
80 | state: Pick,
81 | ): string {
82 | const { status, position } = fromFlashState(state);
83 | if (position !== undefined) {
84 | return `${position} ${status}`;
85 | }
86 | return status;
87 | }
88 |
--------------------------------------------------------------------------------
/lib/gui/app/os/dialog.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as electron from 'electron';
18 | import * as remote from '@electron/remote';
19 | import * as _ from 'lodash';
20 |
21 | import * as errors from '../../../shared/errors';
22 | import * as settings from '../../../gui/app/models/settings';
23 | import { SUPPORTED_EXTENSIONS } from '../../../shared/supported-formats';
24 | import * as i18next from 'i18next';
25 |
26 | async function mountSourceDrive() {
27 | // sourceDrivePath is the name of the link in /dev/disk/by-path
28 | const sourceDrivePath = await settings.get('automountOnFileSelect');
29 | if (sourceDrivePath) {
30 | try {
31 | await electron.ipcRenderer.invoke('mount-drive', sourceDrivePath);
32 | } catch (error: any) {
33 | // noop
34 | }
35 | }
36 | }
37 |
38 | /**
39 | * @summary Open an image selection dialog
40 | *
41 | * @description
42 | * Notice that by image, we mean *.img/*.iso/*.zip/etc files.
43 | */
44 | export async function selectImage(): Promise {
45 | await mountSourceDrive();
46 | const options: electron.OpenDialogOptions = {
47 | // This variable is set when running in GNU/Linux from
48 | // inside an AppImage, and represents the working directory
49 | // from where the AppImage was run (which might not be the
50 | // place where the AppImage is located). `OWD` stands for
51 | // "Original Working Directory".
52 | //
53 | // See: https://github.com/probonopd/AppImageKit/commit/1569d6f8540aa6c2c618dbdb5d6fcbf0003952b7
54 | defaultPath: process.env.OWD,
55 | properties: ['openFile', 'treatPackageAsDirectory'],
56 | filters: [
57 | {
58 | name: i18next.t('source.osImages'),
59 | extensions: SUPPORTED_EXTENSIONS,
60 | },
61 | {
62 | name: i18next.t('source.allFiles'),
63 | extensions: ['*'],
64 | },
65 | ],
66 | };
67 | const currentWindow = remote.getCurrentWindow();
68 | const [file] = (await remote.dialog.showOpenDialog(currentWindow, options))
69 | .filePaths;
70 | return file;
71 | }
72 |
73 | /**
74 | * @summary Open a warning dialog
75 | */
76 | export async function showWarning(options: {
77 | confirmationLabel: string;
78 | rejectionLabel: string;
79 | title: string;
80 | description: string;
81 | }): Promise {
82 | _.defaults(options, {
83 | confirmationLabel: i18next.t('ok'),
84 | rejectionLabel: i18next.t('cancel'),
85 | });
86 |
87 | const BUTTONS = [options.confirmationLabel, options.rejectionLabel];
88 |
89 | const BUTTON_CONFIRMATION_INDEX = _.indexOf(
90 | BUTTONS,
91 | options.confirmationLabel,
92 | );
93 | const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, options.rejectionLabel);
94 |
95 | const { response } = await remote.dialog.showMessageBox(
96 | remote.getCurrentWindow(),
97 | {
98 | type: 'warning',
99 | buttons: BUTTONS,
100 | defaultId: BUTTON_REJECTION_INDEX,
101 | cancelId: BUTTON_REJECTION_INDEX,
102 | title: i18next.t('attention'),
103 | message: options.title,
104 | detail: options.description,
105 | },
106 | );
107 | return response === BUTTON_CONFIRMATION_INDEX;
108 | }
109 |
110 | /**
111 | * @summary Show error dialog for an Error instance
112 | */
113 | export function showError(error: Error) {
114 | const title = errors.getTitle(error);
115 | const message = errors.getDescription(error);
116 | remote.dialog.showErrorBox(title, message);
117 | }
118 |
--------------------------------------------------------------------------------
/lib/gui/app/os/notification.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as remote from '@electron/remote';
18 |
19 | import * as settings from '../models/settings';
20 |
21 | /**
22 | * @summary Send a notification
23 | */
24 | export async function send(title: string, body: string, icon: string) {
25 | // Bail out if desktop notifications are disabled
26 | if (!(await settings.get('desktopNotifications'))) {
27 | return;
28 | }
29 |
30 | // `app.dock` is only defined in OS X
31 | if (remote.app.dock) {
32 | remote.app.dock.bounce();
33 | }
34 |
35 | return new window.Notification(title, { body, icon });
36 | }
37 |
--------------------------------------------------------------------------------
/lib/gui/app/os/open-external/services/open-external.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 balena.io and Alex313031
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as electron from 'electron';
18 | import * as electronLog from 'electron-log';
19 | import * as settings from '../../../models/settings';
20 | import { logEvent } from '../../../modules/analytics';
21 |
22 | /**
23 | * @summary Open an external resource
24 | */
25 | export async function open(url: string) {
26 | // Don't open links if they're disabled by the env var
27 | if (await settings.get('disableExternalLinks')) {
28 | return;
29 | }
30 |
31 | logEvent('Open external link', { url });
32 |
33 | if (url) {
34 | electronLog.info('Opening external browser to', url);
35 | electron.shell.openExternal(url);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/gui/app/os/open-internal-remote/services/open-internal-remote.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 balena.io and Alex313031
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as remote from '@electron/remote';
18 | import * as electronLog from 'electron-log/renderer';
19 |
20 | /**
21 | * @summary Open an external resource in a new BrowserWindow
22 | * using the remote module.
23 | */
24 | export async function open(url: string) {
25 | if (url) {
26 | electronLog.info(`Opening remote internal window to ` + `'` + url + `'`);
27 | const remoteWin = new remote.BrowserWindow({
28 | width: 1024,
29 | height: 768,
30 | useContentSize: true,
31 | webPreferences: {
32 | sandbox: true,
33 | },
34 | });
35 | remoteWin.loadURL(url);
36 | remoteWin.on('close', () => {
37 | electronLog.info('Closed a remote internal window');
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/gui/app/os/open-internal/services/open-internal.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 balena.io and Alex313031
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as electron from 'electron';
18 | import * as electronLog from 'electron-log';
19 |
20 | /**
21 | * @summary Open an external resource in a new BrowserWindow
22 | */
23 | export async function open(url: string) {
24 | if (url) {
25 | electronLog.info(`Opening internal window to ` + `'` + url + `'`);
26 | const newWin = new electron.BrowserWindow({
27 | width: 1024,
28 | height: 768,
29 | useContentSize: true,
30 | webPreferences: {
31 | sandbox: true,
32 | },
33 | });
34 | newWin.loadURL(url);
35 | newWin.on('close', () => {
36 | electronLog.info('Closed an internal window');
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/gui/app/os/window-progress.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import * as remote from '@electron/remote';
18 |
19 | import { percentageToFloat } from '../../../shared/utils';
20 | import { FlashState, titleFromFlashState } from '../modules/progress-status';
21 |
22 | /**
23 | * @summary The title of the main window upon program launch
24 | */
25 | const INITIAL_TITLE = document.title;
26 |
27 | /**
28 | * @summary Make the full window status title
29 | */
30 | function getWindowTitle(state?: FlashState) {
31 | if (state) {
32 | return `${INITIAL_TITLE} – ${titleFromFlashState(state)}`;
33 | }
34 | return INITIAL_TITLE;
35 | }
36 |
37 | /**
38 | * @summary A reference to the current renderer Electron window
39 | *
40 | * @description
41 | * We expose this property to `this` for testability purposes.
42 | */
43 | export const currentWindow = remote.getCurrentWindow();
44 |
45 | /**
46 | * @summary Set operating system window progress
47 | *
48 | * @description
49 | * Show progress inline in operating system task bar
50 | */
51 | export function set(state: FlashState) {
52 | if (state.percentage != null) {
53 | currentWindow.setProgressBar(percentageToFloat(state.percentage));
54 | }
55 | currentWindow.setTitle(getWindowTitle(state));
56 | }
57 |
58 | /**
59 | * @summary Clear the window progress bar
60 | */
61 | export function clear() {
62 | // Passing 0 or null/undefined doesn't work.
63 | currentWindow.setProgressBar(-1);
64 | currentWindow.setTitle(getWindowTitle(undefined));
65 | }
66 |
--------------------------------------------------------------------------------
/lib/gui/app/os/windows-network-drives.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { exec } from 'child_process';
18 | import { withTmpFile } from 'etcher-sdk/build/tmp';
19 | import { readFile } from 'fs';
20 | import { chain, trim } from 'lodash';
21 | import { platform } from 'os';
22 | import { join } from 'path';
23 | import { env } from 'process';
24 | import { promisify } from 'util';
25 |
26 | const readFileAsync = promisify(readFile);
27 |
28 | const execAsync = promisify(exec);
29 |
30 | /**
31 | * @summary Returns wmic's output for network drives
32 | */
33 | async function getWmicNetworkDrivesOutput(): Promise {
34 | // When trying to read wmic's stdout directly from node, it is encoded with the current
35 | // console codepage (depending on the computer).
36 | // Decoding this would require getting this codepage somehow and using iconv as node
37 | // doesn't know how to read cp850 directly for example.
38 | // We could also use wmic's "/output:" switch but it doesn't work when the filename
39 | // contains a space and the os temp dir may contain spaces ("D:\Windows Temp Files" for example).
40 | // So we just redirect to a file and read it afterwards as we know it will be ucs2 encoded.
41 | const options = {
42 | // Close the file once it's created
43 | keepOpen: false,
44 | // Wmic fails with "Invalid global switch" when the "/output:" switch filename contains a dash ("-")
45 | prefix: 'tmp',
46 | };
47 | return withTmpFile(options, async ({ path }) => {
48 | const command = [
49 | join(env.SystemRoot as string, 'System32', 'Wbem', 'wmic'),
50 | 'path',
51 | 'Win32_LogicalDisk',
52 | 'Where',
53 | 'DriveType="4"',
54 | 'get',
55 | 'DeviceID,ProviderName',
56 | '>',
57 | `"${path}"`,
58 | ];
59 | await execAsync(command.join(' '), { windowsHide: true });
60 | return readFileAsync(path, 'ucs2');
61 | });
62 | }
63 |
64 | /**
65 | * @summary returns a Map of drive letter -> network locations on Windows: 'Z:' -> '\\\\192.168.0.1\\Public'
66 | */
67 | async function getWindowsNetworkDrives(
68 | getWmicOutput: () => Promise,
69 | ): Promise