Repository: pop-os/popsicle (language: rust)

Issue:
Use Faster Threading Primitives
# **crossbeam-channel** versus **std::sync::mpsc**

![](https://raw.githubusercontent.com/stjepang/channel/master/benchmarks/plot.png)

# parking_lot Mutex vs std Mutex

> Higher values are better

```
- Running with 1 threads
- 1 iterations inside lock, 0 iterations outside lock
- 3 seconds per test
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |  60402.850 kHz |  61753.175 kHz |   1350.325 kHz
std::sync::Mutex     |  46016.558 kHz |  46626.734 kHz |    610.176 kHz
pthread_mutex_t      |  53268.642 kHz |  53359.425 kHz |     90.782 kHz
- Running with 2 threads
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |  29672.847 kHz |  35965.545 kHz |   6629.604 kHz
std::sync::Mutex     |   4099.018 kHz |   4642.389 kHz |    626.056 kHz
pthread_mutex_t      |   7086.428 kHz |   7230.676 kHz |    196.112 kHz
- Running with 3 threads
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |  19867.183 kHz |  24563.728 kHz |  10232.785 kHz
std::sync::Mutex     |   4136.010 kHz |   4175.220 kHz |     73.005 kHz
pthread_mutex_t      |   5045.504 kHz |   5090.151 kHz |     81.731 kHz
- Running with 4 threads
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |  14809.114 kHz |  17544.133 kHz |   7808.036 kHz
std::sync::Mutex     |   2118.016 kHz |   2125.304 kHz |     24.925 kHz
pthread_mutex_t      |   3104.926 kHz |   3142.101 kHz |     51.442 kHz
- Running with 5 threads
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |  11326.408 kHz |  13043.124 kHz |   6007.196 kHz
std::sync::Mutex     |   1675.045 kHz |   1702.958 kHz |     91.218 kHz
pthread_mutex_t      |   2285.452 kHz |   2316.768 kHz |    116.759 kHz
- Running with 6 threads
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |   9424.928 kHz |  12319.226 kHz |   5667.318 kHz
std::sync::Mutex     |   1258.374 kHz |   1271.011 kHz |     34.273 kHz
pthread_mutex_t      |   1770.459 kHz |   1773.616 kHz |     68.696 kHz
- Running with 7 threads
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |   7887.535 kHz |  10105.100 kHz |   4147.824 kHz
std::sync::Mutex     |   1043.273 kHz |   1038.135 kHz |     22.699 kHz
pthread_mutex_t      |   1482.260 kHz |   1473.764 kHz |     40.721 kHz
- Running with 8 threads
        name         |    average     |     median     |    std.dev.   
parking_lot::Mutex   |   6739.492 kHz |   8044.099 kHz |   2204.661 kHz
std::sync::Mutex     |    890.111 kHz |    889.019 kHz |     13.639 kHz
pthread_mutex_t      |   1273.654 kHz |   1271.624 kHz |     23.562 kHz
```

# parking_lot RwLock vs std RwLock

> Higher values are better

```
- 1 iterations inside lock, 0 iterations outside lock
- 3 seconds per test
parking_lot::RwLock  - [write]      0.000 kHz [read]  52719.563 kHz
seqlock::SeqLock     - [write]      0.000 kHz [read] 236922.089 kHz
std::sync::RwLock    - [write]      0.000 kHz [read]  28657.696 kHz
pthread_rwlock_t     - [write]      0.000 kHz [read]  34604.594 kHz
- Running with 0 writer threads and 2 reader threads
parking_lot::RwLock  - [write]      0.000 kHz [read]  49628.359 kHz
seqlock::SeqLock     - [write]      0.000 kHz [read] 448663.138 kHz
std::sync::RwLock    - [write]      0.000 kHz [read]   7430.355 kHz
pthread_rwlock_t     - [write]      0.000 kHz [read]  10277.398 kHz
- Running with 0 writer threads and 3 reader threads
parking_lot::RwLock  - [write]      0.000 kHz [read]  48633.633 kHz
seqlock::SeqLock     - [write]      0.000 kHz [read] 668788.347 kHz
std::sync::RwLock    - [write]      0.000 kHz [read]   7526.618 kHz
pthread_rwlock_t     - [write]      0.000 kHz [read]   8301.649 kHz
- Running with 0 writer threads and 4 reader threads
parking_lot::RwLock  - [write]      0.000 kHz [read]  47424.554 kHz
seqlock::SeqLock     - [write]      0.000 kHz [read] 852084.755 kHz
std::sync::RwLock    - [write]      0.000 kHz [read]   7550.919 kHz
pthread_rwlock_t     - [write]      0.000 kHz [read]   6467.605 kHz
- Running with 1 writer threads and 0 reader threads
parking_lot::RwLock  - [write]  58991.526 kHz [read]      0.000 kHz
seqlock::SeqLock     - [write]  58952.036 kHz [read]      0.000 kHz
std::sync::RwLock    - [write]  30732.579 kHz [read]      0.000 kHz
pthread_rwlock_t     - [write]  34976.304 kHz [read]      0.000 kHz
- Running with 1 writer threads and 1 reader threads
parking_lot::RwLock  - [write]  60365.084 kHz [read]   2504.651 kHz
seqlock::SeqLock     - [write]  45803.276 kHz [read]   9734.377 kHz
std::sync::RwLock    - [write]    651.436 kHz [read]    601.020 kHz
pthread_rwlock_t     - [write]   4062.541 kHz [read]   4624.594 kHz
- Running with 1 writer threads and 2 reader threads
parking_lot::RwLock  - [write]  58498.529 kHz [read]   2530.441 kHz
seqlock::SeqLock     - [write]  36707.296 kHz [read]  22981.270 kHz
std::sync::RwLock    - [write]    156.200 kHz [read]   2048.605 kHz
pthread_rwlock_t     - [write]   2916.363 kHz [read]   5380.056 kHz
- Running with 1 writer threads and 3 reader threads
parking_lot::RwLock  - [write]  54709.932 kHz [read]   4259.414 kHz
seqlock::SeqLock     - [write]  31652.296 kHz [read]  20743.579 kHz
std::sync::RwLock    - [write]    108.414 kHz [read]   3471.831 kHz
pthread_rwlock_t     - [write]   1710.729 kHz [read]   4729.281 kHz
- Running with 1 writer threads and 4 reader threads
parking_lot::RwLock  - [write]  41931.769 kHz [read]  12514.711 kHz
seqlock::SeqLock     - [write]  27640.776 kHz [read]  31556.929 kHz
std::sync::RwLock    - [write]     42.332 kHz [read]   5662.952 kHz
pthread_rwlock_t     - [write]   1207.303 kHz [read]   4606.489 kHz
- Running with 2 writer threads and 0 reader threads
parking_lot::RwLock  - [write]  62614.957 kHz [read]      0.000 kHz
seqlock::SeqLock     - [write]  53983.536 kHz [read]      0.000 kHz
std::sync::RwLock    - [write]   7533.987 kHz [read]      0.000 kHz
pthread_rwlock_t     - [write]   8383.159 kHz [read]      0.000 kHz
- Running with 2 writer threads and 1 reader threads
parking_lot::RwLock  - [write]  58545.375 kHz [read]   2638.517 kHz
seqlock::SeqLock     - [write]  40492.168 kHz [read]  13461.550 kHz
std::sync::RwLock    - [write]    366.482 kHz [read]   1644.005 kHz
pthread_rwlock_t     - [write]   5472.980 kHz [read]   2835.566 kHz
- Running with 2 writer threads and 2 reader threads
parking_lot::RwLock  - [write]  57471.938 kHz [read]   1715.004 kHz
seqlock::SeqLock     - [write]  34525.371 kHz [read]  25672.695 kHz
std::sync::RwLock    - [write]    156.684 kHz [read]   1933.383 kHz
pthread_rwlock_t     - [write]   3300.043 kHz [read]   3011.416 kHz
- Running with 2 writer threads and 3 reader threads
parking_lot::RwLock  - [write]  48778.401 kHz [read]   7428.756 kHz
seqlock::SeqLock     - [write]  27742.067 kHz [read]  34696.893 kHz
std::sync::RwLock    - [write]    107.839 kHz [read]   3248.390 kHz
pthread_rwlock_t     - [write]   2314.413 kHz [read]   3457.917 kHz
- Running with 2 writer threads and 4 reader threads
parking_lot::RwLock  - [write]  47613.610 kHz [read]   7328.761 kHz
seqlock::SeqLock     - [write]  29363.052 kHz [read]  36264.828 kHz
std::sync::RwLock    - [write]     40.587 kHz [read]   5667.128 kHz
pthread_rwlock_t     - [write]   1754.821 kHz [read]   3544.354 kHz
- Running with 3 writer threads and 0 reader threads
parking_lot::RwLock  - [write]  61451.173 kHz [read]      0.000 kHz
seqlock::SeqLock     - [write]  48341.429 kHz [read]      0.000 kHz
std::sync::RwLock    - [write]   8349.189 kHz [read]      0.000 kHz
pthread_rwlock_t     - [write]   8228.863 kHz [read]      0.000 kHz
- Running with 3 writer threads and 1 reader threads
parking_lot::RwLock  - [write]  57919.760 kHz [read]   1144.595 kHz
seqlock::SeqLock     - [write]  36219.542 kHz [read]  15650.754 kHz
std::sync::RwLock    - [write]    223.381 kHz [read]    367.214 kHz
pthread_rwlock_t     - [write]   4946.966 kHz [read]   1472.755 kHz
- Running with 3 writer threads and 2 reader threads
parking_lot::RwLock  - [write]  52903.982 kHz [read]   4711.847 kHz
seqlock::SeqLock     - [write]  31673.358 kHz [read]  27105.980 kHz
std::sync::RwLock    - [write]    161.620 kHz [read]   2055.015 kHz
pthread_rwlock_t     - [write]   3474.153 kHz [read]   2317.335 kHz
- Running with 3 writer threads and 3 reader threads
parking_lot::RwLock  - [write]  49773.336 kHz [read]   5466.810 kHz
seqlock::SeqLock     - [write]  28000.003 kHz [read]  39288.238 kHz
std::sync::RwLock    - [write]    108.594 kHz [read]   3280.566 kHz
pthread_rwlock_t     - [write]   2682.776 kHz [read]   2661.312 kHz
- Running with 3 writer threads and 4 reader threads
parking_lot::RwLock  - [write]  48860.565 kHz [read]   5279.842 kHz
seqlock::SeqLock     - [write]  27043.554 kHz [read]  46468.848 kHz
std::sync::RwLock    - [write]     40.954 kHz [read]   5691.570 kHz
pthread_rwlock_t     - [write]   2152.390 kHz [read]   2724.305 kHz
- Running with 4 writer threads and 0 reader threads
parking_lot::RwLock  - [write]  58395.404 kHz [read]      0.000 kHz
seqlock::SeqLock     - [write]  42643.841 kHz [read]      0.000 kHz
std::sync::RwLock    - [write]   5878.185 kHz [read]      0.000 kHz
pthread_rwlock_t     - [write]   6334.077 kHz [read]      0.000 kHz
- Running with 4 writer threads and 1 reader threads
parking_lot::RwLock  - [write]  54407.372 kHz [read]   3683.116 kHz
seqlock::SeqLock     - [write]  32643.727 kHz [read]  18531.820 kHz
std::sync::RwLock    - [write]    249.974 kHz [read]    424.784 kHz
pthread_rwlock_t     - [write]   4633.299 kHz [read]   1162.709 kHz
- Running with 4 writer threads and 2 reader threads
parking_lot::RwLock  - [write]  53071.442 kHz [read]   3500.791 kHz
seqlock::SeqLock     - [write]  28644.046 kHz [read]  31088.420 kHz
std::sync::RwLock    - [write]    167.455 kHz [read]   1930.530 kHz
pthread_rwlock_t     - [write]   3521.325 kHz [read]   1799.898 kHz
- Running with 4 writer threads and 3 reader threads
parking_lot::RwLock  - [write]  49381.039 kHz [read]   5024.164 kHz
seqlock::SeqLock     - [write]  27936.083 kHz [read]  40144.285 kHz
std::sync::RwLock    - [write]    110.427 kHz [read]   3190.550 kHz
pthread_rwlock_t     - [write]   2833.992 kHz [read]   2096.695 kHz
- Running with 4 writer threads and 4 reader threads
parking_lot::RwLock  - [write]  39091.665 kHz [read]   2887.356 kHz
seqlock::SeqLock     - [write]  16447.604 kHz [read]  34305.151 kHz
std::sync::RwLock    - [write]     38.380 kHz [read]   5781.112 kHz
pthread_rwlock_t     - [write]   2350.162 kHz [read]   2278.875 kHz
```


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5b253af..8b319c4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,26 +3,51 @@ name = "ansi_term"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "arrayref"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "atk-sys"
+name = "arrayvec"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atk"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "atk-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atomic"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "atomic-option"
 version = "0.1.2"
@@ -33,35 +58,41 @@ name = "atty"
 version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
  "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "autocfg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "backtrace"
-version = "0.3.9"
+version = "0.3.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "backtrace-sys 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "backtrace-sys"
-version = "0.1.23"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bitflags"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -69,18 +100,19 @@ name = "block-buffer"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bus"
-version = "2.0.0"
+version = "2.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "atomic-option 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -88,7 +120,7 @@ name = "bus_writer"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bus 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bus 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -97,31 +129,28 @@ name = "byte-tools"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
-[[package]]
-name = "c_vec"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "cairo-rs"
-version = "0.4.1"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "c_vec 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cairo-sys-rs"
-version = "0.6.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -131,12 +160,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "cc"
-version = "1.0.18"
+version = "1.0.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "cfg-if"
-version = "0.1.5"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -146,44 +175,132 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "crossbeam"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "crossbeam-channel"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "digest"
-version = "0.7.5"
+version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "err-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "errno"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "failure"
-version = "0.1.2"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "failure_derive"
-version = "0.1.2"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.14.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -192,78 +309,78 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "fuchsia-zircon"
-version = "0.3.3"
+name = "fragile"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
-name = "fuchsia-zircon-sys"
-version = "0.3.3"
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "gcc"
+version = "0.3.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "gdk"
-version = "0.8.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-pixbuf 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gdk-pixbuf"
-version = "0.4.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gdk-pixbuf-sys"
-version = "0.6.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gdk-sys"
-version = "0.6.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -276,103 +393,102 @@ dependencies = [
 
 [[package]]
 name = "gio"
-version = "0.4.1"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fragile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gio-sys"
-version = "0.6.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "glib"
-version = "0.5.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "glib-sys"
-version = "0.6.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gobject-sys"
-version = "0.6.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gtk"
-version = "0.4.1"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-pixbuf 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gtk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gtk-sys"
-version = "0.6.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "atk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -396,16 +512,31 @@ dependencies = [
 
 [[package]]
 name = "lazy_static"
-version = "1.1.0"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lock_api"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "libc"
-version = "0.2.43"
+name = "loopdev"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "md-5"
@@ -414,48 +545,92 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "digest 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "memoffset"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "mnt"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "nodrop"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "num_cpus"
-version = "1.8.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "pango"
+name = "owning_ref"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "owning_ref"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "pango-sys"
+name = "pango"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -463,10 +638,42 @@ name = "parking_lot_core"
 version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "partition-identity"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -475,24 +682,23 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
  "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "pkg-config"
-version = "0.3.13"
+version = "0.3.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "popsicle"
 version = "0.1.5"
 dependencies = [
- "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
  "mnt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -502,72 +708,204 @@ version = "0.1.5"
 dependencies = [
  "bus_writer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
  "pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "popsicle 0.1.5",
 ]
 
 [[package]]
 name = "popsicle_gtk"
-version = "0.1.5"
+version = "0.2.0"
 dependencies = [
+ "atomic 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "bus_writer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "cascade 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "digest 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "gdk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "gtk 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-channel 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gtk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex-view 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
  "md-5 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "popsicle 0.1.5",
+ "proc-mounts 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sys-mount 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sysfs-class 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "proc-macro2"
-version = "0.4.12"
+version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "proc-mounts"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "partition-identity 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "pwd"
 version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "quote"
-version = "0.6.6"
+version = "0.6.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "rand"
-version = "0.4.2"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "redox_syscall"
-version = "0.1.40"
+version = "0.1.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -575,12 +913,38 @@ name = "redox_termios"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "rustc-demangle"
-version = "0.1.9"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "scopeguard"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -590,17 +954,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "digest 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "smallvec"
-version = "0.6.4"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
 name = "strsim"
@@ -609,32 +975,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "syn"
-version = "0.14.8"
+version = "0.15.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "synstructure"
-version = "0.9.0"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.14.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "sys-mount"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "loopdev 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sysfs-class"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "termion"
 version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -648,12 +1029,12 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.1.40"
+version = "0.1.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -671,29 +1052,11 @@ name = "unicode-xid"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
-[[package]]
-name = "unreachable"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "vec_map"
 version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
-[[package]]
-name = "version_check"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "void"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "winapi"
 version = "0.2.8"
@@ -701,7 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "winapi"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -725,80 +1088,121 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [metadata]
 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
-"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
-"checksum atk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8dc233521f7bffd3042c31082ea71bd08820abf44bac938fb36591e20f76f39"
+"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
+"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
+"checksum atk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ce8fd2055aa2ce617754c5a701682a06b4b57093cbbcbd8f068622a8debae96"
+"checksum atk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83744274abbcc2242f845a25cf84b9adb5991eaecc8f7c67cffd69ea636f4ff8"
+"checksum atomic 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e5208a0c1ca6d26af893a20ad156a6def478f23c26900b1f95e2086aeb870146"
 "checksum atomic-option 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0db678acb667b525ac40a324fc5f7d3390e29239b31c7327bb8157f5b4fff593"
 "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
-"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
-"checksum backtrace-sys 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "bff67d0c06556c0b8e6b5f090f0eac52d950d9dfd1d35ba04e4ca3543eaf6a7e"
-"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
+"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
+"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4"
+"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
+"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
 "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
-"checksum bus 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c9bae43b6e578339f337e4d591b08022f0107e3d7cfb3712010fc7940fa50e0f"
+"checksum bus 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "504f634a58035c52be107e638291fd395c51f6d98305daeaea6c557438475d9e"
 "checksum bus_writer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6aaffc6884911feab0fd34bb129230af80142c395f6c2f6fa5d200b5080b7d79"
 "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
-"checksum c_vec 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6c32b15e95ce816aaf991a41420854e6ba772a2679a9296d906eab1114f1b4e9"
-"checksum cairo-rs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a110f269c2fd382df5fe8bd46dfa5f1b83608aa717fecb6e7a28c08c202f0e13"
-"checksum cairo-sys-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0395175ecba60accac076a02c31d143b9dcd9d5eb5316d7163a3273803b765c7"
+"checksum cairo-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9e09d8a818b2ccc8983f04d95a9350c3cf8d24cc456cedca3b88fa3a81fdc0e2"
+"checksum cairo-sys-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3fa13914fdc013387afa771f554f2f71d6ae931f4e5be9246c337d60c3dc484"
 "checksum cascade 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4b5cbaa4408b6ddb46140f9a1d7c9b8d5e971cb30d1eef9465acac891868e623"
-"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
-"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
+"checksum cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" = "d01c69d08ff207f231f07196e30f84c70f1c815b04f980f8b7b01ff01f05eb92"
+"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
 "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
+"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
 "checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19"
-"checksum digest 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5b29c278aa8fd30796bd977169e8004b4aa88cdcd2f32a6eb22bc2d5d38df94a"
-"checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9"
-"checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426"
+"checksum crossbeam-channel 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "862becd07e73da5746de6d9b3ba055c9bb8b10afd0d2b51155a6e30d81cd20b3"
+"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b"
+"checksum crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2af0e75710d6181e234c8ecc79f14a97907850a541b13b0be1dd10992f2e4620"
+"checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b"
+"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c"
+"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
+"checksum err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3d8ff65eb6c2fc68e76557239d16f5698fd56603925b89856d3f0f7105fd4543"
+"checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e"
+"checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
+"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
+"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
 "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
-"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
-"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-"checksum gdk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd30051ff3d908ff2fc7e5776ffe1c699821e043809f294c3a61004f11d6c3a9"
-"checksum gdk-pixbuf 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c2d2199eba47ebcb9977ce28179649bdd59305ef465c4e6f9b65aaa41c24e6b5"
-"checksum gdk-pixbuf-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df6a3b73e04fafc07f5ebc083f1096a773412e627828e1103a55e921f81187d8"
-"checksum gdk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3162ff940526ddff71bf1f630facee6b5e05d282d125ba0c4c803842819b80c3"
+"checksum fragile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f8140122fa0d5dcb9fc8627cfce2b37cc1500f752636d46ea28bc26785c2f9"
+"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
+"checksum gdk 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c229b642a3cf701f92246b131fda2eaabaa5f44f4a255dee65939ebd0ab0141"
+"checksum gdk-pixbuf 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9a1bcb11b461ff5638f6bf03c9f173d3dc250eabc8e57a60644b6f529d3a7c91"
+"checksum gdk-pixbuf-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10e1115ca451907b6d775fc0392641d14c89277662d98828412fb68555e7e8c8"
+"checksum gdk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "458dc6243bdda4aa1693f5f18231f55b98e9c99c50b3b9bd673cf461ef1710ea"
 "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
-"checksum gio 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2db9fad8f1b0d4c7338a210a6cbdf081dcc1a3c223718c698c4f313f6c288acb"
-"checksum gio-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a57872499171d279f8577ce83837da4cae62b08dd32892236ed67ab7ea61030"
-"checksum glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e0be1b1432e227bcd1a9b28db9dc1474a7e7fd4227e08e16f35304f32d09b61"
-"checksum glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615bef979b5838526aee99241afc80cfb2e34a8735d4bcb8ec6072598c18a408"
-"checksum gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70409d6405db8b1591602fcd0cbe8af52cd9976dd39194442b4c149ba343f86d"
-"checksum gtk 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d695d6be4110618a97c19cd068e8a00e53e33b87e3c65cdc5397667498b1bc24"
-"checksum gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d9554cf5b3a85a13fb39258c65b04b262989c1d7a758f8f555b77a478621a91"
+"checksum gio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff120a666f4a0f41cca7554fbac82defc2562e90eccf3b26a30e6461fa1bbde8"
+"checksum gio-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04957c40df00dd72f7c9ec8e382b01ff8ed4a904fcd2fa608b7ae7fbaccfda8f"
+"checksum glib 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e8fdc159c196a5dfa53a92929ac4c10c8a6637ffb43951f3fff89c2cd2365"
+"checksum glib-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bda542f3caee39a027638e9644ff89204101ad916fd7370b585ad2c5fc97e61"
+"checksum gobject-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23e05a14290d3dc255223cba51db4b0f3da438d5250657996fa99b2a30faf43e"
+"checksum gtk 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b52cfbd8c3e4a000c8374676352f3850c2d62c0f42ef0a993e5d3fcf47e0be64"
+"checksum gtk-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "849835f05d46f605b00ee9fefb6d5921238c570af406f35ee7426ed01cd6795c"
 "checksum hex-view 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "71d52599ac1d8f67b4e6cf183d0235eec1ef5d68ec0348f10772bf71ecc4ee6e"
 "checksum humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
-"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7"
-"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
+"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
+"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1"
+"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
+"checksum loopdev 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ac9e35cfb6646d67059f2ca8913a90e6c60633053c103df423975297f33d6fcc"
 "checksum md-5 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9402eaae33a9e144ce18ef488a0e4ca19869673c7bcdbbfe2030fdc3f84211cd"
+"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
 "checksum mnt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1587ebb20a5b04738f16cffa7e2526f1b8496b84f92920facd518362ff1559eb"
-"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
-"checksum pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45374801e224373c3c0393cd48073c81093494c8735721e81d1dbaa4096b2767"
-"checksum pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94039b3921a4af4058a3e4335e5d15099101f298a92f5afc40bab3a3027594a1"
+"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
+"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
+"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
+"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
+"checksum pango 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4e36b55d7cd522bd183efeb3dfabc547bda1f26eadf8a1241dac09ab3fd3242c"
+"checksum pango-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "747ab9cd4d537e6dc5ef0e4308c10dde8b706673b0237fed4e056b8d2c0b23c8"
+"checksum parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4d05f1349491390b1730afba60bb20d55761bef489a954546b58b4b34e1e2ac"
+"checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5"
 "checksum parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa"
+"checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c"
+"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
+"checksum partition-identity 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b33909267193c7babe396e0570d0a12ba997b539edfdb2aea1e767e0ea728f05"
 "checksum pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907"
-"checksum pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "104630aa1c83213cbc76db0703630fcb0421dac3585063be4ce9a8a2feeaa745"
-"checksum proc-macro2 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "7a17a4d77bc20d344179de803a34694c0ac7a0b3fb4384bee99783215a8e0410"
+"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
+"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
+"checksum proc-mounts 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dbfc3eb15d90df4f4b3c0631f6b1e85e987c01b188bfadd02a504e5c95d9007"
 "checksum pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5dd32d8bece608e144ca20251e714ed107cdecdabb20c2d383cfc687825106a5"
-"checksum quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ed7d650913520df631972f21e104a4fa2f9c82a14afc65d17b388a2e29731e7c"
-"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
-"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
+"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
+"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
+"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
+"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
+"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832"
+"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
-"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
+"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619"
+"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
+"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0"
-"checksum smallvec 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "211a489e65e94b103926d2054ae515a1cdb5d515ea0ef414fee23b7e043ce748"
+"checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be"
+"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
 "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
-"checksum syn 0.14.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b7bfcbb0c068d0f642a0ffbd5c604965a360a61f99e8add013cef23a838614f3"
-"checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7"
+"checksum syn 0.15.28 (registry+https://github.com/rust-lang/crates.io-index)" = "218aa5a01ab9805df6e9e48074c8d88f317cc9660b1ad6c3dabac2d627d185d6"
+"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
+"checksum sys-mount 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "574db4a99f40b407e9a3f7c5bcc96ad160ef07c5b8daab9cbb22315d86760280"
+"checksum sysfs-class 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "48eda022981a576eb4a94023ac833ef425be8d1237cef6515654347feaccf612"
 "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
 "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
-"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
+"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
 "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
 "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
 "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
-"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
 "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
-"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051"
-"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
-"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
+"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
index f0af4a8..6362b55 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ authors = [
 ]
 license = "MIT"
 readme = "README.md"
+edition = "2018"
 
 [lib]
 name = "popsicle"
@@ -18,6 +19,5 @@ members = ["cli", "gtk"]
 
 [dependencies]
 libc = "0.2"
-failure = "0.1.1"
-failure_derive = "0.1.1"
 mnt = "0.3.1"
+err-derive = "0.1.5"
diff --git a/Makefile b/Makefile
index 67e694c..17592fc 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,15 @@ libdir = $(exec_prefix)/lib
 includedir = $(prefix)/include
 datarootdir = $(prefix)/share
 datadir = $(datarootdir)
+DEBUG ?= 0
+RELEASE = debug
+
+ifeq (0,$(DEBUG))
+	ARGS = --release
+	RELEASE = release
+endif
+
+TARGET = target/$(RELEASE)
 
 .PHONY: all clean distclean install uninstall update
 
@@ -25,14 +34,11 @@ ICONS=\
 	24x24@2x/apps/$(BIN).png \
 	512x512@2x/apps/$(BIN).png
 
-BEFORE := $(shell echo $(default_prefix) | sed 's/\//\\\//g')
-AFTER := $(shell echo $(prefix) | sed 's/\//\\\//g')
-
 all: cli gtk
 
-cli: target/release/$(BIN) target/release/$(BIN).1.gz
+cli: $(TARGET)/$(BIN) $(TARGET)/$(BIN).1.gz
 
-gtk: target/release/$(GTK_BIN)
+gtk: $(TARGET)/$(GTK_BIN)
 
 clean:
 	cargo clean
@@ -41,11 +47,11 @@ distclean: clean
 	rm -rf .cargo vendor
 
 install-cli: cli
-	install -D -m 0755 "target/release/$(BIN)" "$(DESTDIR)$(bindir)/$(BIN)"
-	install -D -m 0755 "target/release/$(BIN).1.gz" "$(DESTDIR)$(datadir)/man/man1/$(BIN).1.gz"
+	install -D -m 0755 "$(TARGET)/$(BIN)" "$(DESTDIR)$(bindir)/$(BIN)"
+	install -D -m 0755 "$(TARGET)/$(BIN).1.gz" "$(DESTDIR)$(datadir)/man/man1/$(BIN).1.gz"
 
 install-gtk: gtk
-	install -D -m 0755 "target/release/$(GTK_BIN)" "$(DESTDIR)$(bindir)/$(GTK_BIN)"
+	install -D -m 0755 "$(TARGET)/$(GTK_BIN)" "$(DESTDIR)$(bindir)/$(GTK_BIN)"
 	install -D -m 0755 "gtk/assets/popsicle-pkexec" "$(DESTDIR)$(bindir)/$(PKEXEC_BIN)"
 	install -D -m 0644 "gtk/assets/popsicle.desktop" "$(DESTDIR)$(datadir)/applications/popsicle.desktop"
 	install -D -m 0644 "gtk/assets/$(POLICY)" "$(DESTDIR)$(datadir)/polkit-1/actions/$(POLICY)"
@@ -54,9 +60,9 @@ install-gtk: gtk
 	done
 
 	# Fix paths in assets
-	sed -i -e 's/$(BEFORE)/$(AFTER)/g' $(DESTDIR)$(datadir)/applications/popsicle.desktop \
-		&& sed -i -e 's/$(BEFORE)/$(AFTER)/g' $(DESTDIR)$(datadir)/polkit-1/actions/$(POLICY) \
-		&& sed -i -e 's/$(BEFORE)/$(AFTER)/g' $(DESTDIR)$(bindir)/$(PKEXEC_BIN)
+	sed -i -e 's#$(default_prefix)#$(prefix)#g' $(DESTDIR)$(datadir)/applications/popsicle.desktop \
+		&& sed -i -e 's#$(default_prefix)#$(prefix)#g' $(DESTDIR)$(datadir)/polkit-1/actions/$(POLICY) \
+		&& sed -i -e 's#$(default_prefix)#$(prefix)#g' $(DESTDIR)$(bindir)/$(PKEXEC_BIN)
 
 install: all install-cli install-gtk
 
@@ -86,22 +92,23 @@ vendor: .cargo/config
 	cargo vendor --explicit-version --locked
 	touch vendor
 
-target/release/$(BIN):
+$(TARGET)/$(BIN):
+	echo $(TARGET): $(DEBUG): $(ARGS)
 	if [ -d vendor ]; \
 	then \
-		cargo build --manifest-path cli/Cargo.toml --release --frozen; \
+		cargo build --manifest-path cli/Cargo.toml $(ARGS) --frozen; \
 	else \
-		cargo build --manifest-path cli/Cargo.toml --release; \
+		cargo build --manifest-path cli/Cargo.toml $(ARGS); \
 	fi
 
-target/release/$(GTK_BIN):
+$(TARGET)/$(GTK_BIN):
 	if [ -d vendor ]; \
 	then \
-		cargo build --manifest-path gtk/Cargo.toml --release --frozen; \
+		cargo build --manifest-path gtk/Cargo.toml $(ARGS) --frozen; \
 	else \
-		cargo build --manifest-path gtk/Cargo.toml --release; \
+		cargo build --manifest-path gtk/Cargo.toml $(ARGS); \
 	fi
 
-target/release/$(BIN).1.gz: target/release/$(BIN)
+$(TARGET)/$(BIN).1.gz: $(TARGET)/$(BIN)
 	help2man --no-info $< | gzip -c > $@.partial
 	mv $@.partial $@
diff --git a/cli/src/main.rs b/cli/src/main.rs
index 7cc35be..67f81ae 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -11,11 +11,11 @@ use clap::{App, Arg};
 use pbr::{MultiBar, Units};
 use std::fs::File;
 use std::io::{self, Write};
-use std::thread::{self, JoinHandle};
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::{Arc, Mutex};
 use std::path::Path;
 use std::process;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Mutex};
+use std::thread::{self, JoinHandle};
 
 use popsicle::mnt;
 
@@ -23,46 +23,24 @@ fn popsicle() -> Result<(), String> {
     let matches = App::new(env!("CARGO_PKG_NAME"))
         .about(env!("CARGO_PKG_DESCRIPTION"))
         .version(env!("CARGO_PKG_VERSION"))
-        .arg(
-            Arg::with_name("IMAGE")
-                .help("Input image file")
-                .required(true),
-        )
-        .arg(
-            Arg::with_name("DISKS")
-                .help("Output disk devices")
-                .multiple(true),
-        )
-        .arg(
-            Arg::with_name("all")
-                .help("Flash all detected USB drives")
-                .short("a")
-                .long("all"),
-        )
+        .arg(Arg::with_name("IMAGE").help("Input image file").required(true))
+        .arg(Arg::with_name("DISKS").help("Output disk devices").multiple(true))
+        .arg(Arg::with_name("all").help("Flash all detected USB drives").short("a").long("all"))
         .arg(
             Arg::with_name("check")
                 .help("Check written image matches read image")
                 .short("c")
                 .long("check"),
         )
-        .arg(
-            Arg::with_name("unmount")
-                .help("Unmount mounted devices")
-                .short("u")
-                .long("unmount"),
-        )
-        .arg(
-            Arg::with_name("yes")
-                .help("Continue without confirmation")
-                .short("y")
-                .long("yes"),
-        )
+        .arg(Arg::with_name("unmount").help("Unmount mounted devices").short("u").long("unmount"))
+        .arg(Arg::with_name("yes").help("Continue without confirmation").short("y").long("yes"))
         .get_matches();
 
     let image_path = matches.value_of("IMAGE").expect("IMAGE not set");
     let mut image = File::open(image_path)
         .map_err(|why| format!("error with image at '{}': {}", image_path, why))?;
-    let image_size = image.metadata()
+    let image_size = image
+        .metadata()
         .map(|x| x.len())
         .map_err(|why| format!("image metadata error at '{}': {}", image_path, why))?;
 
@@ -88,17 +66,12 @@ fn popsicle() -> Result<(), String> {
         }
     };
 
-    let disks = popsicle::disks_from_args(
-        disk_args.into_iter(),
-        &mounts,
-        matches.is_present("unmount"),
-    ).map_err(|why| format!("disk error: {}", why))?;
+    let disks =
+        popsicle::disks_from_args(disk_args.into_iter(), &mounts, matches.is_present("unmount"))
+            .map_err(|why| format!("disk error: {}", why))?;
 
     if !matches.is_present("yes") {
-        println!(
-            "Are you sure you want to flash '{}' to the following drives?",
-            image_path
-        );
+        println!("Are you sure you want to flash '{}' to the following drives?", image_path);
 
         for disk_tuple in &disks {
             println!("  - {}", disk_tuple.0);
@@ -124,11 +97,7 @@ fn popsicle() -> Result<(), String> {
     let mut paths = Vec::with_capacity(disks.len());
     let mut destinations = Vec::with_capacity(disks.len());
     let mut pbs = Vec::with_capacity(disks.len());
-    let errored = Arc::new(
-        (0..disks.len())
-            .map(|_| AtomicBool::new(false))
-            .collect::<Vec<_>>()
-    );
+    let errored = Arc::new((0..disks.len()).map(|_| AtomicBool::new(false)).collect::<Vec<_>>());
 
     for (disk_path, disk) in disks {
         let mut pb = mb.create_bar(image_size);
@@ -141,95 +110,101 @@ fn popsicle() -> Result<(), String> {
 
     let pbs = Arc::new(Mutex::new(pbs));
     let errored_ = errored.clone();
-    let handle: JoinHandle<io::Result<()>> = thread::Builder::new().stack_size(10 * 1024 * 1024).spawn(move || {
-        let mut bucket = [0u8; 8 * 1024 * 1024];
-        BusWriter::new(
-            &mut image,
-            &mut destinations,
-            |event| {
-                let pbs = &mut pbs.lock().unwrap();
-                match event {
-                    BusWriterMessage::Written { id, bytes_written } => {
-                        pbs[id].set(bytes_written);
-                    },
-                    BusWriterMessage::Completed { id } => {
-                        pbs[id].set(image_size);
-                        if ! check {
-                            pbs[id].finish();
-                        }
-                    }
-                    BusWriterMessage::Errored { id, why } => {
-                        let pb = &mut pbs[id];
-                        pb.message(&format!("E {}: {}", paths[id], why));
-                        pb.finish();
-                        errored_[id].store(true, Ordering::SeqCst);
-                    }
-                }
-            },
-            // TODO Ctrl + C signal handling
-            || false
-        ).with_bucket(&mut bucket).write()?;
-
-        let mut unerrored_ids = Vec::new();
-        let mut unerrored_destinations = Vec::new();
-
-        for (id, dest) in destinations.iter().enumerate()
-            .filter(|&(id, _)| !errored_[id].load(Ordering::SeqCst))
-        {
-            unerrored_ids.push(id);
-            unerrored_destinations.push(dest);
-        }
-
-        if check {
-            for (pb, path) in pbs.lock().unwrap().iter_mut().zip(paths.iter()) {
-                pb.set(0);
-                pb.message(&format!("V {}: ", path));
-            }
-
-            BusVerifier::new(
-                image,
-                &mut unerrored_destinations,
+    let handle: JoinHandle<io::Result<()>> = thread::Builder::new()
+        .stack_size(10 * 1024 * 1024)
+        .spawn(move || {
+            let mut bucket = [0u8; 8 * 1024 * 1024];
+            BusWriter::new(
+                &mut image,
+                &mut destinations,
                 |event| {
                     let pbs = &mut pbs.lock().unwrap();
                     match event {
-                        BusVerifierMessage::Read { id, bytes_read } => {
-                            pbs[unerrored_ids[id]].set(bytes_read);
+                        BusWriterMessage::Written { id, bytes_written } => {
+                            pbs[id].set(bytes_written);
                         }
-                        BusVerifierMessage::Invalid { id } => {
-                            pbs[unerrored_ids[id]].finish();
-                            errored_[unerrored_ids[id]].store(true, Ordering::SeqCst);
+                        BusWriterMessage::Completed { id } => {
+                            pbs[id].set(image_size);
+                            if !check {
+                                pbs[id].finish();
+                            }
                         }
-                        BusVerifierMessage::Valid { id } => {
-                            pbs[unerrored_ids[id]].set(image_size);
-                            pbs[unerrored_ids[id]].finish();
-                        }
-                        BusVerifierMessage::Errored { id, why } => {
-                            let pb = &mut pbs[unerrored_ids[id]];
-                            pb.message(&format!("E {}: {}", paths[unerrored_ids[id]], why));
+                        BusWriterMessage::Errored { id, why } => {
+                            let pb = &mut pbs[id];
+                            pb.message(&format!("E {}: {}", paths[id], why));
                             pb.finish();
-                            errored_[unerrored_ids[id]].store(true, Ordering::SeqCst);
+                            errored_[id].store(true, Ordering::SeqCst);
                         }
                     }
                 },
-                || false
-            ).with_bucket(&mut bucket).verify()
-        } else {
-            Ok(())
-        }
-    }).unwrap();
+                // TODO Ctrl + C signal handling
+                || false,
+            )
+            .with_bucket(&mut bucket)
+            .write()?;
+
+            let mut unerrored_ids = Vec::new();
+            let mut unerrored_destinations = Vec::new();
+
+            for (id, dest) in destinations
+                .iter()
+                .enumerate()
+                .filter(|&(id, _)| !errored_[id].load(Ordering::SeqCst))
+            {
+                unerrored_ids.push(id);
+                unerrored_destinations.push(dest);
+            }
 
-    mb.listen();
+            if check {
+                for (pb, path) in pbs.lock().unwrap().iter_mut().zip(paths.iter()) {
+                    pb.set(0);
+                    pb.message(&format!("V {}: ", path));
+                }
 
-    handle.join()
-        .unwrap()
-        .map_err(|why| format!("bus failed: {}", why))
-        .and_then(|_| {
-            if errored.iter().any(|x| x.load(Ordering::SeqCst)) {
-                Err("a device failed".to_owned())
+                BusVerifier::new(
+                    image,
+                    &mut unerrored_destinations,
+                    |event| {
+                        let pbs = &mut pbs.lock().unwrap();
+                        match event {
+                            BusVerifierMessage::Read { id, bytes_read } => {
+                                pbs[unerrored_ids[id]].set(bytes_read);
+                            }
+                            BusVerifierMessage::Invalid { id } => {
+                                pbs[unerrored_ids[id]].finish();
+                                errored_[unerrored_ids[id]].store(true, Ordering::SeqCst);
+                            }
+                            BusVerifierMessage::Valid { id } => {
+                                pbs[unerrored_ids[id]].set(image_size);
+                                pbs[unerrored_ids[id]].finish();
+                            }
+                            BusVerifierMessage::Errored { id, why } => {
+                                let pb = &mut pbs[unerrored_ids[id]];
+                                pb.message(&format!("E {}: {}", paths[unerrored_ids[id]], why));
+                                pb.finish();
+                                errored_[unerrored_ids[id]].store(true, Ordering::SeqCst);
+                            }
+                        }
+                    },
+                    || false,
+                )
+                .with_bucket(&mut bucket)
+                .verify()
             } else {
                 Ok(())
             }
         })
+        .unwrap();
+
+    mb.listen();
+
+    handle.join().unwrap().map_err(|why| format!("bus failed: {}", why)).and_then(|_| {
+        if errored.iter().any(|x| x.load(Ordering::SeqCst)) {
+            Err("a device failed".to_owned())
+        } else {
+            Ok(())
+        }
+    })
 }
 
 fn main() {
diff --git a/gtk/Cargo.toml b/gtk/Cargo.toml
index 5a03eeb..5335dcf 100644
--- a/gtk/Cargo.toml
+++ b/gtk/Cargo.toml
@@ -1,28 +1,34 @@
 [package]
 name = "popsicle_gtk"
 description = "USB Flasher"
-version = "0.1.5"
+version = "0.2.0"
 authors = [ "Michael Aaron Murphy <michael@system76.com>" ]
 license = "MIT"
 readme = "README.md"
+edition = "2018"
 
 [[bin]]
 name = "popsicle-gtk"
 path = "src/main.rs"
 
 [dependencies]
-bus_writer = "0.1.1"
+atomic = "0.4.3"
+bus_writer = "0.1.2"
 cascade = "0.1.2"
-failure = "0.1.1"
-failure_derive = "0.1.1"
-gdk = "0.8.0"
-gtk = { version = "0.4.1", features = ["v3_22"] }
+crossbeam-channel = "0.1"
+digest = "0.7.2"
+gdk = "0.10.0"
+glib = "0.7.1"
+gtk = { version = "0.6.0", features = ["v3_22"] }
+hex-view = "0.1"
 humansize = "1.1.0"
 libc = "0.2"
+md-5 = "0.7.0"
+pango = "0.6.0"
+parking_lot = "0.6.3"
 popsicle = { path = ".." }
-pango = "0.4.0"
+proc-mounts = "0.1.2"
 pwd = "1.2"
 sha2 = "0.7.0"
-digest = "0.7.2"
-md-5 = "0.7.0"
-hex-view = "0.1"
+sys-mount = "1.1.0"
+sysfs-class = "0.1.1"
diff --git a/gtk/src/app/content/devices.rs b/gtk/src/app/content/devices.rs
deleted file mode 100644
index 3f9fbbc..0000000
--- a/gtk/src/app/content/devices.rs
+++ /dev/null
@@ -1,150 +0,0 @@
-use app::state::State;
-use block::BlockDevice;
-use gtk;
-use gtk::prelude::*;
-use humansize::{FileSize, file_size_opts as options};
-use popsicle;
-use std::path::Path;
-use std::sync::Arc;
-use super::View;
-
-pub struct DevicesView {
-    pub view: View,
-    pub list: DeviceList,
-}
-
-impl DevicesView {
-    pub fn new() -> DevicesView {
-        let select_all = gtk::CheckButton::new_with_label("Select all");
-        let list = gtk::ListBox::new();
-        list.get_style_context().map(|c| c.add_class("devices"));
-
-        let list_box = cascade! {
-            gtk::Box::new(gtk::Orientation::Vertical, 0);
-            ..pack_start(&select_all, false, false, 0);
-            ..pack_start(&gtk::Separator::new(gtk::Orientation::Horizontal), false, false, 0);
-            ..pack_start(&list, true, true, 0);
-        };
-
-        let select_scroller = cascade! {
-            gtk::ScrolledWindow::new(None, None);
-            ..add(&list_box);
-        };
-
-        let view = View::new(
-            "drive-removable-media-usb",
-            "Select Drives",
-            "Flashing will erase all data on the selected drives.",
-            |right_panel| right_panel.pack_start(&select_scroller, true, true, 0),
-        );
-
-        DevicesView {
-            view,
-            list: DeviceList {
-                list,
-                select_all,
-            }
-        }
-    }
-}
-
-pub struct DeviceList {
-    pub list:       gtk::ListBox,
-    pub select_all: gtk::CheckButton,
-}
-
-impl DeviceList {
-    pub fn clear(&self) {
-        self.list.get_children()
-            .into_iter()
-            .for_each(|widget| widget.destroy());
-    }
-
-    pub fn connect_select_all<F>(&self, state: Arc<State>, result: F )
-        where F: 'static + Fn(Result<(), String>)
-    {
-        self.select_all.connect_clicked(move |all| result(
-            state.devices.lock()
-                .map_err(|why| format!("devices mutex lock failed: {}", why))
-                .map(|ref devices| {
-                    devices.iter()
-                        .for_each(|&(_, ref device)| device.set_active(
-                            all.get_active() && device.is_sensitive()
-                        ))
-                }
-            )
-        ));
-    }
-
-    fn devices_differ(devices: &[String], device_list: &[(String, gtk::CheckButton)]) -> bool {
-        devices.iter()
-            .zip(device_list.iter())
-            .any(|(ref x, &(ref y, _))| x.as_str() != y.as_str())
-    }
-
-    pub fn refresh(
-        &self,
-        device_list: &mut Vec<(String, gtk::CheckButton)>,
-        devices: &[String],
-        image_sectors: u64,
-    ) -> Result<(), String> {
-        device_list.clear();
-        self.clear();
-
-        let mut all_is_sensitive = false;
-        for device in devices {
-            // Attempt to get the canonical path of the device.
-            // Display the error view if this fails.
-            let name = Path::new(&device).canonicalize()
-                .map_err(|why| format!("unable to get canonical path of '{}': {}", device, why))?;
-
-            let button = if let Some(block) = BlockDevice::new(&name) {
-                let block_sectors = block.sectors();
-                let too_small = block_sectors < image_sectors;
-
-                let button = gtk::CheckButton::new_with_label(&{
-                    let label = block.label();
-                    let name = name.to_string_lossy();
-                    let size = (block_sectors * 512).file_size(options::BINARY).unwrap();
-                    if too_small {
-                        format!("{} ({}, {}): Device is too small", &label, &name, size)
-                    } else {
-                        format!("{} ({}, {})", &label, &name, size)
-                    }
-                });
-
-                if too_small {
-                    button.set_tooltip_text("Device is too small");
-                    button.set_has_tooltip(true);
-                    button.set_sensitive(false);
-                } else {
-                    all_is_sensitive = true;
-                }
-                button
-            } else {
-                gtk::CheckButton::new_with_label(&name.to_string_lossy())
-            };
-
-            self.list.insert(&button, -1);
-            device_list.push((device.clone(), button));
-        }
-
-        self.list.show_all();
-        self.select_all.set_sensitive(all_is_sensitive);
-        Ok(())
-    }
-
-    pub fn requires_refresh(device_list: &[(String, gtk::CheckButton)]) -> Option<Vec<String>> {
-        let mut devices = vec![];
-        if let Err(why) = popsicle::get_disk_args(&mut devices) {
-            eprintln!("popsicle: unable to get devices: {}", why);
-            return None;
-        }
-
-        if devices.len() != device_list.len() || Self::devices_differ(&devices, &device_list) {
-            Some(devices)
-        } else {
-            None
-        }
-    }
-}
diff --git a/gtk/src/app/content/mod.rs b/gtk/src/app/content/mod.rs
deleted file mode 100644
index c21cdcf..0000000
--- a/gtk/src/app/content/mod.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-mod devices;
-mod error;
-mod flashing;
-mod images;
-mod summary;
-mod view;
-
-pub use self::devices::{DeviceList, DevicesView};
-pub use self::error::ErrorView;
-pub use self::flashing::FlashView;
-pub use self::images::ImageView;
-pub use self::summary::SummaryView;
-pub use self::view::View;
-
-use gtk::*;
-
-use super::set_margins;
-
-pub struct Content {
-    pub container:    Stack,
-    pub image_view:   ImageView,
-    pub devices_view: DevicesView,
-    pub error_view:   ErrorView,
-    pub flash_view:   FlashView,
-    pub summary_view: SummaryView,
-}
-
-impl Content {
-    pub fn new() -> Content {
-        let image_view = ImageView::new();
-        let devices_view = DevicesView::new();
-        let flash_view = FlashView::new();
-        let summary_view = SummaryView::new();
-        let error_view = ErrorView::new();
-
-        let container = cascade! {
-            container: Stack::new();
-            ..add_named(&image_view.view.container, "image");
-            ..add_named(&devices_view.view.container, "devices");
-            ..add_named(&flash_view.view.container, "flash");
-            ..add_named(&summary_view.view.container, "summary");
-            ..add_named(&error_view.view.container, "error");
-            ..set_visible_child_name("image");
-            | set_margins(&container, 12);
-        };
-
-        Content {
-            container,
-            image_view,
-            devices_view,
-            flash_view,
-            summary_view,
-            error_view,
-        }
-    }
-}
diff --git a/gtk/src/app/events/mod.rs b/gtk/src/app/events/mod.rs
new file mode 100644
index 0000000..7c02b06
--- /dev/null
+++ b/gtk/src/app/events/mod.rs
@@ -0,0 +1,131 @@
+use crate::block::BlockDevice;
+use crate::flash::FlashRequest;
+use crate::hash::hasher;
+use crossbeam_channel::{Receiver, Sender};
+use md5::Md5;
+use popsicle;
+use sha2::Sha256;
+use std::collections::HashMap;
+use std::io;
+use std::mem;
+use std::path::{Path, PathBuf};
+use std::thread::{self, JoinHandle};
+use std::time::Instant;
+
+pub enum UiEvent {
+    SetImageLabel(PathBuf),
+    RefreshDevices(Box<[BlockDevice]>),
+    SetHash(io::Result<String>),
+    Flash(JoinHandle<io::Result<Vec<io::Result<()>>>>),
+    Reset,
+}
+
+pub enum BackgroundEvent {
+    GenerateHash(PathBuf, &'static str),
+    RefreshDevices,
+}
+
+pub enum PrivilegedEvent {
+    Flash(FlashRequest),
+}
+
+/// Actions which require root authentication will be spawned as background threads from here.
+///
+/// This function should be called before `downgrade_permissions()`.
+pub fn privileged(events_tx: Sender<UiEvent>, events_rx: Receiver<PrivilegedEvent>) {
+    thread::spawn(move || {
+        while let Ok(PrivilegedEvent::Flash(request)) = events_rx.recv() {
+            let _ = events_tx.send(UiEvent::Flash(
+                thread::Builder::new()
+                    .stack_size(10 * 1024 * 1024)
+                    .spawn(move || request.write())
+                    .unwrap(),
+            ));
+        }
+    });
+}
+
+pub fn unprivileged(events_tx: Sender<UiEvent>, events_rx: Receiver<BackgroundEvent>) {
+    thread::spawn(move || {
+        let mut hashed: HashMap<(PathBuf, &'static str), String> = HashMap::new();
+
+        let mut devices = Vec::new();
+        let mut devices_cmp = Vec::new();
+
+        loop {
+            match events_rx.recv() {
+                Ok(BackgroundEvent::GenerateHash(path, kind)) => {
+                    // Check if the cache already contains this hash, and return it.
+                    if let Some(result) = hashed.get(&(path.clone(), kind)) {
+                        let _ = events_tx.send(UiEvent::SetHash(Ok(result.clone())));
+                        continue;
+                    }
+
+                    // Hash the file at the given path.
+                    let result = match kind {
+                        "MD5" => hasher::<Md5>(&path),
+                        "SHA256" => hasher::<Sha256>(&path),
+                        _ => Err(io::Error::new(
+                            io::ErrorKind::InvalidInput,
+                            "hash kind not supported",
+                        )),
+                    };
+
+                    // If successful, cache the result.
+                    if let Ok(ref result) = result {
+                        hashed.insert((path.clone(), kind), result.clone());
+                    }
+
+                    // Send this result back to the main thread.
+                    let _ = events_tx.send(UiEvent::SetHash(result));
+                }
+                Ok(BackgroundEvent::RefreshDevices) => {
+                    // Fetch the current list of USB devices from popsicle.
+                    match popsicle::get_disk_args(&mut devices_cmp) {
+                        Ok(()) => {
+                            if devices_cmp != devices {
+                                // If they differed, the new device vec is the current device vec.
+                                mem::swap(&mut devices_cmp, &mut devices);
+
+                                // Attempt to fetch more detailed information on the block devices.
+                                match fetch_block_devices(&devices) {
+                                    Ok(devices) => {
+                                        // Signal to the UI to refresh the list.
+                                        let _ = events_tx.send(UiEvent::RefreshDevices(devices));
+                                    }
+                                    Err(why) => eprintln!("failed to fetch block info: {}", why),
+                                }
+                            }
+
+                            devices_cmp.clear();
+                        }
+                        Err(why) => eprintln!("failed to refresh devices: {}", why),
+                    }
+                }
+                Err(_) => break,
+            }
+        }
+    });
+}
+
+fn fetch_block_devices(devices: &[String]) -> io::Result<Box<[BlockDevice]>> {
+    let mut output = Vec::new();
+    let start = Instant::now();
+
+    for device in devices {
+        if let Ok(ref device) = Path::new(&device).canonicalize() {
+            let mut block = BlockDevice::new_from(device)?;
+
+            // In the case of a device with a sector count of 0, it will be polled again.
+            // The polling will stop if there is no change after 5 seconds since this function
+            // began execution.
+            while block.sectors == 0 && Instant::now().duration_since(start).as_secs() < 5 {
+                block.recheck_size();
+            }
+
+            output.push(block);
+        }
+    }
+
+    Ok(output.into_boxed_slice())
+}
diff --git a/gtk/src/app/mod.rs b/gtk/src/app/mod.rs
index 771e845..b315829 100644
--- a/gtk/src/app/mod.rs
+++ b/gtk/src/app/mod.rs
@@ -1,528 +1,213 @@
-mod content;
-mod dialogs;
-mod header;
-mod misc;
-mod state;
-
-use self::content::{Content, DeviceList};
-pub use self::dialogs::OpenDialog;
-use self::header::Header;
-pub use self::misc::*;
-pub use self::state::{Connect, FlashTask, State, FLASHING, KILL, CANCELLED};
-
-// TODO: Use AtomicU64 / Bool when https://github.com/rust-lang/rust/issues/32976 is stable.
-
-use block::BlockDevice;
-use flash::FlashRequest;
-use gtk;
-use gtk::*;
-use hash::HashState;
-use popsicle::mnt::{self, MountEntry};
-use popsicle::{self, DiskError};
-use std::path::{Path, PathBuf};
-use std::process;
-use std::io;
-use std::rc::Rc;
-use std::sync::{Arc, Mutex};
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::mpsc::{Sender, Receiver};
-use std::thread;
-use std::time::{Duration, Instant};
-use std::fs::File;
-use std::thread::JoinHandle;
+pub mod events;
+pub mod signals;
+pub mod state;
+pub mod views;
+pub mod widgets;
+
+use self::events::*;
+use self::state::*;
+use self::views::*;
+use self::widgets::*;
+
+use gtk::{self, prelude::*};
+use std::{fs::File, process, rc::Rc, sync::Arc};
 
 const CSS: &str = include_str!("ui.css");
 
 pub struct App {
-    pub widgets: Rc<AppWidgets>,
-    pub state:   Arc<State>,
+    pub ui: Rc<GtkUi>,
+    pub state: Arc<State>,
 }
 
 impl App {
-    pub(crate) fn new(
-        hash: Arc<HashState>,
-        hash_request: Sender<(PathBuf, &'static str)>,
-        devices_request: Sender<(Vec<String>, Vec<MountEntry>)>,
-        devices_response: Receiver<Result<Vec<(String, File)>, DiskError>>,
-        flash_request: Sender<FlashRequest>,
-        flash_response: Receiver<JoinHandle<io::Result<Vec<io::Result<()>>>>>,
-    ) -> App {
-        // Initialize GTK before proceeding.
+    pub fn new(state: State) -> Self {
         if gtk::init().is_err() {
             eprintln!("failed to initialize GTK Application");
             process::exit(1);
         }
 
+        App { ui: Rc::new(GtkUi::new()), state: Arc::new(state) }
+    }
+
+    pub fn connect_events(self) -> Self {
+        self.connect_back();
+        self.connect_next();
+        self.connect_ui_events();
+        self.connect_image_chooser();
+        self.connect_image_drag_and_drop();
+        self.connect_hash();
+        self.connect_view_ready();
+
+        self
+    }
+
+    pub fn then_execute(self) {
+        self.ui.window.show_all();
+        gtk::main();
+    }
+}
+
+pub struct GtkUi {
+    window: gtk::Window,
+    header: Header,
+    content: Content,
+}
 
+impl GtkUi {
+    pub fn new() -> Self {
         // Create a the headerbar and it's associated content.
         let header = Header::new();
-        // Create the content container and all of it's widgets.
         let content = Content::new();
 
         // Create a new top level window.
         let window = cascade! {
-            Window::new(WindowType::Toplevel);
+            gtk::Window::new(gtk::WindowType::Toplevel);
             // Set the headerbar as the title bar widget.
             ..set_titlebar(&header.container);
             // Set the title of the window.
             ..set_title("Popsicle");
-            // Set the window manager class.
-            ..set_wmclass("popsicle", "Popsicle");
             // The default size of the window to create.
             ..set_default_size(500, 250);
-            // Add the content to the window.
             ..add(&content.container);
         };
 
         // Add a custom CSS style
         let screen = window.get_screen().unwrap();
-        let style = CssProvider::new();
-        let _ = CssProviderExt::load_from_data(&style, CSS.as_bytes());
-        StyleContext::add_provider_for_screen(&screen, &style, STYLE_PROVIDER_PRIORITY_USER);
+        let style = gtk::CssProvider::new();
+        let _ = gtk::CssProviderExt::load_from_data(&style, CSS.as_bytes());
+        gtk::StyleContext::add_provider_for_screen(
+            &screen,
+            &style,
+            gtk::STYLE_PROVIDER_PRIORITY_USER,
+        );
 
         // The icon the app will display.
-        Window::set_default_icon_name("iconname");
+        gtk::Window::set_default_icon_name("iconname");
 
         // Programs what to do when the exit button is used.
         window.connect_delete_event(move |_, _| {
-            main_quit();
-            Inhibit(false)
+            gtk::main_quit();
+            gtk::Inhibit(false)
         });
 
-        // Return the application structure.
-        App {
-            widgets: Rc::new(AppWidgets { window, header, content }),
-            state: Arc::new(State::new(hash, hash_request, devices_request, devices_response, flash_request, flash_response)),
-        }
+        GtkUi { header, window, content }
     }
-}
-
-pub struct AppWidgets {
-    pub window:  Window,
-    pub header:  Header,
-    pub content: Content,
-}
 
-impl AppWidgets {
-    pub fn set_image(&self, state: &State, image: &Path) {
-        let next = self.header.next.clone();
-        let image_label = self.content.image_view.image_path.clone();
-        let hash_button = self.content.image_view.hash.clone();
-
-        // TODO: Write an error message on failure.
-        if let Ok(file) = File::open(image) {
-            if let Ok(size) = file.metadata().map(|m| m.len() as usize) {
-                image_label.set_text(&image.file_name()
-                    .expect("file chooser can't select directories")
-                    .to_string_lossy());
-                *state.image.write().unwrap() = Some((image.to_path_buf(), size));
-                next.set_sensitive(true);
-                hash_button.set_sensitive(true);
+    pub fn errorck<T, E: ::std::fmt::Display>(
+        &self,
+        state: &State,
+        result: Result<T, E>,
+        context: &'static str,
+    ) -> Result<T, ()> {
+        match result {
+            Ok(value) => Ok(value),
+            Err(why) => {
+                self.content.error_view.view.description.set_text(&format!("{}: {}", context, why));
+                self.switch_to(state, ActiveView::Error);
+
+                Err(())
             }
         }
     }
 
-    pub fn switch_to_main(&self, state: &State) {
-        // If tasks are running, signify that tasks should be considered as completed.
-        if FLASHING == state.flash_state.load(Ordering::SeqCst) {
-            state.flash_state.store(KILL, Ordering::SeqCst);
+    pub fn errorck_option<T>(
+        &self,
+        state: &State,
+        result: Option<T>,
+        context: &'static str,
+    ) -> Result<T, ()> {
+        match result {
+            Some(value) => Ok(value),
+            None => {
+                self.content
+                    .error_view
+                    .view
+                    .description
+                    .set_text(&format!("{}: no value found", context));
+                self.switch_to(state, ActiveView::Error);
+
+                Err(())
+            }
         }
-
-        self.content.devices_view.list.select_all.set_active(false);
-
-        cascade! {
-            &self.content.container;
-            ..set_transition_type(StackTransitionType::SlideRight);
-            ..set_visible_child_name("image");
-        };
-
-        cascade! {
-            &self.header.back;
-            ..set_visible(true);
-            ..set_label("Cancel");
-            ..get_style_context().map(|c| {
-                c.remove_class("back-button");
-                c.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
-            });
-        };
-
-        cascade! {
-            &self.header.next;
-            ..set_visible(true);
-            ..set_label("Next");
-            ..set_sensitive(true);
-            ..get_style_context().map(|c| {
-                c.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
-                c.add_class(&gtk::STYLE_CLASS_SUGGESTED_ACTION);
-            });
-        };
-
-        state.view.set(0)
     }
 
-    pub fn switch_to_device_selection(&self, state: &State) {
-        let stack = &self.content.container;
+    pub fn switch_to(&self, state: &State, view: ActiveView) {
         let back = &self.header.back;
         let next = &self.header.next;
-        let list = &self.content.devices_view.list;
-
-        back.set_label("Back");
-        back.get_style_context().map(|c| {
-            c.add_class("back-button");
-        });
-        next.set_label("Flash");
-        next.get_style_context().map(|c| {
-            c.remove_class(&gtk::STYLE_CLASS_SUGGESTED_ACTION);
-            c.add_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
-        });
-        stack.set_visible_child_name("devices");
-
-        let image_sectors = {
-            let read_guard = state.image.read().unwrap();
-            (*read_guard).as_ref().map_or(0, |ref d| d.1 / 512 + 1) as u64
-        };
-
-        let mut devices = vec![];
-        if let Err(why) = popsicle::get_disk_args(&mut devices) {
-            eprintln!("popsicle: unable to get devices: {}", why);
-        }
-
-        if let Err(why) = state.devices.lock()
-            .map_err(|why| format!("mutex lock failed: {}", why))
-            .and_then(|ref mut device_list| {
-                list.refresh(device_list, &devices, image_sectors)
-            })
-        {
-            self.set_error(state, &why);
-        }
-    }
+        let stack = &self.content.container;
 
-    pub fn watch_device_selection(widgets: Rc<AppWidgets>, state: Arc<State>) {
-        gtk::timeout_add(16, move || {
-            let list = &widgets.content.devices_view.list;
-            let next = &widgets.header.next;
+        let back_ctx = back.get_style_context();
+        let next_ctx = next.get_style_context();
 
-            let image_length: usize = {
-                let read_guard = state.image.read().unwrap();
-                (*read_guard).as_ref().map_or(0, |ref d| d.1)
-            };
+        let widget = match view {
+            ActiveView::Images => {
+                back.set_label("Cancel");
+                back_ctx.remove_class("back-button");
+                back_ctx.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
 
-            if state.view.get() != 1 {
-                return gtk::Continue(false);
-            }
-
-            let mut disable_select_all = false;
-
-            if let Ok(ref mut device_list) = state.devices.try_lock() {
-                let mut check_refresh = || -> Result<(), String> {
-                    match DeviceList::requires_refresh(&device_list) {
-                        Some(devices) => {
-                            let image_sectors = (image_length / 512 + 1) as u64;
-                            list.refresh(device_list, &devices, image_sectors)?;
-                            disable_select_all = true;
-                            next.set_sensitive(false);
-                        }
-                        None => {
-                            next.set_sensitive(device_list.iter().any(|x| x.1.get_active()));
-                        }
-                    }
-
-                    Ok(())
-                };
+                next.set_visible(true);
+                next.set_sensitive(true);
+                next_ctx.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
+                next_ctx.add_class(&gtk::STYLE_CLASS_SUGGESTED_ACTION);
 
-                if let Err(why) = check_refresh() {
-                    widgets.set_error(&state, &why);
-                }
+                &self.content.image_view.view.container
             }
+            ActiveView::Devices => {
+                next_ctx.remove_class(&gtk::STYLE_CLASS_SUGGESTED_ACTION);
+                next_ctx.add_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
+                next.set_sensitive(false);
 
-            if disable_select_all {
-                list.select_all.set_active(false);
+                let _ = state.back_event_tx.send(BackgroundEvent::RefreshDevices);
+                &self.content.devices_view.view.container
             }
-
-            gtk::Continue(true)
-        });
-    }
-
-    pub fn switch_to_device_flashing(&self, state: &State) {
-        let back = &self.header.back;
-        let next = &self.header.next;
-        let stack = &self.content.container;
-        let summary_grid = &self.content.flash_view.progress_list;
-        let task_handles = &state.task_handles;
-        let bars = &state.bars;
-        let start = &state.start;
-        let tasks = &state.tasks;
-
-        macro_rules! try_or_error {
-            ($action:expr, $msg:expr) => {{
-                match $action {
-                    Ok(value) => value,
-                    Err(why) => {
-                        self.set_error(state, &format!("{}: {}", $msg, why));
-                        return;
-                    }
-                }
-            }}
-        }
-
-        // Wait for the flash state to be 0 before proceeding.
-        while state.flash_state.load(Ordering::SeqCst) != 0 {
-            thread::sleep(Duration::from_millis(16));
-        }
-
-        {
-            let path = {
-                let image_lock = try_or_error!(
-                    state.image.read(),
-                    "failed to lock buffer.data mutex"
-                );
-
-                let &(ref path, _) = image_lock.as_ref().unwrap();
-                path.clone()
-            };
-
-            let image = try_or_error!(
-                 File::open(path),
-                 "unable to open source for reading"
-            );
-
-            let device_list = try_or_error!(
-                state.devices.lock(),
-                "device list mutex lock failure"
-            );
-
-            let devs = device_list
-                .iter()
-                .filter(|x| x.1.get_active())
-                .map(|x| x.0.clone())
-                .collect::<Vec<_>>();
-
-            let mounts = try_or_error!(
-                mnt::get_submounts(Path::new("/")),
-                "unable to obtain mount points"
-            );
-
-            try_or_error!(
-                state.devices_request.send((devs, mounts.clone())),
-                "unable to send device request"
-            );
-
-            let disks_result = try_or_error!(
-                state.devices_response.recv(),
-                "unable to get device request response"
-            );
-
-            let disks = try_or_error!(
-                disks_result,
-                "unable to get devices"
-            );
-
-            back.get_style_context().map(|c| {
-                c.remove_class("back-button");
-                c.add_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
-            });
-
-            back.set_label("Cancel");
-            back.set_visible(true);
-            next.set_visible(false);
-            stack.set_visible_child_name("flash");
-
-            // Clear the progress bar summaries.
-            let mut bars = bars.borrow_mut();
-            bars.clear();
-            summary_grid.get_children().iter().for_each(|c| c.destroy());
-
-            *start.borrow_mut() = Instant::now();
-            let mut tasks = try_or_error!(
-                tasks.lock(),
-                "tasks mutex lock failure"
-            );
-
-            let mut task_handles = try_or_error!(
-                task_handles.lock(),
-                "task handles mutex lock failure"
-            );
-
-            state.flash_state.store(FLASHING, Ordering::SeqCst);
-
-            let mut destinations = Vec::new();
-
-            for (id, (disk_path, mut disk)) in disks.into_iter().enumerate() {
-                let id = id as i32;
-                let pbar = ProgressBar::new();
-                pbar.set_hexpand(true);
-
-                let label = {
-                    let disk_path = try_or_error!(
-                        Path::new(&disk_path).canonicalize(),
-                        format!("unable to get canonical path of {}", disk_path)
-                    );
-                    if let Some(block) = BlockDevice::new(&disk_path) {
-                        gtk::Label::new(
-                            [&block.label(), " (", &disk_path.to_string_lossy(), ")"]
-                                .concat()
-                                .as_str(),
-                        )
-                    } else {
-                        gtk::Label::new(disk_path.to_string_lossy().as_ref())
-                    }
-                };
-
-                label.set_justify(gtk::Justification::Right);
-                label.get_style_context().map(|c| c.add_class("bold"));
-                let bar_label = gtk::Label::new("");
-                bar_label.set_halign(gtk::Align::Center);
-
-                let bar_container = cascade! {
-                    gtk::Box::new(Orientation::Vertical, 0);
-                    ..pack_start(&pbar, false, false, 0);
-                    ..pack_start(&bar_label, false, false, 0);
+            ActiveView::Flashing => {
+                match self.errorck(
+                    &state,
+                    File::open(&*state.image_path.borrow()),
+                    "Failed to open ISO",
+                ) {
+                    Ok(file) => *state.image.borrow_mut() = Some(file),
+                    Err(()) => return,
                 };
 
-                summary_grid.attach(&label, 0, id, 1, 1);
-                summary_grid.attach(&bar_container, 1, id, 1, 1);
-                bars.push((pbar, bar_label));
-
-                destinations.push(disk);
-            }
-
-            let ndestinations = destinations.len();
-            let progress = Arc::new((0..ndestinations).map(|_| AtomicUsize::new(0)).collect::<Vec<_>>());
-            let finished = Arc::new((0..ndestinations).map(|_| AtomicUsize::new(0)).collect::<Vec<_>>());
-
-            // Spawn a thread that will update the progress value over time.
-            //
-            // The value will be stored within an intermediary atomic integer,
-            // because it is unsafe to send GTK widgets across threads.
-            *task_handles = {
-                let _ = state.flash_request.send(FlashRequest::new(
-                    image,
-                    destinations,
-                    state.flash_state.clone(),
-                    progress.clone(),
-                    finished.clone()
-                ));
-
-                Some(state.flash_response.recv().expect("expected join handle to be returned"))
-            };
-
-            *tasks = Some(FlashTask {
-                previous: Arc::new(Mutex::new(vec![[0; 7]; ndestinations])),
-                progress,
-                finished,
-            });
-        }
+                let all_devices = state.available_devices.borrow();
+                let mut devices = state.selected_devices.borrow_mut();
 
-        summary_grid.show_all();
-    }
+                devices.clear();
 
-    pub fn switch_to_summary(&self, state: &State, ntasks: usize) -> Result<(), ()> {
-        let stack = &self.content.container;
-        let back = &self.header.back;
-        let next = &self.header.next;
-        let description = &self.content.summary_view.view.description.clone();
-        let list = &self.content.summary_view.list.clone();
-        let devices = &state.devices;
-        let task_handles = &state.task_handles;
-
-        back.set_label("Flash Again");
-        back.get_style_context()
-            .map(|c| c.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION));
-
-        next.set_label("Done");
-        next.set_visible(true);
-        next.get_style_context()
-            .map(|c| c.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION));
-
-        stack.set_visible_child_name("summary");
-
-        macro_rules! try_or_error {
-            ($action:expr, $msg:expr) => {{
-                match $action {
-                    Ok(value) => value,
-                    Err(why) => {
-                        self.set_error(state, &format!("{}: {:?}", $msg, why));
-                        return Err(());
-                    }
+                for active_id in self.content.devices_view.get_active_ids() {
+                    devices.push(all_devices[active_id].clone());
                 }
-            }}
-        }
-
-        {
-            let mut errored: Vec<(String, io::Error)> = Vec::new();
-
-            let mut handle = try_or_error!(
-                task_handles.lock(),
-                "task handles mutex lock failure"
-            );
-
-            let results = try_or_error!(
-                handle.take().unwrap().join(),
-                "failed to join flashing thread"
-            );
-
-            let device_results = try_or_error!(
-                results,
-                "main flashing process failed"
-            );
 
-            let mut devices = try_or_error!(
-                devices.lock(),
-                "devices mutex lock failure"
-            );
+                back_ctx.remove_class("back-button");
+                back_ctx.add_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
 
-            for ((device, _), result) in devices.drain(..).zip(device_results.into_iter()) {
-                if let Err(why) = result {
-                    errored.push((device.clone(), why));
-                }
+                next.set_visible(false);
+                &self.content.flash_view.view.container
             }
-
-            if errored.is_empty() {
-                description.set_text(&format!("{} devices successfully flashed", ntasks));
-                list.set_visible(false);
-            } else {
-                description.set_text(&format!(
-                    "{} of {} devices successfully flashed",
-                    ntasks - errored.len(),
-                    ntasks
-                ));
-                list.set_visible(true);
-                for (device, why) in errored {
-                    let device = Label::new(device.as_str());
-                    let why = Label::new(format!("{}", why).as_str());
-                    let container = cascade! {
-                        Box::new(Orientation::Horizontal, 0);
-                        ..pack_start(&device, false, false, 0);
-                        ..pack_start(&why, true, true, 0);
-                    };
-                    list.insert(&container, -1);
-                }
+            ActiveView::Summary => {
+                back_ctx.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
+                back.set_label("Flash Again");
+
+                next_ctx.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
+                next.set_visible(true);
+                next.set_label("Done");
+                &self.content.summary_view.view.container
             }
-        }
-
-        state.reset();
+            ActiveView::Error => {
+                back.set_label("Flash Again");
+                back_ctx.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
 
-        Ok(())
-    }
+                next.set_visible(true);
+                next.set_label("Close");
+                next_ctx.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
+                next_ctx.remove_class(&gtk::STYLE_CLASS_SUGGESTED_ACTION);
 
-    pub fn set_error(&self, state: &State, msg: &str) {
-        let stack = &self.content.container;
-        let back = &self.header.back;
-        let error = &self.content.error_view.view.description;
-
-        back.set_visible(false);
-        cascade! {
-            &self.header.next;
-            ..set_visible(true);
-            ..set_label("Close");
-            ..get_style_context().map(|c| {
-                c.remove_class(&gtk::STYLE_CLASS_DESTRUCTIVE_ACTION);
-                c.remove_class(&gtk::STYLE_CLASS_SUGGESTED_ACTION);
-            });
+                &self.content.error_view.view.container
+            }
         };
-        error.set_text(&msg);
-        stack.set_visible_child_name("error");
-        state.view.set(2);
-        state.reset();
+
+        stack.set_visible_child(widget);
+        state.active_view.set(view);
     }
 }
diff --git a/gtk/src/app/signals/devices.rs b/gtk/src/app/signals/devices.rs
new file mode 100644
index 0000000..82d5c7d
--- /dev/null
+++ b/gtk/src/app/signals/devices.rs
@@ -0,0 +1,9 @@
+use crate::app::App;
+use gtk::prelude::*;
+
+impl App {
+    pub fn connect_view_ready(&self) {
+        let next = self.ui.header.next.clone();
+        self.ui.content.devices_view.connect_view_ready(move |ready| next.set_sensitive(ready));
+    }
+}
diff --git a/gtk/src/app/signals/images.rs b/gtk/src/app/signals/images.rs
new file mode 100644
index 0000000..eff7018
--- /dev/null
+++ b/gtk/src/app/signals/images.rs
@@ -0,0 +1,63 @@
+use crate::app::events::{BackgroundEvent, UiEvent};
+use crate::app::state::State;
+use crate::app::widgets::OpenDialog;
+use crate::app::{App, GtkUi};
+use crate::misc;
+use gtk::prelude::*;
+use std::path::{Path, PathBuf};
+
+impl App {
+    pub fn connect_image_chooser(&self) {
+        let state = self.state.clone();
+        let ui = self.ui.clone();
+        self.ui.content.image_view.chooser.connect_clicked(move |_| {
+            if let Some(path) = OpenDialog::new(None).run() {
+                let _ = state.ui_event_tx.send(UiEvent::SetImageLabel(path));
+                set_hash_widget(&state, &ui);
+            }
+        });
+    }
+
+    pub fn connect_hash(&self) {
+        let state = self.state.clone();
+        let ui = self.ui.clone();
+        self.ui.content.image_view.hash.connect_changed(move |_| {
+            set_hash_widget(&state, &ui);
+        });
+    }
+
+    pub fn connect_image_drag_and_drop(&self) {
+        let state = self.state.clone();
+        let ui = self.ui.clone();
+        let image_view = ui.content.image_view.view.container.clone();
+
+        misc::drag_and_drop(&image_view, move |data| {
+            if let Some(uri) = data.get_text() {
+                if uri.starts_with("file://") {
+                    let path = Path::new(&uri[7..uri.len() - 1]);
+                    if path.extension().map_or(false, |ext| ext == "iso" || ext == "img")
+                        && path.exists()
+                    {
+                        let _ = state.ui_event_tx.send(UiEvent::SetImageLabel(path.to_path_buf()));
+                        set_hash_widget(&state, &ui);
+                    }
+                }
+            }
+        });
+    }
+}
+
+fn set_hash_widget(state: &State, ui: &GtkUi) {
+    let hash = &ui.content.image_view.hash;
+
+    let path = state.image_path.borrow();
+    let kind = match hash.get_active() {
+        Some(1) => "SHA256",
+        Some(2) => "MD5",
+        _ => return,
+    };
+
+    ui.content.image_view.chooser_container.set_visible_child_name("checksum");
+
+    let _ = state.back_event_tx.send(BackgroundEvent::GenerateHash(PathBuf::from(&*path), kind));
+}
diff --git a/gtk/src/app/signals/mod.rs b/gtk/src/app/signals/mod.rs
new file mode 100644
index 0000000..300f371
--- /dev/null
+++ b/gtk/src/app/signals/mod.rs
@@ -0,0 +1,323 @@
+mod devices;
+mod images;
+
+use crate::app::events::{BackgroundEvent, PrivilegedEvent, UiEvent};
+use crate::app::state::ActiveView;
+use crate::app::App;
+use crate::flash::{FlashRequest, FlashStatus, FlashTask};
+use atomic::Atomic;
+use crossbeam_channel::TryRecvError;
+use gtk::{self, prelude::*};
+use std::fs::File;
+use std::sync::atomic::Ordering;
+use std::sync::{Arc, Mutex};
+use std::time::Instant;
+
+impl App {
+    pub fn connect_back(&self) {
+        let state = self.state.clone();
+        let ui = self.ui.clone();
+
+        self.ui.header.connect_back(move || {
+            let back = match state.active_view.get() {
+                ActiveView::Images => {
+                    gtk::main_quit();
+                    return;
+                }
+                _ => ActiveView::Images,
+            };
+
+            let _ = state.ui_event_tx.send(UiEvent::Reset);
+            ui.content.devices_view.reset();
+
+            ui.switch_to(&state, back);
+        });
+    }
+
+    pub fn connect_next(&self) {
+        let state = self.state.clone();
+        let ui = self.ui.clone();
+
+        self.ui.header.connect_next(move || {
+            let next = match state.active_view.get() {
+                ActiveView::Images => ActiveView::Devices,
+                ActiveView::Devices => ActiveView::Flashing,
+                _ => {
+                    gtk::main_quit();
+                    return;
+                }
+            };
+
+            ui.switch_to(&state, next);
+        });
+    }
+
+    pub fn connect_ui_events(&self) {
+        let state = self.state.clone();
+        let ui = self.ui.clone();
+
+        let mut last_device_refresh = Instant::now();
+        let mut flashing_devices: Vec<(gtk::ProgressBar, gtk::Label)> = Vec::new();
+        let flash_status = Arc::new(Atomic::new(FlashStatus::Inactive));
+        let mut flash_handles = None;
+        let mut tasks = None;
+
+        gtk::timeout_add(16, move || {
+            match state.ui_event_rx.try_recv() {
+                Err(TryRecvError::Disconnected) => return gtk::Continue(false),
+                Err(TryRecvError::Empty) => (),
+                Ok(UiEvent::SetHash(hash)) => {
+                    ui.content.image_view.set_hash(&match hash {
+                        Ok(hash) => hash,
+                        Err(why) => format!("error: {}", why),
+                    });
+
+                    ui.content.image_view.chooser_container.set_visible_child_name("chooser");
+                }
+                Ok(UiEvent::SetImageLabel(path)) => {
+                    if let Ok(file) = File::open(&path) {
+                        let image_size = file.metadata().ok().map_or(0, |m| m.len());
+
+                        ui.content.image_view.set_image_path(&path);
+                        ui.content.image_view.hash.set_sensitive(true);
+                        ui.content.image_view.hash_label.set_sensitive(true);
+                        ui.header.next.set_sensitive(true);
+
+                        state.image_size.store(image_size, Ordering::SeqCst);
+                        *state.image_path.borrow_mut() = path;
+                    }
+                }
+                Ok(UiEvent::RefreshDevices(devices)) => {
+                    let size = state.image_size.load(Ordering::SeqCst);
+                    ui.content.devices_view.refresh(&devices, size);
+                    *state.available_devices.borrow_mut() = devices;
+                }
+                Ok(UiEvent::Flash(handle)) => flash_handles = Some(handle),
+                Ok(UiEvent::Reset) => {
+                    match flash_status.load(Ordering::SeqCst) {
+                        FlashStatus::Active => {
+                            flash_status.store(FlashStatus::Killing, Ordering::SeqCst)
+                        }
+                        FlashStatus::Inactive | FlashStatus::Killing => (),
+                    }
+
+                    flash_handles = None;
+                    tasks = None;
+                    flashing_devices.clear();
+                }
+            }
+
+            match state.active_view.get() {
+                ActiveView::Devices => {
+                    let now = Instant::now();
+
+                    // Only attempt to refresh the devices if the last refresh was >= 3 seconds ago.
+                    if now.duration_since(last_device_refresh).as_secs() >= 3 {
+                        last_device_refresh = now;
+                        let _ = state.back_event_tx.send(BackgroundEvent::RefreshDevices);
+                    }
+                }
+                ActiveView::Flashing => match state.image.borrow_mut().take() {
+                    // When the flashing view is active, and an image has not started flashing.
+                    Some(image) => {
+                        let summary_grid = &ui.content.flash_view.progress_list;
+                        summary_grid.foreach(WidgetExt::destroy);
+                        let mut destinations = Vec::new();
+
+                        let selected_devices = state.selected_devices.borrow_mut();
+                        for (id, device) in selected_devices.iter().enumerate() {
+                            let id = id as i32;
+
+                            let pbar = cascade! {
+                                gtk::ProgressBar::new();
+                                ..set_hexpand(true);
+                            };
+
+                            let label = cascade! {
+                                gtk::Label::new(device.label().as_str());
+                                ..set_justify(gtk::Justification::Right);
+                                ..get_style_context().add_class("bold");
+                            };
+
+                            let bar_label = cascade! {
+                                gtk::Label::new(None);
+                                ..set_halign(gtk::Align::Center);
+                            };
+
+                            let bar_container = cascade! {
+                                gtk::Box::new(gtk::Orientation::Vertical, 0);
+                                ..add(&pbar);
+                                ..add(&bar_label);
+                            };
+
+                            summary_grid.attach(&label, 0, id, 1, 1);
+                            summary_grid.attach(&bar_container, 1, id, 1, 1);
+
+                            flashing_devices.push((pbar, bar_label));
+                            destinations.push(device.path.clone());
+                        }
+
+                        summary_grid.show_all();
+                        let ndestinations = destinations.len();
+                        let progress = Arc::new(
+                            (0..ndestinations).map(|_| Atomic::new(0u64)).collect::<Vec<_>>(),
+                        );
+                        let finished = Arc::new(
+                            (0..ndestinations).map(|_| Atomic::new(false)).collect::<Vec<_>>(),
+                        );
+
+                        let _ =
+                            state.priv_event_tx.send(PrivilegedEvent::Flash(FlashRequest::new(
+                                image,
+                                destinations,
+                                flash_status.clone(),
+                                progress.clone(),
+                                finished.clone(),
+                            )));
+
+                        tasks = Some(FlashTask {
+                            previous: Arc::new(Mutex::new(vec![[0; 7]; ndestinations])),
+                            progress,
+                            finished,
+                        });
+                    }
+                    // When the flashing view is active, and thus an image is flashing.
+                    None => {
+                        let now = Instant::now();
+
+                        // Only attempt to refresh the devices if the last refresh was >= 500ms ago.
+                        let time_since = now.duration_since(last_device_refresh);
+                        if time_since.as_secs() > 1 || time_since.subsec_millis() >= 500 {
+                            last_device_refresh = now;
+
+                            let mut all_tasks_finished = true;
+                            let length = state.image_size.load(Ordering::SeqCst);
+                            let tasks = tasks.as_mut().expect("no flash task");
+                            let mut previous = tasks.previous.lock().expect("mutex lock");
+
+                            for (id, &(ref pbar, ref label)) in flashing_devices.iter().enumerate()
+                            {
+                                let prev_values = &mut previous[id];
+                                let progress = &tasks.progress[id];
+                                let finished = &tasks.finished[id];
+
+                                let raw_value = progress.load(Ordering::SeqCst);
+                                let task_is_finished = finished.load(Ordering::SeqCst);
+                                let value = if task_is_finished {
+                                    1.0f64
+                                } else {
+                                    all_tasks_finished = false;
+                                    raw_value as f64 / length as f64
+                                };
+
+                                pbar.set_fraction(value);
+
+                                if task_is_finished {
+                                    label.set_label("Complete");
+                                } else {
+                                    prev_values[1] = prev_values[2];
+                                    prev_values[2] = prev_values[3];
+                                    prev_values[3] = prev_values[4];
+                                    prev_values[4] = prev_values[5];
+                                    prev_values[5] = prev_values[6];
+                                    prev_values[6] = raw_value - prev_values[0];
+                                    prev_values[0] = raw_value;
+
+                                    let sum: u64 = prev_values.iter().skip(1).sum();
+                                    let per_second = sum / 3;
+                                    label.set_label(&if per_second > (1024 * 1024) {
+                                        format!("{} MiB/s", per_second / (1024 * 1024))
+                                    } else {
+                                        format!("{} KiB/s", per_second / 1024)
+                                    });
+                                }
+                            }
+
+                            drop(previous);
+
+                            if all_tasks_finished {
+                                eprintln!("all tasks finished");
+
+                                let taken_handles = match ui.errorck_option(
+                                    &state,
+                                    flash_handles.take(),
+                                    "Taking flash handles failed",
+                                ) {
+                                    Ok(results) => {
+                                        results.join().map_err(|why| format!("{:?}", why))
+                                    }
+                                    Err(()) => return gtk::Continue(true),
+                                };
+
+                                let handle = match ui.errorck(
+                                    &state,
+                                    taken_handles,
+                                    "Failed to join flash thread",
+                                ) {
+                                    Ok(results) => results,
+                                    Err(()) => return gtk::Continue(true),
+                                };
+
+                                let results = match ui.errorck(
+                                    &state,
+                                    handle,
+                                    "Errored starting flashing process",
+                                ) {
+                                    Ok(results) => results,
+                                    Err(()) => return gtk::Continue(true),
+                                };
+
+                                let mut errors = Vec::new();
+                                let mut selected_devices = state.selected_devices.borrow_mut();
+                                let ntasks = selected_devices.len();
+
+                                for (device, result) in
+                                    selected_devices.drain(..).zip(results.into_iter())
+                                {
+                                    if let Err(why) = result {
+                                        errors.push((device, why));
+                                    }
+                                }
+
+                                ui.switch_to(&state, ActiveView::Summary);
+                                let list = &ui.content.summary_view.list;
+                                let description = &ui.content.summary_view.view.description;
+
+                                if errors.is_empty() {
+                                    let desc = format!("{} devices successfully flashed", ntasks);
+                                    description.set_text(&desc);
+                                    list.hide();
+                                } else {
+                                    let desc = format!(
+                                        "{} of {} devices successfully flashed",
+                                        ntasks - errors.len(),
+                                        ntasks
+                                    );
+
+                                    description.set_text(&desc);
+                                    list.show();
+
+                                    for (device, why) in errors {
+                                        let device = gtk::Label::new(device.label().as_str());
+                                        let why = gtk::Label::new(format!("{}", why).as_str());
+
+                                        let container = cascade! {
+                                            gtk::Box::new(gtk::Orientation::Horizontal, 0);
+                                            ..pack_start(&device, false, false, 0);
+                                            ..pack_start(&why, true, true, 0);
+                                        };
+
+                                        list.insert(&container, -1);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                _ => (),
+            }
+
+            gtk::Continue(true)
+        });
+    }
+}
diff --git a/gtk/src/app/state.rs b/gtk/src/app/state.rs
deleted file mode 100644
index fc1bdf1..0000000
--- a/gtk/src/app/state.rs
+++ /dev/null
@@ -1,374 +0,0 @@
-use flash::FlashRequest;
-
-use super::{App, OpenDialog};
-use app::{misc, AppWidgets};
-use hash::HashState;
-use std::io;
-use std::fs::File;
-use std::cell::{Cell, RefCell};
-use std::mem;
-use std::path::{Path, PathBuf};
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::mpsc::{Sender, Receiver};
-use std::sync::{Arc, Mutex, RwLock};
-use std::thread::JoinHandle;
-use std::time::Instant;
-
-use gtk;
-use gtk::*;
-use popsicle::mnt::MountEntry;
-use popsicle::DiskError;
-
-pub const FLASHING: usize = 1;
-pub const KILL: usize = 2;
-pub const CANCELLED: usize = 3;
-
-/// Contains all of the state that needs to be shared across the program's lifetime.
-pub struct State {
-    /// Contains all of the progress bars in the flash view.
-    pub bars: RefCell<Vec<(ProgressBar, Label)>>,
-    /// Contains a list of devices detected, and their check buttons.
-    pub devices: Mutex<Vec<(String, CheckButton)>>,
-    /// Manages the state of hash requests
-    pub(crate) hash: Arc<HashState>,
-    /// Requests the background thread to generate a new hash.
-    pub(crate) hash_request: Sender<(PathBuf, &'static str)>,
-    /// Points to the location of the image to be flashed.
-    pub image: RwLock<Option<(PathBuf, usize)>>,
-    /// Stores the time when the flashing process began.
-    pub start: RefCell<Instant>,
-    /// Holds the task threads that write the image to each device.
-    /// The handles may contain errors when joined, for printing on the summary page.
-    pub task_handles: Mutex<Option<JoinHandle<io::Result<Vec<io::Result<()>>>>>>,
-    /// Contains progress data regarding each active flash task -- namely the progress.
-    pub tasks: Mutex<Option<FlashTask>>,
-    /// Stores an integer which defines the currently-active view.
-    pub view: Cell<u8>,
-    /// Requests for a list of devices to be returned by an authenticated user (ie: root).
-    pub devices_request: Sender<(Vec<String>, Vec<MountEntry>)>,
-    /// The accompanying response that follows a device request.
-    pub devices_response: Receiver<Result<Vec<(String, File)>, DiskError>>,
-    /// Requests for a device to be flashed by an authenticated user (ie: root).
-    pub flash_request: Sender<FlashRequest>,
-    /// Contains the join handle to the thread where the task is being flashed.
-    pub flash_response: Receiver<JoinHandle<io::Result<Vec<io::Result<()>>>>>,
-    /// Signifies the status of flashing
-    pub flash_state: Arc<AtomicUsize>,
-}
-
-impl State {
-    /// Initailizes a new structure for managing the state of the application.
-    pub(crate) fn new(
-        hash: Arc<HashState>,
-        hash_request: Sender<(PathBuf, &'static str)>,
-        devices_request: Sender<(Vec<String>, Vec<MountEntry>)>,
-        devices_response: Receiver<Result<Vec<(String, File)>, DiskError>>,
-        flash_request: Sender<FlashRequest>,
-        flash_response: Receiver<JoinHandle<io::Result<Vec<io::Result<()>>>>>,
-    ) -> State {
-        State {
-            bars: RefCell::new(Vec::new()),
-            devices: Mutex::new(Vec::new()),
-            task_handles: Mutex::new(None),
-            tasks: Mutex::new(None),
-            view: Cell::new(0),
-            start: RefCell::new(unsafe { mem::uninitialized() }),
-            flash_state: Arc::new(AtomicUsize::new(0)),
-            hash,
-            hash_request,
-            image: RwLock::new(None),
-            devices_request,
-            devices_response,
-            flash_request,
-            flash_response,
-        }
-    }
-
-    pub fn reset(&self) {
-        self.bars.borrow_mut().clear();
-        self.devices.lock().unwrap().clear();
-        *self.tasks.lock().unwrap() = None;
-        self.flash_state.store(0, Ordering::SeqCst);
-    }
-}
-
-pub struct FlashTask {
-    pub progress: Arc<Vec<AtomicUsize>>,
-    pub previous: Arc<Mutex<Vec<[usize; 7]>>>,
-    pub finished: Arc<Vec<AtomicUsize>>,
-}
-
-
-pub struct Connected(App);
-
-impl Connected {
-    /// Display the window, and execute the gtk main event loop.
-    pub fn then_execute(self) {
-        self.0.widgets.window.show_all();
-        gtk::main();
-    }
-}
-
-pub trait Connect {
-    /// Creates external state, and maps all of the UI functionality to the UI.
-    fn connect_events(self) -> Connected;
-
-    /// Programs the button for selecting an image.
-    fn connect_image_chooser(&self);
-
-    /// Sets the image via a drag and drop.
-    fn connect_image_drag_and_drop(&self);
-
-    /// Programs the combo box which generates the hash sum for initial image selection view.
-    fn connect_hash_generator(&self);
-
-    /// Programs the back button, whose behavior changes based on the currently active view.
-    fn connect_back_button(&self);
-
-    /// Programs the next button, whose behavior changes based on the currently active view.
-    fn connect_next_button(&self);
-
-    /// Programs the action that will be performed when the check all button is clicked.
-    fn connect_check_all(&self);
-
-    /// Adds a function for GTK to execute when the application is idle, to monitor and
-    /// update the progress bars for devices that are being flashed, and to generate
-    /// the summary view after all devices have been flashed.
-    fn watch_flashing_devices(&self);
-}
-
-impl Connect for App {
-    fn connect_events(self) -> Connected {
-        self.connect_image_chooser();
-        self.connect_image_drag_and_drop();
-        self.connect_hash_generator();
-        self.connect_back_button();
-        self.connect_next_button();
-        self.connect_check_all();
-        self.watch_flashing_devices();
-
-        Connected(self)
-    }
-
-    fn connect_image_chooser(&self) {
-        let state = self.state.clone();
-        let widgets = self.widgets.clone();
-        self.widgets.content.image_view.chooser.connect_clicked(move |_| {
-            if let Some(path) = OpenDialog::new(None).run() {
-                widgets.set_image(&state, &path);
-            }
-        });
-    }
-
-    fn connect_image_drag_and_drop(&self) {
-        let state = self.state.clone();
-        let widgets = self.widgets.clone();
-        let image_view = widgets.content.image_view.view.container.clone();
-
-        misc::drag_and_drop(&image_view, move |data| {
-            if let Some(uri) = data.get_text() {
-                if uri.starts_with("file://") {
-                    let path = Path::new(&uri[7..uri.len() - 1]);
-                    if path.extension().map_or(false, |ext| ext == "iso" || ext == "img") && path.exists() {
-                        widgets.set_image(&state, path);
-                    }
-                }
-            }
-        });
-    }
-
-    fn connect_hash_generator(&self) {
-        let state = self.state.clone();
-        let hash_label = self.widgets.content.image_view.hash_label.clone();
-        let chooser = self.widgets.content.image_view.chooser_container.clone();
-
-        self.widgets.content
-            .image_view
-            .hash
-            .connect_changed(move |hash_kind| {
-                if let Some((ref path, _)) = *state.image.read().unwrap() {
-                    let hash_kind = match hash_kind.get_active() {
-                        1 => Some("SHA256"),
-                        2 => Some("MD5"),
-                        _ => None,
-                    };
-
-                    if let Some(hash_kind) = hash_kind {
-                        let hash = state.hash.clone();
-
-                        let _ = state.hash_request.send((path.clone(), hash_kind));
-
-                        let hash_label = hash_label.clone();
-                        let path = path.clone();
-                        let chooser = chooser.clone();
-                        gtk::timeout_add(16, move || {
-                            match hash.try_obtain(&path, hash_kind) {
-                                Some(hash) => {
-                                    hash_label.set_text(&hash);
-                                    chooser.set_visible_child_name("chooser");
-                                    Continue(false)
-                                }
-                                None => {
-                                    chooser.set_visible_child_name("checksum");
-                                    Continue(true)
-                                }
-                            }
-                        });
-                    }
-                }
-            });
-    }
-
-    fn connect_back_button(&self) {
-        let widgets = self.widgets.clone();
-        let state = self.state.clone();
-        self.widgets.header.back.connect_clicked(move |_| {
-            match state.view.get() {
-                0 => {
-                    gtk::main_quit();
-                    return;
-                },
-                _ => widgets.switch_to_main(&state),
-            }
-        });
-    }
-
-    fn connect_next_button(&self) {
-        #[allow(unused_variables)]
-        let widgets = self.widgets.clone();
-        let next = widgets.header.next.clone();
-        let stack = widgets.content.container.clone();
-        let state = self.state.clone();
-
-        next.connect_clicked(move |_| {
-            let view = &state.view;
-            let view_value = view.get();
-            stack.set_transition_type(StackTransitionType::SlideLeft);
-
-            match view_value {
-                0 => widgets.switch_to_device_selection(&state),
-                1 => widgets.switch_to_device_flashing(&state),
-                2 => gtk::main_quit(),
-                _ => unreachable!(),
-            }
-
-            view.set(view_value + 1);
-
-            if view.get() == 1 {
-                AppWidgets::watch_device_selection(widgets.clone(), state.clone());
-            }
-        });
-    }
-
-    fn connect_check_all(&self) {
-        let state = self.state.clone();
-        let widgets = self.widgets.clone();
-        widgets.clone().content.devices_view.list.connect_select_all(
-            state.clone(),
-            move |result| if let Err(why) = result {
-                widgets.set_error(&state, &format!("select all failed: {}", why));
-            }
-        )
-    }
-
-    fn watch_flashing_devices(&self) {
-        let state = self.state.clone();
-        let widgets = self.widgets.clone();
-
-        gtk::timeout_add(500, move || {
-            let tasks = &state.tasks;
-            let bars = &state.bars;
-
-            macro_rules! try_or_error {
-                ($action:expr, $msg:expr, $val:expr) => {{
-                    match $action {
-                        Ok(value) => value,
-                        Err(why) => {
-                            widgets.set_error(&state, &format!("{}: {:?}", $msg, why));
-                            return $val;
-                        }
-                    }
-                }}
-            }
-
-            let mut all_tasks_finished = true;
-            let ntasks;
-
-            {
-                // Ensure that an image has been selected before continuing
-                let length = match *state.image.read().unwrap() {
-                    Some(ref image) => image.1,
-                    None => {
-                        return Continue(true);
-                    }
-                };
-
-                let tasks = try_or_error!(
-                    tasks.lock(),
-                    "tasks mutex lock failure",
-                    Continue(false)
-                );
-
-                let tasks = match tasks.as_ref() {
-                    Some(tasks) => tasks,
-                    None => return Continue(true),
-                };
-
-                ntasks = tasks.progress.len();
-
-                let mut previous = try_or_error!(
-                    tasks.previous.lock(),
-                    "tasks.previous mutex lock failure",
-                    Continue(false)
-                );
-
-                for (id, &(ref pbar, ref label)) in bars.borrow().iter().enumerate() {
-                    let prev_values = &mut previous[id];
-                    let progress = &tasks.progress[id];
-                    let finished = &tasks.finished[id];
-
-                    let raw_value = progress.load(Ordering::SeqCst);
-                    let task_is_finished = finished.load(Ordering::SeqCst) == 1;
-                    let value = if task_is_finished {
-                        1.0f64
-                    } else {
-                        all_tasks_finished = false;
-                        raw_value as f64 / length as f64
-                    };
-
-                    pbar.set_fraction(value);
-
-                    if task_is_finished {
-                        label.set_label("Complete");
-                    } else {
-                        prev_values[1] = prev_values[2];
-                        prev_values[2] = prev_values[3];
-                        prev_values[3] = prev_values[4];
-                        prev_values[4] = prev_values[5];
-                        prev_values[5] = prev_values[6];
-                        prev_values[6] = raw_value - prev_values[0];
-                        prev_values[0] = raw_value;
-
-                        let sum: usize = prev_values.iter().skip(1).sum();
-                        let per_second = sum / 3;
-                        label.set_label(&if per_second > (1024 * 1024) {
-                            format!("{} MiB/s", per_second / (1024 * 1024))
-                        } else {
-                            format!("{} KiB/s", per_second / 1024)
-                        });
-                    }
-                }
-            }
-
-            match state.flash_state.load(Ordering::SeqCst) {
-                stat if stat == CANCELLED => state.reset(),
-                stat if stat == FLASHING => if all_tasks_finished && widgets.switch_to_summary(&state, ntasks).is_err() {
-                    return Continue(false);
-                },
-                _ => ()
-            }
-
-            Continue(true)
-        });
-    }
-}
diff --git a/gtk/src/app/state/mod.rs b/gtk/src/app/state/mod.rs
new file mode 100644
index 0000000..ef61b5b
--- /dev/null
+++ b/gtk/src/app/state/mod.rs
@@ -0,0 +1,81 @@
+use crate::app::events::{self, BackgroundEvent, PrivilegedEvent, UiEvent};
+use crate::block::BlockDevice;
+use atomic::Atomic;
+use crossbeam_channel::{unbounded, Receiver, Sender};
+use libc;
+use std::cell::{Cell, RefCell};
+use std::env;
+use std::fs::File;
+use std::path::PathBuf;
+use std::sync::Arc;
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ActiveView {
+    Images,
+    Devices,
+    Flashing,
+    Summary,
+    Error,
+}
+
+pub struct State {
+    pub ui_event_tx: Sender<UiEvent>,
+    pub ui_event_rx: Receiver<UiEvent>,
+    pub back_event_tx: Sender<BackgroundEvent>,
+    pub priv_event_tx: Sender<PrivilegedEvent>,
+
+    pub active_view: Cell<ActiveView>,
+
+    pub image: RefCell<Option<File>>,
+    pub image_path: RefCell<PathBuf>,
+    pub image_size: Arc<Atomic<u64>>,
+
+    pub available_devices: RefCell<Box<[BlockDevice]>>,
+    pub selected_devices: RefCell<Vec<BlockDevice>>,
+}
+
+impl State {
+    pub fn new() -> Self {
+        let (back_event_tx, back_event_rx) = unbounded();
+        let (priv_event_tx, priv_event_rx) = unbounded();
+        let (ui_event_tx, ui_event_rx) = unbounded();
+
+        events::privileged(ui_event_tx.clone(), priv_event_rx);
+
+        // If running in pkexec or sudo, restore home directory for open dialog,
+        // and then downgrade permissions back to a regular user.
+        if let Ok(pkexec_uid) = env::var("PKEXEC_UID").or_else(|_| env::var("SUDO_UID")) {
+            if let Ok(uid) = pkexec_uid.parse::<u32>() {
+                if let Some(passwd) = pwd::Passwd::from_uid(uid) {
+                    env::set_var("HOME", passwd.dir);
+                    downgrade_permissions(passwd.uid, passwd.gid);
+                }
+            }
+        }
+
+        events::unprivileged(ui_event_tx.clone(), back_event_rx);
+
+        Self {
+            ui_event_rx,
+            ui_event_tx,
+            back_event_tx,
+            priv_event_tx,
+            active_view: Cell::new(ActiveView::Images),
+            image: RefCell::new(None),
+            image_path: RefCell::new(PathBuf::new()),
+            image_size: Arc::new(Atomic::new(0u64)),
+            available_devices: RefCell::new(Box::new([])),
+            selected_devices: RefCell::new(Vec::new()),
+        }
+    }
+}
+
+/// Downgrades the permissions of the current thread to the specified user and group ID.
+fn downgrade_permissions(uid: u32, gid: u32) {
+    unsafe {
+        // By using system calls directly, we apply this on a per-thread basis.
+        // The setresuid() and setresguid() functions apply to all threads.
+        libc::syscall(libc::SYS_setresgid, gid, gid, gid);
+        libc::syscall(libc::SYS_setresuid, uid, uid, uid);
+    }
+}
diff --git a/gtk/src/app/views/devices.rs b/gtk/src/app/views/devices.rs
new file mode 100644
index 0000000..d3e4806
--- /dev/null
+++ b/gtk/src/app/views/devices.rs
@@ -0,0 +1,135 @@
+use super::View;
+use crate::block::BlockDevice;
+use gtk;
+use gtk::prelude::*;
+use std::cell::{Cell, RefCell};
+use std::rc::Rc;
+
+type ViewReadySignal = Rc<RefCell<Box<dyn Fn(bool)>>>;
+
+pub struct DevicesView {
+    pub view: View,
+    pub list: gtk::ListBox,
+    pub select_all: gtk::CheckButton,
+    view_ready: ViewReadySignal,
+}
+
+impl DevicesView {
+    pub fn new() -> DevicesView {
+        let list = cascade! {
+            gtk::ListBox::new();
+            ..get_style_context().add_class("devices");
+            ..set_hexpand(true);
+            ..set_vexpand(true);
+        };
+
+        let list_ = list.clone();
+        let select_all = cascade! {
+            gtk::CheckButton::new_with_label("Select all");
+            ..set_margin_start(4);
+            ..set_margin_bottom(3);
+            ..connect_toggled(move |all| {
+                let state = all.get_active();
+
+                for row in list_.get_children() {
+                    if let Ok(row) = row.downcast::<gtk::ListBoxRow>() {
+                        if let Some(widget) = row.get_children().get(0) {
+                            if let Some(button) = widget.downcast_ref::<gtk::CheckButton>() {
+                                button.set_active(button.get_sensitive() && state);
+                            }
+                        }
+                    }
+                }
+            });
+        };
+
+        let list_box = cascade! {
+            gtk::Box::new(gtk::Orientation::Vertical, 0);
+            ..add(&select_all);
+            ..add(&gtk::Separator::new(gtk::Orientation::Horizontal));
+            ..add(&list);
+        };
+
+        let select_scroller = cascade! {
+            gtk::ScrolledWindow::new(gtk::NONE_ADJUSTMENT, gtk::NONE_ADJUSTMENT);
+            ..set_hexpand(true);
+            ..set_vexpand(true);
+            ..add(&list_box);
+        };
+
+        let view = View::new(
+            "drive-removable-media-usb",
+            "Select Drives",
+            "Flashing will erase all data on the selected drives.",
+            |right_panel| right_panel.add(&select_scroller),
+        );
+
+        let view_ready: ViewReadySignal = Rc::new(RefCell::new(Box::new(|_| ())));
+
+        DevicesView { view, list, select_all, view_ready }
+    }
+
+    pub fn get_buttons(&self) -> impl Iterator<Item = gtk::CheckButton> {
+        self.list
+            .get_children()
+            .into_iter()
+            .filter_map(|row| row.downcast::<gtk::ListBoxRow>().ok())
+            .filter_map(|row| row.get_children().get(0).cloned())
+            .filter_map(|row| row.downcast::<gtk::CheckButton>().ok())
+    }
+
+    pub fn get_active_ids(&self) -> impl Iterator<Item = usize> {
+        self.get_buttons()
+            .enumerate()
+            .filter_map(|(id, button)| if button.get_active() { Some(id) } else { None })
+    }
+
+    pub fn refresh(&self, devices: &[BlockDevice], image_size: u64) {
+        self.list.foreach(WidgetExt::destroy);
+
+        let nselected = Rc::new(Cell::new(0));
+
+        for device in devices {
+            let valid_size = device.size_in_bytes() >= image_size;
+
+            let name = if valid_size {
+                format!("<b>{}</b>", device.label())
+            } else {
+                format!("<b>{}: Device too small</b>", device.label())
+            };
+
+            let view_ready = self.view_ready.clone();
+            let nselected = nselected.clone();
+
+            let row = cascade! {
+                gtk::CheckButton::new();
+                ..set_sensitive(valid_size);
+                ..add(&cascade! {
+                    gtk::Label::new(name.as_str());
+                    ..set_use_markup(true);
+                });
+                ..connect_toggled(move |button| {
+                    if button.get_active() {
+                        nselected.set(nselected.get() + 1);
+                    } else {
+                        nselected.set(nselected.get() - 1);
+                    }
+
+                    (*view_ready.borrow())(nselected.get() != 0);
+                });
+            };
+            self.list.insert(&row, -1);
+        }
+
+        self.list.show_all();
+    }
+
+    pub fn reset(&self) {
+        self.select_all.set_active(false);
+        self.get_buttons().for_each(|c| c.set_active(false));
+    }
+
+    pub fn connect_view_ready<F: Fn(bool) + 'static>(&self, func: F) {
+        *self.view_ready.borrow_mut() = Box::new(func);
+    }
+}
diff --git a/gtk/src/app/content/error.rs b/gtk/src/app/views/error.rs
similarity index 51%
rename from gtk/src/app/content/error.rs
rename to gtk/src/app/views/error.rs
index 5690c01..26f78bd 100644
--- a/gtk/src/app/content/error.rs
+++ b/gtk/src/app/views/error.rs
@@ -6,8 +6,6 @@ pub struct ErrorView {
 
 impl ErrorView {
     pub fn new() -> ErrorView {
-        ErrorView {
-            view: View::new("dialog-error", "Critical Error Occurred", "", |_| ()),
-        }
+        ErrorView { view: View::new("dialog-error", "Critical Error Occurred", "", |_| ()) }
     }
 }
diff --git a/gtk/src/app/content/flashing.rs b/gtk/src/app/views/flashing.rs
similarity index 81%
rename from gtk/src/app/content/flashing.rs
rename to gtk/src/app/views/flashing.rs
index 82ec39d..b12e608 100644
--- a/gtk/src/app/content/flashing.rs
+++ b/gtk/src/app/views/flashing.rs
@@ -2,7 +2,7 @@ use super::View;
 use gtk::*;
 
 pub struct FlashView {
-    pub view:          View,
+    pub view: View,
     pub progress_list: Grid,
 }
 
@@ -15,7 +15,7 @@ impl FlashView {
         };
 
         let progress_scroller = cascade! {
-            ScrolledWindow::new(None, None);
+            ScrolledWindow::new(gtk::NONE_ADJUSTMENT, gtk::NONE_ADJUSTMENT);
             ..add(&progress_list);
         };
 
@@ -26,9 +26,6 @@ impl FlashView {
             |right_panel| right_panel.pack_start(&progress_scroller, true, true, 0),
         );
 
-        FlashView {
-            view,
-            progress_list,
-        }
+        FlashView { view, progress_list }
     }
 }
diff --git a/gtk/src/app/content/images.rs b/gtk/src/app/views/images.rs
similarity index 78%
rename from gtk/src/app/content/images.rs
rename to gtk/src/app/views/images.rs
index 30419e3..229577b 100644
--- a/gtk/src/app/content/images.rs
+++ b/gtk/src/app/views/images.rs
@@ -1,15 +1,15 @@
 use super::View;
 use gtk::*;
 use pango::EllipsizeMode;
-use super::set_margins;
+use std::path::Path;
 
 pub struct ImageView {
-    pub view:              View,
+    pub view: View,
     pub chooser_container: Stack,
-    pub chooser:           Button,
-    pub image_path:        Label,
-    pub hash:              ComboBoxText,
-    pub hash_label:        Entry,
+    pub chooser: Button,
+    pub image_path: Label,
+    pub hash: ComboBoxText,
+    pub hash_label: Entry,
 }
 
 impl ImageView {
@@ -23,7 +23,7 @@ impl ImageView {
         let image_path = cascade! {
             Label::new("No image selected");
             ..set_ellipsize(EllipsizeMode::End);
-            ..get_style_context().map(|c| c.add_class("bold"));
+            ..get_style_context().add_class("bold");
         };
 
         let button_box = cascade! {
@@ -37,7 +37,7 @@ impl ImageView {
 
         let spinner_label = cascade! {
             Label::new("Generating Checksum");
-            ..get_style_context().map(|c| c.add_class("bold"));
+            ..get_style_context().add_class("bold");
         };
 
         let spinner_box = cascade! {
@@ -67,14 +67,14 @@ impl ImageView {
             Box::new(Orientation::Horizontal, 0);
             ..add(&hash);
             ..pack_start(&hash_label, true, true, 0);
-            ..get_style_context().map(|c| c.add_class("linked"));
+            ..get_style_context().add_class("linked");
         };
 
         let hash_container = cascade! {
             tmp: Box::new(Orientation::Horizontal, 0);
             ..pack_start(&label, false, false, 0);
             ..pack_start(&combo_container, true, true, 0);
-            | set_margins(&tmp, 6);
+            ..set_border_width(6);
         };
 
         let chooser_container = cascade! {
@@ -97,13 +97,16 @@ impl ImageView {
             },
         );
 
-        ImageView {
-            view,
-            chooser_container,
-            chooser,
-            image_path,
-            hash,
-            hash_label,
-        }
+        ImageView { view, chooser_container, chooser, image_path, hash, hash_label }
+    }
+
+    pub fn set_hash(&self, hash: &str) {
+        self.hash_label.set_text(hash);
+    }
+
+    pub fn set_image_path(&self, path: &Path) {
+        self.image_path.set_label(
+            &path.file_name().expect("file chooser can't select directories").to_string_lossy(),
+        );
     }
 }
diff --git a/gtk/src/app/views/mod.rs b/gtk/src/app/views/mod.rs
new file mode 100644
index 0000000..820af16
--- /dev/null
+++ b/gtk/src/app/views/mod.rs
@@ -0,0 +1,47 @@
+mod devices;
+mod error;
+mod flashing;
+mod images;
+mod summary;
+mod view;
+
+pub use self::devices::DevicesView;
+pub use self::error::ErrorView;
+pub use self::flashing::FlashView;
+pub use self::images::ImageView;
+pub use self::summary::SummaryView;
+pub use self::view::View;
+
+use gtk::*;
+
+pub struct Content {
+    pub container: Stack,
+    pub image_view: ImageView,
+    pub devices_view: DevicesView,
+    pub error_view: ErrorView,
+    pub flash_view: FlashView,
+    pub summary_view: SummaryView,
+}
+
+impl Content {
+    pub fn new() -> Content {
+        let image_view = ImageView::new();
+        let devices_view = DevicesView::new();
+        let flash_view = FlashView::new();
+        let summary_view = SummaryView::new();
+        let error_view = ErrorView::new();
+
+        let container = cascade! {
+            Stack::new();
+            ..add(&image_view.view.container);
+            ..add(&devices_view.view.container);
+            ..add(&flash_view.view.container);
+            ..add(&summary_view.view.container);
+            ..add(&error_view.view.container);
+            ..set_visible_child(&image_view.view.container);
+            ..set_border_width(12);
+        };
+
+        Content { container, image_view, devices_view, flash_view, summary_view, error_view }
+    }
+}
diff --git a/gtk/src/app/content/summary.rs b/gtk/src/app/views/summary.rs
similarity index 53%
rename from gtk/src/app/content/summary.rs
rename to gtk/src/app/views/summary.rs
index f8b9a65..831eb9a 100644
--- a/gtk/src/app/content/summary.rs
+++ b/gtk/src/app/views/summary.rs
@@ -11,14 +11,9 @@ impl SummaryView {
         let list = ListBox::new();
         list.set_visible(false);
 
-        let view = View::new(
-            "process-completed",
-            "Flashing Completed",
-            "",
-            |right_panel| {
-                right_panel.pack_start(&list, true, true, 0);
-            },
-        );
+        let view = View::new("process-completed", "Flashing Completed", "", |right_panel| {
+            right_panel.pack_start(&list, true, true, 0);
+        });
 
         SummaryView { view, list }
     }
diff --git a/gtk/src/app/content/view.rs b/gtk/src/app/views/view.rs
similarity index 73%
rename from gtk/src/app/content/view.rs
rename to gtk/src/app/views/view.rs
index 337d5fc..86a9c84 100644
--- a/gtk/src/app/content/view.rs
+++ b/gtk/src/app/views/view.rs
@@ -2,11 +2,11 @@ use gtk::prelude::*;
 use gtk::{self, Align, Image, Label, Orientation};
 
 pub struct View {
-    pub container:   gtk::Box,
-    pub icon:        Image,
-    pub topic:       Label,
+    pub container: gtk::Box,
+    pub icon: Image,
+    pub topic: Label,
     pub description: Label,
-    pub panel:       gtk::Box,
+    pub panel: gtk::Box,
 }
 
 impl View {
@@ -16,35 +16,35 @@ impl View {
         description: &str,
         configure_panel: F,
     ) -> View {
-        let icon = Image::new_from_icon_name(icon, 6);
+        let icon = Image::new_from_icon_name(icon, gtk::IconSize::Dialog);
         icon.set_valign(Align::Start);
 
         let topic = cascade! {
             Label::new(topic);
             ..set_halign(Align::Start);
-            ..get_style_context().map(|c| c.add_class("h2"));
+            ..get_style_context().add_class("h2");
             ..set_margin_bottom(6);
         };
 
         let description = cascade! {
             Label::new(description);
             ..set_line_wrap(true);
-            ..set_halign(Align::Start);
-            ..get_style_context().map(|c| c.add_class("desc"));
+            ..set_xalign(0.0);
+            ..get_style_context().add_class("desc");
             ..set_margin_bottom(6);
         };
 
         let left_panel = cascade! {
             gtk::Box::new(Orientation::Vertical, 0);
             ..add(&icon);
-            ..get_style_context().map(|c| c.add_class("left-panel"));
+            ..get_style_context().add_class("left-panel");
         };
 
         let right_panel = cascade! {
             panel: gtk::Box::new(Orientation::Vertical, 0);
             ..add(&topic);
             ..add(&description);
-            ..get_style_context().map(|c| c.add_class("right-panel"));
+            ..get_style_context().add_class("right-panel");
             | configure_panel(&panel);
         };
 
diff --git a/gtk/src/app/dialogs.rs b/gtk/src/app/widgets/dialogs.rs
similarity index 92%
rename from gtk/src/app/dialogs.rs
rename to gtk/src/app/widgets/dialogs.rs
index 72e531d..446bcdd 100644
--- a/gtk/src/app/dialogs.rs
+++ b/gtk/src/app/widgets/dialogs.rs
@@ -6,6 +6,7 @@ pub struct OpenDialog(FileChooserDialog);
 
 impl OpenDialog {
     pub fn new(path: Option<PathBuf>) -> OpenDialog {
+        #[allow(unused_mut)]
         OpenDialog(cascade! {
             dialog: FileChooserDialog::new(
                 Some("Open"),
@@ -33,5 +34,7 @@ impl OpenDialog {
 }
 
 impl Drop for OpenDialog {
-    fn drop(&mut self) { self.0.destroy(); }
+    fn drop(&mut self) {
+        self.0.destroy();
+    }
 }
diff --git a/gtk/src/app/header.rs b/gtk/src/app/widgets/header.rs
similarity index 60%
rename from gtk/src/app/header.rs
rename to gtk/src/app/widgets/header.rs
index c87e527..05e9dd8 100644
--- a/gtk/src/app/header.rs
+++ b/gtk/src/app/widgets/header.rs
@@ -2,21 +2,21 @@ use gtk::*;
 
 pub struct Header {
     pub container: HeaderBar,
-    pub back:      Button,
-    pub next:      Button,
+    pub back: Button,
+    pub next: Button,
 }
 
 impl Header {
     pub fn new() -> Header {
         let back = cascade! {
             Button::new_with_label("Cancel");
-            ..get_style_context().map(|c| c.add_class("back"));
+            ..get_style_context().add_class("back");
         };
 
         let next = cascade! {
             Button::new_with_label("Next");
             ..set_sensitive(false);
-            ..get_style_context().map(|c| c.add_class(&STYLE_CLASS_SUGGESTED_ACTION));
+            ..get_style_context().add_class(&STYLE_CLASS_SUGGESTED_ACTION);
         };
 
         // Returns the header and all of it's state
@@ -31,4 +31,12 @@ impl Header {
             next,
         }
     }
+
+    pub fn connect_back<F: Fn() + 'static>(&self, signal: F) {
+        self.back.connect_clicked(move |_| signal());
+    }
+
+    pub fn connect_next<F: Fn() + 'static>(&self, signal: F) {
+        self.next.connect_clicked(move |_| signal());
+    }
 }
diff --git a/gtk/src/app/widgets/mod.rs b/gtk/src/app/widgets/mod.rs
new file mode 100644
index 0000000..5486c11
--- /dev/null
+++ b/gtk/src/app/widgets/mod.rs
@@ -0,0 +1,5 @@
+mod dialogs;
+mod header;
+
+pub use self::dialogs::*;
+pub use self::header::*;
diff --git a/gtk/src/block.rs b/gtk/src/block.rs
index b66e620..fcce6c9 100644
--- a/gtk/src/block.rs
+++ b/gtk/src/block.rs
@@ -1,63 +1,63 @@
-use std::default::Default;
-use std::fs::File;
-use std::io::Read;
+use std::io;
 use std::path::{Path, PathBuf};
-use std::thread::sleep;
-use std::time::Duration;
-
-const SLEEP_AFTER_FAIL: u64 = 500;
-const ATTEMPTS: u64 = 10_000 / SLEEP_AFTER_FAIL;
-
-fn read_file(path: &Path) -> String {
-    File::open(path)
-        .and_then(|mut file| {
-            let mut string = String::new();
-            file.read_to_string(&mut string)
-                .map(|_| string.trim().to_owned())
-        })
-        .unwrap_or_else(|_| String::default())
-}
+use sysfs_class::{Block, SysClass};
 
+#[derive(Clone)]
 pub struct BlockDevice {
-    path: PathBuf,
+    block: Block,
+    pub path: PathBuf,
+    pub vendor: String,
+    pub model: String,
+    pub sectors: u64,
+    pub sector_size: u64,
 }
 
 impl BlockDevice {
-    pub fn new(path: &Path) -> Option<BlockDevice> {
-        path.file_name().and_then(|file_name| {
-            let path = PathBuf::from("/sys/class/block/").join(file_name);
-            if path.exists() {
-                Some(BlockDevice { path })
-            } else {
-                None
-            }
-        })
+    pub fn new_from<P: AsRef<Path>>(path: P) -> io::Result<BlockDevice> {
+        let path = path.as_ref();
+
+        let file_name = path.file_name().ok_or_else(|| {
+            io::Error::new(
+                io::ErrorKind::InvalidInput,
+                "BlockDevice::new_from path does not have a file name",
+            )
+        })?;
+
+        let file_name = file_name.to_str().ok_or_else(|| {
+            io::Error::new(io::ErrorKind::InvalidData, "BlockDevice::new_from path is not UTF-8")
+        })?;
+
+        let block = Block::new(file_name)?;
+
+        let device = BlockDevice {
+            path: path.to_path_buf(),
+            vendor: block.device_vendor()?.trim().to_owned(),
+            model: block.device_model()?.trim().to_owned(),
+            sectors: block.size()?,
+            sector_size: block.queue_hw_sector_size()?,
+            block,
+        };
+
+        Ok(device)
     }
 
-    pub fn sectors(&self) -> u64 {
-        let get_sectors = || read_file(&self.path.join("size")).parse::<u64>().unwrap_or(0);
-        let (mut result, mut attempts) = (get_sectors(), 0);
-
-        while result == 0 || attempts == ATTEMPTS {
-            result = get_sectors();
-            sleep(Duration::from_millis(SLEEP_AFTER_FAIL));
-            attempts += 1;
+    pub fn recheck_size(&mut self) {
+        if self.sector_size == 0 {
+            if let Ok(size) = self.block.queue_hw_sector_size() {
+                self.sector_size = size;
+            }
         }
 
-        result
+        if let Ok(size) = self.block.size() {
+            self.sectors = size;
+        }
     }
 
-    pub fn vendor(&self) -> String { read_file(&self.path.join("device/vendor")) }
-
-    pub fn model(&self) -> String { read_file(&self.path.join("device/model")) }
+    pub fn size_in_bytes(&self) -> u64 {
+        self.sectors * self.sector_size
+    }
 
     pub fn label(&self) -> String {
-        let model = self.model();
-        let vendor = self.vendor();
-        if vendor.is_empty() {
-            model.replace("_", " ")
-        } else {
-            [&vendor, " ", &model].concat().replace("_", " ")
-        }
+        format!("{} {} ({})", self.vendor, self.model, self.path.display())
     }
 }
diff --git a/gtk/src/flash.rs b/gtk/src/flash.rs
index 5b6caa4..3618cb1 100644
--- a/gtk/src/flash.rs
+++ b/gtk/src/flash.rs
@@ -1,70 +1,108 @@
+use atomic::Atomic;
 use bus_writer::{BusWriter, BusWriterMessage};
+use libc;
+use proc_mounts::MountList;
+use std::fs::{self, File};
 use std::io;
-use std::fs::File;
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::Arc;
-use app::{KILL, CANCELLED};
+use std::os::unix::fs::OpenOptionsExt;
+use std::path::PathBuf;
+use std::sync::atomic::Ordering;
+use std::sync::{Arc, Mutex};
+use sys_mount::{unmount, UnmountFlags};
+
+#[derive(Clone, Copy, PartialEq)]
+pub enum FlashStatus {
+    Inactive,
+    Active,
+    Killing,
+}
 
 pub struct FlashRequest {
-    source:       File,
-    destinations: Vec<File>,
-    status:       Arc<AtomicUsize>,
-    progress:     Arc<Vec<AtomicUsize>>,
-    finished:     Arc<Vec<AtomicUsize>>,
+    source: File,
+    destinations: Vec<PathBuf>,
+    status: Arc<Atomic<FlashStatus>>,
+    progress: Arc<Vec<Atomic<u64>>>,
+    finished: Arc<Vec<Atomic<bool>>>,
+}
+
+pub struct FlashTask {
+    pub progress: Arc<Vec<Atomic<u64>>>,
+    pub previous: Arc<Mutex<Vec<[u64; 7]>>>,
+    pub finished: Arc<Vec<Atomic<bool>>>,
 }
 
 impl FlashRequest {
     pub fn new(
         source: File,
-        destinations: Vec<File>,
-        status: Arc<AtomicUsize>,
-        progress: Arc<Vec<AtomicUsize>>,
-        finished: Arc<Vec<AtomicUsize>>,
+        destinations: Vec<PathBuf>,
+        status: Arc<Atomic<FlashStatus>>,
+        progress: Arc<Vec<Atomic<u64>>>,
+        finished: Arc<Vec<Atomic<bool>>>,
     ) -> FlashRequest {
-        FlashRequest {
-            source,
-            destinations,
-            status,
-            progress,
-            finished,
-        }
+        FlashRequest { source, destinations, status, progress, finished }
     }
 
     pub fn write(self) -> io::Result<Vec<io::Result<()>>> {
-        let status = self.status;
-        let progress = self.progress;
-        let finished = self.finished;
-        let mut destinations = self.destinations;
+        let status = &self.status;
+        let progress = &self.progress;
+        let finished = &self.finished;
+        let destinations = self.destinations;
         let mut source = self.source;
 
-        let mut errors = (0..destinations.len())
-            .map(|_| Ok(()))
-            .collect::<Vec<_>>();
+        status.store(FlashStatus::Active, Ordering::SeqCst);
+
+        // Unmount the devices beforehand.
+        if let Ok(mounts) = MountList::new() {
+            for file in &destinations {
+                for mount in mounts.source_starts_with(file) {
+                    let _ = unmount(&mount.dest, UnmountFlags::DETACH);
+                }
+            }
+        }
+
+        // Then open them for writing to.
+        let mut files = Vec::new();
+        for file in &destinations {
+            let file = fs::OpenOptions::new()
+                .read(true)
+                .write(true)
+                .custom_flags(libc::O_SYNC)
+                .open(file)?;
+            files.push(file);
+        }
+
+        let mut errors = (0..files.len()).map(|_| Ok(())).collect::<Vec<_>>();
 
+        // How many bytes to write at a given time.
         let mut bucket = [0u8; 8 * 1024 * 1024];
 
         let result = BusWriter::new(
             &mut source,
-            &mut destinations,
+            &mut files,
             |event| match event {
                 BusWriterMessage::Written { id, bytes_written } => {
-                    progress[id].store(bytes_written as usize, Ordering::SeqCst);
+                    progress[id].store(bytes_written, Ordering::SeqCst);
                 }
                 BusWriterMessage::Completed { id } => {
-                    finished[id].store(1, Ordering::SeqCst);
+                    finished[id].store(true, Ordering::SeqCst);
                 }
                 BusWriterMessage::Errored { id, why } => {
                     errors[id] = Err(why);
                 }
             },
             // Write will exit early when this is true
-            || KILL == status.load(Ordering::SeqCst),
-        ).with_bucket(&mut bucket[..]).write().map(|_| errors);
+            || FlashStatus::Killing == status.load(Ordering::SeqCst),
+        )
+        .with_bucket(&mut bucket[..])
+        .write()
+        .map(|_| errors);
 
-        if status.load(Ordering::SeqCst) == KILL {
-            status.store(CANCELLED, Ordering::SeqCst);
+        for atomic in finished.iter() {
+            atomic.store(true, Ordering::SeqCst);
         }
 
+        status.store(FlashStatus::Inactive, Ordering::SeqCst);
+
         result
     }
 }
diff --git a/gtk/src/hash.rs b/gtk/src/hash.rs
index 70695cd..0708e4b 100644
--- a/gtk/src/hash.rs
+++ b/gtk/src/hash.rs
@@ -1,116 +1,8 @@
 use digest::Digest;
 use hex_view::HexView;
-use md5::Md5;
-use sha2::Sha256;
-use std::hash::Hasher;
-use std::collections::BTreeMap;
-use std::collections::hash_map::DefaultHasher;
-use std::io::{self, Read};
 use std::fs::File;
-use std::path::{Path, PathBuf};
-use std::sync::Mutex;
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::mpsc::Receiver;
-use std::thread;
-use std::time::Duration;
-use std::os::unix::ffi::OsStrExt;
-
-pub(crate) const SLEEPING: usize = 0;
-pub(crate) const PROCESSING: usize = 1;
-
-pub(crate) struct HashState {
-    // K = Variant, V = Hashed Value
-    pub(crate) data: Mutex<HashData>,
-    // Defines if a hash is being generated.
-    pub(crate) state: AtomicUsize,
-}
-
-impl HashState {
-    pub(crate) fn new() -> HashState {
-        HashState {
-            data:  Mutex::new(HashData::new()),
-            state: AtomicUsize::new(SLEEPING)
-        }
-    }
-
-    /// If it's true, it's not sleeping.
-    fn is_busy(&self) -> bool { self.state.load(Ordering::SeqCst) != SLEEPING }
-
-    /// Attempt to receive the hash for the given path.
-    pub(crate) fn try_obtain(&self, requested: &Path, hash: &'static str) -> Option<String> {
-        if let Ok(data) = self.data.try_lock() {
-            if self.is_busy() {
-                None
-            } else {
-                let value: String = match data.store.get(&hash_id(requested, hash)) {
-                    Some(ref hash) => hash.as_ref()
-                        .map(|x| x.clone())
-                        .unwrap_or_else(|why| format!("ERROR: {}", why)),
-                    None => return None,
-                };
-
-                Some(value)
-            }
-        } else {
-            None
-        }
-    }
-}
-
-pub(crate) struct HashData {
-    store: BTreeMap<u64, io::Result<String>>,
-}
-
-impl HashData {
-    fn new() -> HashData {
-        HashData {
-            store: BTreeMap::new(),
-        }
-    }
-}
-
-pub(crate) fn event_loop(
-    images: &Receiver<(PathBuf, &'static str)>,
-    hash: &HashState
-) {
-    let mut set = false;
-    loop {
-        while let Ok((image, type_of)) = images.try_recv() {
-            set = true;
-            let identifying_hash = hash_id(&image, type_of);
-
-            hash.state.store(PROCESSING, Ordering::SeqCst);
-            let mut hash_data = hash.data.lock().unwrap();
-            if hash_data.store.get(&identifying_hash).map_or(true, |e| !e.is_ok()) {
-                hash_data.store.insert(
-                    identifying_hash,
-                    match type_of {
-                        "MD5" => hasher::<Md5>(&image),
-                        "SHA256" => hasher::<Sha256>(&image),
-                        _ => Err(io::Error::new(
-                            io::ErrorKind::InvalidInput,
-                            format!("{} not supported", type_of)
-                        )),
-                    },
-                );
-            }
-        }
-
-        if set {
-            hash.state.store(SLEEPING, Ordering::SeqCst);
-            set = false;
-        }
-
-        thread::sleep(Duration::from_millis(16));
-    }
-}
-
-fn hash_id(image: &Path, kind: &'static str) -> u64 {
-    let mut hasher = DefaultHasher::new();
-    hasher.write(image.as_os_str().as_bytes());
-    hasher.write(kind.as_bytes());
-    hasher.finish()
-}
+use std::io::{self, Read};
+use std::path::Path;
 
 pub(crate) fn hasher<H: Digest>(image: &Path) -> io::Result<String> {
     File::open(image).and_then(move |mut file| {
@@ -119,7 +11,9 @@ pub(crate) fn hasher<H: Digest>(image: &Path) -> io::Result<String> {
 
         loop {
             let read = file.read(&mut buffer)?;
-            if read == 0 { break }
+            if read == 0 {
+                break;
+            }
             hasher.input(&buffer[..read]);
         }
 
diff --git a/gtk/src/main.rs b/gtk/src/main.rs
index aabf16f..cd9d0eb 100644
--- a/gtk/src/main.rs
+++ b/gtk/src/main.rs
@@ -1,140 +1,32 @@
 #![allow(unknown_lints)]
-#![allow(option_map_unit_fn)]
 
-extern crate bus_writer;
 #[macro_use]
 extern crate cascade;
-extern crate digest;
-extern crate gdk;
-extern crate gtk;
-extern crate humansize;
-extern crate hex_view;
-extern crate libc;
-extern crate md5;
-extern crate pango;
-extern crate popsicle;
-extern crate pwd;
-extern crate sha2;
 
 mod app;
 mod block;
 mod flash;
 mod hash;
+mod misc;
 
-use app::{App, Connect};
-use hash::HashState;
+use crate::app::events::UiEvent;
+use crate::app::state::State;
+use crate::app::App;
 use std::env;
-use std::path::{Path, PathBuf};
-use std::thread;
-use std::io;
-use std::time::Duration;
-use std::fs::File;
-use std::sync::mpsc::{channel, Sender, Receiver, TryRecvError};
-use std::sync::Arc;
-use std::thread::JoinHandle;
-
-pub use block::BlockDevice;
-pub use flash::FlashRequest;
-
-use popsicle::mnt::MountEntry;
-use popsicle::DiskError;
+use std::path::PathBuf;
 
 fn main() {
-    let (devices_request, devices_request_receiver) =
-        channel::<(Vec<String>, Vec<MountEntry>)>();
-    let (devices_response_sender, devices_response) =
-        channel::<Result<Vec<(String, File)>, DiskError>>();
-    let (flash_request, flash_request_receiver) = channel::<FlashRequest>();
-    let (flash_response_sender, flash_response) = channel();
-
-    authenticated_threads(
-        devices_request_receiver,
-        devices_response_sender,
-        flash_request_receiver,
-        flash_response_sender,
-    );
-
-    // If running in pkexec or sudo, restore home directory for open dialog,
-    // and then downgrade permissions back to a regular user.
-    if let Ok(pkexec_uid) = env::var("PKEXEC_UID").or_else(|_| env::var("SUDO_UID")) {
-        if let Ok(uid) = pkexec_uid.parse::<u32>() {
-            if let Some(passwd) = pwd::Passwd::from_uid(uid) {
-                env::set_var("HOME", passwd.dir);
-                downgrade_permissions(passwd.uid, passwd.gid);
-            }
-        }
-    }
-
-    let (hash_tx, hash_rx) = channel::<(PathBuf, &'static str)>();
-    let hash_state = Arc::new(HashState::new());
+    glib::set_program_name("Popsicle".into());
+    glib::set_application_name("Popsicle");
 
-    {
-        let hash_state = hash_state.clone();
-        thread::spawn(move || hash::event_loop(&hash_rx, &hash_state));
-    }
-
-    let app = App::new(hash_state, hash_tx, devices_request, devices_response, flash_request, flash_response);
+    let app = App::new(State::new());
 
     if let Some(iso_argument) = env::args().nth(1) {
-        let path: &Path = &PathBuf::from(iso_argument);
+        let path = PathBuf::from(iso_argument);
         if path.extension().map_or(false, |ext| ext == "iso" || ext == "img") && path.exists() {
-            app.widgets.set_image(&app.state, path);
+            let _ = app.state.ui_event_tx.send(UiEvent::SetImageLabel(path));
         }
     }
 
-    app.connect_events().then_execute()
-}
-
-/// Actions which require root authentication will be spawned as background threads from here.
-///
-/// This function should be called before `downgrade_permissions()`.
-fn authenticated_threads(
-    devices_request: Receiver<(Vec<String>, Vec<MountEntry>)>,
-    devices_response: Sender<Result<Vec<(String, File)>, DiskError>>,
-    flash_request: Receiver<FlashRequest>,
-    flash_response: Sender<JoinHandle<io::Result<Vec<io::Result<()>>>>>,
-) {
-    thread::spawn(move || {
-        loop {
-            let mut disconnected = 0;
-
-            match devices_request.try_recv() {
-                Ok((devs, mounts)) => {
-                    let resp = popsicle::disks_from_args(devs.into_iter(), &mounts, true);
-                    let _ = devices_response.send(resp);
-                }
-                Err(TryRecvError::Empty) => (),
-                Err(TryRecvError::Disconnected) => disconnected += 1,
-            }
-
-            match flash_request.try_recv() {
-                Ok(flash_request) => {
-                    let _ = flash_response.send(
-                        thread::Builder::new()
-                            .stack_size(10 * 1024 * 1024)
-                            .spawn(move || flash_request.write())
-                            .unwrap()
-                    );
-                }
-                Err(TryRecvError::Empty) => (),
-                Err(TryRecvError::Disconnected) => disconnected += 1,
-            }
-
-            if disconnected == 2 {
-                break
-            }
-
-            thread::sleep(Duration::from_millis(1));
-        }
-    });
-}
-
-/// Downgrades the permissions of the current thread to the specified user and group ID.
-fn downgrade_permissions(uid: u32, gid: u32) {
-    unsafe {
-        // By using system calls directly, we apply this on a per-thread basis.
-        // The setresuid() and setresguid() functions apply to all threads.
-        libc::syscall(libc::SYS_setresgid, gid, gid, gid);
-        libc::syscall(libc::SYS_setresuid, uid, uid, uid);
-    }
+    app.connect_events().then_execute();
 }
diff --git a/gtk/src/app/misc.rs b/gtk/src/misc.rs
similarity index 63%
rename from gtk/src/app/misc.rs
rename to gtk/src/misc.rs
index 3c7ad8a..805c215 100644
--- a/gtk/src/app/misc.rs
+++ b/gtk/src/misc.rs
@@ -1,18 +1,11 @@
-// use gdk::{self, DragContextExt, DragContextExtManual};
-use gdk::{self, DragContextExt, DragContextExtManual};
-use gtk::{self, Inhibit, SelectionData, WidgetExt, WidgetExtManual};
-
-pub fn set_margins<W: WidgetExt>(widget: &W, value: i32) {
-    widget.set_margin_top(value);
-    widget.set_margin_bottom(value);
-    widget.set_margin_start(value);
-    widget.set_margin_end(value);
-}
+use gdk;
+use gtk::{self, prelude::*, SelectionData};
 
 // Implements drag and drop support for a GTK widget.
 pub fn drag_and_drop<W, F>(widget: &W, action: F)
-where W: WidgetExt + WidgetExtManual,
-      F: 'static + Fn(&SelectionData)
+where
+    W: WidgetExt + WidgetExtManual,
+    F: 'static + Fn(&SelectionData),
 {
     // Configure the view as a possible drop destination.
     widget.drag_dest_set(gtk::DestDefaults::empty(), &[], gdk::DragAction::empty());
@@ -32,7 +25,5 @@ where W: WidgetExt + WidgetExtManual,
     });
 
     // Then handle the dropped data, setting the image if the dropped data is valid.
-    widget.connect_drag_data_received(move |_view, _ctx, _x, _y, data, _info, _time| {
-        action(data)
-    });
+    widget.connect_drag_data_received(move |_view, _ctx, _x, _y, data, _info, _time| action(data));
 }
diff --git a/rust-toolchain b/rust-toolchain
new file mode 100644
index 0000000..34aae15
--- /dev/null
+++ b/rust-toolchain
@@ -0,0 +1,1 @@
+1.31.0
diff --git a/rustfmt.toml b/rustfmt.toml
index fd968e4..6490928 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,15 +1,6 @@
-unstable_features = true
-comment_width = 100
-condense_wildcard_suffixes = true
-fn_single_line = true
-format_strings = true
-imports_indent = "Block"
 max_width = 100
-normalize_comments = true
-reorder_imported_names = true
 reorder_imports = true
-reorder_imports_in_group = true
-struct_field_align_threshold = 30
+reorder_modules = true
 use_field_init_shorthand = true
-wrap_comments = true
-write_mode = "overwrite"
+use_small_heuristics = "Max"
+
diff --git a/src/lib.rs b/src/lib.rs
index 1d583bd..cd04712 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,5 @@
-extern crate failure;
 #[macro_use]
-extern crate failure_derive;
-extern crate libc;
+extern crate err_derive;
 pub extern crate mnt;
 
 use mnt::MountEntry;
@@ -13,59 +11,59 @@ use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
 use std::path::PathBuf;
 use std::process::{Command, ExitStatus};
 
-#[derive(Debug, Fail)]
+#[derive(Debug, Error)]
 #[cfg_attr(rustfmt, rustfmt_skip)]
 pub enum ImageError {
-    #[fail(display = "image could not be opened: {}", why)]
+    #[error(display = "image could not be opened: {}", why)]
     Open { why: io::Error },
-    #[fail(display = "unable to get image metadata: {}", why)]
+    #[error(display = "unable to get image metadata: {}", why)]
     Metadata { why: io::Error },
-    #[fail(display = "image was not a file")]
+    #[error(display = "image was not a file")]
     NotAFile,
-    #[fail(display = "unable to read image: {}", why)]
+    #[error(display = "unable to read image: {}", why)]
     ReadError { why: io::Error },
-    #[fail(display = "reached EOF prematurely")]
+    #[error(display = "reached EOF prematurely")]
     Eof,
 }
 
-#[derive(Debug, Fail)]
+#[derive(Debug, Error)]
 #[cfg_attr(rustfmt, rustfmt_skip)]
 pub enum DiskError {
-    #[fail(display = "unable to open directory at '{}': {}", dir, why)]
+    #[error(display = "unable to open directory at '{}': {}", dir, why)]
     Directory { dir: &'static str, why: io::Error },
-    #[fail(display = "writing to the device was killed")]
+    #[error(display = "writing to the device was killed")]
     Killed,
-    #[fail(display = "unable to read directory entry at '{:?}': invalid UTF-8", dir)]
+    #[error(display = "unable to read directory entry at '{:?}': invalid UTF-8", dir)]
     UTF8 { dir: PathBuf },
-    #[fail(display = "unable to find disk '{}': {}", disk, why)]
+    #[error(display = "unable to find disk '{}': {}", disk, why)]
     NoDisk { disk: String, why: io::Error },
-    #[fail(display = "failed to unmount {:?}: exit status {}", path, status)]
+    #[error(display = "failed to unmount {:?}: exit status {}", path, status)]
     UnmountStatus { path: String, status: ExitStatus },
-    #[fail(display = "failed to unmount {:?}: {}", path, why)]
+    #[error(display = "failed to unmount {:?}: {}", path, why)]
     UnmountCommand { path: String, why: io::Error },
-    #[fail(display = "error using disk '{}': {:?} already mounted at {:?}", arg, source, dest)]
+    #[error(display = "error using disk '{}': {:?} already mounted at {:?}", arg, source, dest)]
     AlreadyMounted { arg: String, source: String, dest: PathBuf },
-    #[fail(display = "'{}' is not a block device", arg)]
+    #[error(display = "'{}' is not a block device", arg)]
     NotABlock { arg: String },
-    #[fail(display = "unable to get metadata of disk '{}': {}", arg, why)]
+    #[error(display = "unable to get metadata of disk '{}': {}", arg, why)]
     Metadata { arg: String, why: io::Error },
-    #[fail(display = "unable to open disk '{}': {}", disk, why)]
+    #[error(display = "unable to open disk '{}': {}", disk, why)]
     Open { disk: String, why: io::Error },
-    #[fail(display = "error writing disk '{}': {}", disk, why)]
+    #[error(display = "error writing disk '{}': {}", disk, why)]
     Write { disk: String, why: io::Error },
-    #[fail(display = "error writing disk '{}': reached EOF", disk)]
+    #[error(display = "error writing disk '{}': reached EOF", disk)]
     WriteEOF { disk: String },
-    #[fail(display = "unable to flush disk '{}': {}", disk, why)]
+    #[error(display = "unable to flush disk '{}': {}", disk, why)]
     Flush { disk: String, why: io::Error },
-    #[fail(display = "error seeking disk '{}': seeked to {} instead of 0", disk, invalid)]
+    #[error(display = "error seeking disk '{}': seeked to {} instead of 0", disk, invalid)]
     SeekInvalid { disk: String, invalid: u64 },
-    #[fail(display = "error seeking disk '{}': {}", disk, why)]
+    #[error(display = "error seeking disk '{}': {}", disk, why)]
     Seek { disk: String, why: io::Error },
-    #[fail(display = "error verifying disk '{}': {}", disk, why)]
+    #[error(display = "error verifying disk '{}': {}", disk, why)]
     Verify { disk: String, why: io::Error },
-    #[fail(display = "error verifying disk '{}': reached EOF", disk)]
+    #[error(display = "error verifying disk '{}': reached EOF", disk)]
     VerifyEOF { disk: String },
-    #[fail(display = "error verifying disk '{}': mismatch at {}:{}", disk, x, y)]
+    #[error(display = "error verifying disk '{}': mismatch at {}:{}", disk, x, y)]
     VerifyMismatch { disk: String, x: usize, y: usize },
 }
 
@@ -85,9 +83,7 @@ pub fn get_disk_args(disks: &mut Vec<String>) -> Result<(), DiskError> {
         if let Some(filename_os) = path.file_name() {
             if is_usb(filename_os.to_str().unwrap()) {
                 disks.push(
-                    path.to_str()
-                        .ok_or_else(|| DiskError::UTF8 { dir: path.clone() })?
-                        .into(),
+                    path.to_str().ok_or_else(|| DiskError::UTF8 { dir: path.clone() })?.into(),
                 );
             }
         }
@@ -104,17 +100,11 @@ pub fn disks_from_args<D: Iterator<Item = String>>(
     let mut disks = Vec::new();
 
     for disk_arg in disk_args {
-        let canonical_path = canonicalize(&disk_arg).map_err(|why| DiskError::NoDisk {
-            disk: disk_arg.clone(),
-            why,
-        })?;
+        let canonical_path = canonicalize(&disk_arg)
+            .map_err(|why| DiskError::NoDisk { disk: disk_arg.clone(), why })?;
 
         for mount in mounts.iter() {
-            if mount
-                .spec
-                .as_bytes()
-                .starts_with(canonical_path.as_os_str().as_bytes())
-            {
+            if mount.spec.as_bytes().starts_with(canonical_path.as_os_str().as_bytes()) {
                 if unmount {
                     eprintln!(
                         "unmounting '{}': {:?} is mounted at {:?}",
@@ -124,25 +114,19 @@ pub fn disks_from_args<D: Iterator<Item = String>>(
                     Command::new("umount")
                         .arg(&mount.spec)
                         .status()
-                        .map_err(|why| DiskError::UnmountCommand {
-                            path: mount.spec.clone(),
-                            why,
-                        })
+                        .map_err(|why| DiskError::UnmountCommand { path: mount.spec.clone(), why })
                         .and_then(|status| {
                             if !status.success() {
-                                Err(DiskError::UnmountStatus {
-                                    path: mount.spec.clone(),
-                                    status,
-                                })
+                                Err(DiskError::UnmountStatus { path: mount.spec.clone(), status })
                             } else {
                                 Ok(())
                             }
                         })?;
                 } else {
                     return Err(DiskError::AlreadyMounted {
-                        arg:    disk_arg.clone(),
+                        arg: disk_arg.clone(),
                         source: mount.spec.clone(),
-                        dest:   mount.file.clone(),
+                        dest: mount.file.clone(),
                     });
                 }
             }
@@ -150,15 +134,10 @@ pub fn disks_from_args<D: Iterator<Item = String>>(
 
         let metadata = canonical_path
             .metadata()
-            .map_err(|why| DiskError::Metadata {
-                arg: disk_arg.clone(),
-                why,
-            })?;
+            .map_err(|why| DiskError::Metadata { arg: disk_arg.clone(), why })?;
 
         if !metadata.file_type().is_block_device() {
-            return Err(DiskError::NotABlock {
-                arg: disk_arg.clone(),
-            });
+            return Err(DiskError::NotABlock { arg: disk_arg.clone() });
         }
 
         let disk = OpenOptions::new()
@@ -166,10 +145,7 @@ pub fn disks_from_args<D: Iterator<Item = String>>(
             .write(true)
             .custom_flags(libc::O_SYNC)
             .open(&canonical_path)
-            .map_err(|why| DiskError::Open {
-                disk: disk_arg.clone(),
-                why,
-            })?;
+            .map_err(|why| DiskError::Open { disk: disk_arg.clone(), why })?;
 
         disks.push((disk_arg, disk));
     }


Test patch: