From 935d1daa8700a0b3545a953a48f11b925f0f6f5a Mon Sep 17 00:00:00 2001
From: Gregory <gregory.mkv@gmail.com>
Date: Sat, 6 Jun 2020 00:24:32 -0400
Subject: [PATCH] Reorganize and partially rewrite

---
 Cargo.lock                          | 397 ++++++++++++++++++++++------
 Cargo.toml                          |   4 +-
 src/apps/mod.rs                     |   5 +
 src/apps/system.rs                  |  47 ++++
 src/{mimeapps.rs => apps/user.rs}   |  82 ++----
 src/common.rs                       | 188 -------------
 src/{mime_types.rs => common/db.rs} |  30 +--
 src/common/desktop_entry.rs         | 129 +++++++++
 src/common/handler.rs               |  41 +++
 src/{ => common}/ini.pest           |   0
 src/common/mime_types.rs            |  80 ++++++
 src/common/mod.rs                   |   9 +
 src/error.rs                        |  26 +-
 src/main.rs                         | 135 ++++++----
 14 files changed, 749 insertions(+), 424 deletions(-)
 create mode 100644 src/apps/mod.rs
 create mode 100644 src/apps/system.rs
 rename src/{mimeapps.rs => apps/user.rs} (69%)
 delete mode 100644 src/common.rs
 rename src/{mime_types.rs => common/db.rs} (51%)
 create mode 100644 src/common/desktop_entry.rs
 create mode 100644 src/common/handler.rs
 rename src/{ => common}/ini.pest (100%)
 create mode 100644 src/common/mime_types.rs
 create mode 100644 src/common/mod.rs

diff --git a/Cargo.lock b/Cargo.lock
index f111698..23ceae2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -50,6 +50,18 @@ version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
 
+[[package]]
+name = "base64"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
+
+[[package]]
+name = "bitflags"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
+
 [[package]]
 name = "bitflags"
 version = "1.2.1"
@@ -67,6 +79,12 @@ dependencies = [
  "constant_time_eq",
 ]
 
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
 [[package]]
 name = "block-buffer"
 version = "0.7.3"
@@ -90,9 +108,9 @@ dependencies = [
 
 [[package]]
 name = "bumpalo"
-version = "3.3.0"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6"
+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
 
 [[package]]
 name = "byte-tools"
@@ -124,6 +142,17 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
 
+[[package]]
+name = "chrono"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
+dependencies = [
+ "num-integer",
+ "num-traits",
+ "time",
+]
+
 [[package]]
 name = "clap"
 version = "3.0.0-beta.1"
@@ -131,7 +160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936"
 dependencies = [
  "atty",
- "bitflags",
+ "bitflags 1.2.1",
  "clap_derive",
  "indexmap",
  "lazy_static",
@@ -152,8 +181,8 @@ dependencies = [
  "heck",
  "proc-macro-error",
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
 ]
 
 [[package]]
@@ -189,6 +218,16 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "dbus"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b17a12ffaff26515889b006fc029493a3e340366a137c13cec2cdd545ea3b8"
+dependencies = [
+ "libc",
+ "libdbus-sys",
+]
+
 [[package]]
 name = "digest"
 version = "0.8.1"
@@ -198,6 +237,17 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "dirs"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi 0.3.8",
+]
+
 [[package]]
 name = "dirs"
 version = "2.0.2"
@@ -274,7 +324,7 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
  "fuchsia-zircon-sys",
 ]
 
@@ -373,11 +423,13 @@ name = "handlr"
 version = "0.3.2"
 dependencies = [
  "ascii_table",
+ "atty",
  "clap",
  "itertools",
  "json",
  "mime",
  "mime-db",
+ "notify-rust",
  "once_cell",
  "pest",
  "pest_derive",
@@ -436,9 +488,9 @@ checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
 
 [[package]]
 name = "hyper"
-version = "0.13.5"
+version = "0.13.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14"
+checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -450,8 +502,8 @@ dependencies = [
  "httparse",
  "itoa",
  "log",
- "net2",
  "pin-project",
+ "socket2",
  "time",
  "tokio",
  "tower-service",
@@ -484,9 +536,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "1.3.2"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
+checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
 dependencies = [
  "autocfg",
 ]
@@ -517,9 +569,9 @@ checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
 
 [[package]]
 name = "js-sys"
-version = "0.3.39"
+version = "0.3.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7"
+checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -553,7 +605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f"
 dependencies = [
  "arrayvec 0.4.12",
- "bitflags",
+ "bitflags 1.2.1",
  "cfg-if",
  "rustc_version",
  "ryu",
@@ -562,9 +614,18 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.70"
+version = "0.2.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
+checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
+
+[[package]]
+name = "libdbus-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0"
+dependencies = [
+ "pkg-config",
+]
 
 [[package]]
 name = "log"
@@ -575,6 +636,27 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "mac-notification-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb6b71a9a89cd38b395d994214297447e8e63b1ba5708a9a2b0b1048ceda76"
+dependencies = [
+ "cc",
+ "chrono",
+ "dirs 1.0.5",
+ "objc-foundation",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "maplit"
 version = "1.0.2"
@@ -698,6 +780,36 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "notify-rust"
+version = "4.0.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "963d07a7dc24a56e2e0036d444b37caf4f0418cfd31c4b4b018ffc92cc06b56b"
+dependencies = [
+ "dbus",
+ "mac-notification-sys",
+ "winrt-notification",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "num_cpus"
 version = "1.13.0"
@@ -708,6 +820,35 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.4.0"
@@ -726,7 +867,7 @@ version = "0.10.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
 dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
  "cfg-if",
  "foreign-types",
  "lazy_static",
@@ -793,8 +934,8 @@ dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
 ]
 
 [[package]]
@@ -810,29 +951,29 @@ dependencies = [
 
 [[package]]
 name = "pin-project"
-version = "0.4.17"
+version = "0.4.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791"
+checksum = "ba3a1acf4a3e70849f8a673497ef984f043f95d2d8252dcdf74d54e6a1e47e8a"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "0.4.17"
+version = "0.4.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
+checksum = "194e88048b71a3e02eb4ee36a6995fed9b8236c11a7bb9f7247a9d9835b3f265"
 dependencies = [
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
 ]
 
 [[package]]
 name = "pin-project-lite"
-version = "0.1.5"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f"
+checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715"
 
 [[package]]
 name = "pin-utils"
@@ -860,8 +1001,8 @@ checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
 dependencies = [
  "proc-macro-error-attr",
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
  "version_check",
 ]
 
@@ -872,21 +1013,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
 dependencies = [
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
  "syn-mid",
  "version_check",
 ]
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.17"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
+checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
 dependencies = [
- "unicode-xid",
+ "unicode-xid 0.2.0",
 ]
 
+[[package]]
+name = "quote"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
+
 [[package]]
 name = "quote"
 version = "1.0.6"
@@ -980,11 +1127,11 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.10.4"
+version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2"
+checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680"
 dependencies = [
- "base64",
+ "base64 0.12.1",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -1004,7 +1151,6 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_urlencoded",
- "time",
  "tokio",
  "tokio-tls",
  "url",
@@ -1020,7 +1166,7 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
 dependencies = [
- "base64",
+ "base64 0.11.0",
  "blake2b_simd",
  "constant_time_eq",
  "crossbeam-utils",
@@ -1037,9 +1183,9 @@ dependencies = [
 
 [[package]]
 name = "ryu"
-version = "1.0.4"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
 
 [[package]]
 name = "schannel"
@@ -1057,7 +1203,7 @@ version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
 dependencies = [
- "bitflags",
+ "bitflags 1.2.1",
  "core-foundation",
  "core-foundation-sys",
  "libc",
@@ -1091,22 +1237,22 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
 [[package]]
 name = "serde"
-version = "1.0.110"
+version = "1.0.111"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c"
+checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.110"
+version = "1.0.111"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
+checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
 dependencies = [
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
 ]
 
 [[package]]
@@ -1162,6 +1308,18 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
 
+[[package]]
+name = "socket2"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "winapi 0.3.8",
+]
+
 [[package]]
 name = "static_assertions"
 version = "0.3.4"
@@ -1175,14 +1333,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
-name = "syn"
-version = "1.0.23"
+name = "strum"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269"
+checksum = "4ca6e4730f517e041e547ffe23d29daab8de6b73af4b6ae2a002108169f5e7da"
+
+[[package]]
+name = "strum_macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3384590878eb0cab3b128e844412e2d010821e7e091211b9d87324173ada7db8"
+dependencies = [
+ "quote 0.3.15",
+ "syn 0.11.11",
+]
+
+[[package]]
+name = "syn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
+dependencies = [
+ "quote 0.3.15",
+ "synom",
+ "unicode-xid 0.0.4",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2"
 dependencies = [
  "proc-macro2",
- "quote",
- "unicode-xid",
+ "quote 1.0.6",
+ "unicode-xid 0.2.0",
 ]
 
 [[package]]
@@ -1192,8 +1377,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
 dependencies = [
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
+]
+
+[[package]]
+name = "synom"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
+dependencies = [
+ "unicode-xid 0.0.4",
 ]
 
 [[package]]
@@ -1244,8 +1438,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
 dependencies = [
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
 ]
 
 [[package]]
@@ -1284,8 +1478,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
 dependencies = [
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
 ]
 
 [[package]]
@@ -1375,6 +1569,12 @@ version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
 
+[[package]]
+name = "unicode-xid"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.0"
@@ -1394,9 +1594,9 @@ dependencies = [
 
 [[package]]
 name = "vcpkg"
-version = "0.2.8"
+version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
+checksum = "55d1e41d56121e07f1e223db0a4def204e45c85425f6a16d462fd07c8d10d74c"
 
 [[package]]
 name = "vec_map"
@@ -1428,9 +1628,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.62"
+version = "0.2.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551"
+checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0"
 dependencies = [
  "cfg-if",
  "serde",
@@ -1440,24 +1640,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.62"
+version = "0.2.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94"
+checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101"
 dependencies = [
  "bumpalo",
  "lazy_static",
  "log",
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.12"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04"
+checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -1467,38 +1667,38 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.62"
+version = "0.2.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776"
+checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3"
 dependencies = [
- "quote",
+ "quote 1.0.6",
  "wasm-bindgen-macro-support",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.62"
+version = "0.2.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a"
+checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92"
 dependencies = [
  "proc-macro2",
- "quote",
- "syn",
+ "quote 1.0.6",
+ "syn 1.0.30",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.62"
+version = "0.2.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad"
+checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd"
 
 [[package]]
 name = "web-sys"
-version = "0.3.39"
+version = "0.3.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642"
+checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -1549,13 +1749,35 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "winreg"
-version = "0.6.2"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
 dependencies = [
  "winapi 0.3.8",
 ]
 
+[[package]]
+name = "winrt"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e30cba82e22b083dc5a422c2ee77e20dc7927271a0dc981360c57c1453cb48d"
+dependencies = [
+ "winapi 0.3.8",
+]
+
+[[package]]
+name = "winrt-notification"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c31a65da50d792c6f9bd2e3216249566c4fb1d2d34f9b7d2d66d2e93f62a242"
+dependencies = [
+ "strum",
+ "strum_macros",
+ "winapi 0.3.8",
+ "winrt",
+ "xml-rs",
+]
+
 [[package]]
 name = "ws2_32-sys"
 version = "0.2.1"
@@ -1577,9 +1799,18 @@ name = "xdg-mime"
 version = "0.3.0"
 source = "git+https://github.com/ebassi/xdg-mime-rs#68f9130f316a67c6fbce440698788b94b45380c5"
 dependencies = [
- "dirs",
+ "dirs 2.0.2",
  "glob",
  "mime",
  "nom",
  "unicase",
 ]
+
+[[package]]
+name = "xml-rs"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1945e12e16b951721d7976520b0832496ef79c31602c7a29d950de79ba74621"
+dependencies = [
+ "bitflags 0.9.1",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 66aefab..082582c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ description = "Manage mimeapps.list and default applications with ease"
 [dependencies]
 pest = "2.1.3"
 pest_derive = "2.1.0"
-clap = { version = "3.0.0-beta.1" }
+clap = "3.0.0-beta.1"
 url = "2.1.1"
 itertools = "0.9.0"
 json = "0.12.4"
@@ -22,6 +22,8 @@ once_cell = "1.4.0"
 regex = { version = "1.3.9", default-features = false, features = ["std"] }
 mime-db = "0.1.5"
 xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" }
+atty = "0.2.14"
+notify-rust = "4.0.0-rc.1"
 
 [profile.release]
 opt-level=3
diff --git a/src/apps/mod.rs b/src/apps/mod.rs
new file mode 100644
index 0000000..57f7b93
--- /dev/null
+++ b/src/apps/mod.rs
@@ -0,0 +1,5 @@
+mod system;
+mod user;
+
+pub use system::SystemApps;
+pub use user::MimeApps;
diff --git a/src/apps/system.rs b/src/apps/system.rs
new file mode 100644
index 0000000..4d07339
--- /dev/null
+++ b/src/apps/system.rs
@@ -0,0 +1,47 @@
+use crate::{
+    common::{DesktopEntry, Handler},
+    Result,
+};
+use mime::Mime;
+use std::{collections::HashMap, convert::TryFrom, ffi::OsStr};
+
+#[derive(Debug)]
+pub struct SystemApps(pub HashMap<Mime, Vec<Handler>>);
+
+impl SystemApps {
+    pub fn get_handlers(&self, mime: &Mime) -> Option<Vec<Handler>> {
+        Some(self.0.get(mime)?.clone())
+    }
+    pub fn get_handler(&self, mime: &Mime) -> Option<Handler> {
+        Some(self.get_handlers(mime)?.get(0).unwrap().clone())
+    }
+    pub fn populate() -> Result<Self> {
+        let mut map = HashMap::<Mime, Vec<Handler>>::with_capacity(50);
+
+        xdg::BaseDirectories::new()?
+            .get_data_dirs()
+            .into_iter()
+            .map(|mut data_dir| {
+                data_dir.push("applications");
+                data_dir
+            })
+            .filter_map(|data_dir| std::fs::read_dir(data_dir).ok())
+            .for_each(|dir| {
+                dir.filter_map(Result::ok)
+                    .filter(|p| {
+                        p.path().extension() == Some(OsStr::new("desktop"))
+                    })
+                    .filter_map(|p| DesktopEntry::try_from(p.path()).ok())
+                    .for_each(|entry| {
+                        let (file_name, mimes) = (entry.file_name, entry.mimes);
+                        mimes.into_iter().for_each(|mime| {
+                            map.entry(mime)
+                                .or_default()
+                                .push(Handler::assume_valid(file_name.clone()));
+                        });
+                    });
+            });
+
+        Ok(Self(map))
+    }
+}
diff --git a/src/mimeapps.rs b/src/apps/user.rs
similarity index 69%
rename from src/mimeapps.rs
rename to src/apps/user.rs
index b0aee22..5aacf4a 100644
--- a/src/mimeapps.rs
+++ b/src/apps/user.rs
@@ -1,12 +1,14 @@
-use crate::{DesktopEntry, Error, Handler, Mime, Result};
+use crate::{apps::SystemApps, DesktopEntry, Error, Handler, Result};
+use mime::Mime;
 use pest::Parser;
 use std::{
     collections::{HashMap, VecDeque},
     path::PathBuf,
+    str::FromStr,
 };
 
 #[derive(Debug, pest_derive::Parser)]
-#[grammar = "ini.pest"]
+#[grammar = "common/ini.pest"]
 pub struct MimeApps {
     added_associations: HashMap<Mime, VecDeque<Handler>>,
     default_apps: HashMap<Mime, VecDeque<Handler>>,
@@ -42,7 +44,7 @@ impl MimeApps {
             .or_else(|| self.added_associations.get(mime))
             .map(|hs| hs.get(0).unwrap().clone())
             .or_else(|| self.system_apps.get_handler(mime))
-            .ok_or(Error::NotFound)
+            .ok_or(Error::NotFound(mime.to_string()))
     }
     pub fn show_handler(&self, mime: &Mime, output_json: bool) -> Result<()> {
         let handler = self.get_handler(mime)?;
@@ -90,7 +92,6 @@ impl MimeApps {
                     let name = inner_rules.next().unwrap().as_str();
                     let handlers = {
                         use itertools::Itertools;
-                        use std::str::FromStr;
 
                         inner_rules
                             .next()
@@ -104,13 +105,17 @@ impl MimeApps {
                     };
 
                     if !handlers.is_empty() {
-                        match current_section_name.as_str() {
-                            "Added Associations" => conf
-                                .added_associations
-                                .insert(Mime(name.to_owned()), handlers),
-                            "Default Applications" => conf
-                                .default_apps
-                                .insert(Mime(name.to_owned()), handlers),
+                        match (
+                            Mime::from_str(name),
+                            current_section_name.as_str(),
+                        ) {
+                            (Ok(mime), "Added Associations") => {
+                                conf.added_associations.insert(mime, handlers)
+                            }
+
+                            (Ok(mime), "Default Applications") => {
+                                conf.default_apps.insert(mime, handlers)
+                            }
                             _ => None,
                         };
                     }
@@ -134,7 +139,7 @@ impl MimeApps {
 
         writer.write_all(b"[Added Associations]\n")?;
         for (k, v) in self.added_associations.iter().sorted() {
-            writer.write_all(k.0.as_ref())?;
+            writer.write_all(k.essence_str().as_ref())?;
             writer.write_all(b"=")?;
             writer.write_all(v.iter().join(";").as_ref())?;
             writer.write_all(b";\n")?;
@@ -142,7 +147,7 @@ impl MimeApps {
 
         writer.write_all(b"\n[Default Applications]\n")?;
         for (k, v) in self.default_apps.iter().sorted() {
-            writer.write_all(k.0.as_ref())?;
+            writer.write_all(k.essence_str().as_ref())?;
             writer.write_all(b"=")?;
             writer.write_all(v.iter().join(";").as_ref())?;
             writer.write_all(b";\n")?;
@@ -158,7 +163,7 @@ impl MimeApps {
             .default_apps
             .iter()
             .sorted()
-            .map(|(k, v)| vec![k.0.clone(), v.iter().join(", ")])
+            .map(|(k, v)| vec![k.to_string(), v.iter().join(", ")])
             .collect::<Vec<_>>();
 
         ascii_table::AsciiTable::default().print(rows);
@@ -166,7 +171,7 @@ impl MimeApps {
         Ok(())
     }
     pub fn list_handlers(&self) -> Result<()> {
-        use std::{convert::TryFrom, io::Write};
+        use std::{convert::TryFrom, io::Write, os::unix::ffi::OsStrExt};
 
         let stdout = std::io::stdout();
         let mut stdout = stdout.lock();
@@ -188,50 +193,3 @@ impl MimeApps {
         Ok(())
     }
 }
-
-#[derive(Debug)]
-pub struct SystemApps(pub HashMap<Mime, Vec<Handler>>);
-
-impl SystemApps {
-    pub fn get_handlers(&self, mime: &Mime) -> Option<Vec<Handler>> {
-        Some(self.0.get(mime)?.clone())
-    }
-    pub fn get_handler(&self, mime: &Mime) -> Option<Handler> {
-        Some(self.get_handlers(mime)?.get(0).unwrap().clone())
-    }
-    pub fn populate() -> Result<Self> {
-        use std::convert::TryFrom;
-
-        let mut map = HashMap::<Mime, Vec<Handler>>::with_capacity(50);
-
-        xdg::BaseDirectories::new()?
-            .get_data_dirs()
-            .into_iter()
-            .map(|mut data_dir| {
-                data_dir.push("applications");
-                data_dir
-            })
-            .filter_map(|data_dir| std::fs::read_dir(data_dir).ok())
-            .for_each(|dir| {
-                dir.filter_map(Result::ok)
-                    .filter(|p| {
-                        p.path()
-                            .extension()
-                            .map(std::ffi::OsStr::to_str)
-                            .flatten()
-                            == Some("desktop")
-                    })
-                    .filter_map(|p| DesktopEntry::try_from(p.path()).ok())
-                    .for_each(|entry| {
-                        let (file_name, mimes) = (entry.file_name, entry.mimes);
-                        mimes.into_iter().for_each(|mime| {
-                            map.entry(mime)
-                                .or_default()
-                                .push(Handler::assume_valid(file_name.clone()));
-                        });
-                    });
-            });
-
-        Ok(Self(map))
-    }
-}
diff --git a/src/common.rs b/src/common.rs
deleted file mode 100644
index ccc2029..0000000
--- a/src/common.rs
+++ /dev/null
@@ -1,188 +0,0 @@
-use crate::{mime_types, Error, Result};
-use std::{convert::TryFrom, path::PathBuf};
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Mime(pub String);
-
-impl Mime {
-    pub fn try_from_path(path: &str) -> Result<Self> {
-        if let Ok(url) = url::Url::parse(path) {
-            return Ok(Mime(format!("x-scheme-handler/{}", url.scheme())));
-        }
-
-        mime_types::from_file(path)
-    }
-}
-
-impl std::str::FromStr for Mime {
-    type Err = Error;
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        if s.starts_with(".") {
-            mime_types::from_file(s)
-        } else {
-            Ok(Mime(mime_types::verify(&s)?.to_owned()))
-        }
-    }
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Handler(String);
-
-impl std::fmt::Display for Handler {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(&self.0)
-    }
-}
-
-impl std::str::FromStr for Handler {
-    type Err = Error;
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Self::resolve(s.to_owned())
-    }
-}
-
-impl Handler {
-    pub fn assume_valid(name: String) -> Self {
-        Self(name)
-    }
-    pub fn get_path(name: &str) -> Option<PathBuf> {
-        xdg::BaseDirectories::new()
-            .ok()?
-            .find_data_file(&format!("applications/{}", name))
-    }
-    pub fn resolve(name: String) -> Result<Self> {
-        let path = Self::get_path(&name).ok_or(Error::NotFound)?;
-        DesktopEntry::try_from(path)?;
-        Ok(Self(name))
-    }
-    pub fn get_entry(&self) -> Result<DesktopEntry> {
-        DesktopEntry::try_from(Self::get_path(&self.0).unwrap())
-    }
-    pub fn open(&self, arg: String) -> Result<()> {
-        let (cmd, args) = self.get_entry()?.get_cmd(Some(arg))?;
-        if self.get_entry()?.term {
-            std::process::Command::new(cmd)
-                .args(args)
-                .status()?;
-        } else {
-            std::process::Command::new(cmd)
-                .args(args)
-                .stdout(std::process::Stdio::null())
-                .spawn()?;
-        }
-        Ok(())
-    }
-    pub fn launch(&self, args: Vec<String>) -> Result<()> {
-        let (cmd, mut base_args) = self.get_entry()?.get_cmd(None)?;
-        base_args.extend_from_slice(&args);
-        if self.get_entry()?.term {
-            std::process::Command::new(cmd)
-                .args(base_args)
-                .status()?;
-        } else {
-            std::process::Command::new(cmd)
-                .args(base_args)
-                .stdout(std::process::Stdio::null())
-                .stderr(std::process::Stdio::null())
-                .spawn()?;
-        }
-        Ok(())
-    }
-}
-
-#[derive(Debug, Clone, pest_derive::Parser, Default, PartialEq, Eq)]
-#[grammar = "ini.pest"]
-pub struct DesktopEntry {
-    pub(crate) name: String,
-    pub(crate) exec: String,
-    pub(crate) file_name: String,
-    pub(crate) term: bool,
-    pub(crate) mimes: Vec<Mime>,
-}
-
-impl DesktopEntry {
-    pub fn get_cmd(
-        &self,
-        arg: Option<String>,
-    ) -> Result<(String, Vec<String>)> {
-        let special = regex::Regex::new("%(f|F|u|U)").unwrap();
-        let mut split = shlex::split(&self.exec)
-            .ok_or(Error::BadCmd)?
-            .into_iter()
-            .map(|s| match s.as_str() {
-                "%f" | "%F" | "%u" | "%U" => arg.clone(),
-                s if special.is_match(s) => Some(
-                    special
-                        .replace_all(s, arg.as_deref().unwrap_or_default())
-                        .into(),
-                ),
-                _ => Some(s),
-            })
-            .filter_map(std::convert::identity)
-            .collect::<Vec<_>>();
-
-        Ok((split.remove(0), split))
-    }
-}
-
-impl TryFrom<PathBuf> for DesktopEntry {
-    type Error = Error;
-    fn try_from(p: PathBuf) -> Result<DesktopEntry> {
-        use pest::Parser;
-        let raw = std::fs::read_to_string(&p)?;
-        let file = Self::parse(Rule::file, &raw)?.next().unwrap();
-
-        let mut section: &str = Default::default();
-        let mut entry = Self::default();
-        entry.file_name = p.file_name().unwrap().to_str().unwrap().to_owned();
-
-        for line in file.into_inner() {
-            match line.as_rule() {
-                Rule::section => {
-                    section = line.into_inner().as_str();
-                }
-                Rule::property if section == "Desktop Entry" => {
-                    let mut inner_rules = line.into_inner(); // { name ~ "=" ~ value }
-
-                    let name = inner_rules.next().unwrap().as_str();
-                    match name {
-                        "Name" if entry.name.is_empty() => {
-                            entry.name =
-                                inner_rules.next().unwrap().as_str().into();
-                        }
-                        "Exec" => {
-                            entry.exec =
-                                inner_rules.next().unwrap().as_str().into();
-                        }
-                        "MimeType" => {
-                            let mut mimes = inner_rules
-                                .next()
-                                .unwrap()
-                                .as_str()
-                                .split(";")
-                                .map(ToOwned::to_owned)
-                                .map(Mime)
-                                .collect::<Vec<_>>();
-                            mimes.pop();
-                            entry.mimes = mimes;
-                        }
-                        "Terminal" => {
-                            entry.term = match inner_rules.next().unwrap().as_str() {
-                                "true" => true,
-                                _ => false,
-                            }
-                        }
-                        _ => {}
-                    }
-                }
-                _ => {}
-            }
-        }
-
-        if !entry.name.is_empty() && !entry.exec.is_empty() {
-            Ok(entry)
-        } else {
-            Err(Error::BadCmd)
-        }
-    }
-}
diff --git a/src/mime_types.rs b/src/common/db.rs
similarity index 51%
rename from src/mime_types.rs
rename to src/common/db.rs
index 12056ff..31fccfc 100644
--- a/src/mime_types.rs
+++ b/src/common/db.rs
@@ -1,9 +1,9 @@
-use crate::{Error, Mime, Result};
+use crate::Result;
 use once_cell::sync::Lazy;
-use std::path::Path;
 use xdg_mime::SharedMimeInfo;
 
-static SHARED_MIME_DB: Lazy<SharedMimeInfo> = Lazy::new(SharedMimeInfo::new);
+pub static SHARED_MIME_DB: Lazy<SharedMimeInfo> =
+    Lazy::new(SharedMimeInfo::new);
 
 static CUSTOM_MIMES: &[&'static str] = &[
     "inode/directory",
@@ -11,29 +11,7 @@ static CUSTOM_MIMES: &[&'static str] = &[
     "x-scheme-handler/https",
 ];
 
-pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Mime> {
-    let guess = SHARED_MIME_DB.guess_mime_type().path(path).guess();
-
-    match guess.mime_type().essence_str() {
-        "application/octet-stream" => Err(Error::Ambiguous),
-        mime => Ok(Mime(mime.to_owned())),
-    }
-}
-
-pub fn verify(mime: &str) -> Result<&str> {
-    if mime.starts_with("x-scheme-handler/") || CUSTOM_MIMES.contains(&mime) {
-        return Ok(mime);
-    }
-
-    mime_db::TYPES
-        .iter()
-        .find(|(m, _, _)| m == &mime)
-        .ok_or(Error::BadMime(mime.to_owned()))?;
-
-    Ok(mime)
-}
-
-pub fn list() -> Result<()> {
+pub fn autocomplete() -> Result<()> {
     use std::io::Write;
 
     let stdout = std::io::stdout();
diff --git a/src/common/desktop_entry.rs b/src/common/desktop_entry.rs
new file mode 100644
index 0000000..e580277
--- /dev/null
+++ b/src/common/desktop_entry.rs
@@ -0,0 +1,129 @@
+use crate::{Error, Result};
+use mime::Mime;
+use std::{
+    convert::TryFrom,
+    ffi::OsString,
+    path::PathBuf,
+    process::{Command, Stdio},
+    str::FromStr,
+};
+
+#[derive(Debug, Clone, pest_derive::Parser, Default, PartialEq, Eq)]
+#[grammar = "common/ini.pest"]
+pub struct DesktopEntry {
+    pub(crate) name: String,
+    pub(crate) exec: String,
+    pub(crate) file_name: OsString,
+    pub(crate) term: bool,
+    pub(crate) mimes: Vec<Mime>,
+}
+
+impl DesktopEntry {
+    pub fn exec(&self, arguments: Vec<String>) -> Result<()> {
+        let supports_multiple =
+            self.exec.contains("%F") || self.exec.contains("%U");
+        if arguments.is_empty() {
+            self.exec_inner(None)?
+        } else if supports_multiple {
+            self.exec_inner(Some(arguments.join(" ")))?;
+        } else {
+            for arg in arguments {
+                self.exec_inner(Some(arg))?;
+            }
+        };
+
+        Ok(())
+    }
+    fn exec_inner(&self, arg: Option<String>) -> Result<()> {
+        let (cmd, args) = self.get_cmd(arg)?;
+        let mut cmd = Command::new(cmd);
+        cmd.args(args);
+        if self.term {
+            cmd.status()?;
+        } else {
+            cmd.stdout(Stdio::null()).stderr(Stdio::null()).spawn()?;
+        };
+        Ok(())
+    }
+    pub fn get_cmd(
+        &self,
+        arg: Option<String>,
+    ) -> Result<(String, Vec<String>)> {
+        let special = regex::Regex::new("%(f|F|u|U)").unwrap();
+
+        let mut split = shlex::split(&self.exec)
+            .unwrap()
+            .into_iter()
+            .filter_map(|s| match s.as_str() {
+                "%f" | "%F" | "%u" | "%U" => arg.clone(),
+                s if special.is_match(s) => Some(
+                    special
+                        .replace_all(s, arg.as_deref().unwrap_or_default())
+                        .into(),
+                ),
+                _ => Some(s),
+            })
+            .collect::<Vec<_>>();
+
+        Ok((split.remove(0), split))
+    }
+}
+
+impl TryFrom<PathBuf> for DesktopEntry {
+    type Error = Error;
+    fn try_from(p: PathBuf) -> Result<DesktopEntry> {
+        use pest::Parser;
+        let raw = std::fs::read_to_string(&p)?;
+        let file = Self::parse(Rule::file, &raw)?.next().unwrap();
+
+        let mut entry = Self::default();
+        entry.file_name = p.file_name().unwrap().to_owned();
+        let mut section = "";
+
+        for line in file.into_inner() {
+            match line.as_rule() {
+                Rule::section => {
+                    section = line.into_inner().as_str();
+                }
+                Rule::property if section == "Desktop Entry" => {
+                    let mut inner_rules = line.into_inner(); // { name ~ "=" ~ value }
+
+                    let name = inner_rules.next().unwrap().as_str();
+                    match name {
+                        "Name" if entry.name.is_empty() => {
+                            entry.name =
+                                inner_rules.next().unwrap().as_str().into();
+                        }
+                        "Exec" => {
+                            entry.exec =
+                                inner_rules.next().unwrap().as_str().into();
+                        }
+                        "MimeType" => {
+                            let mut mimes = inner_rules
+                                .next()
+                                .unwrap()
+                                .as_str()
+                                .split(";")
+                                .filter_map(|m| Mime::from_str(m).ok())
+                                .collect::<Vec<_>>();
+                            mimes.pop();
+                            entry.mimes = mimes;
+                        }
+                        "Terminal" => {
+                            entry.term =
+                                inner_rules.next().unwrap().as_str() == "true"
+                        }
+                        _ => {}
+                    }
+                }
+                _ => {}
+            }
+        }
+
+        if !entry.name.is_empty() && !entry.exec.is_empty() {
+            Ok(entry)
+        } else {
+            Err(Error::BadEntry(p.clone()))
+        }
+    }
+}
diff --git a/src/common/handler.rs b/src/common/handler.rs
new file mode 100644
index 0000000..e8d76f6
--- /dev/null
+++ b/src/common/handler.rs
@@ -0,0 +1,41 @@
+use crate::{common::DesktopEntry, Error, Result};
+use std::{convert::TryFrom, ffi::OsString, path::PathBuf};
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Handler(OsString);
+
+impl std::fmt::Display for Handler {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(&self.0.to_string_lossy())
+    }
+}
+
+impl std::str::FromStr for Handler {
+    type Err = Error;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Self::resolve(s.into())
+    }
+}
+
+impl Handler {
+    pub fn assume_valid(name: OsString) -> Self {
+        Self(name)
+    }
+    pub fn get_path(name: &std::ffi::OsStr) -> Option<PathBuf> {
+        let mut path = PathBuf::from("applications");
+        path.push(name);
+        xdg::BaseDirectories::new().ok()?.find_data_file(path)
+    }
+    pub fn resolve(name: OsString) -> Result<Self> {
+        let path = Self::get_path(&name)
+            .ok_or(Error::NotFound(name.to_string_lossy().into()))?;
+        DesktopEntry::try_from(path)?;
+        Ok(Self(name))
+    }
+    pub fn get_entry(&self) -> Result<DesktopEntry> {
+        DesktopEntry::try_from(Self::get_path(&self.0).unwrap())
+    }
+    pub fn launch(&self, args: Vec<String>) -> Result<()> {
+        self.get_entry()?.exec(args)
+    }
+}
diff --git a/src/ini.pest b/src/common/ini.pest
similarity index 100%
rename from src/ini.pest
rename to src/common/ini.pest
diff --git a/src/common/mime_types.rs b/src/common/mime_types.rs
new file mode 100644
index 0000000..d12fdd3
--- /dev/null
+++ b/src/common/mime_types.rs
@@ -0,0 +1,80 @@
+use crate::{common::SHARED_MIME_DB, Error, Result};
+use mime::Mime;
+use std::{
+    convert::TryFrom,
+    path::{Path, PathBuf},
+    str::FromStr,
+};
+
+// A mime derived from a path or URL
+#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FlexibleMime(pub Mime);
+
+impl TryFrom<&str> for FlexibleMime {
+    type Error = Error;
+
+    fn try_from(arg: &str) -> Result<Self> {
+        if let Ok(url) = url::Url::parse(arg) {
+            Ok(Self(
+                format!("x-scheme-handler/{}", url.scheme())
+                    .parse::<Mime>()
+                    .unwrap(),
+            ))
+        } else {
+            Self::try_from(&*PathBuf::from(arg))
+        }
+    }
+}
+
+impl TryFrom<&Path> for FlexibleMime {
+    type Error = Error;
+    fn try_from(path: &Path) -> Result<Self> {
+        let guess = SHARED_MIME_DB.guess_mime_type().path(path).guess();
+        let guess = guess.mime_type().clone();
+
+        if guess == mime::APPLICATION_OCTET_STREAM {
+            Err(Error::Ambiguous(path.to_string_lossy().into()))
+        } else {
+            Ok(Self(guess))
+        }
+    }
+}
+
+// Mime derived from user input: extension(.pdf) or type like image/jpg
+#[derive(Debug)]
+pub struct MimeOrExtension(pub Mime);
+impl FromStr for MimeOrExtension {
+    type Err = Error;
+    fn from_str(s: &str) -> Result<Self> {
+        if s.starts_with(".") {
+            Ok(Self(FlexibleMime::try_from(s)?.0))
+        } else {
+            Ok(Self(Mime::from_str(s)?))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn user_input() {
+        "image/jpg".parse::<MimeOrExtension>().unwrap();
+        ".jpg".parse::<MimeOrExtension>().unwrap();
+        "image//jpg".parse::<MimeOrExtension>().unwrap_err();
+        "image".parse::<MimeOrExtension>().unwrap_err();
+    }
+
+    #[test]
+    fn from_path_with_extension() {
+        assert_eq!(
+            FlexibleMime::try_from(".pdf").unwrap().0,
+            mime::APPLICATION_PDF
+        );
+        assert_eq!(
+            FlexibleMime::try_from(".").unwrap().0.essence_str(),
+            "inode/directory"
+        );
+    }
+}
diff --git a/src/common/mod.rs b/src/common/mod.rs
new file mode 100644
index 0000000..9568d8d
--- /dev/null
+++ b/src/common/mod.rs
@@ -0,0 +1,9 @@
+mod db;
+mod desktop_entry;
+mod handler;
+mod mime_types;
+
+pub use self::db::{autocomplete as db_autocomplete, SHARED_MIME_DB};
+pub use desktop_entry::{DesktopEntry, Rule as PestRule};
+pub use handler::Handler;
+pub use mime_types::{FlexibleMime, MimeOrExtension};
diff --git a/src/error.rs b/src/error.rs
index b4de979..08e6cba 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,25 +1,21 @@
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
     #[error(transparent)]
-    Parse(#[from] pest::error::Error<crate::common::Rule>),
+    Parse(#[from] pest::error::Error<crate::common::PestRule>),
     #[error(transparent)]
     Io(#[from] std::io::Error),
-    #[error("no handler defined for this mime/extension")]
-    NotFound,
-    #[error("badly-formatted desktop entry")]
-    BadCmd,
-    #[error("could not locate config dir")]
-    NoConfigDir,
-    #[error("could not figure out the mime type")]
-    Ambiguous,
-    #[error("could not figure out the mime type from extension .{0}")]
-    UnknownExtension(String),
-    #[error("invalid mime {0}")]
-    BadMime(String),
-    #[error("either mime (via -m) or extension (via -e) must be provided")]
-    MissingMimeOrExt,
+    #[error(transparent)]
+    Notify(#[from] notify_rust::error::Error),
     #[error(transparent)]
     Xdg(#[from] xdg::BaseDirectoriesError),
+    #[error("no handler defined for this mime/extension")]
+    NotFound(String),
+    #[error("could not figure out the mime type .{0}")]
+    Ambiguous(String),
+    #[error(transparent)]
+    BadMimeType(#[from] mime::FromStrError),
+    #[error("Malformed desktop entry")]
+    BadEntry(std::path::PathBuf),
 }
 
 pub type Result<T, E = Error> = std::result::Result<T, E>;
diff --git a/src/main.rs b/src/main.rs
index 0ad8fdc..8969c20 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,39 +1,57 @@
 use clap::Clap;
 use error::{Error, Result};
+use notify_rust::Notification;
+use std::convert::TryFrom;
 
+mod apps;
 mod common;
 mod error;
-mod mime_types;
-mod mimeapps;
 
-pub use common::{DesktopEntry, Handler, Mime};
+use common::{DesktopEntry, FlexibleMime, Handler, MimeOrExtension};
 
 #[derive(Clap)]
+#[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)]
+#[clap(global_setting = clap::AppSettings::DisableHelpSubcommand)]
+#[clap(version = clap::crate_version!())]
 enum Cmd {
+    /// List default apps and the associated handlers
     List,
+
+    /// Open a path/URL with its default handler
     Open {
-        path: String,
+        #[clap(required = true)]
+        path: Vec<String>,
     },
+
+    /// Set the default handler for mime/extension
+    Set {
+        mime: MimeOrExtension,
+        handler: Handler,
+    },
+
+    /// Unset the default handler for mime/extension
+    Unset { mime: MimeOrExtension },
+
+    /// Launch the handler for specified extension/mime with optional arguments
+    Launch {
+        mime: MimeOrExtension,
+        args: Vec<String>,
+    },
+
+    /// Get handler for this mime/extension
     Get {
         #[clap(long)]
         json: bool,
-        mime: Mime,
-    },
-    Launch {
-        mime: Mime,
-        args: Vec<String>,
+        mime: MimeOrExtension,
     },
+
+    /// Add a handler for given mime/extension
+    /// Note that the first handler is the default
     Add {
-        mime: Mime,
+        mime: MimeOrExtension,
         handler: Handler,
     },
-    Set {
-        mime: Mime,
-        handler: Handler,
-    },
-    Unset {
-        mime: Mime,
-    },
+
     #[clap(setting = clap::AppSettings::Hidden)]
     Autocomplete {
         #[clap(short)]
@@ -44,41 +62,60 @@ enum Cmd {
 }
 
 fn main() -> Result<()> {
-    let mut apps = mimeapps::MimeApps::read()?;
+    let mut apps = apps::MimeApps::read()?;
 
-    match Cmd::parse() {
-        Cmd::Set { mime, handler } => {
-            apps.set_handler(mime, handler)?;
-        }
-        Cmd::Add { mime, handler } => {
-            apps.add_handler(mime, handler)?;
-        }
-        Cmd::Launch { mime, args } => {
-            apps.get_handler(&mime)?.launch(args)?;
-        }
-        Cmd::Get { mime, json } => {
-            apps.show_handler(&mime, json)?;
-        }
-        Cmd::Open { path } => {
-            apps.get_handler(&Mime::try_from_path(&path)?)?.open(path)?;
-        }
-        Cmd::List => {
-            apps.print()?;
-        }
-        Cmd::Unset { mime } => {
-            apps.remove_handler(&mime)?;
-        }
-        Cmd::Autocomplete {
-            desktop_files,
-            mimes,
-        } => {
-            if desktop_files {
-                apps.list_handlers()?;
-            } else if mimes {
-                mime_types::list()?;
+    let res = || -> Result<()> {
+        match Cmd::parse() {
+            Cmd::Set { mime, handler } => {
+                apps.set_handler(mime.0, handler)?;
+            }
+            Cmd::Add { mime, handler } => {
+                apps.add_handler(mime.0, handler)?;
+            }
+            Cmd::Launch { mime, args } => {
+                apps.get_handler(&mime.0)?.launch(args)?;
+            }
+            Cmd::Get { mime, json } => {
+                apps.show_handler(&mime.0, json)?;
+            }
+            Cmd::Open { path } => {
+                std::process::Command::new("notify-send")
+                    .arg(&format!("{:?}", path))
+                    .spawn()?;
+                apps.get_handler(
+                    &FlexibleMime::try_from(path.get(0).unwrap().as_str())?.0,
+                )?
+                .launch(path)?;
+            }
+            Cmd::List => {
+                apps.print()?;
+            }
+            Cmd::Unset { mime } => {
+                apps.remove_handler(&mime.0)?;
+            }
+            Cmd::Autocomplete {
+                desktop_files,
+                mimes,
+            } => {
+                if desktop_files {
+                    apps.list_handlers()?;
+                } else if mimes {
+                    common::db_autocomplete()?;
+                }
             }
         }
-    };
+        Ok(())
+    }();
 
+    match (res, atty::is(atty::Stream::Stdout)) {
+        (Err(e), true) => eprintln!("{}", e),
+        (Err(e), false) => {
+            Notification::new()
+                .summary("handlr error")
+                .body(&e.to_string())
+                .show()?;
+        }
+        _ => {}
+    };
     Ok(())
 }