Repository: espanso/espanso (language: rust)

Issue:
Unexpected exit code from worker 90 on Wayland
Now espanso was finally working fine on Wayland, but as of yesterday it stopped working. :/

`espanso start` appears to start espanso, but the replacements aren't working. And if I execute `espanso restart`, it says that espanso wasn't running, but it also doesn't start it.

`#acfg#` and `#pacfg#` aren't working either, but the log does say that it quit with an unexpected exit code.

```
& thefish @ hx90 in ~/fromsvngit/espanso (master ✔) 0 [11:32:36]
~ espanso log
11:32:38 [daemon(673798)] [INFO] reading configs from: "/home/thefish/.config/espanso"
11:32:38 [daemon(673798)] [INFO] reading packages from: "/home/thefish/.config/espanso/match/packages"
11:32:38 [daemon(673798)] [INFO] using runtime dir: "/home/thefish/.local/share/espanso"
11:32:38 [daemon(673798)] [WARN] keyboard layout watcher couldn't determine active layout.
11:32:38 [daemon(673798)] [INFO] watching for changes in path: "/home/thefish/.config/espanso"
11:32:38 [daemon(673798)] [INFO] espanso version: 2.1.5-beta
11:32:38 [daemon(673798)] [INFO] spawning the worker process...
11:32:38 [daemon(673798)] [INFO] binded to IPC unix socket: /home/thefish/.local/share/espanso/espansodaemonv2.sock
11:32:38 [worker(673804)] [INFO] reading configs from: "/home/thefish/.config/espanso"
11:32:38 [worker(673804)] [INFO] reading packages from: "/home/thefish/.config/espanso/match/packages"
11:32:38 [worker(673804)] [INFO] using runtime dir: "/home/thefish/.local/share/espanso"
11:32:38 [worker(673804)] [INFO] binded to IPC unix socket: /home/thefish/.local/share/espanso/espansoworkerv2.sock
11:32:38 [worker(673804)] [INFO] monitoring the status of the daemon process
11:32:38 [worker(673804)] [INFO] using WaylandAppInfoProvider
11:32:38 [worker(673804)] [INFO] detection module will use this keyboard layout: [R=evdev, M=pc86, L=us, V=dvorak-intl, O=terminate:ctrl_alt_bksp]
11:32:38 [worker(673804)] [INFO] using EVDEVSource
11:32:38 [worker(673804)] [INFO] Querying modifier status...
11:32:39 [daemon(673798)] [ERROR] received unexpected exit code from worker 90, exiting

& thefish @ hx90 in ~/fromsvngit/espanso (master ✔) 0 [11:32:41]
~
```

OS: Arch Linux
KDE: 5.24
Espanso version: latest git master

The matches file isn't corrupt and I didn't even edit it in the past few weeks.

How can we further investigate this?
package install fails on debian trixie
**Logs**

```
The following packages have unmet dependencies:
 espanso-wayland : Depends: libwxgtk3.0-gtk3-0v5 but it is not installable
                   Depends: libwxgtk3.0-gtk3-0v5 (>= 3.0.4+dfsg) but it is not installable
                   Depends: libwxbase3.0-0v5 (>= 3.0.4+dfsg) but it is not installable
E: Unable to correct problems, you have held broken packages.
```

**Setup information**
- OS: What OS are you using? debian trixie (testing)
- Version: which version of Espanso are you running? attempting to install 2.1.8

**Analysis**

the issue is that debian does not have these versions of wx any longer:

```
$ apt search '^libwx(base|gtk3)'
Sorting... Done
Full Text Search... Done
libwxbase3.2-1/testing,unstable,now 3.2.2+dfsg-2 amd64 [installed,automatic]
  wxBase library (runtime) - non-GUI support classes of wxWidgets toolkit

libwxgtk3.2-1/testing,unstable,now 3.2.2+dfsg-2 amd64 [installed,automatic]
  wxWidgets Cross-platform C++ GUI toolkit (GTK 3 runtime)

libwxgtk3.2-dev/testing,unstable,now 3.2.2+dfsg-2 amd64 [installed,automatic]
  wxWidgets Cross-platform C++ GUI toolkit (GTK 3 development)
```

however, this seems to be more than simply a packaging issue. when i extract the deb and manually execute the binary, it fails because it is linked to `libwx_gtk3u_html-3.0.so` which doesn't exist on my system. i'm not sure if there were breaking APi changes between these versions of libgtk.


Discussion / hints:
Hey @Vistaus,

I'm sorry you experienced this problem! Could you please try running the following and post the output here?

```
espanso -vvv worker
```

Cheers :)

Hey Frederico! :)

That command produced some more output:

```
& thefish @ hx90 in ~/Downloads 0 [20:20:12]
~ espanso -vvv worker
22:34:07 [worker(838091)] [DEBUG] (1) espanso_path: detected config directory in $HOME/.config/espanso
22:34:07 [worker(838091)] [DEBUG] (1) espanso_path: detected legacy runtime dir: "/home/thefish/.local/share/espanso"
22:34:07 [worker(838091)] [DEBUG] (1) espanso_path: detected default packages dir: "/home/thefish/.config/espanso/match/packages"
22:34:07 [worker(838091)] [INFO] reading configs from: "/home/thefish/.config/espanso"
22:34:07 [worker(838091)] [INFO] reading packages from: "/home/thefish/.config/espanso/match/packages"
22:34:07 [worker(838091)] [INFO] using runtime dir: "/home/thefish/.local/share/espanso"
22:34:07 [worker(838091)] [DEBUG] (1) espanso_config::config::store: loaded default config at path: "/home/thefish/.config/espanso/config/default.yml"
22:34:07 [worker(838091)] [DEBUG] (1) espanso_config::config::store: loaded config at path: "/home/thefish/.config/espanso/config/firefox.yml"
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'alacritty_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'emacs_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'gedit_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'generic_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'kitty_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'konsole_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'libreoffice_writer_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'simple_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'simple_terminal_2_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'terminator_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'termite_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'thunderbird_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'tilix_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'urxvt_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'xterm_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'yakuake_terminal_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::patch::config_store: skipping 'virtualbox_x11' patch
22:34:07 [worker(838091)] [DEBUG] (1) espanso::cli::worker: starting with start-reason = None
22:34:07 [worker(838091)] [DEBUG] (1) espanso::icon: skipping extraction for '"/home/thefish/.local/share/espanso/iconv2.png"', as it's already present
22:34:07 [worker(838091)] [DEBUG] (1) espanso::icon: skipping extraction for '"/home/thefish/.local/share/espanso/search.png"', as it's already present
22:34:07 [worker(838091)] [DEBUG] (1) espanso::icon: skipping extraction for '"/home/thefish/.local/share/espanso/icon_no_background.png"', as it's already present
22:34:07 [worker(838091)] [INFO] binded to IPC unix socket: /home/thefish/.local/share/espanso/espansoworkerv2.sock
22:34:07 [worker(838091)] [INFO] using WaylandAppInfoProvider
22:34:07 [worker(838091)] [DEBUG] (2) espanso::cli::worker::engine: using linux capabilities to grant permissions needed by EVDEV backend
22:34:07 [worker(838091)] [DEBUG] (2) espanso::cli::worker::engine: successfully granted permissions using capabilities
22:34:07 [worker(838091)] [INFO] detection module will use this keyboard layout: [R=evdev, M=pc104, L=us, V=dvorak-intl, O=]
22:34:07 [worker(838091)] [INFO] using EVDEVSource
22:34:07 [worker(838091)] [INFO] Querying modifier status...
[wayland-client error] Attempted to dispatch unknown opcode 0 for wl_shm, aborting.
fish: Job 1, 'espanso -vvv worker' terminated by signal SIGABRT (Abort)

& thefish @ hx90 in ~/Downloads 0 [22:34:08]
~
```
@Vistaus Thanks! That's interesting. Did you make some changes with your Desktop Environment? It seems something is wrong with the wayland protocol exchange
@federico-terzi No, I'm still using the same KDE Plasma that I was using before.
@Vistaus Interesting, I don't have many ideas at the moment but I'll definitely investigate :)
@federico-terzi That'd be great. Thank you for looking into it! :)
I missed Espanso too much, so I couldn't wait for this to be solved. So I switched to openSUSE Tumbleweed, eventually got Espanso to compile and now it's working again. So I'll close this issue. :)
It's back again after the Plasma 5.25 update on openSUSE Tumbleweed today. espanso can't be started anymore because of this issue... :(
The output of espanso -vvv worker is the same as above.
I hope this can be fixed. I really miss Espanso (esp. when using it for work). :(
I can reproduce this issue. 
```
❯ ./target/release/espanso daemon                                                                              at  19:25:03
19:25:03 [daemon(73121)] [INFO] reading configs from: "/home/ss/.config/espanso"
19:25:03 [daemon(73121)] [INFO] reading packages from: "/home/ss/.config/espanso/match/packages"
19:25:03 [daemon(73121)] [INFO] using runtime dir: "/home/ss/.cache/espanso"
19:25:03 [daemon(73121)] [INFO] system info: Fedora Linux v36 - kernel: 5.18.13-200.fc36.x86_64
19:25:03 [daemon(73121)] [INFO] watching for changes in path: "/home/ss/.config/espanso"
19:25:03 [daemon(73121)] [INFO] espanso version: 2.1.7-beta
19:25:03 [daemon(73121)] [INFO] spawning the worker process...
19:25:03 [daemon(73121)] [INFO] binded to IPC unix socket: /home/ss/.cache/espanso/espansodaemonv2.sock
19:25:03 [worker(73128)] [INFO] reading configs from: "/home/ss/.config/espanso"
19:25:03 [worker(73128)] [INFO] reading packages from: "/home/ss/.config/espanso/match/packages"
19:25:03 [worker(73128)] [INFO] using runtime dir: "/home/ss/.cache/espanso"
19:25:03 [worker(73128)] [INFO] system info: Fedora Linux v36 - kernel: 5.18.13-200.fc36.x86_64
19:25:03 [worker(73128)] [INFO] using WaylandAppInfoProvider
19:25:03 [worker(73128)] [INFO] binded to IPC unix socket: /home/ss/.cache/espanso/espansoworkerv2.sock
19:25:03 [worker(73128)] [WARN] this version of espanso doesn't come with modulo support, so graphical features (such as Forms and Search) might not be available
19:25:03 [worker(73128)] [WARN] EVDEV backend is being used, but without enabling linux capabilities.
19:25:03 [worker(73128)] [WARN]   Although you CAN run espanso EVDEV backend as root, it's not recommended due
19:25:03 [worker(73128)] [WARN]   to security reasons. Espanso supports linux capabilities to limit the attack surface
19:25:03 [worker(73128)] [INFO] monitoring the status of the daemon process
19:25:03 [worker(73128)] [WARN]   area by only leveraging on the CAP_DAC_OVERRIDE capability (needed to work with
19:25:03 [worker(73128)] [WARN]   /dev/input/* devices to detect and inject text) and disabling it as soon as the
19:25:03 [worker(73128)] [WARN]   initial setup is completed.
19:25:03 [worker(73128)] [INFO] detection module will use this keyboard layout: [R=, M=, L=us, V=, O=]
19:25:03 [worker(73128)] [INFO] using EVDEVSource
19:25:03 [worker(73128)] [INFO] Querying modifier status...
[wayland-client error] Attempted to dispatch unknown opcode 0 for wl_shm, aborting.
19:25:03 [daemon(73121)] [ERROR] received unexpected exit code from worker 90, exiting

```
KDE Plasma again, on Fedora Kinoite. 
Same problem under Archlinux with KDE 5.97.0 / Plasma 5.25.4 (plasma wayland session)
Please check if you have installed wl-clipboard?  At one point there was an overlooked or not-mentioned dependency for this. At one time, I believe I was getting a "worker 90" type of error. My logs:

$ espanso stop
$ espanso daemon
 21:56:15 [daemon(12924)] [INFO] reading configs from: "/home/ykill/.config/espanso"
 21:56:15 [daemon(12924)] [INFO] reading packages from: "/home/ykill/.config/espanso/match/packages"
 21:56:15 [daemon(12924)] [INFO] using runtime dir: "/home/ykill/.cache/espanso"
 21:56:15 [daemon(12924)] [INFO] system info: openSUSE Tumbleweed v20220925 - kernel: 5.19.10-1-default
 21:56:15 [daemon(12924)] [INFO] espanso version: 2.1.7-beta
 21:56:15 [daemon(12924)] [INFO] spawning the worker process...
 21:56:15 [daemon(12924)] [INFO] watching for changes in path: "/home/ykill/.config/espanso"
 21:56:15 [daemon(12924)] [INFO] binded to IPC unix socket: /home/ykill/.cache/espanso/espansodaemonv2.sock
 21:56:15 [daemon(12924)] [WARN] keyboard layout watcher couldn't determine active layout.
 21:56:15 [worker(12931)] [INFO] reading configs from: "/home/ykill/.config/espanso"
 21:56:15 [worker(12931)] [INFO] reading packages from: "/home/ykill/.config/espanso/match/packages"
 21:56:15 [worker(12931)] [INFO] using runtime dir: "/home/ykill/.cache/espanso"
 21:56:15 [worker(12931)] [INFO] system info: openSUSE Tumbleweed v20220925 - kernel: 5.19.10-1-default
 21:56:15 [worker(12931)] [INFO] binded to IPC unix socket: /home/ykill/.cache/espanso/espansoworkerv2.sock
 21:56:15 [worker(12931)] [INFO] using WaylandAppInfoProvider
 21:56:15 [worker(12931)] [INFO] monitoring the status of the daemon process
 21:56:15 [worker(12931)] [INFO] detection module will use this keyboard layout: [R=, M=, L=us, V=, O=]
 21:56:15 [worker(12931)] [INFO] using EVDEVSource
 21:56:16 [worker(12931)] [INFO] Querying modifier status...
 21:56:16 [worker(12931)] [INFO] inject module will use this keyboard layout: [R=, M=, L=us, V=, O=]
 21:56:16 [worker(12931)] [INFO] using EVDEVInjector
 21:56:16 [worker(12931)] [INFO] using WaylandFallbackClipboard

At this point, I can test, and espanso is working. I don't see any further log messages. Please excuse my formatting at this time.

I originally had used a AppImage for espanso, however I now compile from source.

I added: 
wl-clipboard-2.1.0-1.4.x86_64

My setup:
openSUSE TW current
gnome-session-wayland-43
xwayland-22.1.3-1.2


@ykill wl-clipboard is installed for me, however, it's still not working on my openSUSE TW install.
I have the same issue on Arch with KDE 5.26.2. I tried going back to the 2.1.5 beta and it still didn't work. I'm going to try going back further, but perhaps this line from the `espanso -vvv worker` output is helpful (seems like everyone above has the same error):

`[wayland-client error] Attempted to dispatch unknown opcode 0 for wl_shm, aborting.`

I'll report back if I have any success with older versions of espanso.

**Update**: I just went back and tried a few versions all the way back to espanso 2.0.3-alpha and none of them worked, so this may be an issue with a recent KDE update, because I'm quite certain I used that version on KDE Wayland about a year ago. Not sure if this is something that needs to be fixed in espanso or in KDE. 

For now it's quite unfortunate and it's making me choose between espanso and KDE.

**Update 2**: I switched to the LTS kernel (5.15.75) just in case that had any effect and that didn't help either.
I was able to fix this by updating smithay-client-toolkit (sctk) to version 0.15.4. I've submitted a PR, but before that gets merged you can fix this by building espanso from source:

1. Edit `espanso/espanso-detect/Cargo.toml`
2. In the line `sctk = { package = "smithay-client-toolkit", version = "0.14.0", optional = true }` change the version to `0.15.4`.
3. Save the changes and build espanso as normal. It should now work perfectly on KDE Plasma with Wayland.
> I was able to fix this by updating smithay-client-toolkit (sctk) to version 0.15.4. I've submitted a PR, but before that gets merged you can fix this by building espanso from source:
> 
> 1. Edit `espanso/espanso-detect/Cargo.toml`
> 2. In the line `sctk = { package = "smithay-client-toolkit", version = "0.14.0", optional = true }` change the version to `0.15.4`.
> 3. Save the changes and build espanso as normal. It should now work perfectly on KDE Plasma with Wayland.

You, lakoliu, are a true hero! I can confirm that this fixed the issue. Finally I can use espanso again! 🙂

I'm sure your PR will be accepted, so I'll close this issue.
@lakoliu You are a life saver! Can confirm it works on Fedora 36 Kinoite (KDE Plasma Wayland) :heart: 
@lakoliu thanks!
Just for completeness:
```
# Clone repo:
mkdir git
cd git
git clone https://github.com/espanso/espanso.git
cd espanso
# compile:
cargo make --env NO_X11=true --profile release build-binary
cd ~/git/espanso/target/release # or where ever you cloned it.....
# Copy to correct place:
sudo cp espanso /usr/bin/espanso
# Then:
sudo setcap "cap_dac_override+p" $(which espanso)
```
My Espanso sprang to life and started working!!!!
@xircon (And _espanso service register_ if you want it to autostart. 😉)
Already done, ages ago :smile: That is why it sprang to life.


Fix patch:
diff --git a/.github/scripts/ubuntu/Dockerfile b/.github/scripts/ubuntu/Dockerfile
index 50379afa..d36de51a 100644
--- a/.github/scripts/ubuntu/Dockerfile
+++ b/.github/scripts/ubuntu/Dockerfile
@@ -9,7 +9,7 @@ RUN apt-get update \
 ENV RUSTUP_HOME=/usr/local/rustup \
     CARGO_HOME=/usr/local/cargo \
     PATH=/usr/local/cargo/bin:$PATH \
-    RUST_VERSION=1.57.0
+    RUST_VERSION=1.70.0
 
 RUN set -eux; \
     dpkgArch="$(dpkg --print-architecture)"; \
diff --git a/.github/scripts/ubuntu/build_deb.sh b/.github/scripts/ubuntu/build_deb.sh
index d8263929..ebea2b97 100755
--- a/.github/scripts/ubuntu/build_deb.sh
+++ b/.github/scripts/ubuntu/build_deb.sh
@@ -3,7 +3,7 @@
 set -e
 
 echo "Installing cargo-deb"
-cargo install cargo-deb --version 1.34.0
+cargo install cargo-deb --version 1.44.1
 
 cd espanso
 
diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml
new file mode 100644
index 00000000..03cf4914
--- /dev/null
+++ b/.github/workflows/dev-release.yml
@@ -0,0 +1,186 @@
+# Huge thanks to Alacritty, as their configuration served as a starting point for this one!
+# See: https://github.com/alacritty/alacritty
+
+name: Dev Release
+
+on:
+  push:
+    branches:
+      - dev
+
+env:
+  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  CARGO_TERM_COLOR: always
+
+jobs:
+  extract-version:
+    name: extract-version
+    runs-on: ubuntu-latest
+    outputs:
+      espanso_version: ${{ steps.version.outputs.version }}
+    steps:
+      - uses: actions/checkout@v2
+      - name: "Extract version"
+        id: "version"
+        run: |
+          ESPANSO_VERSION=$(grep '^version' espanso/Cargo.toml | awk -F '"' '{ print $2 }')
+          echo version: $ESPANSO_VERSION
+          echo "::set-output name=version::v$ESPANSO_VERSION"
+
+  windows:
+    needs: ["extract-version"]
+    runs-on: windows-latest
+
+    defaults:
+      run:
+        shell: bash
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Print target version
+        run: |
+          echo Using version ${{ needs.extract-version.outputs.espanso_version }}
+      - name: Install rust-script and cargo-make
+        run: |
+          cargo install rust-script --version "0.7.0"
+          cargo install --force cargo-make --version 0.34.0
+      - name: Test
+        run: cargo make test-binary --profile release
+      - name: Build resources
+        run: cargo make build-windows-resources --profile release
+      - name: Build installer
+        run: cargo make build-windows-installer --profile release --skip-tasks build-windows-resources
+      - name: Build portable mode archive 
+        run: cargo make build-windows-portable --profile release --skip-tasks build-windows-resources
+      - name: Create portable mode archive
+        shell: powershell
+        run: |
+          Rename-Item target/windows/portable espanso-portable
+          Compress-Archive target/windows/espanso-portable target/windows/Espanso-Win-Portable-x86_64.zip
+      - name: Calculate hashes
+        shell: powershell
+        run: |
+          Get-FileHash target/windows/Espanso-Win-Portable-x86_64.zip -Algorithm SHA256 | select-object -ExpandProperty Hash > target/windows/Espanso-Win-Portable-x86_64.zip.sha256.txt
+          Get-FileHash target/windows/installer/Espanso-Win-Installer-x86_64.exe -Algorithm SHA256 | select-object -ExpandProperty Hash > target/windows/installer/Espanso-Win-Installer-x86_64.exe.sha256.txt
+      - uses: actions/upload-artifact@v2
+        name: "Upload artifacts"
+        with:
+          name: Windows Artifacts
+          path: |
+            target/windows/installer/Espanso-Win-Installer-x86_64.exe
+            target/windows/Espanso-Win-Portable-x86_64.zip
+            target/windows/installer/Espanso-Win-Installer-x86_64.exe.sha256.txt
+            target/windows/Espanso-Win-Portable-x86_64.zip.sha256.txt
+  
+  linux-x11:
+    needs: ["extract-version"]
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Print target version
+        run: |
+          echo Using version ${{ needs.extract-version.outputs.espanso_version }}
+      - name: Build docker image
+        run: |
+          sudo docker build -t espanso-ubuntu . -f .github/scripts/ubuntu/Dockerfile
+      - name: Build AppImage
+        run: |
+          sudo docker run --rm -v "$(pwd):/shared" espanso-ubuntu espanso/.github/scripts/ubuntu/build_appimage.sh
+      - uses: actions/upload-artifact@v2
+        name: "Upload artifacts"
+        with:
+          name: Linux X11 Artifacts
+          path: |
+            Espanso-X11.AppImage
+            Espanso-X11.AppImage.sha256.txt
+  
+  linux-deb:
+    needs: ["extract-version"]
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Print target version
+        run: |
+          echo Using version ${{ needs.extract-version.outputs.espanso_version }}
+      - name: Build docker image
+        run: |
+          sudo docker build -t espanso-ubuntu . -f .github/scripts/ubuntu/Dockerfile
+      - name: Build Deb packages
+        run: |
+          sudo docker run --rm -v "$(pwd):/shared" espanso-ubuntu espanso/.github/scripts/ubuntu/build_deb.sh
+      - uses: actions/upload-artifact@v2
+        name: "Upload artifacts"
+        with:
+          name: Ubuntu-Debian Artifacts
+          path: |
+            espanso-debian-x11-amd64.deb
+            espanso-debian-wayland-amd64.deb
+
+
+  macos-intel:
+    needs: ["extract-version"]
+    runs-on: macos-11
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Print target version
+        run: |
+          echo Using version ${{ needs.extract-version.outputs.espanso_version }}
+      - name: Install rust-script and cargo-make
+        run: |
+          cargo install rust-script --version "0.7.0"
+          cargo install --force cargo-make --version 0.34.0
+      - name: Test
+        run: cargo make test-binary --profile release
+        env:
+          MACOSX_DEPLOYMENT_TARGET: "10.13"
+      - name: Build
+        run: cargo make create-bundle --profile release
+        env:
+          MACOSX_DEPLOYMENT_TARGET: "10.13"
+      - name: Create ZIP archive
+        run: |
+          ditto -c -k --sequesterRsrc --keepParent target/mac/Espanso.app Espanso-Mac-Intel.zip
+      - name: Calculate hashes
+        run: |
+          shasum -a 256 Espanso-Mac-Intel.zip > Espanso-Mac-Intel.zip.sha256.txt
+      - uses: actions/upload-artifact@v2
+        name: "Upload artifacts"
+        with:
+          name: Mac Intel Artifacts
+          path: |
+            Espanso-Mac-Intel.zip
+            Espanso-Mac-Intel.zip.sha256.txt
+  
+  macos-m1:
+    needs: ["extract-version"]
+    runs-on: macos-11
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Print target version
+        run: |
+          echo Using version ${{ needs.extract-version.outputs.espanso_version }}
+      - name: Install rust target
+        run: rustup update && rustup target add aarch64-apple-darwin
+      - name: Install rust-script and cargo-make
+        run: |
+          cargo install rust-script --version "0.7.0"
+          cargo install --force cargo-make --version 0.34.0
+      - name: Build
+        run: cargo make create-bundle --profile release --env BUILD_ARCH=aarch64-apple-darwin
+      - name: Create ZIP archive
+        run: |
+          ditto -c -k --sequesterRsrc --keepParent target/mac/Espanso.app Espanso-Mac-M1.zip
+      - name: Calculate hashes
+        run: |
+          shasum -a 256 Espanso-Mac-M1.zip > Espanso-Mac-M1.zip.sha256.txt
+      - uses: actions/upload-artifact@v2
+        name: "Upload artifacts"
+        with:
+          name: Mac M1 Artifacts
+          path: |
+            Espanso-Mac-M1.zip
+            Espanso-Mac-M1.zip.sha256.txt
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/prod-release.yml
similarity index 98%
rename from .github/workflows/release.yml
rename to .github/workflows/prod-release.yml
index 38d00cf8..7091bdf5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/prod-release.yml
@@ -1,13 +1,12 @@
 # Huge thanks to Alacritty, as their configuration served as a starting point for this one!
 # See: https://github.com/alacritty/alacritty
 
-name: Release
+name: Prod Release
 
 on:
   push:
     branches:
       - master
-      - dev
 
 env:
   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -31,6 +30,7 @@ jobs:
   create-release:
     name: create-release
     needs: ["extract-version"]
+    environment: production
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
@@ -49,6 +49,7 @@ jobs:
   windows:
     needs: ["extract-version", "create-release"]
     runs-on: windows-latest
+    environment: production
 
     defaults:
       run:
@@ -110,6 +111,7 @@ jobs:
   linux-x11:
     needs: ["extract-version", "create-release"]
     runs-on: ubuntu-latest
+    environment: production
 
     steps:
       - uses: actions/checkout@v2
@@ -137,6 +139,7 @@ jobs:
   linux-deb:
     needs: ["extract-version", "create-release"]
     runs-on: ubuntu-latest
+    environment: production
 
     steps:
       - uses: actions/checkout@v2
@@ -165,6 +168,7 @@ jobs:
   macos-intel:
     needs: ["extract-version", "create-release"]
     runs-on: macos-11
+    environment: production
 
     steps:
       - uses: actions/checkout@v2
@@ -235,6 +239,7 @@ jobs:
   macos-m1:
     needs: ["extract-version", "create-release"]
     runs-on: macos-11
+    environment: production
 
     steps:
       - uses: actions/checkout@v2
@@ -301,6 +306,7 @@ jobs:
   macos-publish-homebrew:
     needs: ["extract-version", "create-release", "macos-m1", "macos-intel"]
     runs-on: macos-11
+    environment: production
 
     steps:
       - uses: actions/checkout@v2
diff --git a/Cargo.lock b/Cargo.lock
index 82f195f1..6d414c0d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -76,6 +76,12 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
 [[package]]
 name = "bitflags"
 version = "0.9.1"
@@ -88,6 +94,12 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
 
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
 [[package]]
 name = "blake2b_simd"
 version = "0.5.11"
@@ -166,12 +178,15 @@ dependencies = [
 
 [[package]]
 name = "calloop"
-version = "0.9.3"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82"
+checksum = "595eb0438b3c6d262395fe30e6de9a61beb57ea56290b00a07f227fe6e20cbf2"
 dependencies = [
  "log",
- "nix 0.22.3",
+ "nix",
+ "slotmap",
+ "thiserror",
+ "vec_map",
 ]
 
 [[package]]
@@ -258,12 +273,6 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
-[[package]]
-name = "const-sha1"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d"
-
 [[package]]
 name = "const_format"
 version = "0.2.14"
@@ -582,7 +591,7 @@ dependencies = [
 
 [[package]]
 name = "espanso"
-version = "2.1.8"
+version = "2.2.0"
 dependencies = [
  "anyhow",
  "caps",
@@ -881,7 +890,7 @@ dependencies = [
  "serde_json",
  "thiserror",
  "widestring",
- "winrt-notification 0.3.1",
+ "winrt-notification 0.5.1",
 ]
 
 [[package]]
@@ -1042,19 +1051,6 @@ version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
 
-[[package]]
-name = "futures-macro"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
-dependencies = [
- "autocfg",
- "proc-macro-hack",
- "proc-macro2",
- "quote 1.0.9",
- "syn 1.0.67",
-]
-
 [[package]]
 name = "futures-sink"
 version = "0.3.17"
@@ -1076,13 +1072,10 @@ dependencies = [
  "autocfg",
  "futures-core",
  "futures-io",
- "futures-macro",
  "futures-task",
  "memchr",
  "pin-project-lite",
  "pin-utils",
- "proc-macro-hack",
- "proc-macro-nested",
  "slab",
 ]
 
@@ -1132,9 +1125,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
 
 [[package]]
 name = "h2"
-version = "0.3.4"
+version = "0.3.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472"
+checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21"
 dependencies = [
  "bytes",
  "fnv",
@@ -1212,7 +1205,7 @@ checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
 dependencies = [
  "bytes",
  "fnv",
- "itoa",
+ "itoa 0.4.7",
 ]
 
 [[package]]
@@ -1228,9 +1221,9 @@ dependencies = [
 
 [[package]]
 name = "httparse"
-version = "1.5.1"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
 
 [[package]]
 name = "httpdate"
@@ -1240,9 +1233,9 @@ checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
 
 [[package]]
 name = "hyper"
-version = "0.14.12"
+version = "0.14.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13f67199e765030fa08fe0bd581af683f0d5bc04ea09c2b1102012c5fb90e7fd"
+checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1253,7 +1246,7 @@ dependencies = [
  "http-body",
  "httparse",
  "httpdate",
- "itoa",
+ "itoa 1.0.6",
  "pin-project-lite",
  "socket2",
  "tokio",
@@ -1264,17 +1257,15 @@ dependencies = [
 
 [[package]]
 name = "hyper-rustls"
-version = "0.22.1"
+version = "0.23.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
+checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
 dependencies = [
- "futures-util",
+ "http",
  "hyper",
- "log",
  "rustls",
  "tokio",
  "tokio-rustls",
- "webpki",
 ]
 
 [[package]]
@@ -1394,6 +1385,12 @@ version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
 
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
 [[package]]
 name = "js-sys"
 version = "0.3.53"
@@ -1427,9 +1424,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.126"
+version = "0.2.144"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
 
 [[package]]
 name = "libdbus-sys"
@@ -1558,9 +1555,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
 [[package]]
 name = "memmap2"
-version = "0.3.1"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
 dependencies = [
  "libc",
 ]
@@ -1603,7 +1600,7 @@ dependencies = [
  "kernel32-sys",
  "libc",
  "log",
- "miow 0.2.2",
+ "miow",
  "net2",
  "slab",
  "winapi 0.2.8",
@@ -1611,15 +1608,14 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.7.13"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
 dependencies = [
  "libc",
  "log",
- "miow 0.3.7",
- "ntapi",
- "winapi 0.3.9",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys",
 ]
 
 [[package]]
@@ -1646,15 +1642,6 @@ dependencies = [
  "ws2_32-sys",
 ]
 
-[[package]]
-name = "miow"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
-dependencies = [
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "mockall"
 version = "0.9.1"
@@ -1693,9 +1680,9 @@ dependencies = [
 
 [[package]]
 name = "native-tls"
-version = "0.2.8"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
 dependencies = [
  "lazy_static",
  "libc",
@@ -1732,19 +1719,6 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
 
-[[package]]
-name = "nix"
-version = "0.22.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
-dependencies = [
- "bitflags 1.2.1",
- "cc",
- "cfg-if 1.0.0",
- "libc",
- "memoffset",
-]
-
 [[package]]
 name = "nix"
 version = "0.24.2"
@@ -1804,9 +1778,9 @@ dependencies = [
 
 [[package]]
 name = "ntapi"
-version = "0.3.6"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
 dependencies = [
  "winapi 0.3.9",
 ]
@@ -1893,18 +1867,30 @@ dependencies = [
 
 [[package]]
 name = "openssl"
-version = "0.10.36"
+version = "0.10.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a"
+checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45"
 dependencies = [
- "bitflags 1.2.1",
+ "bitflags 2.4.1",
  "cfg-if 1.0.0",
  "foreign-types",
  "libc",
  "once_cell",
+ "openssl-macros",
  "openssl-sys",
 ]
 
+[[package]]
+name = "openssl-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.9",
+ "syn 1.0.67",
+]
+
 [[package]]
 name = "openssl-probe"
 version = "0.1.4"
@@ -1913,11 +1899,10 @@ checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.66"
+version = "0.9.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82"
+checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b"
 dependencies = [
- "autocfg",
  "cc",
  "libc",
  "pkg-config",
@@ -2075,12 +2060,6 @@ version = "0.5.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
 
-[[package]]
-name = "proc-macro-nested"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.24"
@@ -2323,15 +2302,16 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.11.4"
+version = "0.11.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22"
+checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
 dependencies = [
- "base64",
+ "base64 0.21.0",
  "bytes",
  "encoding_rs",
  "futures-core",
  "futures-util",
+ "h2",
  "http",
  "http-body",
  "hyper",
@@ -2339,24 +2319,27 @@ dependencies = [
  "hyper-tls",
  "ipnet",
  "js-sys",
- "lazy_static",
  "log",
  "mime",
  "native-tls",
+ "once_cell",
  "percent-encoding",
  "pin-project-lite",
  "rustls",
+ "rustls-pemfile",
  "serde",
+ "serde_json",
  "serde_urlencoded",
  "tokio",
  "tokio-native-tls",
  "tokio-rustls",
+ "tower-service",
  "url",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
  "webpki-roots",
- "winreg 0.7.0",
+ "winreg 0.10.1",
 ]
 
 [[package]]
@@ -2380,7 +2363,7 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
 dependencies = [
- "base64",
+ "base64 0.13.0",
  "blake2b_simd",
  "constant_time_eq",
  "crossbeam-utils",
@@ -2388,17 +2371,25 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.19.1"
+version = "0.20.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
 dependencies = [
- "base64",
  "log",
  "ring",
  "sct",
  "webpki",
 ]
 
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
+dependencies = [
+ "base64 0.21.0",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.5"
@@ -2438,9 +2429,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
 name = "sct"
-version = "0.6.1"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
 dependencies = [
  "ring",
  "untrusted",
@@ -2495,19 +2486,19 @@ version = "1.0.62"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
 dependencies = [
- "itoa",
+ "itoa 0.4.7",
  "ryu",
  "serde",
 ]
 
 [[package]]
 name = "serde_urlencoded"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
 dependencies = [
  "form_urlencoded",
- "itoa",
+ "itoa 1.0.6",
  "ryu",
  "serde",
 ]
@@ -2560,6 +2551,15 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
 
+[[package]]
+name = "slotmap"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
+dependencies = [
+ "version_check",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.6.1"
@@ -2568,9 +2568,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
 
 [[package]]
 name = "smithay-client-toolkit"
-version = "0.15.4"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3"
+checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9"
 dependencies = [
  "bitflags 1.2.1",
  "calloop",
@@ -2578,7 +2578,7 @@ dependencies = [
  "lazy_static",
  "log",
  "memmap2",
- "nix 0.22.3",
+ "nix",
  "pkg-config",
  "wayland-client",
  "wayland-cursor",
@@ -2587,9 +2587,9 @@ dependencies = [
 
 [[package]]
 name = "socket2"
-version = "0.4.1"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
 dependencies = [
  "libc",
  "winapi 0.3.9",
@@ -2601,12 +2601,6 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
-[[package]]
-name = "squote"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fccf17fd09e2455ea796d2ad267b64fa2c5cbd8701b2a93b555d2aa73449f7d"
-
 [[package]]
 name = "string_cache"
 version = "0.8.1"
@@ -2646,11 +2640,11 @@ checksum = "4ca6e4730f517e041e547ffe23d29daab8de6b73af4b6ae2a002108169f5e7da"
 
 [[package]]
 name = "strum"
-version = "0.20.0"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
+checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
 dependencies = [
- "strum_macros 0.20.1",
+ "strum_macros 0.22.0",
 ]
 
 [[package]]
@@ -2665,9 +2659,9 @@ dependencies = [
 
 [[package]]
 name = "strum_macros"
-version = "0.20.1"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
+checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -2721,9 +2715,9 @@ dependencies = [
 
 [[package]]
 name = "sysinfo"
-version = "0.24.5"
+version = "0.28.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d80929a3b477bce3a64360ca82bfb361eacce1dcb7b1fb31e8e5e181e37c212"
+checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b"
 dependencies = [
  "cfg-if 1.0.0",
  "core-foundation-sys",
@@ -2858,17 +2852,18 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.10.1"
+version = "1.19.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92036be488bb6594459f2e03b60e42df6f937fe6ca5c5ffdcb539c6b84dc40f5"
+checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
 dependencies = [
- "autocfg",
  "bytes",
  "libc",
  "memchr",
- "mio 0.7.13",
+ "mio 0.8.6",
  "num_cpus",
+ "once_cell",
  "pin-project-lite",
+ "socket2",
  "winapi 0.3.9",
 ]
 
@@ -2884,9 +2879,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-rustls"
-version = "0.22.0"
+version = "0.23.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
 dependencies = [
  "rustls",
  "tokio",
@@ -2895,16 +2890,16 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.6.7"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
+checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
 dependencies = [
  "bytes",
  "futures-core",
  "futures-sink",
- "log",
  "pin-project-lite",
  "tokio",
+ "tracing",
 ]
 
 [[package]]
@@ -3098,6 +3093,12 @@ version = "0.10.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.76"
@@ -3105,8 +3106,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0"
 dependencies = [
  "cfg-if 1.0.0",
- "serde",
- "serde_json",
  "wasm-bindgen-macro",
 ]
 
@@ -3175,7 +3174,7 @@ dependencies = [
  "bitflags 1.2.1",
  "downcast-rs",
  "libc",
- "nix 0.24.2",
+ "nix",
  "scoped-tls",
  "wayland-commons",
  "wayland-scanner",
@@ -3188,7 +3187,7 @@ version = "0.29.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
 dependencies = [
- "nix 0.24.2",
+ "nix",
  "once_cell",
  "smallvec",
  "wayland-sys",
@@ -3200,7 +3199,7 @@ version = "0.29.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
 dependencies = [
- "nix 0.24.2",
+ "nix",
  "wayland-client",
  "xcursor",
 ]
@@ -3225,7 +3224,7 @@ checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
 dependencies = [
  "proc-macro2",
  "quote 1.0.9",
- "xml-rs 0.8.3",
+ "xml-rs 0.8.8",
 ]
 
 [[package]]
@@ -3251,9 +3250,9 @@ dependencies = [
 
 [[package]]
 name = "webpki"
-version = "0.21.4"
+version = "0.22.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
+checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
 dependencies = [
  "ring",
  "untrusted",
@@ -3261,9 +3260,9 @@ dependencies = [
 
 [[package]]
 name = "webpki-roots"
-version = "0.21.1"
+version = "0.22.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
+checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
 dependencies = [
  "webpki",
 ]
@@ -3319,89 +3318,120 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows"
-version = "0.3.1"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "426842497696b65fbfc575691d94ef65befb248ed1a8c4361e293c724e7ebe61"
+checksum = "a9f39345ae0c8ab072c0ac7fe8a8b411636aa34f89be19ddd0d9226544f13944"
 dependencies = [
- "const-sha1",
- "windows_gen",
- "windows_macros",
- "windows_winmd",
+ "windows_i686_gnu 0.24.0",
+ "windows_i686_msvc 0.24.0",
+ "windows_x86_64_gnu 0.24.0",
+ "windows_x86_64_msvc 0.24.0",
 ]
 
 [[package]]
-name = "windows_gen"
-version = "0.3.1"
+name = "windows-sys"
+version = "0.45.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77ac8f0f06b647f42ee5459a8e1ffe41795647582c5926ec3fa363a91aad7d77"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
 dependencies = [
- "proc-macro2",
- "quote 1.0.9",
- "squote",
- "syn 1.0.67",
- "windows_gen_macros",
- "windows_winmd",
+ "windows-targets",
 ]
 
 [[package]]
-name = "windows_gen_macros"
-version = "0.3.1"
+name = "windows-targets"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23eac2169a20173b890c496f9e0e1149a92ef29fe4ba96026b72eec363b993f9"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
 dependencies = [
- "proc-macro2",
- "quote 1.0.9",
- "syn 1.0.67",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc 0.42.2",
 ]
 
 [[package]]
-name = "windows_macros"
-version = "0.3.1"
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9edc57c944eec106c7823b425ab0fd9f90163489e50a4df747f65fcf9030e1fb"
-dependencies = [
- "proc-macro2",
- "quote 1.0.9",
- "squote",
- "syn 1.0.67",
- "windows_gen",
- "windows_winmd",
-]
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
 [[package]]
-name = "windows_winmd"
-version = "0.3.1"
+name = "windows_aarch64_msvc"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16d44527d04c9713312ed598f5d6ce3c453754dbfc03ddc376615be4415ffc88"
-dependencies = [
- "windows_winmd_macros",
-]
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
 
 [[package]]
-name = "windows_winmd_macros"
-version = "0.3.1"
+name = "windows_i686_gnu"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2353f43f512938450614a176abf2b6cb31ac3b84fd71c88470fee571303e3f36"
-dependencies = [
- "proc-macro2",
- "quote 1.0.9",
- "syn 1.0.67",
-]
+checksum = "c0866510a3eca9aed73a077490bbbf03e5eaac4e1fd70849d89539e5830501fd"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf0ffed56b7e9369a29078d2ab3aaeceea48eb58999d2cff3aa2494a275b95c6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384a173630588044205a2993b6864a2f56e5a8c1e7668c07b93ec18cf4888dc4"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bd8f062d8ca5446358159d79a90be12c543b3a965c847c8f3eedf14b321d399"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
 
 [[package]]
 name = "winreg"
-version = "0.7.0"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
+checksum = "16cdb3898397cf7f624c294948669beafaeebc5577d5ec53d0afb76633593597"
 dependencies = [
  "winapi 0.3.9",
 ]
 
 [[package]]
 name = "winreg"
-version = "0.9.0"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16cdb3898397cf7f624c294948669beafaeebc5577d5ec53d0afb76633593597"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
 dependencies = [
  "winapi 0.3.9",
 ]
@@ -3439,13 +3469,13 @@ dependencies = [
 
 [[package]]
 name = "winrt-notification"
-version = "0.3.1"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5ee9845acda665033013f93baec4f71ac0e60a391b530a5a83bdb966c1807ca"
+checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4"
 dependencies = [
- "strum 0.20.0",
+ "strum 0.22.0",
  "windows",
- "xml-rs 0.8.3",
+ "xml-rs 0.8.8",
 ]
 
 [[package]]
@@ -3478,9 +3508,9 @@ dependencies = [
 
 [[package]]
 name = "xml-rs"
-version = "0.8.3"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
+checksum = "4f20f14e2bd1fef6ec891d50dbb37c7290a0568a6bbd9020bf62010d02b6f80e"
 
 [[package]]
 name = "xml5ever"
diff --git a/Compilation.md b/Compilation.md
index 860ffac4..9cf2c01c 100644
--- a/Compilation.md
+++ b/Compilation.md
@@ -8,7 +8,8 @@ These are the basic tools required to build espanso:
 
 * A recent Rust compiler. You can install it following these instructions: https://www.rust-lang.org/tools/install
 * A C/C++ compiler. There are multiple of them depending on the platform, but espanso officially supports the following:
-  * On Windows, you should use the MSVC compiler. The easiest way to install it is by downloading Visual Studio and checking "Desktop development with C++" in the installer: https://visualstudio.microsoft.com/
+  * On Windows, you should use the MSVC compiler. The easiest way to install it is by downloading [Visual Studio](https://visualstudio.microsoft.com/) and checking "Desktop development with C++" in the installer.
+  Note that [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) alone doesn't fulfill all the requirements for espanso.
   * On macOS, you should use the official build tools that come with Xcode. If you don't want to install Xcode, you should be able to download only the build tools by executing `xcode-select —install` and following the instructions.
   * On Linux, you should use the default C/C++ compiler (it's usually GCC). On Ubuntu/Debian systems, you can install them with `sudo apt install build-essential`
 
@@ -16,24 +17,80 @@ These are the basic tools required to build espanso:
 steps. You can install it by running:
 
 ```
+cargo install rust-script --version "0.7.0"
 cargo install --force cargo-make --version 0.34.0
 ```
 
+# Windows
+
+After installing the prerequisites, you are ready to compile Espanso on Windows.
+
+Espanso supports multiple targets on Windows: plain executable, installer and portable mode. The following sections explain how to build Espanso for these configurations.
+
+## Plain executable
+
+If you only want to build the "plain" Espanso executable, you can do so by running:
+
+```
+cargo make build-binary --profile release
+```
+
+This will create an `espanso` executable in the `target/release` directory.
+
+## Installer
+
+If you want to build the Installer (the executable that installs Espanso on a machine), you can run:
+
+```
+cargo make build-windows-installer --profile release
+```
+
+This will generate the installer in the `target/windows/installer` directory.
+
+## Portable mode bundle
+
+You can also generate a portable-mode bundle (a self-contained ZIP archive that does not require installation) by running:
+
+```
+cargo make build-windows-portable --profile release
+```
+This will generate the executable in the `target/windows/portable` directory.
+There are README instructions inside!.
+
+# MacOS
+
+After installing the prerequisites, you are ready to build Espanso on macOS.
+
+Espanso supports two targets on macOS: plain executable and App Bundle. For most cases, the App Bundle format is preferrable.
+
+## App Bundle
+
+You can build the App Bundle by running:
+
+```
+cargo make create-bundle --profile release
+```
+
+This will create the `Espanso.app` bundle in the `target/mac` directory.
+
 # Linux
 
-Espanso on Linux comes in two different flavors: one for X11 and one for Wayland. 
+Espanso on Linux comes in two different flavors: one for X11 and one for Wayland.
 If you don't know which one to choose, follow these steps to determine which one you are running: https://unix.stackexchange.com/a/325972
 
 ## Compiling for X11
 
 ### Necessary packages
 
-If compiling on Ubuntu X11:
+If compiling on a version of Ubuntu X11 before 22.04 (including 22.04):
 * `sudo apt install libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.0-gtk3-dev`
 
+If compiling on a version of Ubuntu X11 after 22.04:
+* `sudo apt install libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.2-dev`
+
 ### AppImage
 
-The AppImage is a convenient format to distribute Linux applications, as besides the binary, 
+The AppImage is a convenient format to distribute Linux applications, as besides the binary,
 it also bundles all the required libraries.
 
 You can create the AppImage by running (this will work on X11 systems):
@@ -46,11 +103,23 @@ You will find the resulting AppImage in the `target/linux/AppImage/out` folder.
 
 ### Binary
 
-TODO
+You can build the Espanso binary on X11 by running the following command:
+
+```
+cargo make build-binary --profile release
+```
+
+You'll then find the `espanso` binary in the `target/release` directory.
 
 ## Compiling on Wayland
 
-TODO
+You can build Espanso on Wayland by running:
+
+```
+cargo make build-binary --env NO_X11=true --profile release
+```
+
+You'll then find the `espanso` binary in the `target/release` directory.
 
 ## Advanced
 
diff --git a/README.md b/README.md
index 903051d6..1e21fa10 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,9 @@ Visit the [espanso website](https://espanso.org).
 #### What is a Text Expander?
 
 A *text expander* is a program that detects when you type
-a specific **keyword** and replaces it with **something else**. 
+a specific **keyword** and replaces it with **something else**.
 This is useful in many ways:
+
 * **Save a lot of typing**, expanding common sentences.
 * Create **system-wide** code snippets.
 * Execute **custom scripts**
@@ -50,12 +51,10 @@ Visit the [official documentation](https://espanso.org/docs/).
 If you need some help to setup espanso, want to ask a question or simply get involved
 in the community, [Join the official Subreddit](https://www.reddit.com/r/espanso/)! :)
 
-
-
 ## Donations
 
 espanso is a free, open source software developed in my (little) spare time.
-If you liked the project and would like to support further development, 
+If you liked the project and would like to support further development,
 please consider making a small donation, it really helps :)
 
 [![Donate with PayPal](images/donate.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FHNLR5DRS267E&source=url)
@@ -64,9 +63,7 @@ please consider making a small donation, it really helps :)
 
 Many people helped the project along the way, thank you to all of you!
 
-<a href="https://github.com/federico-terzi/espanso/graphs/contributors">
-  <img src="https://contrib.rocks/image?repo=federico-terzi/espanso" />
-</a>
+[![Image](https://contrib.rocks/image?repo=federico-terzi/espanso)](https://github.com/federico-terzi/espanso/graphs/contributors)
 
 ## Remarks
 
diff --git a/espanso-clipboard/src/win32/mod.rs b/espanso-clipboard/src/win32/mod.rs
index 5d8023de..61d8be2a 100644
--- a/espanso-clipboard/src/win32/mod.rs
+++ b/espanso-clipboard/src/win32/mod.rs
@@ -48,7 +48,7 @@ impl Clipboard for Win32Clipboard {
     }
   }
 
-  fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> {
+  fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> Result<()> {
     let string = U16CString::from_str(text)?;
     let native_result = unsafe { ffi::clipboard_set_text(string.as_ptr()) };
     if native_result > 0 {
@@ -106,9 +106,9 @@ fn generate_html_descriptor(html: &str) -> String {
   // For more information, look here:
   // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
   // https://docs.microsoft.com/en-za/troubleshoot/cpp/add-html-code-clipboard
-  let content = format!("<!--StartFragment-->{}<!--EndFragment-->", html);
+  let content = format!("<!--StartFragment-->{html}<!--EndFragment-->");
 
-  let tokens = vec![
+  let tokens = [
     "Version:0.9",
     "StartHTML:<<STR*#>",
     "EndHTML:<<END*#>",
diff --git a/espanso-clipboard/src/x11/xclip/mod.rs b/espanso-clipboard/src/x11/xclip/mod.rs
index f2873863..52ca0ee6 100644
--- a/espanso-clipboard/src/x11/xclip/mod.rs
+++ b/espanso-clipboard/src/x11/xclip/mod.rs
@@ -46,7 +46,7 @@ impl Clipboard for XClipClipboard {
       return None;
     }
 
-    match Command::new("xclip").args(&["-o", "-sel", "clip"]).output() {
+    match Command::new("xclip").args(["-o", "-sel", "clip"]).output() {
       Ok(output) => {
         if output.status.success() {
           let s = String::from_utf8_lossy(&output.stdout);
@@ -67,7 +67,7 @@ impl Clipboard for XClipClipboard {
     }
 
     let mut child = Command::new("xclip")
-      .args(&["-sel", "clip"])
+      .args(["-sel", "clip"])
       .stdin(Stdio::piped())
       .spawn()?;
 
@@ -107,7 +107,7 @@ impl Clipboard for XClipClipboard {
     let image_path = image_path.to_string_lossy();
 
     Command::new("xclip")
-      .args(&["-selection", "clipboard", "-t", mime, "-i", &image_path])
+      .args(["-selection", "clipboard", "-t", mime, "-i", &image_path])
       .spawn()?;
 
     Ok(())
@@ -124,7 +124,7 @@ impl Clipboard for XClipClipboard {
     }
 
     let mut child = Command::new("xclip")
-      .args(&["-sel", "clip", "-t", "text/html"])
+      .args(["-sel", "clip", "-t", "text/html"])
       .stdin(Stdio::piped())
       .spawn()?;
 
diff --git a/espanso-config/src/config/mod.rs b/espanso-config/src/config/mod.rs
index ed1bb516..c76f54f5 100644
--- a/espanso-config/src/config/mod.rs
+++ b/espanso-config/src/config/mod.rs
@@ -190,6 +190,7 @@ pub trait Config: Send + Sync {
   // in this issue: https://github.com/federico-terzi/espanso/issues/745
   fn win32_keyboard_layout_cache_interval(&self) -> i64;
 
+  #[allow(clippy::needless_lifetimes)]
   fn is_match<'a>(&self, app: &AppProperties<'a>) -> bool;
 
   fn pretty_dump(&self) -> String {
@@ -270,7 +271,7 @@ pub trait Config: Send + Sync {
 
 pub trait ConfigStore: Send {
   fn default(&self) -> Arc<dyn Config>;
-  fn active<'a>(&'a self, app: &AppProperties) -> Arc<dyn Config>;
+  fn active(&self, app: &AppProperties) -> Arc<dyn Config>;
   fn configs(&self) -> Vec<Arc<dyn Config>>;
 
   fn get_all_match_paths(&self) -> HashSet<String>;
diff --git a/espanso-config/src/config/parse/yaml.rs b/espanso-config/src/config/parse/yaml.rs
index 10e06f41..9beb82fd 100644
--- a/espanso-config/src/config/parse/yaml.rs
+++ b/espanso-config/src/config/parse/yaml.rs
@@ -360,6 +360,6 @@ mod tests {
         filter_os: Some("test7".to_string()),
         filter_title: Some("test8".to_string()),
       }
-    )
+    );
   }
 }
diff --git a/espanso-config/src/config/path.rs b/espanso-config/src/config/path.rs
index fa8a099d..b55c5b06 100644
--- a/espanso-config/src/config/path.rs
+++ b/espanso-config/src/config/path.rs
@@ -101,7 +101,7 @@ pub mod tests {
 
       let result = calculate_paths(
         base,
-        vec![
+        [
           "**/*.yml".to_string(),
           "match/sub/*.yml".to_string(),
           // Invalid path
@@ -127,15 +127,15 @@ pub mod tests {
       create_dir_all(&sub_dir).unwrap();
 
       let base_file = match_dir.join("base.yml");
-      std::fs::write(&base_file, "test").unwrap();
+      std::fs::write(base_file, "test").unwrap();
       let another_file = match_dir.join("another.yml");
-      std::fs::write(&another_file, "test").unwrap();
+      std::fs::write(another_file, "test").unwrap();
       let under_file = match_dir.join("_sub.yml");
-      std::fs::write(&under_file, "test").unwrap();
+      std::fs::write(under_file, "test").unwrap();
       let sub_file = sub_dir.join("sub.yml");
       std::fs::write(&sub_file, "test").unwrap();
 
-      let result = calculate_paths(base, vec!["match/sub/../sub/*.yml".to_string()].iter());
+      let result = calculate_paths(base, ["match/sub/../sub/*.yml".to_string()].iter());
 
       let mut expected = HashSet::new();
       expected.insert(sub_file.to_string_lossy().to_string());
@@ -161,7 +161,7 @@ pub mod tests {
 
       let result = calculate_paths(
         base,
-        vec![
+        [
           format!("{}/**/*.yml", base.to_string_lossy()),
           format!("{}/match/sub/*.yml", base.to_string_lossy()),
           // Invalid path
diff --git a/espanso-config/src/config/resolve.rs b/espanso-config/src/config/resolve.rs
index 76a9c282..3ef2a78e 100644
--- a/espanso-config/src/config/resolve.rs
+++ b/espanso-config/src/config/resolve.rs
@@ -130,7 +130,7 @@ impl Config for ResolvedConfig {
       .parsed
       .backend
       .as_deref()
-      .map(|b| b.to_lowercase())
+      .map(str::to_lowercase)
       .as_deref()
     {
       Some("clipboard") => Backend::Clipboard,
@@ -172,21 +172,21 @@ impl Config for ResolvedConfig {
       .parsed
       .toggle_key
       .as_deref()
-      .map(|key| key.to_lowercase())
+      .map(str::to_lowercase)
       .as_deref()
     {
       Some("ctrl") => Some(ToggleKey::Ctrl),
       Some("alt") => Some(ToggleKey::Alt),
       Some("shift") => Some(ToggleKey::Shift),
-      Some("meta") | Some("cmd") => Some(ToggleKey::Meta),
+      Some("meta" | "cmd") => Some(ToggleKey::Meta),
       Some("right_ctrl") => Some(ToggleKey::RightCtrl),
       Some("right_alt") => Some(ToggleKey::RightAlt),
       Some("right_shift") => Some(ToggleKey::RightShift),
-      Some("right_meta") | Some("right_cmd") => Some(ToggleKey::RightMeta),
+      Some("right_meta" | "right_cmd") => Some(ToggleKey::RightMeta),
       Some("left_ctrl") => Some(ToggleKey::LeftCtrl),
       Some("left_alt") => Some(ToggleKey::LeftAlt),
       Some("left_shift") => Some(ToggleKey::LeftShift),
-      Some("left_meta") | Some("left_cmd") => Some(ToggleKey::LeftMeta),
+      Some("left_meta" | "left_cmd") => Some(ToggleKey::LeftMeta),
       Some("off") => None,
       None => None,
       err => {
@@ -269,7 +269,7 @@ impl Config for ResolvedConfig {
 
   fn search_trigger(&self) -> Option<String> {
     match self.parsed.search_trigger.as_deref() {
-      Some("OFF") | Some("off") => None,
+      Some("OFF" | "off") => None,
       Some(x) => Some(x.to_string()),
       None => None,
     }
@@ -277,7 +277,7 @@ impl Config for ResolvedConfig {
 
   fn search_shortcut(&self) -> Option<String> {
     match self.parsed.search_shortcut.as_deref() {
-      Some("OFF") | Some("off") => None,
+      Some("OFF" | "off") => None,
       Some(x) => Some(x.to_string()),
       None => Some("ALT+SPACE".to_string()),
     }
@@ -300,7 +300,10 @@ impl Config for ResolvedConfig {
   }
 
   fn emulate_alt_codes(&self) -> bool {
-    self.parsed.emulate_alt_codes.unwrap_or(false)
+    self
+      .parsed
+      .emulate_alt_codes
+      .unwrap_or(cfg!(target_os = "windows"))
   }
 
   fn post_form_delay(&self) -> usize {
@@ -443,22 +446,22 @@ impl ResolvedConfig {
     let mut includes = HashSet::new();
 
     if config.use_standard_includes.is_none() || config.use_standard_includes.unwrap() {
-      STANDARD_INCLUDES.iter().for_each(|include| {
-        includes.insert(include.to_string());
-      })
+      for include in STANDARD_INCLUDES {
+        includes.insert((*include).to_string());
+      }
     }
 
     if let Some(yaml_includes) = config.includes.as_ref() {
-      yaml_includes.iter().for_each(|include| {
+      for include in yaml_includes {
         includes.insert(include.to_string());
-      })
-    }
+      }
+    };
 
     if let Some(extra_includes) = config.extra_includes.as_ref() {
-      extra_includes.iter().for_each(|include| {
+      for include in extra_includes {
         includes.insert(include.to_string());
-      })
-    }
+      }
+    };
 
     includes
   }
@@ -467,15 +470,15 @@ impl ResolvedConfig {
     let mut excludes = HashSet::new();
 
     if let Some(yaml_excludes) = config.excludes.as_ref() {
-      yaml_excludes.iter().for_each(|exclude| {
+      for exclude in yaml_excludes {
         excludes.insert(exclude.to_string());
-      })
+      }
     }
 
     if let Some(extra_excludes) = config.extra_excludes.as_ref() {
-      extra_excludes.iter().for_each(|exclude| {
+      for exclude in extra_excludes {
         excludes.insert(exclude.to_string());
-      })
+      }
     }
 
     excludes
@@ -514,7 +517,7 @@ mod tests {
       ResolvedConfig::aggregate_includes(&ParsedConfig {
         ..Default::default()
       }),
-      vec!["../match/**/[!_]*.yml".to_string(),]
+      ["../match/**/[!_]*.yml".to_string()]
         .iter()
         .cloned()
         .collect::<HashSet<_>>()
@@ -539,7 +542,7 @@ mod tests {
         includes: Some(vec!["custom/*.yml".to_string()]),
         ..Default::default()
       }),
-      vec![
+      [
         "../match/**/[!_]*.yml".to_string(),
         "custom/*.yml".to_string()
       ]
@@ -556,7 +559,7 @@ mod tests {
         extra_includes: Some(vec!["custom/*.yml".to_string()]),
         ..Default::default()
       }),
-      vec![
+      [
         "../match/**/[!_]*.yml".to_string(),
         "custom/*.yml".to_string()
       ]
@@ -574,7 +577,7 @@ mod tests {
         extra_includes: Some(vec!["custom/*.yml".to_string()]),
         ..Default::default()
       }),
-      vec![
+      [
         "../match/**/[!_]*.yml".to_string(),
         "custom/*.yml".to_string(),
         "sub/*.yml".to_string()
@@ -614,7 +617,7 @@ mod tests {
         excludes: Some(vec!["custom/*.yml".to_string()]),
         ..Default::default()
       }),
-      vec!["custom/*.yml".to_string()]
+      ["custom/*.yml".to_string()]
         .iter()
         .cloned()
         .collect::<HashSet<_>>()
@@ -628,7 +631,7 @@ mod tests {
         extra_excludes: Some(vec!["custom/*.yml".to_string()]),
         ..Default::default()
       }),
-      vec!["custom/*.yml".to_string()]
+      ["custom/*.yml".to_string()]
         .iter()
         .cloned()
         .collect::<HashSet<_>>()
@@ -643,7 +646,7 @@ mod tests {
         extra_excludes: Some(vec!["custom/*.yml".to_string()]),
         ..Default::default()
       }),
-      vec!["custom/*.yml".to_string(), "sub/*.yml".to_string()]
+      ["custom/*.yml".to_string(), "sub/*.yml".to_string()]
         .iter()
         .cloned()
         .collect::<HashSet<_>>()
@@ -692,7 +695,7 @@ mod tests {
       let another_file = match_dir.join("another.yml");
       std::fs::write(&another_file, "test").unwrap();
       let under_file = match_dir.join("_sub.yml");
-      std::fs::write(&under_file, "test").unwrap();
+      std::fs::write(under_file, "test").unwrap();
       let sub_file = sub_dir.join("sub.yml");
       std::fs::write(&sub_file, "test").unwrap();
 
@@ -724,9 +727,9 @@ mod tests {
       let base_file = match_dir.join("base.yml");
       std::fs::write(&base_file, "test").unwrap();
       let another_file = match_dir.join("another.yml");
-      std::fs::write(&another_file, "test").unwrap();
+      std::fs::write(another_file, "test").unwrap();
       let under_file = match_dir.join("_sub.yml");
-      std::fs::write(&under_file, "test").unwrap();
+      std::fs::write(under_file, "test").unwrap();
       let sub_file = sub_dir.join("another.yml");
       std::fs::write(&sub_file, "test").unwrap();
       let sub_under_file = sub_dir.join("_sub.yml");
@@ -737,9 +740,9 @@ mod tests {
       let parent_file = config_dir.join("parent.yml");
       std::fs::write(
         &parent_file,
-        r#"
+        r"
       excludes: ['../**/another.yml']
-      "#,
+      ",
       )
       .unwrap();
 
@@ -817,7 +820,7 @@ mod tests {
 
       let config = ResolvedConfig::load(&config_file, None).unwrap();
 
-      *result_ref = config.is_match(app)
+      *result_ref = config.is_match(app);
     });
     result
   }
@@ -937,7 +940,7 @@ mod tests {
     };
 
     assert!(test_filter_is_match(
-      &format!("filter_os: {}", current),
+      &format!("filter_os: {current}"),
       &AppProperties {
         title: Some("Google Mail"),
         class: Some("Chrome"),
@@ -946,7 +949,7 @@ mod tests {
     ));
 
     assert!(!test_filter_is_match(
-      &format!("filter_os: {}", another),
+      &format!("filter_os: {another}"),
       &AppProperties {
         title: Some("Google Mail"),
         class: Some("Chrome"),
diff --git a/espanso-config/src/config/store.rs b/espanso-config/src/config/store.rs
index e4e67156..15767261 100644
--- a/espanso-config/src/config/store.rs
+++ b/espanso-config/src/config/store.rs
@@ -35,9 +35,9 @@ impl ConfigStore for DefaultConfigStore {
     Arc::clone(&self.default)
   }
 
-  fn active<'a>(&'a self, app: &super::AppProperties) -> Arc<dyn super::Config> {
+  fn active(&self, app: &super::AppProperties) -> Arc<dyn super::Config> {
     // Find a custom config that matches or fallback to the default one
-    for custom in self.customs.iter() {
+    for custom in &self.customs {
       if custom.is_match(app) {
         return Arc::clone(custom);
       }
@@ -48,7 +48,7 @@ impl ConfigStore for DefaultConfigStore {
   fn configs(&self) -> Vec<Arc<dyn Config>> {
     let mut configs = vec![Arc::clone(&self.default)];
 
-    for custom in self.customs.iter() {
+    for custom in &self.customs {
       configs.push(Arc::clone(custom));
     }
 
@@ -60,7 +60,7 @@ impl ConfigStore for DefaultConfigStore {
     let mut paths = HashSet::new();
 
     paths.extend(self.default().match_paths().iter().cloned());
-    for custom in self.customs.iter() {
+    for custom in &self.customs {
       paths.extend(custom.match_paths().iter().cloned());
     }
 
diff --git a/espanso-config/src/legacy/config.rs b/espanso-config/src/legacy/config.rs
index 1b2bc6a9..77dad508 100644
--- a/espanso-config/src/legacy/config.rs
+++ b/espanso-config/src/legacy/config.rs
@@ -30,6 +30,7 @@ use std::fmt;
 use std::fs::File;
 use std::io::Read;
 use std::path::{Path, PathBuf};
+use std::string::ToString;
 use walkdir::{DirEntry, WalkDir};
 
 pub const DEFAULT_CONFIG_FILE_NAME: &str = "default.yml";
@@ -43,13 +44,13 @@ fn default_parent() -> String {
   "self".to_owned()
 }
 fn default_filter_title() -> String {
-  "".to_owned()
+  String::new()
 }
 fn default_filter_class() -> String {
-  "".to_owned()
+  String::new()
 }
 fn default_filter_exec() -> String {
-  "".to_owned()
+  String::new()
 }
 fn default_log_level() -> i32 {
   0
@@ -386,17 +387,13 @@ impl LegacyConfig {
   }
 }
 
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Default)]
 pub enum BackendType {
   Inject,
   Clipboard,
+  #[default]
   Auto,
 }
-impl Default for BackendType {
-  fn default() -> Self {
-    BackendType::Auto
-  }
-}
 
 impl LegacyConfig {
   fn load_config(path: &Path) -> Result<LegacyConfig, ConfigLoadError> {
@@ -416,7 +413,7 @@ impl LegacyConfig {
         Err(e) => Err(ConfigLoadError::InvalidYAML(path.to_owned(), e.to_string())),
       }
     } else {
-      eprintln!("Error: Cannot load file {:?}", path);
+      eprintln!("Error: Cannot load file {path:?}");
       Err(ConfigLoadError::FileNotFound)
     }
   }
@@ -425,9 +422,9 @@ impl LegacyConfig {
     // Merge matches
     let mut merged_matches = new_config.matches;
     let mut match_trigger_set = HashSet::new();
-    merged_matches.iter().for_each(|m| {
+    for m in &merged_matches {
       match_trigger_set.extend(triggers_for_match(m));
-    });
+    }
     let parent_matches: Vec<Value> = self
       .matches
       .iter()
@@ -445,9 +442,9 @@ impl LegacyConfig {
     // Merge global variables
     let mut merged_global_vars = new_config.global_vars;
     let mut vars_name_set = HashSet::new();
-    merged_global_vars.iter().for_each(|m| {
+    for m in &merged_global_vars {
       vars_name_set.insert(name_for_global_var(m));
-    });
+    }
     let parent_vars: Vec<Value> = self
       .global_vars
       .iter()
@@ -498,7 +495,7 @@ fn triggers_for_match(m: &Value) -> Vec<String> {
   if let Some(triggers) = m.get("triggers").and_then(|v| v.as_sequence()) {
     triggers
       .iter()
-      .filter_map(|v| v.as_str().map(|s| s.to_string()))
+      .filter_map(|v| v.as_str().map(ToString::to_string))
       .collect()
   } else if let Some(trigger) = m.get("trigger").and_then(|v| v.as_str()) {
     vec![trigger.to_string()]
@@ -619,7 +616,7 @@ impl LegacyConfigSet {
           }
         }
         Err(e) => {
-          eprintln!("Warning: Unable to read config file: {}", e);
+          eprintln!("Warning: Unable to read config file: {e}");
         }
       }
 
@@ -657,7 +654,7 @@ impl LegacyConfigSet {
     let mut specific = configs[1..].to_vec();
 
     // Add default entries to specific configs when needed
-    for config in specific.iter_mut() {
+    for config in &mut specific {
       if !config.exclude_default_entries {
         config.merge_no_overwrite(&default);
       }
@@ -707,7 +704,7 @@ impl LegacyConfigSet {
 
     let mut has_conflicts = Self::list_has_conflicts(&sorted_triggers);
 
-    for s in specific.iter() {
+    for s in specific {
       let mut specific_triggers: Vec<String> =
         s.matches.iter().flat_map(triggers_for_match).collect();
       specific_triggers.sort();
@@ -729,8 +726,7 @@ impl LegacyConfigSet {
       if item.starts_with(previous) {
         has_conflicts = true;
         eprintln!(
-          "Warning: trigger '{}' is conflicting with '{}' and may not behave as intended",
-          item, previous
+          "Warning: trigger '{item}' is conflicting with '{previous}' and may not behave as intended"
         );
       }
     }
@@ -841,11 +837,11 @@ mod tests {
   #[test]
   fn test_user_defined_config_does_not_have_reserved_fields() {
     let working_config_file = create_tmp_file(
-      r###"
+      r"
 
         backend: Clipboard
 
-        "###,
+        ",
     );
     let config = LegacyConfig::load_config(working_config_file.path());
     assert!(config.unwrap().validate_user_defined_config());
@@ -854,12 +850,12 @@ mod tests {
   #[test]
   fn test_user_defined_config_has_reserved_fields_config_caching_interval() {
     let working_config_file = create_tmp_file(
-      r###"
+      r"
 
         # This should not happen in an app-specific config
         config_caching_interval: 100
 
-        "###,
+        ",
     );
     let config = LegacyConfig::load_config(working_config_file.path());
     assert!(!config.unwrap().validate_user_defined_config());
@@ -868,12 +864,12 @@ mod tests {
   #[test]
   fn test_user_defined_config_has_reserved_fields_toggle_key() {
     let working_config_file = create_tmp_file(
-      r###"
+      r"
 
         # This should not happen in an app-specific config
         toggle_key: CTRL
 
-        "###,
+        ",
     );
     let config = LegacyConfig::load_config(working_config_file.path());
     assert!(!config.unwrap().validate_user_defined_config());
@@ -882,12 +878,12 @@ mod tests {
   #[test]
   fn test_user_defined_config_has_reserved_fields_toggle_interval() {
     let working_config_file = create_tmp_file(
-      r###"
+      r"
 
         # This should not happen in an app-specific config
         toggle_interval: 1000
 
-        "###,
+        ",
     );
     let config = LegacyConfig::load_config(working_config_file.path());
     assert!(!config.unwrap().validate_user_defined_config());
@@ -896,12 +892,12 @@ mod tests {
   #[test]
   fn test_user_defined_config_has_reserved_fields_backspace_limit() {
     let working_config_file = create_tmp_file(
-      r###"
+      r"
 
         # This should not happen in an app-specific config
         backspace_limit: 10
 
-        "###,
+        ",
     );
     let config = LegacyConfig::load_config(working_config_file.path());
     assert!(!config.unwrap().validate_user_defined_config());
@@ -1014,16 +1010,16 @@ mod tests {
     let user_defined_path = create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r"
         config_caching_interval: 10000
-        "###,
+        ",
     );
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
     assert!(config_set.is_err());
     assert_eq!(
       config_set.unwrap_err(),
       ConfigLoadError::InvalidParameter(user_defined_path)
-    )
+    );
   }
 
   #[test]
@@ -1033,9 +1029,9 @@ mod tests {
     let user_defined_path = create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r"
         backend: Clipboard
-        "###,
+        ",
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
@@ -1043,7 +1039,7 @@ mod tests {
     assert_eq!(
       config_set.unwrap().specific[0].name,
       user_defined_path.to_str().unwrap_or_default()
-    )
+    );
   }
 
   #[test]
@@ -1053,17 +1049,17 @@ mod tests {
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r"
         name: specific1
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific2.yml",
-      r###"
+      r"
         name: specific1
-        "###,
+        ",
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
@@ -1071,31 +1067,31 @@ mod tests {
     assert!(matches!(
       &config_set.unwrap_err(),
       &ConfigLoadError::NameDuplicate(_)
-    ))
+    ));
   }
 
   #[test]
   fn test_user_defined_config_set_merge_with_parent_matches() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
         matches:
             - trigger: ":lol"
               replace: "LOL"
             - trigger: ":yess"
               replace: "Bob"
-        "###,
+        "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific1.yml",
-      r###"
+      r#"
         name: specific1
 
         matches:
             - trigger: "hello"
               replace: "newstring"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1119,25 +1115,25 @@ mod tests {
   #[test]
   fn test_user_defined_config_set_merge_with_parent_matches_child_priority() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
         matches:
             - trigger: ":lol"
               replace: "LOL"
             - trigger: ":yess"
               replace: "Bob"
-        "###,
+        "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific2.yml",
-      r###"
+      r#"
         name: specific1
 
         matches:
             - trigger: ":lol"
               replace: "newstring"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1157,19 +1153,19 @@ mod tests {
   #[test]
   fn test_user_defined_config_set_exclude_merge_with_parent_matches() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
         matches:
             - trigger: ":lol"
               replace: "LOL"
             - trigger: ":yess"
               replace: "Bob"
-        "###,
+        "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific2.yml",
-      r###"
+      r#"
         name: specific1
 
         exclude_default_entries: true
@@ -1177,7 +1173,7 @@ mod tests {
         matches:
             - trigger: "hello"
               replace: "newstring"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1193,19 +1189,19 @@ mod tests {
   #[test]
   fn test_only_yaml_files_are_loaded_from_config() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
             matches:
                 - trigger: ":lol"
                   replace: "LOL"
                 - trigger: ":yess"
                   replace: "Bob"
-            "###,
+            "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.zzz",
-      r###"
+      r#"
         name: specific1
 
         exclude_default_entries: true
@@ -1213,7 +1209,7 @@ mod tests {
         matches:
             - trigger: "hello"
               replace: "newstring"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1223,19 +1219,19 @@ mod tests {
   #[test]
   fn test_hidden_files_are_ignored() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
             matches:
                 - trigger: ":lol"
                   replace: "LOL"
                 - trigger: ":yess"
                   replace: "Bob"
-            "###,
+            "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       ".specific.yml",
-      r###"
+      r#"
         name: specific1
 
         exclude_default_entries: true
@@ -1243,7 +1239,7 @@ mod tests {
         matches:
             - trigger: "hello"
               replace: "newstring"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1257,17 +1253,17 @@ mod tests {
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r"
         name: specific1
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific2.yml",
-      r###"
+      r"
         name: specific2
-        "###,
+        ",
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1277,23 +1273,23 @@ mod tests {
   #[test]
   fn test_config_set_default_parent_works_correctly() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         parent: default
 
         matches:
             - trigger: "hello"
               replace: "world"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1314,21 +1310,21 @@ mod tests {
   #[test]
   fn test_config_set_no_parent_should_not_merge() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         matches:
             - trigger: "hello"
               replace: "world"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1353,36 +1349,36 @@ mod tests {
   #[test]
   fn test_config_set_default_nested_parent_works_correctly() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         name: custom1
         parent: default
 
         matches:
             - trigger: "hello"
               replace: "world"
-        "###,
+        "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific2.yml",
-      r###"
+      r#"
         parent: custom1
 
         matches:
             - trigger: "super"
               replace: "mario"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1408,23 +1404,23 @@ mod tests {
   #[test]
   fn test_config_set_parent_merge_children_priority_should_be_higher() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         parent: default
 
         matches:
             - trigger: "hasta"
               replace: "world"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1440,24 +1436,24 @@ mod tests {
   #[test]
   fn test_config_set_package_configs_default_merge() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_package_file(
       package_dir.path(),
       "package1",
       "package.yml",
-      r###"
+      r#"
         parent: default
 
         matches:
             - trigger: "harry"
               replace: "potter"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1478,24 +1474,24 @@ mod tests {
   #[test]
   fn test_config_set_package_configs_lower_priority_than_user() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_package_file(
       package_dir.path(),
       "package1",
       "package.yml",
-      r###"
+      r#"
         parent: default
 
         matches:
             - trigger: "hasta"
               replace: "potter"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1514,22 +1510,22 @@ mod tests {
   #[test]
   fn test_config_set_package_configs_without_merge() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_package_file(
       package_dir.path(),
       "package1",
       "package.yml",
-      r###"
+      r#"
         matches:
             - trigger: "harry"
               replace: "potter"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1549,37 +1545,37 @@ mod tests {
   #[test]
   fn test_config_set_package_configs_multiple_files() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: hasta
               replace: Hasta la vista
-        "###,
+        ",
     );
 
     create_package_file(
       package_dir.path(),
       "package1",
       "package.yml",
-      r###"
+      r#"
         name: package1
 
         matches:
             - trigger: "harry"
               replace: "potter"
-        "###,
+        "#,
     );
 
     create_package_file(
       package_dir.path(),
       "package1",
       "addon.yml",
-      r###"
+      r#"
         parent: package1
 
         matches:
             - trigger: "ron"
               replace: "weasley"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1618,25 +1614,25 @@ mod tests {
   #[test]
   fn test_has_conflict_no_conflict() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: ac
               replace: Hasta la vista
             - trigger: bc
               replace: Jon
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         name: specific1
 
         matches:
             - trigger: "hello"
               replace: "world"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1649,7 +1645,7 @@ mod tests {
   #[test]
   fn test_has_conflict_conflict_in_default() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: ac
               replace: Hasta la vista
@@ -1657,19 +1653,19 @@ mod tests {
               replace: Jon
             - trigger: acb
               replace: Error
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         name: specific1
 
         matches:
             - trigger: "hello"
               replace: "world"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1682,25 +1678,25 @@ mod tests {
   #[test]
   fn test_has_conflict_conflict_in_specific_and_default() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: ac
               replace: Hasta la vista
             - trigger: bc
               replace: Jon
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         name: specific1
 
         matches:
             - trigger: "bcd"
               replace: "Conflict"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1713,36 +1709,36 @@ mod tests {
   #[test]
   fn test_has_conflict_no_conflict_in_specific_and_specific() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r"
         matches:
             - trigger: ac
               replace: Hasta la vista
             - trigger: bc
               replace: Jon
-        "###,
+        ",
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
         name: specific1
 
         matches:
             - trigger: "bad"
               replace: "Conflict"
-        "###,
+        "#,
     );
     create_user_config_file(
       data_dir.path(),
       "specific2.yml",
-      r###"
+      r#"
         name: specific2
 
         matches:
             - trigger: "badass"
               replace: "Conflict"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1755,25 +1751,25 @@ mod tests {
   #[test]
   fn test_config_set_specific_inherits_default_global_vars() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
         global_vars:
             - name: testvar
               type: date
               params:
                 format: "%m"
-        "###,
+        "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
          global_vars:
             - name: specificvar
               type: date
               params:
                 format: "%m"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1793,26 +1789,26 @@ mod tests {
   #[test]
   fn test_config_set_default_get_variables_from_specific() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
         global_vars:
             - name: testvar
               type: date
               params:
                 format: "%m"
-        "###,
+        "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
          parent: default
          global_vars:
             - name: specificvar
               type: date
               params:
                 format: "%m"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
@@ -1833,19 +1829,19 @@ mod tests {
   #[test]
   fn test_config_set_specific_dont_inherits_default_global_vars_when_exclude_is_on() {
     let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r###"
+      r#"
         global_vars:
             - name: testvar
               type: date
               params:
                 format: "%m"
-        "###,
+        "#,
     );
 
     create_user_config_file(
       data_dir.path(),
       "specific.yml",
-      r###"
+      r#"
          exclude_default_entries: true
 
          global_vars:
@@ -1853,7 +1849,7 @@ mod tests {
               type: date
               params:
                 format: "%m"
-        "###,
+        "#,
     );
 
     let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
diff --git a/espanso-config/src/legacy/mod.rs b/espanso-config/src/legacy/mod.rs
index 33589de7..83a3d0e8 100644
--- a/espanso-config/src/legacy/mod.rs
+++ b/espanso-config/src/legacy/mod.rs
@@ -90,9 +90,9 @@ fn split_config(config: LegacyConfig) -> (LegacyInteropConfig, LegacyMatchGroup)
     .filter_map(|var| {
       let var: YAMLVariable = serde_yaml::from_value(var.clone()).ok()?;
       let (var, warnings) = try_convert_into_variable(var, true).ok()?;
-      warnings.into_iter().for_each(|warning| {
+      for warning in warnings {
         warn!("{}", warning);
-      });
+      }
       Some(var)
     })
     .collect();
@@ -103,9 +103,9 @@ fn split_config(config: LegacyConfig) -> (LegacyInteropConfig, LegacyMatchGroup)
     .filter_map(|var| {
       let m: YAMLMatch = serde_yaml::from_value(var.clone()).ok()?;
       let (m, warnings) = try_convert_into_match(m, true).ok()?;
-      warnings.into_iter().for_each(|warning| {
+      for warning in warnings {
         warn!("{}", warning);
-      });
+      }
       Some(m)
     })
     .collect();
@@ -184,20 +184,20 @@ impl From<config::LegacyConfig> for LegacyInteropConfig {
       config: config.clone(),
       name: config.name.clone(),
       match_paths: vec![config.name],
-      filter_title: if !config.filter_title.is_empty() {
-        Regex::new(&config.filter_title).ok()
-      } else {
+      filter_title: if config.filter_title.is_empty() {
         None
-      },
-      filter_class: if !config.filter_class.is_empty() {
-        Regex::new(&config.filter_class).ok()
       } else {
-        None
+        Regex::new(&config.filter_title).ok()
       },
-      filter_exec: if !config.filter_exec.is_empty() {
-        Regex::new(&config.filter_exec).ok()
+      filter_class: if config.filter_class.is_empty() {
+        None
       } else {
+        Regex::new(&config.filter_class).ok()
+      },
+      filter_exec: if config.filter_exec.is_empty() {
         None
+      } else {
+        Regex::new(&config.filter_exec).ok()
       },
     }
   }
@@ -437,10 +437,10 @@ impl LegacyMatchStore {
 
 impl MatchStore for LegacyMatchStore {
   fn query(&self, paths: &[String]) -> MatchSet {
-    let group = if !paths.is_empty() {
-      self.groups.get(&paths[0])
-    } else {
+    let group = if paths.is_empty() {
       None
+    } else {
+      self.groups.get(&paths[0])
     };
 
     if let Some(group) = group {
@@ -475,9 +475,9 @@ mod tests {
     let package_dir = TempDir::new("tempconfig").unwrap();
 
     callback(
-      &dunce::canonicalize(&dir.path()).unwrap(),
+      &dunce::canonicalize(dir.path()).unwrap(),
       &dunce::canonicalize(&user_dir).unwrap(),
-      &dunce::canonicalize(&package_dir.path()).unwrap(),
+      &dunce::canonicalize(package_dir.path()).unwrap(),
     );
   }
 
diff --git a/espanso-config/src/legacy/model.rs b/espanso-config/src/legacy/model.rs
index 698ca52b..b187f292 100644
--- a/espanso-config/src/legacy/model.rs
+++ b/espanso-config/src/legacy/model.rs
@@ -45,18 +45,13 @@ pub enum KeyModifier {
   CAPS_LOCK,
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
 pub enum PasteShortcut {
-  Default,     // Default one for the current system
+  #[default]
+  Default, // Default one for the current system
   CtrlV,       // Classic Ctrl+V shortcut
   CtrlShiftV,  // Could be used to paste without formatting in many applications
   ShiftInsert, // Often used in Linux systems
   CtrlAltV,    // Used in some Linux terminals (urxvt)
   MetaV,       // Corresponding to Win+V on Windows and Linux, CMD+V on macOS
 }
-
-impl Default for PasteShortcut {
-  fn default() -> Self {
-    PasteShortcut::Default
-  }
-}
diff --git a/espanso-config/src/lib.rs b/espanso-config/src/lib.rs
index 458d2d65..adbbc879 100644
--- a/espanso-config/src/lib.rs
+++ b/espanso-config/src/lib.rs
@@ -53,8 +53,8 @@ pub fn load(
     matches::store::load(&root_paths.into_iter().collect::<Vec<String>>());
 
   let mut non_fatal_errors = Vec::new();
-  non_fatal_errors.extend(non_fatal_config_errors.into_iter());
-  non_fatal_errors.extend(non_fatal_match_errors.into_iter());
+  non_fatal_errors.extend(non_fatal_config_errors);
+  non_fatal_errors.extend(non_fatal_match_errors);
 
   Ok((
     Box::new(config_store),
@@ -91,7 +91,7 @@ mod tests {
     use_test_directory(|base, match_dir, config_dir| {
       let base_file = match_dir.join("base.yml");
       std::fs::write(
-        &base_file,
+        base_file,
         r#"
       matches:
         - trigger: "hello"
@@ -102,7 +102,7 @@ mod tests {
 
       let another_file = match_dir.join("another.yml");
       std::fs::write(
-        &another_file,
+        another_file,
         r#"
       imports:
         - "_sub.yml"
@@ -116,7 +116,7 @@ mod tests {
 
       let under_file = match_dir.join("_sub.yml");
       std::fs::write(
-        &under_file,
+        under_file,
         r#"
       matches:
         - trigger: "hello3"
@@ -126,11 +126,11 @@ mod tests {
       .unwrap();
 
       let config_file = config_dir.join("default.yml");
-      std::fs::write(&config_file, "").unwrap();
+      std::fs::write(config_file, "").unwrap();
 
       let custom_config_file = config_dir.join("custom.yml");
       std::fs::write(
-        &custom_config_file,
+        custom_config_file,
         r#"
       filter_title: "Chrome"
 
@@ -186,7 +186,7 @@ mod tests {
     use_test_directory(|base, match_dir, config_dir| {
       let base_file = match_dir.join("base.yml");
       std::fs::write(
-        &base_file,
+        base_file,
         r#"
       matches:
         - "invalid"invalid
@@ -196,7 +196,7 @@ mod tests {
 
       let another_file = match_dir.join("another.yml");
       std::fs::write(
-        &another_file,
+        another_file,
         r#"
       imports:
         - "_sub.yml"
@@ -210,7 +210,7 @@ mod tests {
 
       let under_file = match_dir.join("_sub.yml");
       std::fs::write(
-        &under_file,
+        under_file,
         r#"
       matches:
         - trigger: "hello3"
@@ -220,11 +220,11 @@ mod tests {
       .unwrap();
 
       let config_file = config_dir.join("default.yml");
-      std::fs::write(&config_file, r#""#).unwrap();
+      std::fs::write(config_file, r"").unwrap();
 
       let custom_config_file = config_dir.join("custom.yml");
       std::fs::write(
-        &custom_config_file,
+        custom_config_file,
         r#"
       filter_title: "Chrome"
       "
@@ -261,7 +261,7 @@ mod tests {
       .unwrap();
 
       let config_file = config_dir.join("default.yml");
-      std::fs::write(&config_file, r#""#).unwrap();
+      std::fs::write(config_file, r"").unwrap();
 
       let (config_store, match_store, errors) = load(base).unwrap();
 
@@ -284,18 +284,18 @@ mod tests {
     use_test_directory(|base, match_dir, config_dir| {
       let base_file = match_dir.join("base.yml");
       std::fs::write(
-        &base_file,
-        r#"
+        base_file,
+        r"
       matches:
         - trigger: hello
           replace: world
-      "#,
+      ",
       )
       .unwrap();
 
       let config_file = config_dir.join("default.yml");
       std::fs::write(
-        &config_file,
+        config_file,
         r#"
       invalid
 
diff --git a/espanso-config/src/matches/group/loader/yaml/mod.rs b/espanso-config/src/matches/group/loader/yaml/mod.rs
index ebe1dea2..3cda3f4a 100644
--- a/espanso-config/src/matches/group/loader/yaml/mod.rs
+++ b/espanso-config/src/matches/group/loader/yaml/mod.rs
@@ -104,10 +104,10 @@ impl Importer for YAMLImporter {
         .context("failed to resolve YAML match group imports")?;
     non_fatal_errors.extend(import_errors);
 
-    let non_fatal_error_set = if !non_fatal_errors.is_empty() {
-      Some(NonFatalErrorSet::new(path, non_fatal_errors))
-    } else {
+    let non_fatal_error_set = if non_fatal_errors.is_empty() {
       None
+    } else {
+      Some(NonFatalErrorSet::new(path, non_fatal_errors))
     };
 
     Ok((
@@ -210,7 +210,7 @@ pub fn try_convert_into_match(
       for yaml_var in yaml_match.vars.unwrap_or_default() {
         let (var, var_warnings) =
           try_convert_into_variable(yaml_var.clone(), use_compatibility_mode)
-            .with_context(|| format!("failed to load variable: {:?}", yaml_var))?;
+            .with_context(|| format!("failed to load variable: {yaml_var:?}"))?;
         warnings.extend(var_warnings);
         vars.push(var);
       }
@@ -233,13 +233,13 @@ pub fn try_convert_into_match(
           VAR_REGEX
             .replace_all(&form_layout, |caps: &Captures| {
               let var_name = caps.get(1).unwrap().as_str();
-              format!("{{{{form1.{}}}}}", var_name)
+              format!("{{{{form1.{var_name}}}}}")
             })
             .to_string(),
           VAR_REGEX
             .replace_all(&form_layout, |caps: &Captures| {
               let var_name = caps.get(1).unwrap().as_str();
-              format!("[[{}]]", var_name)
+              format!("[[{var_name}]]")
             })
             .to_string(),
         )
@@ -248,7 +248,7 @@ pub fn try_convert_into_match(
           FORM_CONTROL_REGEX
             .replace_all(&form_layout, |caps: &Captures| {
               let var_name = caps.get(1).unwrap().as_str();
-              format!("{{{{form1.{}}}}}", var_name)
+              format!("{{{{form1.{var_name}}}}}")
             })
             .to_string(),
           form_layout,
@@ -350,9 +350,11 @@ mod tests {
 
   fn create_match(yaml: &str) -> Result<Match> {
     let (m, warnings) = create_match_with_warnings(yaml, false)?;
-    if !warnings.is_empty() {
-      panic!("warnings were detected but not handled: {:?}", warnings);
-    }
+    assert!(
+      warnings.is_empty(),
+      "warnings were detected but not handled: {:?}",
+      warnings
+    );
     Ok(m)
   }
 
@@ -377,7 +379,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -401,7 +403,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -428,7 +430,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -454,7 +456,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -480,7 +482,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -506,7 +508,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -630,7 +632,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -667,7 +669,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -706,7 +708,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -744,7 +746,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -790,7 +792,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -824,7 +826,7 @@ mod tests {
         }),
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -869,8 +871,8 @@ mod tests {
       assert_eq!(non_fatal_error_set.unwrap().errors.len(), 1);
 
       // Reset the ids to compare them correctly
-      group.matches.iter_mut().for_each(|mut m| m.id = 0);
-      group.global_vars.iter_mut().for_each(|mut v| v.id = 0);
+      group.matches.iter_mut().for_each(|m| m.id = 0);
+      group.global_vars.iter_mut().for_each(|v| v.id = 0);
 
       let vars = vec![Variable {
         name: "var1".to_string(),
@@ -896,7 +898,7 @@ mod tests {
             ..Default::default()
           }],
         }
-      )
+      );
     });
   }
 
@@ -906,16 +908,16 @@ mod tests {
       let base_file = match_dir.join("base.yml");
       std::fs::write(
         &base_file,
-        r#"
+        r"
       imports:
         - invalid
        - indentation
-      "#,
+      ",
       )
       .unwrap();
 
       let importer = YAMLImporter::new();
       assert!(importer.load_group(&base_file).is_err());
-    })
+    });
   }
 }
diff --git a/espanso-config/src/matches/group/path.rs b/espanso-config/src/matches/group/path.rs
index 01fc94ea..94c71986 100644
--- a/espanso-config/src/matches/group/path.rs
+++ b/espanso-config/src/matches/group/path.rs
@@ -36,8 +36,7 @@ pub fn resolve_imports(
     } else {
       return Err(
         ResolveImportError::Failed(format!(
-          "unable to resolve imports for match group starting from current path: {:?}",
-          group_path
+          "unable to resolve imports for match group starting from current path: {group_path:?}"
         ))
         .into(),
       );
@@ -48,7 +47,7 @@ pub fn resolve_imports(
 
   let mut non_fatal_errors = Vec::new();
 
-  for import in imports.iter() {
+  for import in imports {
     let import_path = PathBuf::from(import);
 
     // Absolute or relative import
@@ -59,17 +58,17 @@ pub fn resolve_imports(
     };
 
     match dunce::canonicalize(&full_path)
-      .with_context(|| format!("unable to canonicalize import path: {:?}", full_path))
+      .with_context(|| format!("unable to canonicalize import path: {full_path:?}"))
     {
       Ok(canonical_path) => {
         if canonical_path.exists() && canonical_path.is_file() {
-          paths.push(canonical_path)
+          paths.push(canonical_path);
         } else {
           // Best effort imports
           non_fatal_errors.push(ErrorRecord::error(anyhow!(
             "unable to resolve import at path: {:?}",
             canonical_path
-          )))
+          )));
         }
       }
       Err(error) => non_fatal_errors.push(ErrorRecord::error(error)),
diff --git a/espanso-config/src/matches/mod.rs b/espanso-config/src/matches/mod.rs
index 474b40b0..ea31c398 100644
--- a/espanso-config/src/matches/mod.rs
+++ b/espanso-config/src/matches/mod.rs
@@ -73,7 +73,7 @@ impl Match {
     self
       .search_terms
       .iter()
-      .map(|term| term.as_str())
+      .map(String::as_str)
       .chain(self.cause.search_terms())
       .collect()
   }
@@ -93,7 +93,7 @@ impl MatchCause {
   // TODO: test
   pub fn description(&self) -> Option<&str> {
     if let MatchCause::Trigger(trigger_cause) = &self {
-      trigger_cause.triggers.first().map(|s| s.as_str())
+      trigger_cause.triggers.first().map(String::as_str)
     } else {
       None
     }
@@ -114,7 +114,7 @@ impl MatchCause {
 
   pub fn search_terms(&self) -> Vec<&str> {
     if let MatchCause::Trigger(trigger_cause) = &self {
-      trigger_cause.triggers.iter().map(|s| s.as_str()).collect()
+      trigger_cause.triggers.iter().map(String::as_str).collect()
     } else {
       vec![]
     }
diff --git a/espanso-config/src/matches/store/default.rs b/espanso-config/src/matches/store/default.rs
index 241074dc..3e34a50c 100644
--- a/espanso-config/src/matches/store/default.rs
+++ b/espanso-config/src/matches/store/default.rs
@@ -81,11 +81,11 @@ fn load_match_groups_recursively(
   paths: &[String],
   non_fatal_error_sets: &mut Vec<NonFatalErrorSet>,
 ) {
-  for path in paths.iter() {
+  for path in paths {
     if !groups.contains_key(path) {
       let group_path = PathBuf::from(path);
       match MatchGroup::load(&group_path)
-        .with_context(|| format!("unable to load match group {:?}", group_path))
+        .with_context(|| format!("unable to load match group {group_path:?}"))
       {
         Ok((group, non_fatal_error_set)) => {
           let imports = group.imports.clone();
@@ -114,7 +114,7 @@ fn query_matches_for_paths<'a>(
   global_vars: &mut Vec<&'a Variable>,
   paths: &[String],
 ) {
-  for path in paths.iter() {
+  for path in paths {
     if !visited_paths.contains(path) {
       visited_paths.insert(path.clone());
 
@@ -129,14 +129,14 @@ fn query_matches_for_paths<'a>(
           &group.imports,
         );
 
-        for m in group.matches.iter() {
+        for m in &group.matches {
           if !visited_matches.contains(&m.id) {
             matches.push(m);
             visited_matches.insert(m.id);
           }
         }
 
-        for var in group.global_vars.iter() {
+        for var in &group.global_vars {
           if !visited_global_vars.contains(&var.id) {
             global_vars.push(var);
             visited_global_vars.insert(var.id);
@@ -314,7 +314,7 @@ mod tests {
 
       let another_file = match_dir.join("_another.yml");
       std::fs::write(
-        &another_file,
+        another_file,
         r#"
       imports:
         - "sub/sub.yml"
@@ -330,7 +330,7 @@ mod tests {
 
       let sub_file = sub_dir.join("sub.yml");
       std::fs::write(
-        &sub_file,
+        sub_file,
         r#"
       imports:
         - "../_another.yml"
@@ -376,7 +376,7 @@ mod tests {
 
       let another_file = match_dir.join("_another.yml");
       std::fs::write(
-        &another_file,
+        another_file,
         r#"
       imports:
         - "sub/sub.yml"
@@ -392,7 +392,7 @@ mod tests {
 
       let sub_file = sub_dir.join("sub.yml");
       std::fs::write(
-        &sub_file,
+        sub_file,
         r#"
       global_vars:
         - name: var2
@@ -470,7 +470,7 @@ mod tests {
 
       let another_file = match_dir.join("_another.yml");
       std::fs::write(
-        &another_file,
+        another_file,
         r#"
       imports:
         - "sub/sub.yml"
@@ -486,7 +486,7 @@ mod tests {
 
       let sub_file = sub_dir.join("sub.yml");
       std::fs::write(
-        &sub_file,
+        sub_file,
         r#"
       imports:
         - "../_another.yml"  # Circular import
@@ -567,7 +567,7 @@ mod tests {
 
       let another_file = match_dir.join("_another.yml");
       std::fs::write(
-        &another_file,
+        another_file,
         r#"
       matches:
         - trigger: "hello"
@@ -663,7 +663,7 @@ mod tests {
 
       let another_file = match_dir.join("_another.yml");
       std::fs::write(
-        &another_file,
+        another_file,
         r#"
       imports:
         - "sub/sub.yml"
diff --git a/espanso-config/src/util.rs b/espanso-config/src/util.rs
index 266d4a36..18a31d56 100644
--- a/espanso-config/src/util.rs
+++ b/espanso-config/src/util.rs
@@ -46,7 +46,7 @@ pub mod tests {
     create_dir_all(&config_dir).unwrap();
 
     callback(
-      &dunce::canonicalize(&dir.path()).unwrap(),
+      &dunce::canonicalize(dir.path()).unwrap(),
       &dunce::canonicalize(match_dir).unwrap(),
       &dunce::canonicalize(config_dir).unwrap(),
     );
diff --git a/espanso-detect/Cargo.toml b/espanso-detect/Cargo.toml
index f581dbda..a67be72d 100644
--- a/espanso-detect/Cargo.toml
+++ b/espanso-detect/Cargo.toml
@@ -24,7 +24,7 @@ widestring = "0.4.3"
 [target.'cfg(target_os="linux")'.dependencies]
 libc = "0.2.85"
 scopeguard = "1.1.0"
-sctk = { package = "smithay-client-toolkit", version = "0.15.4", optional = true }
+sctk = { package = "smithay-client-toolkit", version = "0.16.1", optional = true }
 
 [build-dependencies]
 cc = "1.0.73"
diff --git a/espanso-detect/src/evdev/device.rs b/espanso-detect/src/evdev/device.rs
index 5723cccd..ab2173f5 100644
--- a/espanso-detect/src/evdev/device.rs
+++ b/espanso-detect/src/evdev/device.rs
@@ -61,7 +61,7 @@ impl Device {
     let file = OpenOptions::new()
       .read(true)
       .custom_flags(O_NONBLOCK | O_CLOEXEC | O_RDONLY)
-      .open(&path)?;
+      .open(path)?;
 
     if unsafe { is_keyboard_or_mouse(file.as_raw_fd()) == 0 } {
       return Err(DeviceError::InvalidDevice(path.to_string()).into());
diff --git a/espanso-detect/src/evdev/sync/wayland.rs b/espanso-detect/src/evdev/sync/wayland.rs
index 750b0530..c8f33892 100644
--- a/espanso-detect/src/evdev/sync/wayland.rs
+++ b/espanso-detect/src/evdev/sync/wayland.rs
@@ -67,10 +67,7 @@ pub fn get_modifiers_state() -> Result<Option<super::ModifiersState>> {
    * Keyboard initialization
    */
 
-  let mut seats = Vec::<(
-    String,
-    Option<(wl_keyboard::WlKeyboard, calloop::RegistrationToken)>,
-  )>::new();
+  let mut seats = Vec::<(String, Option<wl_keyboard::WlKeyboard>)>::new();
 
   // first process already existing seats
   for seat in env.get_all_seats() {
@@ -89,8 +86,8 @@ pub fn get_modifiers_state() -> Result<Option<super::ModifiersState>> {
           RepeatKind::System,
           move |event, _, _| keyboard_event_handler(event, &result_clone),
         ) {
-          Ok((kbd, repeat_source)) => {
-            seats.push((name, Some((kbd, repeat_source))));
+          Ok(kbd) => {
+            seats.push((name, Some(kbd)));
           }
           Err(e) => {
             error!("Failed to map keyboard on seat {} : {:?}.", name, e);
@@ -127,8 +124,8 @@ pub fn get_modifiers_state() -> Result<Option<super::ModifiersState>> {
           RepeatKind::System,
           move |event, _, _| keyboard_event_handler(event, &result_clone),
         ) {
-          Ok((kbd, repeat_source)) => {
-            *opt_kbd = Some((kbd, repeat_source));
+          Ok(kbd) => {
+            *opt_kbd = Some(kbd);
           }
           Err(e) => {
             eprintln!(
@@ -138,10 +135,9 @@ pub fn get_modifiers_state() -> Result<Option<super::ModifiersState>> {
           }
         }
       }
-    } else if let Some((kbd, source)) = opt_kbd.take() {
+    } else if let Some(kbd) = opt_kbd.take() {
       // the keyboard has been removed, cleanup
       kbd.release();
-      loop_handle.remove(source);
     }
   });
 
diff --git a/espanso-detect/src/hotkey/keys.rs b/espanso-detect/src/hotkey/keys.rs
index c033faf7..8e25a114 100644
--- a/espanso-detect/src/hotkey/keys.rs
+++ b/espanso-detect/src/hotkey/keys.rs
@@ -210,7 +210,7 @@ impl Display for ShortcutKey {
       ShortcutKey::Numpad7 => write!(f, "NUMPAD7"),
       ShortcutKey::Numpad8 => write!(f, "NUMPAD8"),
       ShortcutKey::Numpad9 => write!(f, "NUMPAD9"),
-      ShortcutKey::Raw(code) => write!(f, "RAW({})", code),
+      ShortcutKey::Raw(code) => write!(f, "RAW({code})"),
     }
   }
 }
@@ -591,7 +591,7 @@ impl ShortcutKey {
       ShortcutKey::Numpad7 => Some(0xffb7),
       ShortcutKey::Numpad8 => Some(0xffb8),
       ShortcutKey::Numpad9 => Some(0xffb9),
-      ShortcutKey::Raw(code) => Some(*code as u32),
+      ShortcutKey::Raw(code) => Some(*code),
     }
   }
 }
diff --git a/espanso-detect/src/hotkey/mod.rs b/espanso-detect/src/hotkey/mod.rs
index 1534a133..1f619f4c 100644
--- a/espanso-detect/src/hotkey/mod.rs
+++ b/espanso-detect/src/hotkey/mod.rs
@@ -53,9 +53,9 @@ impl HotKey {
       match key {
         Some(key) => {
           if MODIFIERS.contains(&key) {
-            modifiers.push(key)
+            modifiers.push(key);
           } else {
-            main_key = Some(key)
+            main_key = Some(key);
           }
         }
         None => return Err(HotKeyError::InvalidKey(token).into()),
@@ -96,7 +96,7 @@ impl HotKey {
 
 impl Display for HotKey {
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-    let str_modifiers: Vec<String> = self.modifiers.iter().map(|m| m.to_string()).collect();
+    let str_modifiers: Vec<String> = self.modifiers.iter().map(ToString::to_string).collect();
     let modifiers = str_modifiers.join("+");
     write!(f, "{}+{}", &modifiers, &self.key)
   }
diff --git a/espanso-detect/src/win32/mod.rs b/espanso-detect/src/win32/mod.rs
index c0f87385..6271b323 100644
--- a/espanso-detect/src/win32/mod.rs
+++ b/espanso-detect/src/win32/mod.rs
@@ -27,13 +27,20 @@ use widestring::U16CStr;
 use anyhow::Result;
 use thiserror::Error;
 
-use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
-use crate::event::{Key::*, MouseButton, MouseEvent};
-use crate::{event::Status::*, Source, SourceCallback};
-use crate::{
-  event::{HotKeyEvent, Variant::*},
-  hotkey::HotKey,
+use crate::event::{
+  HotKeyEvent, InputEvent,
+  Key::{
+    self, Alt, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Backspace, CapsLock, Control, End, Enter,
+    Escape, Home, Meta, NumLock, Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, Numpad5, Numpad6,
+    Numpad7, Numpad8, Numpad9, Other, PageDown, PageUp, Shift, Space, Tab, F1, F10, F11, F12, F13,
+    F14, F15, F16, F17, F18, F19, F2, F20, F3, F4, F5, F6, F7, F8, F9,
+  },
+  KeyboardEvent, MouseButton, MouseEvent,
+  Status::{Pressed, Released},
+  Variant::{self, Left, Right},
 };
+use crate::hotkey::HotKey;
+use crate::{Source, SourceCallback};
 
 const INPUT_LEFT_VARIANT: i32 = 1;
 const INPUT_RIGHT_VARIANT: i32 = 2;
@@ -168,9 +175,10 @@ impl Source for Win32Source {
   }
 
   fn eventloop(&self, event_callback: SourceCallback) -> Result<()> {
-    if self.handle.is_null() {
-      panic!("Attempt to start Win32Source eventloop without initialization");
-    }
+    assert!(
+      self.handle.is_null(),
+      "Attempt to start Win32Source eventloop without initialization"
+    );
 
     if self.callback.fill(event_callback).is_err() {
       error!("Unable to set Win32Source event callback");
@@ -192,7 +200,7 @@ impl Source for Win32Source {
       let event: Option<InputEvent> = event.into();
       if let Some(callback) = unsafe { (*_self).callback.borrow() } {
         if let Some(event) = event {
-          callback(event)
+          callback(event);
         } else {
           trace!("Unable to convert raw event to input event");
         }
diff --git a/espanso-detect/src/x11/mod.rs b/espanso-detect/src/x11/mod.rs
index 9680d5ae..98f1d6df 100644
--- a/espanso-detect/src/x11/mod.rs
+++ b/espanso-detect/src/x11/mod.rs
@@ -239,10 +239,10 @@ fn convert_hotkey_to_raw(hk: &HotKey) -> Option<RawHotKeyRequest> {
   let key_sym = hk.key.to_code()?;
   Some(RawHotKeyRequest {
     key_sym,
-    ctrl: if hk.has_ctrl() { 1 } else { 0 },
-    alt: if hk.has_alt() { 1 } else { 0 },
-    shift: if hk.has_shift() { 1 } else { 0 },
-    meta: if hk.has_meta() { 1 } else { 0 },
+    ctrl: i32::from(hk.has_ctrl()),
+    alt: i32::from(hk.has_alt()),
+    shift: i32::from(hk.has_shift()),
+    meta: i32::from(hk.has_meta()),
   })
 }
 
diff --git a/espanso-engine/src/dispatch/default.rs b/espanso-engine/src/dispatch/default.rs
index 146b2c27..05e2d9aa 100644
--- a/espanso-engine/src/dispatch/default.rs
+++ b/espanso-engine/src/dispatch/default.rs
@@ -75,7 +75,7 @@ impl<'a> DefaultDispatcher<'a> {
 
 impl<'a> Dispatcher for DefaultDispatcher<'a> {
   fn dispatch(&self, event: Event) {
-    for executor in self.executors.iter() {
+    for executor in &self.executors {
       if executor.execute(&event) {
         break;
       }
diff --git a/espanso-engine/src/dispatch/executor/html_inject.rs b/espanso-engine/src/dispatch/executor/html_inject.rs
index d7207c0b..55f0bd2d 100644
--- a/espanso-engine/src/dispatch/executor/html_inject.rs
+++ b/espanso-engine/src/dispatch/executor/html_inject.rs
@@ -45,7 +45,7 @@ impl<'a> Executor for HtmlInjectExecutor<'a> {
       // Render the text fallback for those applications that don't support HTML clipboard
       let decorator = html2text::render::text_renderer::TrivialDecorator::new();
       let fallback_text =
-        html2text::from_read_with_decorator(inject_event.html.as_bytes(), 1000000, decorator);
+        html2text::from_read_with_decorator(inject_event.html.as_bytes(), 1_000_000, decorator);
 
       if let Err(error) = self
         .injector
diff --git a/espanso-engine/src/funnel/default.rs b/espanso-engine/src/funnel/default.rs
index cd321793..b2fa4433 100644
--- a/espanso-engine/src/funnel/default.rs
+++ b/espanso-engine/src/funnel/default.rs
@@ -36,7 +36,7 @@ impl<'a> Funnel for DefaultFunnel<'a> {
     let mut select = Select::new();
 
     // First register all the sources to the select operation
-    for source in self.sources.iter() {
+    for source in self.sources {
       source.register(&mut select);
     }
 
diff --git a/espanso-engine/src/process/default.rs b/espanso-engine/src/process/default.rs
index 0c943409..75af636d 100644
--- a/espanso-engine/src/process/default.rs
+++ b/espanso-engine/src/process/default.rs
@@ -132,7 +132,7 @@ impl<'a> DefaultProcessor<'a> {
       };
 
       trace!("--------------- new event -----------------");
-      for middleware in self.middleware.iter() {
+      for middleware in &self.middleware {
         trace!(
           "middleware '{}' received event: {:?}",
           middleware.name(),
diff --git a/espanso-engine/src/process/middleware/alt_code_synthesizer.rs b/espanso-engine/src/process/middleware/alt_code_synthesizer.rs
index 9cbd78f1..0ed28e94 100644
--- a/espanso-engine/src/process/middleware/alt_code_synthesizer.rs
+++ b/espanso-engine/src/process/middleware/alt_code_synthesizer.rs
@@ -62,7 +62,7 @@ impl<'a> Middleware for AltCodeSynthesizerMiddleware<'a> {
 
         if keyboard_event.status == Status::Pressed {
           if let Key::Alt = &keyboard_event.key {
-            *code_buffer = Some("".to_owned());
+            *code_buffer = Some(String::new());
           } else if let Some(buffer) = &mut *code_buffer {
             match keyboard_event.key {
               Key::Numpad0 => buffer.push('0'),
diff --git a/espanso-engine/src/process/middleware/cause.rs b/espanso-engine/src/process/middleware/cause.rs
index b3ae7c98..5d4d3236 100644
--- a/espanso-engine/src/process/middleware/cause.rs
+++ b/espanso-engine/src/process/middleware/cause.rs
@@ -55,9 +55,8 @@ impl Middleware for CauseCompensateMiddleware {
             left_separator: m_event.chosen.left_separator.clone(),
           }),
         );
-      } else {
-        return compensated_event;
       }
+      return compensated_event;
     }
 
     event
diff --git a/espanso-engine/src/process/middleware/disable.rs b/espanso-engine/src/process/middleware/disable.rs
index 41438cfc..57ba126e 100644
--- a/espanso-engine/src/process/middleware/disable.rs
+++ b/espanso-engine/src/process/middleware/disable.rs
@@ -109,7 +109,7 @@ impl Middleware for DisableMiddleware {
         } else {
           EventType::Disabled
         },
-      ))
+      ));
     }
 
     // Block keyboard events when disabled
@@ -128,8 +128,7 @@ fn is_toggle_key(event: &KeyboardEvent, options: &DisableOptions) -> bool {
   if options
     .toggle_key
     .as_ref()
-    .map(|key| key == &event.key)
-    .unwrap_or(false)
+    .is_some_and(|key| key == &event.key)
   {
     if let (Some(variant), Some(e_variant)) = (&options.toggle_key_variant, &event.variant) {
       variant == e_variant
diff --git a/espanso-engine/src/process/middleware/discard.rs b/espanso-engine/src/process/middleware/discard.rs
index 249588dd..038d3d16 100644
--- a/espanso-engine/src/process/middleware/discard.rs
+++ b/espanso-engine/src/process/middleware/discard.rs
@@ -24,7 +24,7 @@ use log::trace;
 use super::super::Middleware;
 use crate::event::{Event, EventType, SourceId};
 
-/// This middleware discards all events that have a source_id between
+/// This middleware discards all events that have a `source_id` between
 /// the given maximum and minimum.
 /// This useful to discard past events that might have been stuck in the
 /// event queue for too long, or events generated while the search bar was open.
diff --git a/espanso-engine/src/process/middleware/image_resolve.rs b/espanso-engine/src/process/middleware/image_resolve.rs
index 7fb11510..9c02b23c 100644
--- a/espanso-engine/src/process/middleware/image_resolve.rs
+++ b/espanso-engine/src/process/middleware/image_resolve.rs
@@ -49,7 +49,7 @@ impl<'a> Middleware for ImageResolverMiddleware<'a> {
       let path = if cfg!(target_os = "windows") {
         m_event.image_path.replace('/', "\\")
       } else {
-        m_event.image_path.to_owned()
+        m_event.image_path.clone()
       };
 
       let path = if path.contains("$CONFIG") {
diff --git a/espanso-engine/src/process/middleware/markdown.rs b/espanso-engine/src/process/middleware/markdown.rs
index ff1bea25..fcc11cc9 100644
--- a/espanso-engine/src/process/middleware/markdown.rs
+++ b/espanso-engine/src/process/middleware/markdown.rs
@@ -54,11 +54,9 @@ impl Middleware for MarkdownMiddleware {
             html: html.to_owned(),
           }),
         );
-      } else {
-        error!("unable to convert markdown to HTML, is it malformed?");
-
-        return Event::caused_by(event.source_id, EventType::NOOP);
       }
+      error!("unable to convert markdown to HTML, is it malformed?");
+      return Event::caused_by(event.source_id, EventType::NOOP);
     }
 
     event
diff --git a/espanso-engine/src/process/middleware/match_exec.rs b/espanso-engine/src/process/middleware/match_exec.rs
index 5b8a6698..4646260a 100644
--- a/espanso-engine/src/process/middleware/match_exec.rs
+++ b/espanso-engine/src/process/middleware/match_exec.rs
@@ -53,11 +53,11 @@ impl<'a> Middleware for MatchExecRequestMiddleware<'a> {
       };
 
       // Inject the request args into the detected matches
-      matches.iter_mut().for_each(|m| {
+      for m in &mut matches {
         for (key, value) in &m_event.args {
           m.args.insert(key.to_string(), value.to_string());
         }
-      });
+      }
 
       if matches.is_empty() {
         warn!("received match exec request, but no matches have been found for the given query.");
diff --git a/espanso-engine/src/process/middleware/matcher.rs b/espanso-engine/src/process/middleware/matcher.rs
index 8692a3bb..6ef269fd 100644
--- a/espanso-engine/src/process/middleware/matcher.rs
+++ b/espanso-engine/src/process/middleware/matcher.rs
@@ -103,10 +103,10 @@ impl<'a, State> Middleware for MatcherMiddleware<'a, State> {
   fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
     if is_event_of_interest(&event.etype) {
       let mut matcher_states = self.matcher_states.borrow_mut();
-      let prev_states = if !matcher_states.is_empty() {
-        matcher_states.back()
-      } else {
+      let prev_states = if matcher_states.is_empty() {
         None
+      } else {
+        matcher_states.back()
       };
 
       if let EventType::Keyboard(keyboard_event) = &event.etype {
@@ -183,10 +183,7 @@ impl<'a, State> Middleware for MatcherMiddleware<'a, State> {
 fn is_event_of_interest(event_type: &EventType) -> bool {
   match event_type {
     EventType::Keyboard(keyboard_event) => {
-      if keyboard_event.status != Status::Pressed {
-        // Skip non-press events
-        false
-      } else {
+      if keyboard_event.status == Status::Pressed {
         // Skip linux Keyboard (XKB) Extension function and modifier keys
         // In hex, they have the byte 3 = 0xfe
         // See list in "keysymdef.h" file
@@ -203,6 +200,9 @@ fn is_event_of_interest(event_type: &EventType) -> bool {
           keyboard_event.key,
           Key::Alt | Key::Shift | Key::CapsLock | Key::Meta | Key::NumLock | Key::Control
         )
+      } else {
+        // Skip non-press events
+        false
       }
     }
     EventType::Mouse(mouse_event) => mouse_event.status == Status::Pressed,
diff --git a/espanso-engine/src/process/middleware/multiplex.rs b/espanso-engine/src/process/middleware/multiplex.rs
index dbd38e74..d32edfb1 100644
--- a/espanso-engine/src/process/middleware/multiplex.rs
+++ b/espanso-engine/src/process/middleware/multiplex.rs
@@ -43,12 +43,11 @@ impl<'a> Middleware for MultiplexMiddleware<'a> {
 
   fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
     if let EventType::CauseCompensatedMatch(m_event) = event.etype {
-      return match self.multiplexer.convert(m_event.m) {
-        Some(new_event) => Event::caused_by(event.source_id, new_event),
-        None => {
-          error!("match multiplexing failed");
-          Event::caused_by(event.source_id, EventType::NOOP)
-        }
+      return if let Some(new_event) = self.multiplexer.convert(m_event.m) {
+        Event::caused_by(event.source_id, new_event)
+      } else {
+        error!("match multiplexing failed");
+        Event::caused_by(event.source_id, EventType::NOOP)
       };
     }
 
diff --git a/espanso-engine/src/process/middleware/render.rs b/espanso-engine/src/process/middleware/render.rs
index d8175e0c..c6700732 100644
--- a/espanso-engine/src/process/middleware/render.rs
+++ b/espanso-engine/src/process/middleware/render.rs
@@ -71,7 +71,7 @@ impl<'a> Middleware for RenderMiddleware<'a> {
       ) {
         Ok(body) => {
           let body = if let Some(right_separator) = m_event.right_separator {
-            format!("{}{}", body, right_separator)
+            format!("{body}{right_separator}")
           } else {
             body
           };
@@ -85,24 +85,22 @@ impl<'a> Middleware for RenderMiddleware<'a> {
             }),
           );
         }
-        Err(err) => match err.downcast_ref::<RendererError>() {
-          Some(RendererError::Aborted) => {
-            return Event::caused_by(event.source_id, EventType::NOOP)
+        Err(err) => {
+          if let Some(RendererError::Aborted) = err.downcast_ref::<RendererError>() {
+            return Event::caused_by(event.source_id, EventType::NOOP);
           }
-          _ => {
-            error!("error during rendering: {:?}", err);
-
-            dispatch(Event::caused_by(
-                event.source_id,
-                EventType::TextInject(TextInjectRequest {
-                  text: "[Espanso]: An error occurred during rendering, please examine the logs for more information.".to_string(),
-                  ..Default::default()
-                }),
-              ));
-
-            return Event::caused_by(event.source_id, EventType::RenderingError);
-          }
-        },
+          error!("error during rendering: {:?}", err);
+
+          dispatch(Event::caused_by(
+                    event.source_id,
+                    EventType::TextInject(TextInjectRequest {
+                      text: "[Espanso]: An error occurred during rendering, please examine the logs for more information.".to_string(),
+                      ..Default::default()
+                    }),
+                  ));
+
+          return Event::caused_by(event.source_id, EventType::RenderingError);
+        }
       }
     }
 
diff --git a/espanso-info/src/win32/mod.rs b/espanso-info/src/win32/mod.rs
index cb40ece5..50d650b5 100644
--- a/espanso-info/src/win32/mod.rs
+++ b/espanso-info/src/win32/mod.rs
@@ -36,38 +36,38 @@ impl WinAppInfoProvider {
 impl AppInfoProvider for WinAppInfoProvider {
   fn get_info(&self) -> AppInfo {
     AppInfo {
-      title: self.get_title(),
+      title: WinAppInfoProvider::get_title(),
       class: None,
-      exec: self.get_exec(),
+      exec: WinAppInfoProvider::get_exec(),
     }
   }
 }
 
 impl WinAppInfoProvider {
-  fn get_exec(&self) -> Option<String> {
+  fn get_exec() -> Option<String> {
     let mut buffer: [u16; 2048] = [0; 2048];
-    if unsafe { info_get_exec(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } != 0 {
+    if unsafe { info_get_exec(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } == 0 {
+      None
+    } else {
       let string = unsafe { U16CStr::from_ptr_str(buffer.as_ptr()) };
       let string = string.to_string_lossy();
-      if !string.is_empty() {
-        Some(string)
-      } else {
+      if string.is_empty() {
         None
+      } else {
+        Some(string)
       }
-    } else {
-      None
     }
   }
 
-  fn get_title(&self) -> Option<String> {
+  fn get_title() -> Option<String> {
     let mut buffer: [u16; 2048] = [0; 2048];
     if unsafe { info_get_title(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) } > 0 {
       let string = unsafe { U16CStr::from_ptr_str(buffer.as_ptr()) };
       let string = string.to_string_lossy();
-      if !string.is_empty() {
-        Some(string)
-      } else {
+      if string.is_empty() {
         None
+      } else {
+        Some(string)
       }
     } else {
       None
diff --git a/espanso-inject/src/evdev/uinput.rs b/espanso-inject/src/evdev/uinput.rs
index 4445b318..4681a85c 100644
--- a/espanso-inject/src/evdev/uinput.rs
+++ b/espanso-inject/src/evdev/uinput.rs
@@ -77,7 +77,7 @@ impl UInputDevice {
   }
 
   pub fn emit(&self, key_code: u32, pressed: bool) {
-    let pressed = if pressed { 1 } else { 0 };
+    let pressed = i32::from(pressed);
     unsafe {
       uinput_emit(self.fd, key_code, pressed);
     }
diff --git a/espanso-inject/src/keys.rs b/espanso-inject/src/keys.rs
index e1d46b21..fccdf607 100644
--- a/espanso-inject/src/keys.rs
+++ b/espanso-inject/src/keys.rs
@@ -226,7 +226,7 @@ impl Display for Key {
       Key::Numpad7 => write!(f, "NUMPAD7"),
       Key::Numpad8 => write!(f, "NUMPAD8"),
       Key::Numpad9 => write!(f, "NUMPAD9"),
-      Key::Raw(code) => write!(f, "RAW({})", code),
+      Key::Raw(code) => write!(f, "RAW({code})"),
     }
   }
 }
diff --git a/espanso-inject/src/lib.rs b/espanso-inject/src/lib.rs
index e07ec7d5..9e02dcc8 100644
--- a/espanso-inject/src/lib.rs
+++ b/espanso-inject/src/lib.rs
@@ -75,6 +75,7 @@ impl Default for InjectionOptions {
     } else if cfg!(target_os = "macos") {
       1
     } else if cfg!(target_os = "linux") {
+      #[allow(clippy::bool_to_int_with_if)]
       if cfg!(feature = "wayland") {
         1
       } else {
diff --git a/espanso-inject/src/win32/mod.rs b/espanso-inject/src/win32/mod.rs
index 45c7d033..d2bfd08f 100644
--- a/espanso-inject/src/win32/mod.rs
+++ b/espanso-inject/src/win32/mod.rs
@@ -47,10 +47,10 @@ impl Win32Injector {
 
   pub fn convert_to_vk_array(keys: &[keys::Key]) -> Result<Vec<i32>> {
     let mut virtual_keys: Vec<i32> = Vec::new();
-    for key in keys.iter() {
+    for key in keys {
       let vk = convert_key_to_vkey(key);
       if let Some(vk) = vk {
-        virtual_keys.push(vk)
+        virtual_keys.push(vk);
       } else {
         return Err(Win32InjectorError::MappingFailure(key.clone()).into());
       }
diff --git a/espanso-inject/src/x11/default/mod.rs b/espanso-inject/src/x11/default/mod.rs
index 2501f761..b90a1cdc 100644
--- a/espanso-inject/src/x11/default/mod.rs
+++ b/espanso-inject/src/x11/default/mod.rs
@@ -414,7 +414,7 @@ impl X11DefaultInjector {
     for (mod_index, modifier_codes) in modifiers_codes.into_iter().enumerate() {
       if (modmask & (1 << mod_index)) != 0 {
         for keycode in modifier_codes {
-          let is_press = if pressed { 1 } else { 0 };
+          let is_press = i32::from(pressed);
           unsafe {
             XTestFakeKeyEvent(self.display, keycode as u32, is_press, 0);
             XSync(self.display, 0);
@@ -430,7 +430,7 @@ impl X11DefaultInjector {
       self.xtest_send_modifiers(record.state, pressed);
     }
 
-    let is_press = if pressed { 1 } else { 0 };
+    let is_press = i32::from(pressed);
     unsafe {
       XTestFakeKeyEvent(self.display, record.code, is_press, 0);
       XSync(self.display, 0);
diff --git a/espanso-ipc/src/unix.rs b/espanso-ipc/src/unix.rs
index 4ba4336e..d18dcc4c 100644
--- a/espanso-ipc/src/unix.rs
+++ b/espanso-ipc/src/unix.rs
@@ -108,7 +108,7 @@ pub struct UnixIPCClient {
 impl UnixIPCClient {
   pub fn new(id: &str, parent_dir: &Path) -> Result<Self> {
     let socket_path = parent_dir.join(format!("{}.sock", id));
-    let stream = UnixStream::connect(&socket_path)?;
+    let stream = UnixStream::connect(socket_path)?;
 
     Ok(Self { stream })
   }
diff --git a/espanso-ipc/src/util.rs b/espanso-ipc/src/util.rs
index 0c5753ca..37cfd2aa 100644
--- a/espanso-ipc/src/util.rs
+++ b/espanso-ipc/src/util.rs
@@ -32,10 +32,8 @@ pub fn read_line<R: std::io::Read>(stream: R) -> Result<Option<String>> {
     if byte == 10 {
       // Newline
       break;
-    } else {
-      buffer.push(byte);
     }
-
+    buffer.push(byte);
     is_eof = false;
   }
 
diff --git a/espanso-ipc/src/windows.rs b/espanso-ipc/src/windows.rs
index 99e8eaf8..d1fd9f6b 100644
--- a/espanso-ipc/src/windows.rs
+++ b/espanso-ipc/src/windows.rs
@@ -34,7 +34,7 @@ pub struct WinIPCServer {
 
 impl WinIPCServer {
   pub fn new(id: &str) -> Result<Self> {
-    let pipe_name = format!("\\\\.\\pipe\\{}", id);
+    let pipe_name = format!("\\\\.\\pipe\\{id}");
 
     let options = PipeOptions::new(&pipe_name);
     let server = Some(options.single()?);
@@ -105,9 +105,9 @@ pub struct WinIPCClient {
 
 impl WinIPCClient {
   pub fn new(id: &str) -> Result<Self> {
-    let pipe_name = format!("\\\\.\\pipe\\{}", id);
+    let pipe_name = format!("\\\\.\\pipe\\{id}");
 
-    let stream = PipeClient::connect_ms(&pipe_name, DEFAULT_CLIENT_TIMEOUT)?;
+    let stream = PipeClient::connect_ms(pipe_name, DEFAULT_CLIENT_TIMEOUT)?;
     Ok(Self { stream })
   }
 }
diff --git a/espanso-match/src/lib.rs b/espanso-match/src/lib.rs
index 4dc94b4a..60f17445 100644
--- a/espanso-match/src/lib.rs
+++ b/espanso-match/src/lib.rs
@@ -39,7 +39,7 @@ impl<Id: Default> Default for MatchResult<Id> {
   fn default() -> Self {
     Self {
       id: Id::default(),
-      trigger: "".to_string(),
+      trigger: String::new(),
       left_separator: None,
       right_separator: None,
       vars: HashMap::new(),
diff --git a/espanso-match/src/regex/mod.rs b/espanso-match/src/regex/mod.rs
index b05e5ca8..f6ac7e40 100644
--- a/espanso-match/src/regex/mod.rs
+++ b/espanso-match/src/regex/mod.rs
@@ -80,7 +80,7 @@ where
     let mut buffer = if let Some(prev_state) = prev_state {
       prev_state.buffer.clone()
     } else {
-      "".to_string()
+      String::new()
     };
 
     if let Event::Key {
@@ -179,7 +179,7 @@ mod tests {
   fn match_result<Id: Default>(id: Id, trigger: &str, vars: &[(&str, &str)]) -> MatchResult<Id> {
     let vars: HashMap<String, String> = vars
       .iter()
-      .map(|(key, value)| (key.to_string(), value.to_string()))
+      .map(|(key, value)| ((*key).to_string(), (*value).to_string()))
       .collect();
 
     MatchResult {
diff --git a/espanso-match/src/rolling/matcher.rs b/espanso-match/src/rolling/matcher.rs
index c26fbcc2..85bdbef6 100644
--- a/espanso-match/src/rolling/matcher.rs
+++ b/espanso-match/src/rolling/matcher.rs
@@ -76,7 +76,7 @@ where
 
     // First compute the old refs
     if let Some(prev_state) = prev_state {
-      for node_path in prev_state.paths.iter() {
+      for node_path in &prev_state.paths {
         next_refs.extend(
           self
             .find_refs(node_path.node, &event, true)
@@ -153,13 +153,13 @@ impl<Id: Clone> RollingMatcher<Id> {
 
     if let Event::Key { key, chars } = event {
       // Key matching
-      if let Some((_, node_ref)) = node.keys.iter().find(|(_key, _)| _key == key) {
+      if let Some((_, node_ref)) = node.keys.iter().find(|(k, _)| k == key) {
         refs.push((node_ref, false));
       }
 
       if let Some(char) = chars {
         // Char matching
-        if let Some((_, node_ref)) = node.chars.iter().find(|(_char, _)| _char == char) {
+        if let Some((_, node_ref)) = node.chars.iter().find(|(c, _)| c == char) {
           refs.push((node_ref, false));
         }
 
@@ -168,7 +168,7 @@ impl<Id: Clone> RollingMatcher<Id> {
         if let Some((_, node_ref)) = node
           .chars_insensitive
           .iter()
-          .find(|(_char, _)| *_char == insensitive_char)
+          .find(|(c, _)| *c == insensitive_char)
         {
           refs.push((node_ref, false));
         }
@@ -177,7 +177,7 @@ impl<Id: Clone> RollingMatcher<Id> {
 
     if self.is_word_separator(event) {
       if let Some(node_ref) = node.word_separators.as_ref() {
-        refs.push((node_ref, true))
+        refs.push((node_ref, true));
       }
     }
 
diff --git a/espanso-match/src/rolling/mod.rs b/espanso-match/src/rolling/mod.rs
index c0713a2d..65c984af 100644
--- a/espanso-match/src/rolling/mod.rs
+++ b/espanso-match/src/rolling/mod.rs
@@ -51,9 +51,9 @@ impl<Id> RollingMatch<Id> {
 
     for c in string.chars() {
       if opt.case_insensitive {
-        items.push(RollingItem::CharInsensitive(c.to_string()))
+        items.push(RollingItem::CharInsensitive(c.to_string()));
       } else {
-        items.push(RollingItem::Char(c.to_string()))
+        items.push(RollingItem::Char(c.to_string()));
       }
     }
 
@@ -96,7 +96,7 @@ mod tests {
           RollingItem::Char("t".to_string()),
         ]
       }
-    )
+    );
   }
 
   #[test]
@@ -120,7 +120,7 @@ mod tests {
           RollingItem::Char("t".to_string()),
         ]
       }
-    )
+    );
   }
 
   #[test]
@@ -144,7 +144,7 @@ mod tests {
           RollingItem::WordSeparator,
         ]
       }
-    )
+    );
   }
 
   #[test]
@@ -167,6 +167,6 @@ mod tests {
           RollingItem::CharInsensitive("t".to_string()),
         ]
       }
-    )
+    );
   }
 }
diff --git a/espanso-match/src/rolling/tree.rs b/espanso-match/src/rolling/tree.rs
index 9b9c1389..0ad958f6 100644
--- a/espanso-match/src/rolling/tree.rs
+++ b/espanso-match/src/rolling/tree.rs
@@ -75,14 +75,14 @@ fn insert_items_recursively<Id>(id: Id, node: &mut MatcherTreeNode<Id>, items: &
           new_matches.extend(matches);
         }
         new_matches.push(id);
-        node.word_separators = Some(MatcherTreeRef::Matches(new_matches))
+        node.word_separators = Some(MatcherTreeRef::Matches(new_matches));
       }
       RollingItem::Key(key) => {
-        if let Some(entry) = node.keys.iter_mut().find(|(_key, _)| _key == key) {
+        if let Some(entry) = node.keys.iter_mut().find(|(k, _)| k == key) {
           if let MatcherTreeRef::Matches(matches) = &mut entry.1 {
-            matches.push(id)
+            matches.push(id);
           } else {
-            entry.1 = MatcherTreeRef::Matches(vec![id])
+            entry.1 = MatcherTreeRef::Matches(vec![id]);
           };
         } else {
           node
@@ -91,11 +91,11 @@ fn insert_items_recursively<Id>(id: Id, node: &mut MatcherTreeNode<Id>, items: &
         }
       }
       RollingItem::Char(c) => {
-        if let Some(entry) = node.chars.iter_mut().find(|(_c, _)| _c == c) {
+        if let Some(entry) = node.chars.iter_mut().find(|(char, _)| char == c) {
           if let MatcherTreeRef::Matches(matches) = &mut entry.1 {
-            matches.push(id)
+            matches.push(id);
           } else {
-            entry.1 = MatcherTreeRef::Matches(vec![id])
+            entry.1 = MatcherTreeRef::Matches(vec![id]);
           };
         } else {
           node
@@ -108,12 +108,12 @@ fn insert_items_recursively<Id>(id: Id, node: &mut MatcherTreeNode<Id>, items: &
         if let Some(entry) = node
           .chars_insensitive
           .iter_mut()
-          .find(|(_c, _)| _c == &uni_char)
+          .find(|(c, _)| c == &uni_char)
         {
           if let MatcherTreeRef::Matches(matches) = &mut entry.1 {
-            matches.push(id)
+            matches.push(id);
           } else {
-            entry.1 = MatcherTreeRef::Matches(vec![id])
+            entry.1 = MatcherTreeRef::Matches(vec![id]);
           };
         } else {
           node
@@ -126,22 +126,22 @@ fn insert_items_recursively<Id>(id: Id, node: &mut MatcherTreeNode<Id>, items: &
     match item {
       RollingItem::WordSeparator => match node.word_separators.as_mut() {
         Some(MatcherTreeRef::Node(next_node)) => {
-          insert_items_recursively(id, next_node.as_mut(), &items[1..])
+          insert_items_recursively(id, next_node.as_mut(), &items[1..]);
         }
         None => {
-          let mut next_node = Box::new(MatcherTreeNode::default());
+          let mut next_node = Box::<MatcherTreeNode<Id>>::default();
           insert_items_recursively(id, next_node.as_mut(), &items[1..]);
           node.word_separators = Some(MatcherTreeRef::Node(next_node));
         }
         _ => {}
       },
       RollingItem::Key(key) => {
-        if let Some(entry) = node.keys.iter_mut().find(|(_key, _)| _key == key) {
+        if let Some(entry) = node.keys.iter_mut().find(|(k, _)| k == key) {
           if let MatcherTreeRef::Node(next_node) = &mut entry.1 {
-            insert_items_recursively(id, next_node, &items[1..])
+            insert_items_recursively(id, next_node, &items[1..]);
           }
         } else {
-          let mut next_node = Box::new(MatcherTreeNode::default());
+          let mut next_node = Box::<MatcherTreeNode<Id>>::default();
           insert_items_recursively(id, next_node.as_mut(), &items[1..]);
           node
             .keys
@@ -149,12 +149,12 @@ fn insert_items_recursively<Id>(id: Id, node: &mut MatcherTreeNode<Id>, items: &
         }
       }
       RollingItem::Char(c) => {
-        if let Some(entry) = node.chars.iter_mut().find(|(_c, _)| _c == c) {
+        if let Some(entry) = node.chars.iter_mut().find(|(char, _)| char == c) {
           if let MatcherTreeRef::Node(next_node) = &mut entry.1 {
-            insert_items_recursively(id, next_node, &items[1..])
+            insert_items_recursively(id, next_node, &items[1..]);
           }
         } else {
-          let mut next_node = Box::new(MatcherTreeNode::default());
+          let mut next_node = Box::<MatcherTreeNode<Id>>::default();
           insert_items_recursively(id, next_node.as_mut(), &items[1..]);
           node
             .chars
@@ -166,13 +166,13 @@ fn insert_items_recursively<Id>(id: Id, node: &mut MatcherTreeNode<Id>, items: &
         if let Some(entry) = node
           .chars_insensitive
           .iter_mut()
-          .find(|(_c, _)| _c == &uni_char)
+          .find(|(c, _)| c == &uni_char)
         {
           if let MatcherTreeRef::Node(next_node) = &mut entry.1 {
-            insert_items_recursively(id, next_node, &items[1..])
+            insert_items_recursively(id, next_node, &items[1..]);
           }
         } else {
-          let mut next_node = Box::new(MatcherTreeNode::default());
+          let mut next_node = Box::<MatcherTreeNode<Id>>::default();
           insert_items_recursively(id, next_node.as_mut(), &items[1..]);
           node
             .chars_insensitive
@@ -227,7 +227,7 @@ mod tests {
         ],
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -264,7 +264,7 @@ mod tests {
         ),],
         ..Default::default()
       }
-    )
+    );
   }
 
   #[test]
@@ -351,6 +351,6 @@ mod tests {
         ),],
         ..Default::default()
       }
-    )
+    );
   }
 }
diff --git a/espanso-match/src/rolling/util.rs b/espanso-match/src/rolling/util.rs
index d137c353..905e1476 100644
--- a/espanso-match/src/rolling/util.rs
+++ b/espanso-match/src/rolling/util.rs
@@ -166,7 +166,7 @@ mod tests {
           false
         ),
       ]),
-      ("".to_string(), None, None)
+      (String::new(), None, None)
     );
   }
 
diff --git a/espanso-match/src/util.rs b/espanso-match/src/util.rs
index be9e62df..62f11414 100644
--- a/espanso-match/src/util.rs
+++ b/espanso-match/src/util.rs
@@ -32,7 +32,7 @@ pub(crate) mod tests {
     let mut matches = Vec::new();
 
     for c in string.chars() {
-      let (state, _matches) = matcher.process(
+      let (state, vec_matches) = matcher.process(
         prev_state.as_ref(),
         Event::Key {
           key: Key::Other,
@@ -41,7 +41,7 @@ pub(crate) mod tests {
       );
 
       prev_state = Some(state);
-      matches = _matches;
+      matches = vec_matches;
     }
 
     matches
diff --git a/espanso-migrate/src/convert.rs b/espanso-migrate/src/convert.rs
index 16edc7e0..655706d5 100644
--- a/espanso-migrate/src/convert.rs
+++ b/espanso-migrate/src/convert.rs
@@ -54,10 +54,7 @@ pub fn convert(input_files: HashMap<String, Hash>) -> HashMap<String, ConvertedF
       let should_underscore = !input_path.starts_with("default") && yaml_parent != Some("default");
       let match_output_path = calculate_output_match_path(&input_path, should_underscore);
       if match_output_path.is_none() {
-        eprintln!(
-          "unable to determine output path for {}, skipping...",
-          input_path
-        );
+        eprintln!("unable to determine output path for {input_path}, skipping...");
         continue;
       }
       let match_output_path = match_output_path.unwrap();
@@ -82,7 +79,7 @@ pub fn convert(input_files: HashMap<String, Hash>) -> HashMap<String, ConvertedF
         if let Yaml::Array(out_global_vars) = output_global_vars {
           out_global_vars.extend(patched_global_vars);
         } else {
-          eprintln!("unable to transform global_vars for file: {}", input_path);
+          eprintln!("unable to transform global_vars for file: {input_path}");
         }
       }
 
@@ -97,7 +94,7 @@ pub fn convert(input_files: HashMap<String, Hash>) -> HashMap<String, ConvertedF
         if let Yaml::Array(out_matches) = output_matches {
           out_matches.extend(patched_matches);
         } else {
-          eprintln!("unable to transform matches for file: {}", input_path);
+          eprintln!("unable to transform matches for file: {input_path}");
         }
       }
 
@@ -220,7 +217,7 @@ pub fn convert(input_files: HashMap<String, Hash>) -> HashMap<String, ConvertedF
           "extra_includes"
         };
 
-        let includes = vec![Yaml::String(format!("../{}", match_file_path))];
+        let includes = vec![Yaml::String(format!("../{match_file_path}"))];
 
         output_yaml.insert(Yaml::String(key_name.to_string()), Yaml::Array(includes));
       }
@@ -261,7 +258,7 @@ fn calculate_output_match_path(path: &str, is_underscored: bool) -> Option<Strin
   let file_name = path_buf.file_name()?.to_string_lossy().to_string();
 
   let path = if is_underscored {
-    path.replace(&file_name, &format!("_{}", file_name))
+    path.replace(&file_name, &format!("_{file_name}"))
   } else {
     path.to_string()
   };
@@ -273,7 +270,7 @@ fn calculate_output_match_path(path: &str, is_underscored: bool) -> Option<Strin
   } else if path == "default.yml" {
     "match/base.yml".to_string()
   } else {
-    format!("match/{}", path)
+    format!("match/{path}")
   })
 }
 
@@ -284,7 +281,7 @@ fn calculate_output_config_path(path: &str) -> String {
   } else if path.starts_with("packages/") {
     format!("config/packages/{}", path.trim_start_matches("packages/"))
   } else {
-    format!("config/{}", path)
+    format!("config/{path}")
   }
 }
 
@@ -303,7 +300,7 @@ fn yaml_get_string<'a>(yaml: &'a Hash, name: &str) -> Option<&'a str> {
 fn yaml_get_bool(yaml: &Hash, name: &str) -> Option<bool> {
   yaml
     .get(&Yaml::String(name.to_string()))
-    .and_then(|v| v.as_bool())
+    .and_then(Yaml::as_bool)
 }
 
 fn copy_field_if_present(
@@ -329,14 +326,14 @@ fn map_field_if_present(
     if let Some(transformed) = transformed {
       output_yaml.insert(Yaml::String(output_field_name.to_string()), transformed);
     } else {
-      eprintln!("could not convert value for field: {}", input_field_name);
+      eprintln!("could not convert value for field: {input_field_name}");
     }
   }
 }
 
 // This is needed to convert the old form's {{control}} syntax to the new [[control]] one.
 fn apply_form_syntax_patch(matches: &mut [Yaml]) {
-  matches.iter_mut().for_each(|m| {
+  for m in matches.iter_mut() {
     if let Yaml::Hash(fields) = m {
       if let Some(Yaml::String(form_option)) = fields.get_mut(&Yaml::String("form".to_string())) {
         let converted = replace_legacy_form_syntax_with_new_one(form_option);
@@ -349,10 +346,10 @@ fn apply_form_syntax_patch(matches: &mut [Yaml]) {
       if let Some(Yaml::Array(vars)) = fields.get_mut(&Yaml::String("vars".to_string())) {
         vars
           .iter_mut()
-          .for_each(apply_form_syntax_patch_to_variable)
+          .for_each(apply_form_syntax_patch_to_variable);
       }
     }
-  })
+  }
 }
 
 fn apply_form_syntax_patch_to_variable(variable: &mut Yaml) {
@@ -383,7 +380,7 @@ fn replace_legacy_form_syntax_with_new_one(layout: &str) -> String {
   LEGACY_FIELD_REGEX
     .replace_all(layout, |caps: &Captures| {
       let field_name = caps.name("name").unwrap().as_str();
-      format!("[[{}]]", field_name)
+      format!("[[{field_name}]]")
     })
     .to_string()
 }
diff --git a/espanso-migrate/src/lib.rs b/espanso-migrate/src/lib.rs
index d3c97106..e0c93968 100644
--- a/espanso-migrate/src/lib.rs
+++ b/espanso-migrate/src/lib.rs
@@ -188,7 +188,7 @@ mod tests {
 
   fn list_files_in_dir(dir: &Path) -> Vec<String> {
     let prefix = dir.to_string_lossy().to_string();
-    fs_extra::dir::get_dir_content(&dir)
+    fs_extra::dir::get_dir_content(dir)
       .unwrap()
       .files
       .into_iter()
diff --git a/espanso-migrate/src/load.rs b/espanso-migrate/src/load.rs
index d288a4fc..d95706ac 100644
--- a/espanso-migrate/src/load.rs
+++ b/espanso-migrate/src/load.rs
@@ -66,7 +66,12 @@ pub fn load(config_dir: &Path) -> Result<HashMap<String, Hash>> {
                 } else {
                   match YamlLoader::load_from_str(&content) {
                     Ok(mut yaml) => {
-                      if !yaml.is_empty() {
+                      if yaml.is_empty() {
+                        eprintln!(
+                          "error, found empty document while reading entry: {}",
+                          entry.path().display()
+                        );
+                      } else {
                         let yaml = yaml.remove(0);
                         if let Yaml::Hash(hash) = yaml {
                           input_files.insert(corrected_path, hash);
@@ -76,11 +81,6 @@ pub fn load(config_dir: &Path) -> Result<HashMap<String, Hash>> {
                             entry.path().display()
                           );
                         }
-                      } else {
-                        eprintln!(
-                          "error, found empty document while reading entry: {}",
-                          entry.path().display()
-                        );
                       }
                     }
                     Err(err) => {
@@ -112,7 +112,7 @@ pub fn load(config_dir: &Path) -> Result<HashMap<String, Hash>> {
         }
       }
       Err(err) => {
-        eprintln!("experienced error while reading entry: {}", err)
+        eprintln!("experienced error while reading entry: {err}");
       }
     }
   }
diff --git a/espanso-modulo/build.rs b/espanso-modulo/build.rs
index ba4b1307..6d4ccb36 100644
--- a/espanso-modulo/build.rs
+++ b/espanso-modulo/build.rs
@@ -35,15 +35,10 @@ fn build_native() {
   let project_dir =
     PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("missing CARGO_MANIFEST_DIR"));
   let wx_archive = project_dir.join("vendor").join(WX_WIDGETS_ARCHIVE_NAME);
-  if !wx_archive.is_file() {
-    panic!("could not find wxWidgets archive!");
-  }
+  assert!(wx_archive.is_file(), "could not find wxWidgets archive!");
 
   let out_dir = if let Ok(out_path) = std::env::var(WX_WIDGETS_BUILD_OUT_DIR_ENV_NAME) {
-    println!(
-      "detected wxWidgets build output directory override: {}",
-      out_path
-    );
+    println!("detected wxWidgets build output directory override: {out_path}");
     let path = PathBuf::from(out_path);
     std::fs::create_dir_all(&path).expect("unable to create wxWidgets out dir");
     path
@@ -88,7 +83,7 @@ fn build_native() {
           .to_string_lossy()
           .to_string(),
       )
-      .args(&[
+      .args([
         "/k",
         &vcvars_path.to_string_lossy(),
         "&",
@@ -212,7 +207,7 @@ fn build_native() {
       // See: https://github.com/actions/virtual-environments/issues/3288#issuecomment-830207746
       Command::new(out_wx_dir.join("configure"))
         .current_dir(build_dir.to_string_lossy().to_string())
-        .args(&[
+        .args([
           "--disable-shared",
           "--without-libtiff",
           "--without-liblzma",
@@ -225,7 +220,7 @@ fn build_native() {
     } else {
       Command::new(out_wx_dir.join("configure"))
         .current_dir(build_dir.to_string_lossy().to_string())
-        .args(&[
+        .args([
           "--disable-shared",
           "--without-libtiff",
           "--without-liblzma",
@@ -247,7 +242,7 @@ fn build_native() {
 
     let mut handle = Command::new("make")
       .current_dir(build_dir.to_string_lossy().to_string())
-      .args(&["-j8"])
+      .args(["-j8"])
       .spawn()
       .expect("failed to execute make");
     if !handle
@@ -320,7 +315,7 @@ fn convert_fat_libraries_to_arm(lib_dir: &Path) {
 
     // Make sure it's a fat library
     let lipo_output = std::process::Command::new("lipo")
-      .args(&["-detailed_info", &path.to_string_lossy()])
+      .args(["-detailed_info", &path.to_string_lossy()])
       .output()
       .expect("unable to check if library is fat");
     let lipo_output = String::from_utf8_lossy(&lipo_output.stdout);
@@ -336,7 +331,7 @@ fn convert_fat_libraries_to_arm(lib_dir: &Path) {
     println!("converting {} to arm", path.to_string_lossy(),);
 
     let result = std::process::Command::new("lipo")
-      .args(&[
+      .args([
         "-thin",
         "arm64",
         &path.to_string_lossy(),
@@ -354,7 +349,7 @@ fn convert_fat_libraries_to_arm(lib_dir: &Path) {
 
 #[cfg(not(target_os = "windows"))]
 fn get_cpp_flags(wx_config_path: &Path) -> Vec<String> {
-  let config_output = std::process::Command::new(&wx_config_path)
+  let config_output = std::process::Command::new(wx_config_path)
     .arg("--cxxflags")
     .output()
     .expect("unable to execute wx-config");
@@ -376,7 +371,7 @@ fn get_cpp_flags(wx_config_path: &Path) -> Vec<String> {
 #[cfg(not(target_os = "windows"))]
 fn generate_linker_flags(wx_config_path: &Path) {
   use regex::Regex;
-  let config_output = std::process::Command::new(&wx_config_path)
+  let config_output = std::process::Command::new(wx_config_path)
     .arg("--libs")
     .output()
     .expect("unable to execute wx-config libs");
diff --git a/espanso-modulo/src/form/config.rs b/espanso-modulo/src/form/config.rs
index 236137ae..1a61bc59 100644
--- a/espanso-modulo/src/form/config.rs
+++ b/espanso-modulo/src/form/config.rs
@@ -68,51 +68,24 @@ pub enum FieldTypeConfig {
   List(ListFieldConfig),
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
 pub struct TextFieldConfig {
   pub default: String,
   pub multiline: bool,
 }
 
-impl Default for TextFieldConfig {
-  fn default() -> Self {
-    Self {
-      default: "".to_owned(),
-      multiline: false,
-    }
-  }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
 pub struct ChoiceFieldConfig {
   pub values: Vec<String>,
   pub default: String,
 }
 
-impl Default for ChoiceFieldConfig {
-  fn default() -> Self {
-    Self {
-      values: Vec::new(),
-      default: "".to_owned(),
-    }
-  }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
 pub struct ListFieldConfig {
   pub values: Vec<String>,
   pub default: String,
 }
 
-impl Default for ListFieldConfig {
-  fn default() -> Self {
-    Self {
-      values: Vec::new(),
-      default: "".to_owned(),
-    }
-  }
-}
-
 impl<'de> serde::Deserialize<'de> for FieldConfig {
   fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   where
@@ -127,7 +100,7 @@ impl<'a> From<&'a AutoFieldConfig> for FieldConfig {
   fn from(other: &'a AutoFieldConfig) -> Self {
     let field_type = match other.field_type.as_ref() {
       "text" => {
-        let mut config: TextFieldConfig = Default::default();
+        let mut config = TextFieldConfig::default();
 
         if let Some(default) = &other.default {
           config.default = default.clone();
diff --git a/espanso-modulo/src/form/generator.rs b/espanso-modulo/src/form/generator.rs
index fb13e5eb..7003d5cc 100644
--- a/espanso-modulo/src/form/generator.rs
+++ b/espanso-modulo/src/form/generator.rs
@@ -19,7 +19,9 @@
 
 use super::config::{FieldConfig, FieldTypeConfig, FormConfig};
 use super::parser::layout::Token;
-use crate::sys::form::types::*;
+use crate::sys::form::types::{
+  ChoiceMetadata, ChoiceType, Field, FieldType, Form, LabelMetadata, RowMetadata, TextMetadata,
+};
 use std::collections::HashMap;
 
 pub fn generate(config: FormConfig) -> Form {
@@ -37,7 +39,7 @@ fn create_field(token: &Token, field_map: &HashMap<String, FieldConfig>) -> Fiel
       let config = if let Some(config) = field_map.get(name) {
         config.clone()
       } else {
-        Default::default()
+        FieldConfig::default()
       };
 
       let field_type = match &config.field_type {
@@ -69,7 +71,7 @@ fn build_form(form: FormConfig, structure: Vec<Vec<Token>>) -> Form {
   let field_map = form.fields;
   let mut fields = Vec::new();
 
-  for row in structure.iter() {
+  for row in &structure {
     let current_field = if row.len() == 1 {
       // Single field
       create_field(&row[0], &field_map)
@@ -88,7 +90,7 @@ fn build_form(form: FormConfig, structure: Vec<Vec<Token>>) -> Form {
       }
     };
 
-    fields.push(current_field)
+    fields.push(current_field);
   }
 
   Form {
diff --git a/espanso-modulo/src/form/parser/layout.rs b/espanso-modulo/src/form/parser/layout.rs
index 9a706a59..ec6bda99 100644
--- a/espanso-modulo/src/form/parser/layout.rs
+++ b/espanso-modulo/src/form/parser/layout.rs
@@ -51,7 +51,7 @@ pub fn parse_layout(layout: &str) -> Vec<Vec<Token>> {
       match state {
         SplitState::Unmatched(text) => {
           if !text.is_empty() {
-            row.push(Token::Text(text.to_owned()))
+            row.push(Token::Text(text.to_owned()));
           }
         }
         SplitState::Captured(caps) => {
diff --git a/espanso-modulo/src/search/algorithm.rs b/espanso-modulo/src/search/algorithm.rs
index 8c90e6ec..9a7a377c 100644
--- a/espanso-modulo/src/search/algorithm.rs
+++ b/espanso-modulo/src/search/algorithm.rs
@@ -78,7 +78,7 @@ fn case_insensitive_keyword(query: &str, items: &[SearchItem]) -> Vec<usize> {
     .iter()
     .enumerate()
     .filter(|(_, item)| {
-      for keyword in keywords.iter() {
+      for keyword in &keywords {
         if !item.label.to_lowercase().contains(keyword)
           && !item
             .trigger
diff --git a/espanso-modulo/src/sys/form/mod.rs b/espanso-modulo/src/sys/form/mod.rs
index 42f3cb97..86ceefe4 100644
--- a/espanso-modulo/src/sys/form/mod.rs
+++ b/espanso-modulo/src/sys/form/mod.rs
@@ -89,6 +89,8 @@ pub mod types {
 
 #[allow(dead_code)]
 mod interop {
+  use crate::sys;
+
   use super::super::interop::*;
   use super::types;
   use std::ffi::{c_void, CString};
@@ -100,27 +102,30 @@ mod interop {
     icon_path: CString,
     fields: Vec<OwnedField>,
 
-    _metadata: Vec<FieldMetadata>,
-    _interop: Box<FormMetadata>,
+    metadata: Vec<FieldMetadata>,
+    interop: Box<FormMetadata>,
   }
 
   impl Interoperable for OwnedForm {
     fn as_ptr(&self) -> *const c_void {
-      &(*self._interop) as *const FormMetadata as *const c_void
+      &(*self.interop) as *const FormMetadata as *const c_void
     }
   }
 
   impl From<types::Form> for OwnedForm {
     fn from(form: types::Form) -> Self {
       let title = CString::new(form.title).expect("unable to convert form title to CString");
-      let fields: Vec<OwnedField> = form.fields.into_iter().map(|field| field.into()).collect();
+      let fields: Vec<OwnedField> = form.fields.into_iter().map(Into::into).collect();
 
-      let _metadata: Vec<FieldMetadata> = fields.iter().map(|field| field.metadata()).collect();
+      let metadata: Vec<FieldMetadata> = fields
+        .iter()
+        .map(sys::form::interop::OwnedField::metadata)
+        .collect();
 
       let icon_path = if let Some(icon_path) = form.icon.as_ref() {
         icon_path.clone()
       } else {
-        "".to_owned()
+        String::new()
       };
 
       let icon_path = CString::new(icon_path).expect("unable to convert form icon to CString");
@@ -131,10 +136,10 @@ mod interop {
         std::ptr::null()
       };
 
-      let _interop = Box::new(FormMetadata {
+      let interop = Box::new(FormMetadata {
         windowTitle: title.as_ptr(),
         iconPath: icon_path_ptr,
-        fields: _metadata.as_ptr(),
+        fields: metadata.as_ptr(),
         fieldSize: fields.len() as c_int,
       });
 
@@ -142,8 +147,8 @@ mod interop {
         title,
         icon_path,
         fields,
-        _metadata,
-        _interop,
+        metadata,
+        interop,
       }
     }
   }
@@ -215,12 +220,12 @@ mod interop {
 
   struct OwnedLabelMetadata {
     text: CString,
-    _interop: Box<LabelMetadata>,
+    interop: Box<LabelMetadata>,
   }
 
   impl Interoperable for OwnedLabelMetadata {
     fn as_ptr(&self) -> *const c_void {
-      &(*self._interop) as *const LabelMetadata as *const c_void
+      &(*self.interop) as *const LabelMetadata as *const c_void
     }
   }
 
@@ -228,21 +233,21 @@ mod interop {
     fn from(label_metadata: types::LabelMetadata) -> Self {
       let text =
         CString::new(label_metadata.text).expect("unable to convert label text to CString");
-      let _interop = Box::new(LabelMetadata {
+      let interop = Box::new(LabelMetadata {
         text: text.as_ptr(),
       });
-      Self { text, _interop }
+      Self { text, interop }
     }
   }
 
   struct OwnedTextMetadata {
     default_text: CString,
-    _interop: Box<TextMetadata>,
+    interop: Box<TextMetadata>,
   }
 
   impl Interoperable for OwnedTextMetadata {
     fn as_ptr(&self) -> *const c_void {
-      &(*self._interop) as *const TextMetadata as *const c_void
+      &(*self.interop) as *const TextMetadata as *const c_void
     }
   }
 
@@ -250,13 +255,13 @@ mod interop {
     fn from(text_metadata: types::TextMetadata) -> Self {
       let default_text = CString::new(text_metadata.default_text)
         .expect("unable to convert default text to CString");
-      let _interop = Box::new(TextMetadata {
+      let interop = Box::new(TextMetadata {
         defaultText: default_text.as_ptr(),
-        multiline: if text_metadata.multiline { 1 } else { 0 },
+        multiline: i32::from(text_metadata.multiline),
       });
       Self {
         default_text,
-        _interop,
+        interop,
       }
     }
   }
@@ -265,12 +270,12 @@ mod interop {
     values: Vec<CString>,
     values_ptr_array: Vec<*const c_char>,
     default_value: CString,
-    _interop: Box<ChoiceMetadata>,
+    interop: Box<ChoiceMetadata>,
   }
 
   impl Interoperable for OwnedChoiceMetadata {
     fn as_ptr(&self) -> *const c_void {
-      &(*self._interop) as *const ChoiceMetadata as *const c_void
+      &(*self.interop) as *const ChoiceMetadata as *const c_void
     }
   }
 
@@ -293,7 +298,7 @@ mod interop {
       let default_value =
         CString::new(metadata.default_value).expect("unable to convert default value to CString");
 
-      let _interop = Box::new(ChoiceMetadata {
+      let interop = Box::new(ChoiceMetadata {
         values: values_ptr_array.as_ptr(),
         valueSize: values.len() as c_int,
         choiceType: choice_type,
@@ -303,7 +308,7 @@ mod interop {
         values,
         values_ptr_array,
         default_value,
-        _interop,
+        interop,
       }
     }
   }
@@ -311,35 +316,34 @@ mod interop {
   struct OwnedRowMetadata {
     fields: Vec<OwnedField>,
 
-    _metadata: Vec<FieldMetadata>,
-    _interop: Box<RowMetadata>,
+    metadata: Vec<FieldMetadata>,
+    interop: Box<RowMetadata>,
   }
 
   impl Interoperable for OwnedRowMetadata {
     fn as_ptr(&self) -> *const c_void {
-      &(*self._interop) as *const RowMetadata as *const c_void
+      &(*self.interop) as *const RowMetadata as *const c_void
     }
   }
 
   impl From<types::RowMetadata> for OwnedRowMetadata {
     fn from(row_metadata: types::RowMetadata) -> Self {
-      let fields: Vec<OwnedField> = row_metadata
-        .fields
-        .into_iter()
-        .map(|field| field.into())
-        .collect();
+      let fields: Vec<OwnedField> = row_metadata.fields.into_iter().map(Into::into).collect();
 
-      let _metadata: Vec<FieldMetadata> = fields.iter().map(|field| field.metadata()).collect();
+      let metadata: Vec<FieldMetadata> = fields
+        .iter()
+        .map(sys::form::interop::OwnedField::metadata)
+        .collect();
 
-      let _interop = Box::new(RowMetadata {
-        fields: _metadata.as_ptr(),
-        fieldSize: _metadata.len() as c_int,
+      let interop = Box::new(RowMetadata {
+        fields: metadata.as_ptr(),
+        fieldSize: metadata.len() as c_int,
       });
 
       Self {
         fields,
-        _metadata,
-        _interop,
+        metadata,
+        interop,
       }
     }
   }
@@ -358,7 +362,7 @@ pub fn show(form: types::Form) -> HashMap<String, String> {
     let values: &[ValuePair] = unsafe { std::slice::from_raw_parts(values, size as usize) };
     let map = map as *mut HashMap<String, String>;
     let map = unsafe { &mut (*map) };
-    for pair in values.iter() {
+    for pair in values {
       unsafe {
         let id = CStr::from_ptr(pair.id);
         let value = CStr::from_ptr(pair.value);
diff --git a/espanso-modulo/src/sys/search/mod.rs b/espanso-modulo/src/sys/search/mod.rs
index ab0021f0..81b51a0a 100644
--- a/espanso-modulo/src/sys/search/mod.rs
+++ b/espanso-modulo/src/sys/search/mod.rs
@@ -41,6 +41,8 @@ pub mod types {
 
 #[allow(dead_code)]
 mod interop {
+  use crate::sys;
+
   use super::super::interop::*;
   use super::types;
   use std::ffi::{c_void, CString};
@@ -51,12 +53,12 @@ mod interop {
     hint: CString,
     items: Vec<OwnedSearchItem>,
     pub(crate) interop_items: Vec<SearchItem>,
-    _interop: Box<SearchMetadata>,
+    interop: Box<SearchMetadata>,
   }
 
   impl Interoperable for OwnedSearch {
     fn as_ptr(&self) -> *const c_void {
-      &(*self._interop) as *const SearchMetadata as *const c_void
+      &(*self.interop) as *const SearchMetadata as *const c_void
     }
   }
 
@@ -65,14 +67,17 @@ mod interop {
       let title =
         CString::new(search.title.clone()).expect("unable to convert search title to CString");
 
-      let items: Vec<OwnedSearchItem> = search.items.iter().map(|item| item.into()).collect();
+      let items: Vec<OwnedSearchItem> = search.items.iter().map(Into::into).collect();
 
-      let interop_items: Vec<SearchItem> = items.iter().map(|item| item.to_search_item()).collect();
+      let interop_items: Vec<SearchItem> = items
+        .iter()
+        .map(sys::search::interop::OwnedSearchItem::to_search_item)
+        .collect();
 
       let icon_path = if let Some(icon_path) = search.icon.as_ref() {
         icon_path.clone()
       } else {
-        "".to_owned()
+        String::new()
       };
 
       let icon_path = CString::new(icon_path).expect("unable to convert search icon to CString");
@@ -86,7 +91,7 @@ mod interop {
       let hint = if let Some(hint) = search.hint.as_ref() {
         hint.clone()
       } else {
-        "".to_owned()
+        String::new()
       };
 
       let hint = CString::new(hint).expect("unable to convert search icon to CString");
@@ -97,7 +102,7 @@ mod interop {
         std::ptr::null()
       };
 
-      let _interop = Box::new(SearchMetadata {
+      let interop = Box::new(SearchMetadata {
         iconPath: icon_path_ptr,
         windowTitle: title.as_ptr(),
         hintText: hint_ptr,
@@ -109,7 +114,7 @@ mod interop {
         hint,
         items,
         interop_items,
-        _interop,
+        interop,
       }
     }
   }
@@ -139,7 +144,7 @@ mod interop {
       let trigger = if let Some(trigger) = item.trigger.as_deref() {
         CString::new(trigger.to_string()).expect("unable to convert item trigger to CString")
       } else {
-        CString::new("".to_string()).expect("unable to convert item trigger to CString")
+        CString::new(String::new()).expect("unable to convert item trigger to CString")
       };
 
       Self { id, label, trigger }
diff --git a/espanso-modulo/src/sys/troubleshooting/mod.rs b/espanso-modulo/src/sys/troubleshooting/mod.rs
index c303f5d0..4133dc57 100644
--- a/espanso-modulo/src/sys/troubleshooting/mod.rs
+++ b/espanso-modulo/src/sys/troubleshooting/mod.rs
@@ -22,6 +22,7 @@ use std::os::raw::{c_char, c_int};
 use std::path::PathBuf;
 use std::sync::Mutex;
 
+use crate::sys;
 use crate::sys::interop::{ErrorSetMetadata, TroubleshootingMetadata};
 use crate::sys::troubleshooting::interop::OwnedErrorSet;
 use crate::sys::util::convert_to_cstring_or_null;
@@ -34,6 +35,7 @@ lazy_static! {
 
 #[allow(dead_code)]
 mod interop {
+  use crate::sys;
   use crate::troubleshooting::{ErrorRecord, ErrorSet};
 
   use super::interop::{ErrorMetadata, ErrorSetMetadata};
@@ -44,7 +46,7 @@ mod interop {
   pub(crate) struct OwnedErrorSet {
     file_path: Option<CString>,
     errors: Vec<OwnedErrorMetadata>,
-    pub(crate) _interop_errors: Vec<ErrorMetadata>,
+    pub(crate) interop_errors: Vec<ErrorMetadata>,
   }
 
   impl OwnedErrorSet {
@@ -57,8 +59,8 @@ mod interop {
 
       ErrorSetMetadata {
         file_path: file_path_ptr,
-        errors: self._interop_errors.as_ptr(),
-        errors_count: self._interop_errors.len() as c_int,
+        errors: self.interop_errors.as_ptr(),
+        errors_count: self.interop_errors.len() as c_int,
       }
     }
   }
@@ -70,16 +72,17 @@ mod interop {
           .expect("unable to convert file_path to CString")
       });
 
-      let errors: Vec<OwnedErrorMetadata> =
-        error_set.errors.iter().map(|item| item.into()).collect();
+      let errors: Vec<OwnedErrorMetadata> = error_set.errors.iter().map(Into::into).collect();
 
-      let _interop_errors: Vec<ErrorMetadata> =
-        errors.iter().map(|item| item.to_error_metadata()).collect();
+      let interop_errors: Vec<ErrorMetadata> = errors
+        .iter()
+        .map(sys::troubleshooting::interop::OwnedErrorMetadata::to_error_metadata)
+        .collect();
 
       Self {
         file_path,
         errors,
-        _interop_errors,
+        interop_errors,
       }
     }
   }
@@ -118,11 +121,10 @@ pub fn show(options: TroubleshootingOptions) -> Result<()> {
   let (_c_window_icon_path, c_window_icon_path_ptr) =
     convert_to_cstring_or_null(options.window_icon_path);
 
-  let owned_error_sets: Vec<OwnedErrorSet> =
-    options.error_sets.iter().map(|set| set.into()).collect();
+  let owned_error_sets: Vec<OwnedErrorSet> = options.error_sets.iter().map(Into::into).collect();
   let error_sets: Vec<ErrorSetMetadata> = owned_error_sets
     .iter()
-    .map(|set| set.to_error_set_metadata())
+    .map(sys::troubleshooting::interop::OwnedErrorSet::to_error_set_metadata)
     .collect();
 
   extern "C" fn dont_show_again_changed(dont_show: c_int) {
@@ -151,12 +153,12 @@ pub fn show(options: TroubleshootingOptions) -> Result<()> {
 
   {
     let mut lock = HANDLERS.lock().expect("unable to acquire handlers lock");
-    *lock = Some(options.handlers)
+    *lock = Some(options.handlers);
   }
 
   let troubleshooting_metadata = TroubleshootingMetadata {
     window_icon_path: c_window_icon_path_ptr,
-    is_fatal_error: if options.is_fatal_error { 1 } else { 0 },
+    is_fatal_error: i32::from(options.is_fatal_error),
     error_sets: error_sets.as_ptr(),
     error_sets_count: error_sets.len() as c_int,
     dont_show_again_changed,
diff --git a/espanso-modulo/src/sys/welcome/mod.rs b/espanso-modulo/src/sys/welcome/mod.rs
index 95570ba6..7e838ae0 100644
--- a/espanso-modulo/src/sys/welcome/mod.rs
+++ b/espanso-modulo/src/sys/welcome/mod.rs
@@ -49,13 +49,13 @@ pub fn show(options: WelcomeOptions) {
 
   {
     let mut lock = HANDLERS.lock().expect("unable to acquire handlers lock");
-    *lock = Some(options.handlers)
+    *lock = Some(options.handlers);
   }
 
   let welcome_metadata = WelcomeMetadata {
     window_icon_path: c_window_icon_path_ptr,
     tray_image_path: c_tray_image_path_ptr,
-    already_running: if options.is_already_running { 1 } else { 0 },
+    already_running: i32::from(options.is_already_running),
     dont_show_again_changed,
   };
 
diff --git a/espanso-modulo/src/sys/wizard/mod.rs b/espanso-modulo/src/sys/wizard/mod.rs
index d1289927..6c73cca6 100644
--- a/espanso-modulo/src/sys/wizard/mod.rs
+++ b/espanso-modulo/src/sys/wizard/mod.rs
@@ -54,11 +54,7 @@ pub fn show(options: WizardOptions) -> bool {
       .expect("unable to acquire lock in is_legacy_version_running method");
     let handlers_ref = (*lock).as_ref().expect("unable to unwrap handlers");
     if let Some(handler_ref) = handlers_ref.is_legacy_version_running.as_ref() {
-      if (*handler_ref)() {
-        1
-      } else {
-        0
-      }
+      i32::from((*handler_ref)())
     } else {
       -1
     }
@@ -87,11 +83,7 @@ pub fn show(options: WizardOptions) -> bool {
       .expect("unable to acquire lock in auto_start method");
     let handlers_ref = (*lock).as_ref().expect("unable to unwrap handlers");
     if let Some(handler_ref) = handlers_ref.auto_start.as_ref() {
-      if (*handler_ref)(auto_start != 0) {
-        1
-      } else {
-        0
-      }
+      i32::from((*handler_ref)(auto_start != 0))
     } else {
       -1
     }
@@ -103,11 +95,7 @@ pub fn show(options: WizardOptions) -> bool {
       .expect("unable to acquire lock in add_to_path method");
     let handlers_ref = (*lock).as_ref().expect("unable to unwrap handlers");
     if let Some(handler_ref) = handlers_ref.add_to_path.as_ref() {
-      if (*handler_ref)() {
-        1
-      } else {
-        0
-      }
+      i32::from((*handler_ref)())
     } else {
       -1
     }
@@ -132,11 +120,7 @@ pub fn show(options: WizardOptions) -> bool {
       .expect("unable to acquire lock in is_accessibility_enabled method");
     let handlers_ref = (*lock).as_ref().expect("unable to unwrap handlers");
     if let Some(handler_ref) = handlers_ref.is_accessibility_enabled.as_ref() {
-      if (*handler_ref)() {
-        1
-      } else {
-        0
-      }
+      i32::from((*handler_ref)())
     } else {
       -1
     }
@@ -148,58 +132,26 @@ pub fn show(options: WizardOptions) -> bool {
       .expect("unable to acquire lock in on_completed method");
     let handlers_ref = (*lock).as_ref().expect("unable to unwrap handlers");
     if let Some(handler_ref) = handlers_ref.on_completed.as_ref() {
-      (*handler_ref)()
+      (*handler_ref)();
     }
   }
 
   {
     let mut lock = HANDLERS.lock().expect("unable to acquire handlers lock");
-    *lock = Some(options.handlers)
+    *lock = Some(options.handlers);
   }
 
   let wizard_metadata = WizardMetadata {
     version: c_version.as_ptr(),
 
-    is_welcome_page_enabled: if options.is_welcome_page_enabled {
-      1
-    } else {
-      0
-    },
-    is_move_bundle_page_enabled: if options.is_move_bundle_page_enabled {
-      1
-    } else {
-      0
-    },
-    is_legacy_version_page_enabled: if options.is_legacy_version_page_enabled {
-      1
-    } else {
-      0
-    },
-    is_wrong_edition_page_enabled: if options.is_wrong_edition_page_enabled {
-      1
-    } else {
-      0
-    },
-    is_migrate_page_enabled: if options.is_migrate_page_enabled {
-      1
-    } else {
-      0
-    },
-    is_auto_start_page_enabled: if options.is_auto_start_page_enabled {
-      1
-    } else {
-      0
-    },
-    is_add_path_page_enabled: if options.is_add_path_page_enabled {
-      1
-    } else {
-      0
-    },
-    is_accessibility_page_enabled: if options.is_accessibility_page_enabled {
-      1
-    } else {
-      0
-    },
+    is_welcome_page_enabled: i32::from(options.is_welcome_page_enabled),
+    is_move_bundle_page_enabled: i32::from(options.is_move_bundle_page_enabled),
+    is_legacy_version_page_enabled: i32::from(options.is_legacy_version_page_enabled),
+    is_wrong_edition_page_enabled: i32::from(options.is_wrong_edition_page_enabled),
+    is_migrate_page_enabled: i32::from(options.is_migrate_page_enabled),
+    is_auto_start_page_enabled: i32::from(options.is_auto_start_page_enabled),
+    is_add_path_page_enabled: i32::from(options.is_add_path_page_enabled),
+    is_accessibility_page_enabled: i32::from(options.is_accessibility_page_enabled),
 
     window_icon_path: c_window_icon_path_ptr,
     welcome_image_path: c_welcome_image_path_ptr,
diff --git a/espanso-package/Cargo.toml b/espanso-package/Cargo.toml
index 01ca9b67..5bb87da6 100644
--- a/espanso-package/Cargo.toml
+++ b/espanso-package/Cargo.toml
@@ -21,7 +21,7 @@ scopeguard = "1.1.0"
 fs_extra = "1.2.0"
 sha2 = "0.9.6"
 hex = "0.4.3"
-reqwest = { version = "0.11.4", features = ["blocking"], default-features = false}
+reqwest = { version = "0.11.16", features = ["blocking"], default-features = false}
 
 # On Linux we don't want to depend on openssl to avoid dependency issues
 # https://github.com/espanso/espanso/issues/1056
diff --git a/espanso-package/src/archive/default.rs b/espanso-package/src/archive/default.rs
index 56ccfdf4..1630de6a 100644
--- a/espanso-package/src/archive/default.rs
+++ b/espanso-package/src/archive/default.rs
@@ -52,7 +52,7 @@ impl Archiver for DefaultArchiver {
     }
 
     // Backup the previous directory if present
-    let backup_dir = self.package_dir.join(&format!("{}.old", package.name()));
+    let backup_dir = self.package_dir.join(format!("{}.old", package.name()));
     let _backup_guard = if target_dir.is_dir() {
       std::fs::rename(&target_dir, &backup_dir)
         .context("unable to backup old package directory")?;
@@ -186,9 +186,9 @@ matches:
 
     write(
       package_dir.join("README.md"),
-      r#"
+      r"
     A very dummy package
-    "#,
+    ",
     )
     .unwrap();
 
@@ -328,7 +328,7 @@ matches:
       assert!(package_out_dir.is_dir());
 
       let legacy_package = dest_dir.join("z_legacypackage1");
-      create_dir_all(&legacy_package).unwrap();
+      create_dir_all(legacy_package).unwrap();
 
       let package_list = archiver.list().unwrap();
 
diff --git a/espanso-package/src/archive/mod.rs b/espanso-package/src/archive/mod.rs
index aa59cce3..0a268e95 100644
--- a/espanso-package/src/archive/mod.rs
+++ b/espanso-package/src/archive/mod.rs
@@ -93,7 +93,7 @@ impl std::fmt::Display for PackageSource {
         repo_url,
         repo_branch: _,
         use_native_git: _,
-      } => write!(f, "git: {}", repo_url),
+      } => write!(f, "git: {repo_url}"),
     }
   }
 }
diff --git a/espanso-package/src/provider/git.rs b/espanso-package/src/provider/git.rs
index d47896d8..020292d4 100644
--- a/espanso-package/src/provider/git.rs
+++ b/espanso-package/src/provider/git.rs
@@ -60,9 +60,8 @@ impl GitPackageProvider {
     if !output.status.success() {
       let stderr = String::from_utf8_lossy(&output.stderr);
       bail!("git command exited with non-zero status: {}", stderr);
-    } else {
-      Ok(())
     }
+    Ok(())
   }
 }
 
diff --git a/espanso-package/src/provider/hub.rs b/espanso-package/src/provider/hub.rs
index 17e9feed..aa87e735 100644
--- a/espanso-package/src/provider/hub.rs
+++ b/espanso-package/src/provider/hub.rs
@@ -109,12 +109,12 @@ impl EspansoHubPackageProvider {
       }
     }
 
-    let new_index = self.download_index()?;
+    let new_index = EspansoHubPackageProvider::download_index()?;
     self.save_index_to_cache(new_index.clone())?;
     Ok(new_index)
   }
 
-  fn download_index(&self) -> Result<PackageIndex> {
+  fn download_index() -> Result<PackageIndex> {
     info_println!("fetching package index...");
     let json_body = read_string_from_url(ESPANSO_HUB_PACKAGE_INDEX_URL)?;
 
@@ -144,7 +144,7 @@ impl EspansoHubPackageProvider {
       index,
     };
     let serialized = serde_json::to_string(&cached_index)?;
-    std::fs::write(&target_file, serialized)?;
+    std::fs::write(target_file, serialized)?;
     Ok(())
   }
 }
diff --git a/espanso-package/src/resolver.rs b/espanso-package/src/resolver.rs
index 49c4801e..6776cff3 100644
--- a/espanso-package/src/resolver.rs
+++ b/espanso-package/src/resolver.rs
@@ -113,13 +113,13 @@ mod tests {
     run_with_temp_dir(|base_dir| {
       std::fs::write(
         base_dir.join("_manifest.yml"),
-        r#"
+        r"
       name: package1
       title: Package 1
       author: Federico
       version: 0.1.0
       description: An awesome package 
-      "#,
+      ",
       )
       .unwrap();
 
@@ -137,7 +137,7 @@ mod tests {
           },
           base_dir: base_dir.to_path_buf(),
         },]
-      )
+      );
     });
   }
 
@@ -150,13 +150,13 @@ mod tests {
 
       std::fs::write(
         version_dir1.join("_manifest.yml"),
-        r#"
+        r"
       name: package1
       title: Package 1
       author: Federico
       version: 0.1.0
       description: An awesome package 
-      "#,
+      ",
       )
       .unwrap();
 
@@ -166,13 +166,13 @@ mod tests {
 
       std::fs::write(
         version_dir2.join("_manifest.yml"),
-        r#"
+        r"
       name: package1
       title: Package 1
       author: Federico
       version: 0.1.1
       description: An awesome package 
-      "#,
+      ",
       )
       .unwrap();
 
@@ -181,13 +181,13 @@ mod tests {
 
       std::fs::write(
         sub_dir3.join("_manifest.yml"),
-        r#"
+        r"
       name: package2
       title: Package 2
       author: Federico
       version: 2.0.0
       description: Another awesome package 
-      "#,
+      ",
       )
       .unwrap();
 
@@ -227,7 +227,7 @@ mod tests {
             base_dir: sub_dir3,
           },
         ]
-      )
+      );
     });
   }
 
@@ -240,13 +240,13 @@ mod tests {
 
       std::fs::write(
         version_dir1.join("_manifest.yml"),
-        r#"
+        r"
       name: package1
       title: Package 1
       author: Federico
       version: 0.1.0
       description: An awesome package 
-      "#,
+      ",
       )
       .unwrap();
 
@@ -256,13 +256,13 @@ mod tests {
 
       std::fs::write(
         version_dir2.join("_manifest.yml"),
-        r#"
+        r"
       name: package1
       title: Package 1
       author: Federico
       version: 0.1.1
       description: An awesome package 
-      "#,
+      ",
       )
       .unwrap();
 
@@ -271,13 +271,13 @@ mod tests {
 
       std::fs::write(
         sub_dir3.join("_manifest.yml"),
-        r#"
+        r"
       name: package2
       title: Package 2
       author: Federico
       version: 2.0.0
       description: Another awesome package 
-      "#,
+      ",
       )
       .unwrap();
 
@@ -340,11 +340,11 @@ mod tests {
     run_with_temp_dir(|base_dir| {
       std::fs::write(
         base_dir.join("_manifest.yml"),
-        r#"
+        r"
       name: package1
       title: Package 1
       author: Federico
-      "#,
+      ",
       )
       .unwrap();
 
diff --git a/espanso-package/src/util/download.rs b/espanso-package/src/util/download.rs
index b1a3089b..c8474ec6 100644
--- a/espanso-package/src/util/download.rs
+++ b/espanso-package/src/util/download.rs
@@ -87,7 +87,7 @@ fn extract_zip(data: Vec<u8>, dest_dir: &Path) -> Result<()> {
     } else {
       if let Some(p) = outpath.parent() {
         if !p.exists() {
-          std::fs::create_dir_all(&p)?;
+          std::fs::create_dir_all(p)?;
         }
       }
       let mut outfile = std::fs::File::create(&outpath)?;
diff --git a/espanso-path/src/lib.rs b/espanso-path/src/lib.rs
index be9893b4..5f72f647 100644
--- a/espanso-path/src/lib.rs
+++ b/espanso-path/src/lib.rs
@@ -55,10 +55,10 @@ pub fn resolve_paths(
     runtime_dir
   } else {
     // Create the runtime directory if not already present
-    let runtime_dir = if !is_portable_mode() {
-      get_default_runtime_path()
-    } else {
+    let runtime_dir = if is_portable_mode() {
       get_portable_runtime_path().expect("unable to obtain runtime directory path")
+    } else {
+      get_default_runtime_path()
     };
     info!("creating runtime directory in {:?}", runtime_dir);
     create_dir_all(&runtime_dir).expect("unable to create runtime directory");
diff --git a/espanso-render/src/extension/choice.rs b/espanso-render/src/extension/choice.rs
index b043680d..27213264 100644
--- a/espanso-render/src/extension/choice.rs
+++ b/espanso-render/src/extension/choice.rs
@@ -66,10 +66,10 @@ impl<'a> Extension for ChoiceExtension<'a> {
         .lines()
         .filter_map(|line| {
           let trimmed_line = line.trim();
-          if !trimmed_line.is_empty() {
-            Some(trimmed_line)
-          } else {
+          if trimmed_line.is_empty() {
             None
+          } else {
+            Some(trimmed_line)
           }
         })
         .map(|line| Choice {
diff --git a/espanso-render/src/extension/clipboard.rs b/espanso-render/src/extension/clipboard.rs
index cf1e800f..69e322b3 100644
--- a/espanso-render/src/extension/clipboard.rs
+++ b/espanso-render/src/extension/clipboard.rs
@@ -57,6 +57,8 @@ pub enum ClipboardExtensionError {
 
 #[cfg(test)]
 mod tests {
+  use std::collections::HashMap;
+
   use super::*;
 
   struct MockClipboardProvider {
@@ -80,7 +82,11 @@ mod tests {
 
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &Params::new())
+        .calculate(
+          &crate::Context::default(),
+          &HashMap::default(),
+          &Params::new()
+        )
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("test".to_string())
@@ -93,7 +99,11 @@ mod tests {
     let extension = ClipboardExtension::new(&provider);
 
     assert!(matches!(
-      extension.calculate(&Default::default(), &Default::default(), &Params::new()),
+      extension.calculate(
+        &crate::Context::default(),
+        &HashMap::default(),
+        &Params::new()
+      ),
       ExtensionResult::Error(_)
     ));
   }
diff --git a/espanso-render/src/extension/date.rs b/espanso-render/src/extension/date.rs
index 12f74c2e..26a0f0cb 100644
--- a/espanso-render/src/extension/date.rs
+++ b/espanso-render/src/extension/date.rs
@@ -64,8 +64,7 @@ impl<'a> Extension for DateExtension<'a> {
     let locale = params
       .get("locale")
       .and_then(|val| val.as_string())
-      .map(String::from)
-      .unwrap_or_else(|| self.locale_provider.get_system_locale());
+      .map_or_else(|| self.locale_provider.get_system_locale(), String::from);
 
     let date = if let Some(Value::String(format)) = format {
       DateExtension::format_date_with_locale_string(now, format, &locale)
@@ -423,6 +422,8 @@ impl DefaultLocaleProvider {
 
 #[cfg(test)]
 mod tests {
+  use std::collections::HashMap;
+
   use super::*;
   use chrono::offset::TimeZone;
 
@@ -457,7 +458,7 @@ mod tests {
       .collect::<Params>();
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("09:10:11".to_string())
@@ -478,7 +479,7 @@ mod tests {
     .collect::<Params>();
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("10:10:11".to_string())
@@ -496,7 +497,7 @@ mod tests {
       .collect::<Params>();
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("martedì".to_string())
@@ -514,7 +515,7 @@ mod tests {
       .collect::<Params>();
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("Tuesday".to_string())
@@ -535,7 +536,7 @@ mod tests {
     .collect::<Params>();
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("martedì".to_string())
diff --git a/espanso-render/src/extension/echo.rs b/espanso-render/src/extension/echo.rs
index 95361c9f..a81ea5f4 100644
--- a/espanso-render/src/extension/echo.rs
+++ b/espanso-render/src/extension/echo.rs
@@ -66,6 +66,8 @@ pub enum EchoExtensionError {
 
 #[cfg(test)]
 mod tests {
+  use std::collections::HashMap;
+
   use super::*;
 
   #[test]
@@ -77,7 +79,7 @@ mod tests {
       .collect::<Params>();
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("test".to_string())
@@ -90,7 +92,7 @@ mod tests {
 
     let param = Params::new();
     assert!(matches!(
-      extension.calculate(&Default::default(), &Default::default(), &param),
+      extension.calculate(&crate::Context::default(), &HashMap::default(), &param),
       ExtensionResult::Error(_)
     ));
   }
diff --git a/espanso-render/src/extension/exec_util.rs b/espanso-render/src/extension/exec_util.rs
index e602e720..4a120962 100644
--- a/espanso-render/src/extension/exec_util.rs
+++ b/espanso-render/src/extension/exec_util.rs
@@ -50,7 +50,7 @@ pub fn determine_default_macos_shell() -> Option<MacShell> {
   use std::process::Command;
 
   let output = Command::new("sh")
-    .args(&["--login", "-c", "dscl . -read ~/ UserShell"])
+    .args(["--login", "-c", "dscl . -read ~/ UserShell"])
     .output()
     .ok()?;
 
diff --git a/espanso-render/src/extension/form.rs b/espanso-render/src/extension/form.rs
index 623b4825..47d99502 100644
--- a/espanso-render/src/extension/form.rs
+++ b/espanso-render/src/extension/form.rs
@@ -59,9 +59,7 @@ impl<'a> Extension for FormExtension<'a> {
     _: &crate::Scope,
     params: &Params,
   ) -> crate::ExtensionResult {
-    let layout = if let Some(Value::String(layout)) = params.get("layout") {
-      layout
-    } else {
+    let Some(Value::String(layout)) = params.get("layout") else {
       return crate::ExtensionResult::Error(FormExtensionError::MissingLayout.into());
     };
 
diff --git a/espanso-render/src/extension/random.rs b/espanso-render/src/extension/random.rs
index bc954ae7..42b5e5eb 100644
--- a/espanso-render/src/extension/random.rs
+++ b/espanso-render/src/extension/random.rs
@@ -70,6 +70,8 @@ pub enum RandomExtensionError {
 
 #[cfg(test)]
 mod tests {
+  use std::collections::HashMap;
+
   use super::*;
 
   #[test]
@@ -88,7 +90,7 @@ mod tests {
     .collect::<Params>();
     assert!(matches!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single(result) if ["first", "second", "third"].contains(&result.as_str())
@@ -101,7 +103,7 @@ mod tests {
 
     let param = Params::new();
     assert!(matches!(
-      extension.calculate(&Default::default(), &Default::default(), &param),
+      extension.calculate(&crate::Context::default(), &HashMap::default(), &param),
       ExtensionResult::Error(_)
     ));
   }
diff --git a/espanso-render/src/extension/script.rs b/espanso-render/src/extension/script.rs
index 663f737e..4ab982eb 100644
--- a/espanso-render/src/extension/script.rs
+++ b/espanso-render/src/extension/script.rs
@@ -64,7 +64,7 @@ impl Extension for ScriptExtension {
       // Replace %HOME% with current user home directory to
       // create cross-platform paths. See issue #265
       // Also replace %CONFIG% and %PACKAGES% path. See issue #380
-      args.iter_mut().for_each(|arg| {
+      for arg in &mut args {
         if arg.contains("%HOME%") {
           *arg = arg.replace("%HOME%", &self.home_path.to_string_lossy());
         }
@@ -79,10 +79,10 @@ impl Extension for ScriptExtension {
         if cfg!(target_os = "windows") {
           let path = PathBuf::from(&arg);
           if path.exists() {
-            *arg = path.to_string_lossy().to_string()
+            *arg = path.to_string_lossy().to_string();
           }
         }
-      });
+      }
 
       let mut command = Command::new(&args[0]);
       command.env("CONFIG", self.config_path.to_string_lossy().to_string());
@@ -175,6 +175,8 @@ pub enum ScriptExtensionError {
 
 #[cfg(test)]
 mod tests {
+  use std::collections::HashMap;
+
   use super::*;
   #[cfg(not(target_os = "windows"))]
   use crate::Scope;
@@ -279,7 +281,7 @@ mod tests {
     .into_iter()
     .collect::<Params>();
     assert!(matches!(
-      extension.calculate(&Default::default(), &Default::default(), &param),
+      extension.calculate(&crate::Context::default(), &HashMap::default(), &param),
       ExtensionResult::Error(_)
     ));
   }
@@ -328,7 +330,7 @@ mod tests {
         .calculate(&Default::default(), &Default::default(), &param)
         .into_success()
         .unwrap(),
-      ExtensionOutput::Single("".to_string())
+      ExtensionOutput::Single(String::new())
     );
   }
 }
diff --git a/espanso-render/src/extension/shell.rs b/espanso-render/src/extension/shell.rs
index a060b7f6..7c41e3b1 100644
--- a/espanso-render/src/extension/shell.rs
+++ b/espanso-render/src/extension/shell.rs
@@ -50,39 +50,39 @@ impl Shell {
     let mut command = match self {
       Shell::Cmd => {
         let mut command = Command::new("cmd");
-        command.args(&["/C", cmd]);
+        command.args(["/C", cmd]);
         command
       }
       Shell::Powershell => {
         let mut command = Command::new("powershell");
-        command.args(&["-Command", cmd]);
+        command.args(["-Command", cmd]);
         command
       }
       Shell::WSL => {
         is_wsl = true;
         let mut command = Command::new("bash");
-        command.args(&["-c", cmd]);
+        command.args(["-c", cmd]);
         command
       }
       Shell::WSL2 => {
         is_wsl = true;
         let mut command = Command::new("wsl");
-        command.args(&["bash", "-c", cmd]);
+        command.args(["bash", "-c", cmd]);
         command
       }
       Shell::Bash => {
         let mut command = Command::new("bash");
-        command.args(&["-c", cmd]);
+        command.args(["-c", cmd]);
         command
       }
       Shell::Sh => {
         let mut command = Command::new("sh");
-        command.args(&["-c", cmd]);
+        command.args(["-c", cmd]);
         command
       }
       Shell::Zsh => {
         let mut command = Command::new("zsh");
-        command.args(&["-c", cmd]);
+        command.args(["-c", cmd]);
         command
       }
     };
@@ -91,7 +91,7 @@ impl Shell {
     super::util::set_command_flags(&mut command);
 
     // Inject all the previous variables
-    for (key, value) in vars.iter() {
+    for (key, value) in vars {
       command.env(key, value);
     }
 
@@ -123,7 +123,7 @@ impl Shell {
       let mut tokens: Vec<&str> = vec!["CONFIG/p"];
 
       // Add all the previous variables
-      for (key, _) in vars.iter() {
+      for key in vars.keys() {
         tokens.push(key);
       }
 
@@ -311,7 +311,7 @@ mod tests {
     if cfg!(target_os = "windows") {
       assert_eq!(
         extension
-          .calculate(&Default::default(), &Default::default(), &param)
+          .calculate(&crate::Context::default(), &HashMap::default(), &param)
           .into_success()
           .unwrap(),
         ExtensionOutput::Single("hello world\r\n".to_string())
@@ -319,7 +319,7 @@ mod tests {
     } else {
       assert_eq!(
         extension
-          .calculate(&Default::default(), &Default::default(), &param)
+          .calculate(&crate::Context::default(), &HashMap::default(), &param)
           .into_success()
           .unwrap(),
         ExtensionOutput::Single("hello world\n".to_string())
@@ -340,7 +340,7 @@ mod tests {
 
     assert_eq!(
       extension
-        .calculate(&Default::default(), &Default::default(), &param)
+        .calculate(&crate::Context::default(), &HashMap::default(), &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("hello world".to_string())
@@ -393,7 +393,7 @@ mod tests {
     scope.insert("var1", ExtensionOutput::Single("hello world".to_string()));
     assert_eq!(
       extension
-        .calculate(&Default::default(), &scope, &param)
+        .calculate(&crate::Context::default(), &scope, &param)
         .into_success()
         .unwrap(),
       ExtensionOutput::Single("hello world".to_string())
@@ -411,7 +411,7 @@ mod tests {
     .into_iter()
     .collect::<Params>();
     assert!(matches!(
-      extension.calculate(&Default::default(), &Default::default(), &param),
+      extension.calculate(&crate::Context::default(), &HashMap::default(), &param),
       ExtensionResult::Error(_)
     ));
   }
diff --git a/espanso-render/src/extension/util.rs b/espanso-render/src/extension/util.rs
index 89eb24b5..b98c3463 100644
--- a/espanso-render/src/extension/util.rs
+++ b/espanso-render/src/extension/util.rs
@@ -23,14 +23,14 @@ use std::{collections::HashMap, process::Command};
 pub fn convert_to_env_variables(scope: &Scope) -> HashMap<String, String> {
   let mut output = HashMap::new();
 
-  for (key, result) in scope.iter() {
+  for (key, result) in scope {
     match result {
       ExtensionOutput::Single(value) => {
         let name = format!("ESPANSO_{}", key.to_uppercase());
         output.insert(name, value.clone());
       }
       ExtensionOutput::Multiple(values) => {
-        for (sub_key, sub_value) in values.iter() {
+        for (sub_key, sub_value) in values {
           let name = format!("ESPANSO_{}_{}", key.to_uppercase(), sub_key.to_uppercase());
           output.insert(name, sub_value.clone());
         }
@@ -46,7 +46,7 @@ pub fn set_command_flags(command: &mut Command) {
   use std::os::windows::process::CommandExt;
   // Avoid showing the shell window
   // See: https://github.com/federico-terzi/espanso/issues/249
-  command.creation_flags(0x08000000);
+  command.creation_flags(0x0800_0000);
 }
 
 #[cfg(not(target_os = "windows"))]
diff --git a/espanso-render/src/lib.rs b/espanso-render/src/lib.rs
index 8d42cb2b..da444b8f 100644
--- a/espanso-render/src/lib.rs
+++ b/espanso-render/src/lib.rs
@@ -89,8 +89,8 @@ pub struct Variable {
 impl Default for Variable {
   fn default() -> Self {
     Self {
-      name: "".to_string(),
-      var_type: "".to_string(),
+      name: String::new(),
+      var_type: String::new(),
       inject_vars: true,
       params: Params::new(),
       depends_on: Vec::new(),
diff --git a/espanso-render/src/renderer/mod.rs b/espanso-render/src/renderer/mod.rs
index 92b38e6a..fd79bce5 100644
--- a/espanso-render/src/renderer/mod.rs
+++ b/espanso-render/src/renderer/mod.rs
@@ -197,7 +197,7 @@ impl<'a> Renderer for DefaultRenderer<'a> {
               let capitalized_word: String = v.into_iter().collect();
               capitalized_word
             } else {
-              "".to_string()
+              String::new()
             }
           })
           .to_string()
@@ -337,8 +337,8 @@ mod tests {
     let renderer = get_renderer();
     let res = renderer.render(
       &template_for_str("plain body"),
-      &Default::default(),
-      &Default::default(),
+      &Context::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "plain body"));
   }
@@ -348,7 +348,7 @@ mod tests {
     let renderer = get_renderer();
     let res = renderer.render(
       &template_for_str("plain body"),
-      &Default::default(),
+      &Context::default(),
       &RenderOptions {
         casing_style: CasingStyle::Capitalize,
       },
@@ -361,7 +361,7 @@ mod tests {
     let renderer = get_renderer();
     let res = renderer.render(
       &template_for_str("ordinary least squares, with other.punctuation !Marks"),
-      &Default::default(),
+      &Context::default(),
       &RenderOptions {
         casing_style: CasingStyle::CapitalizeWords,
       },
@@ -376,7 +376,7 @@ mod tests {
     let renderer = get_renderer();
     let res = renderer.render(
       &template_for_str("plain body"),
-      &Default::default(),
+      &Context::default(),
       &RenderOptions {
         casing_style: CasingStyle::Uppercase,
       },
@@ -388,7 +388,7 @@ mod tests {
   fn basic_variable() {
     let renderer = get_renderer();
     let template = template("hello {{var}}", &[("var", "world")]);
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Success(str) if str == "hello world"));
   }
 
@@ -410,7 +410,7 @@ mod tests {
       }],
       ..Default::default()
     };
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Success(str) if str == "hello dict"));
   }
 
@@ -418,7 +418,7 @@ mod tests {
   fn missing_variable() {
     let renderer = get_renderer();
     let template = template_for_str("hello {{var}}");
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Error(_)));
   }
 
@@ -440,7 +440,7 @@ mod tests {
         }],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "hello world"));
   }
@@ -465,7 +465,7 @@ mod tests {
         }],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "hello dict"));
   }
@@ -506,7 +506,7 @@ mod tests {
         }],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "hello Bob Bob"));
   }
@@ -540,7 +540,7 @@ mod tests {
         ],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "hello world"));
   }
@@ -583,7 +583,7 @@ mod tests {
         ],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Error(_)));
   }
@@ -615,7 +615,7 @@ mod tests {
         ],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Aborted));
   }
@@ -647,7 +647,7 @@ mod tests {
         }],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Aborted));
   }
@@ -678,7 +678,7 @@ mod tests {
         templates: vec![&nested_template],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "hello world"));
   }
@@ -703,7 +703,7 @@ mod tests {
       &Context {
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Error(_)));
   }
@@ -723,7 +723,7 @@ mod tests {
       }],
       ..Default::default()
     };
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Aborted));
   }
 
@@ -742,7 +742,7 @@ mod tests {
       }],
       ..Default::default()
     };
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Error(_)));
   }
 
@@ -781,7 +781,7 @@ mod tests {
       },
     ];
 
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Success(str) if str == "hello John Snow"));
   }
 
@@ -808,7 +808,7 @@ mod tests {
       },
     ];
 
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Success(str) if str == "hello {{first}} two"));
   }
 
@@ -834,7 +834,7 @@ mod tests {
       },
     ];
 
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Success(str) if str == "hello {{first}} two"));
   }
 
@@ -852,7 +852,7 @@ mod tests {
       ..Default::default()
     }];
 
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Error(_)));
   }
 
@@ -891,7 +891,7 @@ mod tests {
         }],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "hello global"));
   }
@@ -935,7 +935,7 @@ mod tests {
         }],
         ..Default::default()
       },
-      &Default::default(),
+      &RenderOptions::default(),
     );
     assert!(matches!(res, RenderResult::Success(str) if str == "hello local"));
   }
@@ -944,7 +944,7 @@ mod tests {
   fn variable_escape() {
     let renderer = get_renderer();
     let template = template("hello \\{\\{var\\}\\}", &[("var", "world")]);
-    let res = renderer.render(&template, &Default::default(), &Default::default());
+    let res = renderer.render(&template, &Context::default(), &RenderOptions::default());
     assert!(matches!(res, RenderResult::Success(str) if str == "hello {{var}}"));
   }
 }
diff --git a/espanso-render/src/renderer/resolve.rs b/espanso-render/src/renderer/resolve.rs
index c1534080..8e421f74 100644
--- a/espanso-render/src/renderer/resolve.rs
+++ b/espanso-render/src/renderer/resolve.rs
@@ -56,7 +56,7 @@ pub(crate) fn resolve_evaluation_order<'a>(
   let eval_order_ref = eval_order.borrow();
 
   let mut ordered_variables = Vec::new();
-  for var_name in (*eval_order_ref).iter() {
+  for var_name in &(*eval_order_ref) {
     let node = node_map
       .get(var_name)
       .ok_or_else(|| anyhow!("could not find dependency node for variable: {}", var_name))?;
@@ -79,7 +79,7 @@ fn generate_nodes<'a>(
     if var.inject_vars {
       dependencies.extend(super::util::get_params_variable_names(&var.params));
     }
-    dependencies.extend(var.depends_on.iter().map(|v| v.as_str()));
+    dependencies.extend(var.depends_on.iter().map(String::as_str));
 
     // Every local variable depends on the one before it.
     // Needed to guarantee execution order within local vars.
@@ -97,7 +97,7 @@ fn generate_nodes<'a>(
     });
   }
 
-  let global_vars_nodes = global_vars.iter().map(|var| create_node_from_var(*var));
+  let global_vars_nodes = global_vars.iter().map(|var| create_node_from_var(var));
 
   // The body depends on all local variables + the variables read inside it (which might be global)
   let mut body_dependencies: HashSet<&str> =
@@ -116,9 +116,9 @@ fn generate_nodes<'a>(
   global_vars_nodes.into_iter().for_each(|node| {
     node_map.insert(node.name, node);
   });
-  local_vars_nodes.into_iter().for_each(|node| {
+  for node in local_vars_nodes {
     node_map.insert(node.name, node);
-  });
+  }
 
   node_map
 }
@@ -128,10 +128,10 @@ fn create_node_from_var(var: &Variable) -> Node {
     let mut vars = HashSet::new();
 
     if var.inject_vars {
-      vars.extend(super::util::get_params_variable_names(&var.params))
+      vars.extend(super::util::get_params_variable_names(&var.params));
     }
 
-    vars.extend(var.depends_on.iter().map(|s| s.as_str()));
+    vars.extend(var.depends_on.iter().map(String::as_str));
 
     Some(vars)
   } else {
@@ -158,7 +158,7 @@ fn resolve_dependencies<'a>(
   }
 
   if let Some(dependencies) = &node.dependencies {
-    for dependency in dependencies.iter() {
+    for dependency in dependencies {
       let has_been_resolved = {
         let resolved_ref = resolved.borrow();
         resolved_ref.contains(dependency)
@@ -171,23 +171,21 @@ fn resolve_dependencies<'a>(
       if !has_been_resolved {
         if has_been_seen {
           return Err(
-            RendererError::CircularDependency(node.name.to_string(), dependency.to_string()).into(),
+            RendererError::CircularDependency(node.name.to_string(), (*dependency).to_string())
+              .into(),
           );
         }
 
-        match node_map.get(dependency) {
-          Some(dependency_node) => {
-            resolve_dependencies(dependency_node, node_map, eval_order, resolved, seen)?
-          }
-          None => {
-            error!("could not resolve variable {:?}", dependency);
-            if let Some(variable) = &node.variable {
-              if variable.var_type == "form" {
-                super::log_new_form_syntax_tip();
-              }
+        if let Some(dependency_node) = node_map.get(dependency) {
+          resolve_dependencies(dependency_node, node_map, eval_order, resolved, seen)?;
+        } else {
+          error!("could not resolve variable {:?}", dependency);
+          if let Some(variable) = &node.variable {
+            if variable.var_type == "form" {
+              super::log_new_form_syntax_tip();
             }
-            return Err(RendererError::MissingVariable(dependency.to_string()).into());
           }
+          return Err(RendererError::MissingVariable((*dependency).to_string()).into());
         }
       }
     }
diff --git a/espanso-render/src/renderer/util.rs b/espanso-render/src/renderer/util.rs
index 3152c4ae..952cd1ad 100644
--- a/espanso-render/src/renderer/util.rs
+++ b/espanso-render/src/renderer/util.rs
@@ -37,7 +37,7 @@ pub(crate) fn get_body_variable_names(body: &str) -> HashSet<&str> {
 pub(crate) fn get_params_variable_names(params: &Params) -> HashSet<&str> {
   let mut names = HashSet::new();
 
-  for (_, value) in params.iter() {
+  for value in params.values() {
     let local_names = get_value_variable_names_recursively(value);
     names.extend(local_names);
   }
@@ -72,34 +72,27 @@ pub(crate) fn render_variables(body: &str, scope: &Scope) -> Result<String> {
     .replace_all(body, |caps: &Captures| {
       let var_name = caps.name("name").unwrap().as_str();
       let var_subname = caps.name("subname");
-      match scope.get(var_name) {
-        Some(output) => match output {
+      if let Some(output) = scope.get(var_name) {
+        match output {
           ExtensionOutput::Single(output) => output,
-          ExtensionOutput::Multiple(results) => match var_subname {
-            Some(var_subname) => {
+          ExtensionOutput::Multiple(results) => {
+            if let Some(var_subname) = var_subname {
               let var_subname = var_subname.as_str();
               results.get(var_subname).map_or("", |value| value)
-            }
-            None => {
-              error!(
-                "nested name missing from multi-value variable: {}",
-                var_name
-              );
+            } else {
+              error!("nested name missing from multi-value variable: {var_name}");
               replacing_error = Some(RendererError::MissingVariable(format!(
-                "nested name missing from multi-value variable: {}",
-                var_name
+                "nested name missing from multi-value variable: {var_name}"
               )));
               ""
             }
-          },
-        },
-        None => {
-          replacing_error = Some(RendererError::MissingVariable(format!(
-            "variable '{}' is missing",
-            var_name
-          )));
-          ""
+          }
         }
+      } else {
+        replacing_error = Some(RendererError::MissingVariable(format!(
+          "variable '{var_name}' is missing"
+        )));
+        ""
       }
     })
     .to_string();
@@ -119,7 +112,7 @@ pub(crate) fn unescape_variable_inections(body: &str) -> String {
 pub(crate) fn inject_variables_into_params(params: &Params, scope: &Scope) -> Result<Params> {
   let mut params = params.clone();
 
-  for (_, value) in params.iter_mut() {
+  for value in &mut params.values_mut() {
     inject_variables_into_value(value, scope)?;
   }
 
diff --git a/espanso-ui/Cargo.toml b/espanso-ui/Cargo.toml
index e1d5b3d0..b128c2ff 100644
--- a/espanso-ui/Cargo.toml
+++ b/espanso-ui/Cargo.toml
@@ -20,7 +20,7 @@ thiserror = "1.0.23"
 [target.'cfg(windows)'.dependencies]
 widestring = "0.4.3"
 lazycell = "1.3.0"
-winrt-notification = "0.3.1"
+winrt-notification = "0.5.1"
 lazy_static = "1.4.0"
 
 [target.'cfg(target_os="macos")'.dependencies]
diff --git a/espanso-ui/src/mac/mod.rs b/espanso-ui/src/mac/mod.rs
index b6c85692..2e28d869 100644
--- a/espanso-ui/src/mac/mod.rs
+++ b/espanso-ui/src/mac/mod.rs
@@ -121,7 +121,7 @@ impl UIEventLoop for MacEventLoop {
     }
 
     let options = RawUIOptions {
-      show_icon: if self.show_icon { 1 } else { 0 },
+      show_icon: i32::from(self.show_icon),
       icon_paths,
       icon_paths_count: self.icons.len() as i32,
     };
diff --git a/espanso-ui/src/win32/mod.rs b/espanso-ui/src/win32/mod.rs
index 41fb2bd0..7b1bbf96 100644
--- a/espanso-ui/src/win32/mod.rs
+++ b/espanso-ui/src/win32/mod.rs
@@ -91,9 +91,7 @@ pub fn create(options: Win32UIOptions) -> Result<(Win32Remote, Win32EventLoop)>
   let handle: Arc<AtomicPtr<c_void>> = Arc::new(AtomicPtr::new(std::ptr::null_mut()));
 
   // Validate icons
-  if options.icon_paths.len() > MAX_ICON_COUNT {
-    panic!("Win32 UI received too many icon paths, please increase the MAX_ICON_COUNT constant to support more");
-  }
+  assert!(options.icon_paths.len() <= MAX_ICON_COUNT, "Win32 UI received too many icon paths, please increase the MAX_ICON_COUNT constant to support more");
 
   // Convert the icon paths to the internal representation
   let mut icon_indexes: HashMap<TrayIcon, usize> = HashMap::new();
@@ -120,8 +118,8 @@ pub struct Win32EventLoop {
   icons: Vec<String>,
 
   // Internal
-  _event_callback: LazyCell<UIEventCallback>,
-  _init_thread_id: LazyCell<ThreadId>,
+  event_callback: LazyCell<UIEventCallback>,
+  init_thread_id: LazyCell<ThreadId>,
 }
 
 impl Win32EventLoop {
@@ -130,8 +128,8 @@ impl Win32EventLoop {
       handle,
       icons,
       show_icon,
-      _event_callback: LazyCell::new(),
-      _init_thread_id: LazyCell::new(),
+      event_callback: LazyCell::new(),
+      init_thread_id: LazyCell::new(),
     }
   }
 }
@@ -154,7 +152,7 @@ impl UIEventLoop for Win32EventLoop {
     }
 
     let options = RawUIOptions {
-      show_icon: if self.show_icon { 1 } else { 0 },
+      show_icon: i32::from(self.show_icon),
       icon_paths,
       icon_paths_count: self.icons.len() as i32,
     };
@@ -189,7 +187,7 @@ impl UIEventLoop for Win32EventLoop {
 
     // Make sure the run() method is called in the same thread as initialize()
     self
-      ._init_thread_id
+      .init_thread_id
       .fill(std::thread::current().id())
       .expect("Unable to set initialization thread id");
 
@@ -198,10 +196,11 @@ impl UIEventLoop for Win32EventLoop {
 
   fn run(&self, event_callback: UIEventCallback) -> Result<()> {
     // Make sure the run() method is called in the same thread as initialize()
-    if let Some(init_id) = self._init_thread_id.borrow() {
-      if init_id != &std::thread::current().id() {
-        panic!("Win32EventLoop run() and initialize() methods should be called in the same thread");
-      }
+    if let Some(init_id) = self.init_thread_id.borrow() {
+      assert!(
+        !(init_id != &std::thread::current().id()),
+        "Win32EventLoop run() and initialize() methods should be called in the same thread"
+      );
     }
 
     let window_handle = self.handle.load(Ordering::Acquire);
@@ -210,16 +209,16 @@ impl UIEventLoop for Win32EventLoop {
       return Err(Win32UIError::InvalidHandle().into());
     }
 
-    if self._event_callback.fill(event_callback).is_err() {
+    if self.event_callback.fill(event_callback).is_err() {
       error!("Unable to set Win32EventLoop callback");
       return Err(Win32UIError::InternalError().into());
     }
 
     extern "C" fn callback(_self: *mut Win32EventLoop, event: RawUIEvent) {
-      if let Some(callback) = unsafe { (*_self)._event_callback.borrow() } {
+      if let Some(callback) = unsafe { (*_self).event_callback.borrow() } {
         let event: Option<UIEvent> = event.into();
         if let Some(event) = event {
-          callback(event)
+          callback(event);
         } else {
           trace!("Unable to convert raw event to input event");
         }
diff --git a/espanso-ui/src/win32/notification.rs b/espanso-ui/src/win32/notification.rs
index 40d2cf37..7eac310e 100644
--- a/espanso-ui/src/win32/notification.rs
+++ b/espanso-ui/src/win32/notification.rs
@@ -90,8 +90,8 @@ pub fn show_notification(msg: &str) -> Result<()> {
 
 fn is_espanso_app_user_model_id_set() -> bool {
   match Command::new("powershell")
-    .args(&["-c", "get-startapps"])
-    .creation_flags(0x08000000)
+    .args(["-c", "get-startapps"])
+    .creation_flags(0x0800_0000)
     .output()
   {
     Ok(output) => {
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index dbf5278f..5efb1e42 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "espanso"
-version = "2.1.8"
+version = "2.2.0"
 authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
 license = "GPL-3.0"
 description = "Cross-platform Text Expander written in Rust"
@@ -27,20 +27,20 @@ wayland = ["espanso-detect/wayland", "espanso-inject/wayland", "espanso-clipboar
 modulo = ["espanso-modulo", "espanso-clipboard/avoid-gdi", "espanso-ui/avoid-gdi"]
 
 [dependencies]
-espanso-detect = { path = "../espanso-detect" } 
-espanso-ui = { path = "../espanso-ui" } 
-espanso-inject = { path = "../espanso-inject" } 
-espanso-config = { path = "../espanso-config" } 
-espanso-match = { path = "../espanso-match" } 
-espanso-clipboard = { path = "../espanso-clipboard" } 
-espanso-info = { path = "../espanso-info" } 
-espanso-render = { path = "../espanso-render" } 
-espanso-path = { path = "../espanso-path" } 
-espanso-ipc = { path = "../espanso-ipc" } 
+espanso-detect = { path = "../espanso-detect" }
+espanso-ui = { path = "../espanso-ui" }
+espanso-inject = { path = "../espanso-inject" }
+espanso-config = { path = "../espanso-config" }
+espanso-match = { path = "../espanso-match" }
+espanso-clipboard = { path = "../espanso-clipboard" }
+espanso-info = { path = "../espanso-info" }
+espanso-render = { path = "../espanso-render" }
+espanso-path = { path = "../espanso-path" }
+espanso-ipc = { path = "../espanso-ipc" }
 espanso-modulo = { path = "../espanso-modulo", optional = true }
-espanso-migrate = { path = "../espanso-migrate" } 
-espanso-kvs = { path = "../espanso-kvs" } 
-espanso-engine = { path = "../espanso-engine" } 
+espanso-migrate = { path = "../espanso-migrate" }
+espanso-kvs = { path = "../espanso-kvs" }
+espanso-engine = { path = "../espanso-engine" }
 espanso-package = { path = "../espanso-package"}
 maplit = "1.0.2"
 simplelog = "0.9.0"
@@ -63,7 +63,7 @@ colored = "2.0.0"
 tempdir = "0.3.7"
 notify = "4.0.17"
 opener = "0.5.0"
-sysinfo = "0.24.5"
+sysinfo = "0.28.4"
 
 [target.'cfg(windows)'.dependencies]
 named_pipe = "0.4.1"
@@ -75,7 +75,7 @@ widestring = "0.4.3"
 libc = "0.2.98"
 
 [target.'cfg(target_os="macos")'.dependencies]
-espanso-mac-utils = { path = "../espanso-mac-utils" } 
+espanso-mac-utils = { path = "../espanso-mac-utils" }
 
 [target.'cfg(target_os="linux")'.dependencies]
 caps = "0.5.2"
@@ -84,12 +84,12 @@ regex = "1.5.5"
 
 [package.metadata.deb]
 maintainer = "Federico Terzi <federicoterzi96@gmail.com>"
-depends = "$auto, systemd, libxtst6, xclip, libnotify-bin, libxkbcommon0, libwxgtk3.0-gtk3-0v5"
+depends = "libc6 (>= 2.27), libdbus-1-3 (>= 1.9.14), libgcc1 (>= 1:4.2), libnotify-bin, libstdc++6 (>=6), libwxgtk3.0-gtk3-0v5 (>= 3.0.4+dfsg) | libwxgtk3.2-1, libx11-6, libxcb1, libxkbcommon0 (>= 0.5.0), libxtst6, systemd, xclip"
 section = "utility"
 license-file = ["../LICENSE", "1"]
 
 [package.metadata.deb.variants.wayland]
-depends = "$auto, systemd, libnotify-bin, libxkbcommon0, libwxgtk3.0-gtk3-0v5, wl-clipboard"
-# TODO: once this issue [1] is fixed, we should create a variant for 
+depends = "libc6 (>= 2.27), libdbus-1-3 (>= 1.9.14), libgcc1 (>= 1:4.2), libnotify-bin, libstdc++6 (>= 5), libwxgtk3.0-gtk3-0v5 (>= 3.0.4+dfsg) | libwxgtk3.2-1, libxkbcommon0 (>= 0.5.0), systemd, wl-clipboard"
+# TODO: once this issue [1] is fixed, we should create a variant for
 # wayland to automatically run the setcap script.
 # [1]: https://github.com/mmstick/cargo-deb/issues/151
diff --git a/espanso/src/cli/cmd.rs b/espanso/src/cli/cmd.rs
index 1dcd4626..01707d1c 100644
--- a/espanso/src/cli/cmd.rs
+++ b/espanso/src/cli/cmd.rs
@@ -55,7 +55,7 @@ fn cmd_main(args: CliModuleArgs) -> i32 {
   };
 
   if let Err(error) = send_event_to_worker(&paths.runtime, event) {
-    eprintln!("unable to send command, error: {:?}", error);
+    eprintln!("unable to send command, error: {error:?}");
     return 2;
   }
 
diff --git a/espanso/src/cli/daemon/keyboard_layout_watcher.rs b/espanso/src/cli/daemon/keyboard_layout_watcher.rs
index f5bb0348..592fd283 100644
--- a/espanso/src/cli/daemon/keyboard_layout_watcher.rs
+++ b/espanso/src/cli/daemon/keyboard_layout_watcher.rs
@@ -42,7 +42,7 @@ fn watcher_main(watcher_notify: &Sender<()>) {
   let mut layout = espanso_detect::get_active_layout();
 
   if layout.is_none() {
-    warn!("keyboard layout watcher couldn't determine active layout.")
+    warn!("keyboard layout watcher couldn't determine active layout.");
   }
 
   loop {
diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs
index 206ad738..9a24a285 100644
--- a/espanso/src/cli/daemon/mod.rs
+++ b/espanso/src/cli/daemon/mod.rs
@@ -89,6 +89,9 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
   // When a guard is dropped, the troubleshooting GUI is killed
   // so this ensures that there is only one troubleshooter running
   // at a given time.
+  //
+  // it is currently unused
+  // #[allow(unused_variables)]
   let mut _current_troubleshoot_guard = None;
 
   let (watcher_notify, watcher_signal) = unbounded::<()>();
@@ -96,14 +99,14 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
   watcher::initialize_and_spawn(&paths.config, watcher_notify)
     .expect("unable to initialize config watcher thread");
 
-  let (_keyboard_layout_watcher_notify, keyboard_layout_watcher_signal) = unbounded::<()>();
+  let (keyboard_layout_watcher_notify, keyboard_layout_watcher_signal) = unbounded::<()>();
 
   #[allow(clippy::redundant_clone)]
   // IMPORTANT: Here we clone the channel instead of simply passing it to avoid
   // dropping the channel immediately on those platforms that don't support the
   // layout watcher (currently Windows and macOS).
   // Otherwise, the select below would always return an error because the channel is closed.
-  keyboard_layout_watcher::initialize_and_spawn(_keyboard_layout_watcher_notify.clone()) // DON'T REMOVE THE CLONE!
+  keyboard_layout_watcher::initialize_and_spawn(keyboard_layout_watcher_notify.clone()) // DON'T REMOVE THE CLONE!
     .expect("unable to initialize keyboard layout watcher thread");
 
   let config_store =
@@ -248,7 +251,7 @@ fn spawn_worker(
   let espanso_exe_path =
     std::env::current_exe().expect("unable to obtain espanso executable location");
 
-  let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
+  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
 
   let mut args = vec!["worker", "--monitor-daemon"];
   if let Some(start_reason) = &start_reason {
@@ -316,9 +319,9 @@ fn restart_worker(
     std::thread::sleep(std::time::Duration::from_millis(100));
   }
 
-  if !has_timed_out {
-    spawn_worker(paths_overrides, exit_notify, start_reason);
-  } else {
+  if has_timed_out {
     error!("could not restart worker, as the exit process has timed out");
+  } else {
+    spawn_worker(paths_overrides, exit_notify, start_reason);
   }
 }
diff --git a/espanso/src/cli/daemon/troubleshoot.rs b/espanso/src/cli/daemon/troubleshoot.rs
index d33a4e30..e103cc52 100644
--- a/espanso/src/cli/daemon/troubleshoot.rs
+++ b/espanso/src/cli/daemon/troubleshoot.rs
@@ -34,8 +34,8 @@ use crate::preferences::Preferences;
 
 pub fn launch_troubleshoot(paths_overrides: &PathsOverrides) -> Result<TroubleshootGuard> {
   let espanso_exe_path = std::env::current_exe()?;
-  let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
-  command.args(&["modulo", "troubleshoot"]);
+  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
+  command.args(["modulo", "troubleshoot"]);
   command.with_paths_overrides(paths_overrides);
 
   let child = command.spawn()?;
diff --git a/espanso/src/cli/daemon/watcher.rs b/espanso/src/cli/daemon/watcher.rs
index ce3f29c8..01f41c84 100644
--- a/espanso/src/cli/daemon/watcher.rs
+++ b/espanso/src/cli/daemon/watcher.rs
@@ -48,7 +48,7 @@ pub fn initialize_and_spawn(config_dir: &Path, watcher_notify: Sender<()>) -> Re
   Ok(())
 }
 
-fn watcher_main(config_dir: &Path, debounce_tx: crossbeam::channel::Sender<()>) {
+fn watcher_main(config_dir: &Path, debounce_tx: Sender<()>) {
   let (tx, rx) = std::sync::mpsc::channel();
 
   let mut watcher: RecommendedWatcher =
@@ -56,7 +56,7 @@ fn watcher_main(config_dir: &Path, debounce_tx: crossbeam::channel::Sender<()>)
       .expect("unable to create file watcher");
 
   watcher
-    .watch(&config_dir, RecursiveMode::Recursive)
+    .watch(config_dir, RecursiveMode::Recursive)
     .expect("unable to start file watcher");
 
   info!("watching for changes in path: {:?}", config_dir);
diff --git a/espanso/src/cli/edit.rs b/espanso/src/cli/edit.rs
index 3050508e..a065071f 100644
--- a/espanso/src/cli/edit.rs
+++ b/espanso/src/cli/edit.rs
@@ -48,12 +48,11 @@ fn edit_main(args: CliModuleArgs) -> i32 {
   let paths = args.paths.expect("missing paths argument");
   let cli_args = args.cli_args.expect("missing cli_args");
 
-  if !paths.config.is_dir() {
-    panic!(
-      "config directory does not exist in path: {:?}",
-      paths.config
-    );
-  }
+  assert!(
+    paths.config.is_dir(),
+    "config directory does not exist in path: {:?}",
+    paths.config
+  );
 
   // Determine which is the file to edit
   let target_file = cli_args.value_of("target_file");
@@ -156,9 +155,9 @@ fn determine_target_path(config_path: &Path, target_file: Option<&str>) -> PathB
       custom => {
         if !custom.ends_with(".yml") && !custom.ends_with(".yaml") {
           if espanso_config::is_legacy_config(config_path) {
-            config_path.join("user").join(format!("{}.yml", custom))
+            config_path.join("user").join(format!("{custom}.yml"))
           } else {
-            config_path.join("match").join(format!("{}.yml", custom))
+            config_path.join("match").join(format!("{custom}.yml"))
           }
         } else {
           config_path.join(custom)
diff --git a/espanso/src/cli/env_path.rs b/espanso/src/cli/env_path.rs
index ea18299c..6417d640 100644
--- a/espanso/src/cli/env_path.rs
+++ b/espanso/src/cli/env_path.rs
@@ -41,16 +41,14 @@ fn env_path_main(args: CliModuleArgs) -> i32 {
   if cli_args.subcommand_matches("register").is_some() {
     if let Err(error) = crate::path::add_espanso_to_path(elevated_priviledge_prompt) {
       error_print_and_log(&format!(
-        "Unable to add 'espanso' command to PATH: {:?}",
-        error
+        "Unable to add 'espanso' command to PATH: {error:?}"
       ));
       return ADD_TO_PATH_FAILURE;
     }
   } else if cli_args.subcommand_matches("unregister").is_some() {
     if let Err(error) = crate::path::remove_espanso_from_path(elevated_priviledge_prompt) {
       error_print_and_log(&format!(
-        "Unable to remove 'espanso' command from PATH: {:?}",
-        error
+        "Unable to remove 'espanso' command from PATH: {error:?}"
       ));
       return ADD_TO_PATH_FAILURE;
     }
@@ -63,6 +61,6 @@ fn env_path_main(args: CliModuleArgs) -> i32 {
 }
 
 fn error_print_and_log(msg: &str) {
-  error!("{}", msg);
-  eprintln!("{}", msg);
+  error!("{msg}");
+  eprintln!("{msg}");
 }
diff --git a/espanso/src/cli/launcher/daemon.rs b/espanso/src/cli/launcher/daemon.rs
index 98a6674f..70551c09 100644
--- a/espanso/src/cli/launcher/daemon.rs
+++ b/espanso/src/cli/launcher/daemon.rs
@@ -28,8 +28,8 @@ use crate::cli::PathsOverrides;
 
 pub fn launch_daemon(paths_overrides: &PathsOverrides) -> Result<()> {
   let espanso_exe_path = std::env::current_exe()?;
-  let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
-  command.args(&["daemon"]);
+  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
+  command.args(["daemon"]);
   command.with_paths_overrides(paths_overrides);
 
   let result = spawn_and_wait(command)?;
diff --git a/espanso/src/cli/launcher/edition_check.rs b/espanso/src/cli/launcher/edition_check.rs
index 1638c524..f604c104 100644
--- a/espanso/src/cli/launcher/edition_check.rs
+++ b/espanso/src/cli/launcher/edition_check.rs
@@ -54,6 +54,6 @@ fn get_session_type() -> Option<String> {
     return None;
   }
 
-  let session_type: Option<&str> = raw_session_type.split('=').into_iter().last();
+  let session_type: Option<&str> = raw_session_type.split('=').last();
   session_type.map(String::from)
 }
diff --git a/espanso/src/cli/launcher/mod.rs b/espanso/src/cli/launcher/mod.rs
index 4f40f1e1..f6da7944 100644
--- a/espanso/src/cli/launcher/mod.rs
+++ b/espanso/src/cli/launcher/mod.rs
@@ -86,7 +86,7 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
   let paths_clone = paths.clone();
   let backup_and_migrate_handler =
     Box::new(move || match util::migrate_configuration(&paths_clone) {
-      Ok(_) => MigrationResult::Success,
+      Ok(()) => MigrationResult::Success,
       Err(error) => match error.downcast_ref::<MigrationError>() {
         Some(MigrationError::Dirty) => MigrationResult::DirtyFailure,
         Some(MigrationError::Clean) => MigrationResult::CleanFailure,
@@ -102,9 +102,9 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
 
     if auto_start {
       match util::configure_auto_start(true) {
-        Ok(_) => true,
+        Ok(()) => true,
         Err(error) => {
-          eprintln!("Service register returned error: {}", error);
+          eprintln!("Service register returned error: {error}");
           false
         }
       }
@@ -126,9 +126,9 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
       false
     };
   let add_to_path_handler = Box::new(move || match util::add_espanso_to_path() {
-    Ok(_) => true,
+    Ok(()) => true,
     Err(error) => {
-      eprintln!("Add to path returned error: {}", error);
+      eprintln!("Add to path returned error: {error}");
       false
     }
   });
diff --git a/espanso/src/cli/launcher/util.rs b/espanso/src/cli/launcher/util.rs
index e4fd3b07..204b7b9e 100644
--- a/espanso/src/cli/launcher/util.rs
+++ b/espanso/src/cli/launcher/util.rs
@@ -36,8 +36,8 @@ pub fn is_legacy_version_running(runtime_path: &Path) -> bool {
 
 pub fn migrate_configuration(paths: &Paths) -> Result<()> {
   let espanso_exe_path = std::env::current_exe()?;
-  let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
-  command.args(&["migrate", "--noconfirm"]);
+  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
+  command.args(["migrate", "--noconfirm"]);
   command.env(
     "ESPANSO_CONFIG_DIR",
     paths.config.to_string_lossy().to_string(),
@@ -79,8 +79,8 @@ pub enum MigrationError {
 
 pub fn add_espanso_to_path() -> Result<()> {
   let espanso_exe_path = std::env::current_exe()?;
-  let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
-  command.args(&["env-path", "--prompt", "register"]);
+  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
+  command.args(["env-path", "--prompt", "register"]);
 
   let mut child = command.spawn()?;
   let result = child.wait()?;
@@ -100,8 +100,8 @@ pub enum AddToPathError {
 
 pub fn show_already_running_warning() -> Result<()> {
   let espanso_exe_path = std::env::current_exe()?;
-  let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
-  command.args(&["modulo", "welcome", "--already-running"]);
+  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
+  command.args(["modulo", "welcome", "--already-running"]);
 
   let mut child = command.spawn()?;
   child.wait()?;
@@ -110,7 +110,7 @@ pub fn show_already_running_warning() -> Result<()> {
 
 pub fn configure_auto_start(auto_start: bool) -> Result<()> {
   let espanso_exe_path = std::env::current_exe()?;
-  let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
+  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
   let mut args = vec!["service"];
   if auto_start {
     args.push("register");
diff --git a/espanso/src/cli/log.rs b/espanso/src/cli/log.rs
index 93b6bcee..6cca6e81 100644
--- a/espanso/src/cli/log.rs
+++ b/espanso/src/cli/log.rs
@@ -44,7 +44,7 @@ fn log_main(args: CliModuleArgs) -> i32 {
   if let Ok(log_file) = log_file {
     let reader = BufReader::new(log_file);
     for line in reader.lines().flatten() {
-      println!("{}", line);
+      println!("{line}");
     }
   } else {
     eprintln!("Error reading log file");
diff --git a/espanso/src/cli/match_cli/exec.rs b/espanso/src/cli/match_cli/exec.rs
index c2d859ed..dffeb9c8 100644
--- a/espanso/src/cli/match_cli/exec.rs
+++ b/espanso/src/cli/match_cli/exec.rs
@@ -33,7 +33,7 @@ pub fn exec_main(cli_args: &ArgMatches, paths: &Paths) -> Result<()> {
   let trigger = cli_args.value_of("trigger");
   let args = cli_args.values_of("arg");
 
-  if trigger.is_none() || trigger.map(str::is_empty).unwrap_or(false) {
+  if trigger.is_none() || trigger.is_some_and(str::is_empty) {
     bail!("You need to specify the --trigger 'trigger' option. Run `espanso match exec --help` for more information.");
   }
 
@@ -50,12 +50,9 @@ pub fn exec_main(cli_args: &ArgMatches, paths: &Paths) -> Result<()> {
       if let Some((key, value)) = tokens {
         match_args.insert(key.to_string(), value.to_string());
       } else {
-        eprintln!(
-          "invalid format for argument '{}', you should follow the 'name=value' format",
-          arg
-        );
+        eprintln!("invalid format for argument '{arg}', you should follow the 'name=value' format");
       }
-    })
+    });
   }
 
   client
diff --git a/espanso/src/cli/match_cli/list.rs b/espanso/src/cli/match_cli/list.rs
index 53614de7..9b1f9735 100644
--- a/espanso/src/cli/match_cli/list.rs
+++ b/espanso/src/cli/match_cli/list.rs
@@ -43,13 +43,17 @@ pub fn list_main(
   if cli_args.is_present("json") {
     print_matches_as_json(&match_set.matches)?;
   } else {
-    print_matches_as_plain(&match_set.matches, only_triggers, preserve_newlines)
+    print_matches_as_plain(&match_set.matches, only_triggers, preserve_newlines)?;
   }
 
   Ok(())
 }
 
-pub fn print_matches_as_plain(match_list: &[&Match], only_triggers: bool, preserve_newlines: bool) {
+pub fn print_matches_as_plain(
+  match_list: &[&Match],
+  only_triggers: bool,
+  preserve_newlines: bool,
+) -> Result<()> {
   for m in match_list {
     let triggers = match &m.cause {
       MatchCause::None => vec!["(none)".to_string()],
@@ -59,24 +63,37 @@ pub fn print_matches_as_plain(match_list: &[&Match], only_triggers: bool, preser
 
     for trigger in triggers {
       if only_triggers {
-        println!("{}", trigger);
+        println!("{trigger}");
       } else {
         let description = m.description();
-
-        if preserve_newlines {
-          println!("{} - {}", trigger, description)
+        if let Some(label) = &m.label {
+          if preserve_newlines {
+            println!("{trigger} - {description} - {label}");
+          } else {
+            println!(
+              "{} - {} - {}",
+              trigger,
+              description.replace('\n', " "),
+              label
+            )
+          }
+        } else if preserve_newlines {
+          println!("{trigger} - {description}");
         } else {
           println!("{} - {}", trigger, description.replace('\n', " "))
         }
       }
     }
   }
+
+  Ok(())
 }
 
 #[derive(Debug, Serialize)]
 struct JsonMatchEntry {
   triggers: Vec<String>,
   replace: String,
+  label: Option<String>,
 }
 
 pub fn print_matches_as_json(match_list: &[&Match]) -> Result<()> {
@@ -91,12 +108,13 @@ pub fn print_matches_as_json(match_list: &[&Match]) -> Result<()> {
     entries.push(JsonMatchEntry {
       triggers,
       replace: m.description().to_string(),
+      label: m.label.clone(),
     })
   }
 
   let json = serde_json::to_string_pretty(&entries)?;
 
-  println!("{}", json);
+  println!("{json}");
 
   Ok(())
 }
diff --git a/espanso/src/cli/match_cli/mod.rs b/espanso/src/cli/match_cli/mod.rs
index 781bc320..f343b125 100644
--- a/espanso/src/cli/match_cli/mod.rs
+++ b/espanso/src/cli/match_cli/mod.rs
@@ -40,12 +40,12 @@ fn match_main(args: CliModuleArgs) -> i32 {
 
   if let Some(sub_args) = cli_args.subcommand_matches("list") {
     if let Err(err) = list::list_main(sub_args, config_store, match_store) {
-      eprintln!("unable to list matches: {:?}", err);
+      eprintln!("unable to list matches: {err:?}");
       return 1;
     }
   } else if let Some(sub_args) = cli_args.subcommand_matches("exec") {
     if let Err(err) = exec::exec_main(sub_args, &paths) {
-      eprintln!("unable to exec match: {:?}", err);
+      eprintln!("unable to exec match: {err:?}");
       return 1;
     }
   } else {
diff --git a/espanso/src/cli/migrate.rs b/espanso/src/cli/migrate.rs
index a5a5cc69..e109c386 100644
--- a/espanso/src/cli/migrate.rs
+++ b/espanso/src/cli/migrate.rs
@@ -172,13 +172,12 @@ fn migrate_main(args: CliModuleArgs) -> i32 {
 fn find_available_backup_dir() -> PathBuf {
   for i in 1..20 {
     let num = if i > 1 {
-      format!("-{}", i)
+      format!("-{i}")
     } else {
-      "".to_string()
+      String::new()
     };
 
-    let target_backup_dir =
-      find_backup_dir_location().join(format!("espanso-migrate-backup{}", num));
+    let target_backup_dir = find_backup_dir_location().join(format!("espanso-migrate-backup{num}"));
 
     if !target_backup_dir.is_dir() {
       return target_backup_dir;
@@ -205,6 +204,6 @@ fn find_backup_dir_location() -> PathBuf {
 }
 
 fn error_print_and_log(msg: &str) {
-  error!("{}", msg);
-  eprintln!("{}", msg);
+  error!("{msg}");
+  eprintln!("{msg}");
 }
diff --git a/espanso/src/cli/mod.rs b/espanso/src/cli/mod.rs
index 7e3639ee..9643a52f 100644
--- a/espanso/src/cli/mod.rs
+++ b/espanso/src/cli/mod.rs
@@ -59,7 +59,7 @@ impl Default for CliModule {
       disable_logs_terminal_output: false,
       requires_paths: false,
       requires_config: false,
-      subcommand: "".to_string(),
+      subcommand: String::new(),
       show_in_dock: false,
       requires_linux_capabilities: false,
       entry: |_| 0,
diff --git a/espanso/src/cli/modulo/form.rs b/espanso/src/cli/modulo/form.rs
index 73e6a4ff..8c1db773 100644
--- a/espanso/src/cli/modulo/form.rs
+++ b/espanso/src/cli/modulo/form.rs
@@ -38,10 +38,10 @@ pub fn form_main(matches: &ArgMatches, _icon_paths: &IconPaths) -> i32 {
     std::fs::read_to_string(input_file).expect("unable to read input file")
   };
 
-  let mut config: config::FormConfig = if !as_json {
-    serde_yaml::from_str(&data).expect("unable to parse form configuration")
-  } else {
+  let mut config: config::FormConfig = if as_json {
     serde_json::from_str(&data).expect("unable to parse form configuration")
+  } else {
+    serde_yaml::from_str(&data).expect("unable to parse form configuration")
   };
 
   // Overwrite the icon
@@ -54,7 +54,7 @@ pub fn form_main(matches: &ArgMatches, _icon_paths: &IconPaths) -> i32 {
   let values = show(form);
 
   let output = serde_json::to_string(&values).expect("unable to encode values as JSON");
-  println!("{}", output);
+  println!("{output}");
 
   0
 }
diff --git a/espanso/src/cli/modulo/search.rs b/espanso/src/cli/modulo/search.rs
index a8d7f519..952e8efc 100644
--- a/espanso/src/cli/modulo/search.rs
+++ b/espanso/src/cli/modulo/search.rs
@@ -39,10 +39,10 @@ pub fn search_main(matches: &ArgMatches, icon_paths: &IconPaths) -> i32 {
     std::fs::read_to_string(input_file).expect("unable to read input file")
   };
 
-  let mut config: config::SearchConfig = if !as_json {
-    serde_yaml::from_str(&data).expect("unable to parse search configuration")
-  } else {
+  let mut config: config::SearchConfig = if as_json {
     serde_json::from_str(&data).expect("unable to parse search configuration")
+  } else {
+    serde_yaml::from_str(&data).expect("unable to parse search configuration")
   };
 
   // Overwrite the icon
@@ -59,7 +59,7 @@ pub fn search_main(matches: &ArgMatches, icon_paths: &IconPaths) -> i32 {
   result_map.insert("selected", result);
 
   let output = serde_json::to_string(&result_map).expect("unable to encode values as JSON");
-  println!("{}", output);
+  println!("{output}");
 
   0
 }
diff --git a/espanso/src/cli/modulo/troubleshoot.rs b/espanso/src/cli/modulo/troubleshoot.rs
index 5a6f2626..570450a8 100644
--- a/espanso/src/cli/modulo/troubleshoot.rs
+++ b/espanso/src/cli/modulo/troubleshoot.rs
@@ -34,7 +34,7 @@ pub fn troubleshoot_main(paths: &Paths, icon_paths: &IconPaths) -> i32 {
 
   let open_file_handler = Box::new(move |file_path: &Path| {
     if let Err(err) = opener::open(file_path) {
-      eprintln!("error opening file: {}", err);
+      eprintln!("error opening file: {err}");
     }
   });
 
@@ -67,7 +67,7 @@ pub fn troubleshoot_main(paths: &Paths, icon_paths: &IconPaths) -> i32 {
         (false, error_sets)
       }
       Err(err) => {
-        let message = format!("{:?}", err);
+        let message = format!("{err:?}");
         let file_path = if message.contains("default.yml") {
           let default_file_path = paths.config.join("config").join("default.yml");
           Some(default_file_path)
@@ -81,7 +81,7 @@ pub fn troubleshoot_main(paths: &Paths, icon_paths: &IconPaths) -> i32 {
             file: file_path,
             errors: vec![espanso_modulo::troubleshooting::ErrorRecord {
               level: espanso_modulo::troubleshooting::ErrorLevel::Error,
-              message: format!("{:?}", err),
+              message: format!("{err:?}"),
             }],
           }],
         )
diff --git a/espanso/src/cli/service/linux.rs b/espanso/src/cli/service/linux.rs
index 3f464da6..8f7ab21e 100644
--- a/espanso/src/cli/service/linux.rs
+++ b/espanso/src/cli/service/linux.rs
@@ -50,7 +50,7 @@ pub fn register() -> Result<()> {
   info_println!("enabling systemd service");
 
   match Command::new("systemctl")
-    .args(&["--user", "enable", LINUX_SERVICE_NAME])
+    .args(["--user", "enable", LINUX_SERVICE_NAME])
     .status()
   {
     Ok(status) => {
@@ -86,7 +86,7 @@ pub fn unregister() -> Result<()> {
   info_println!("disabling espanso systemd service");
 
   match Command::new("systemctl")
-    .args(&["--user", "disable", LINUX_SERVICE_NAME])
+    .args(["--user", "disable", LINUX_SERVICE_NAME])
     .status()
   {
     Ok(status) => {
@@ -120,7 +120,7 @@ pub enum UnregisterError {
 
 pub fn is_registered() -> bool {
   let res = Command::new("systemctl")
-    .args(&["--user", "is-enabled", LINUX_SERVICE_NAME])
+    .args(["--user", "is-enabled", LINUX_SERVICE_NAME])
     .output();
   if let Ok(output) = res {
     if !output.status.success() {
@@ -133,7 +133,7 @@ pub fn is_registered() -> bool {
     }
 
     match Command::new("systemctl")
-      .args(&["--user", "cat", LINUX_SERVICE_NAME])
+      .args(["--user", "cat", LINUX_SERVICE_NAME])
       .output()
     {
       Ok(cmd_output) => {
@@ -177,7 +177,7 @@ pub fn is_registered() -> bool {
 pub fn start_service() -> Result<()> {
   // Check if systemd is available in the system
   match Command::new("systemctl")
-    .args(&["--version"])
+    .args(["--version"])
     .stdin(Stdio::null())
     .stdout(Stdio::null())
     .status()
@@ -220,7 +220,7 @@ pub fn start_service() -> Result<()> {
   }
 
   match Command::new("systemctl")
-    .args(&["--user", "start", LINUX_SERVICE_NAME])
+    .args(["--user", "start", LINUX_SERVICE_NAME])
     .status()
   {
     Ok(status) => {
diff --git a/espanso/src/cli/service/macos.rs b/espanso/src/cli/service/macos.rs
index 5529c154..2941da9d 100644
--- a/espanso/src/cli/service/macos.rs
+++ b/espanso/src/cli/service/macos.rs
@@ -72,7 +72,7 @@ pub fn register() -> Result<()> {
     // Copy the user PATH variable and inject it in the Plist file so that
     // it gets loaded by Launchd.
     // To see why this is necessary: https://github.com/federico-terzi/espanso/issues/233
-    let user_path = std::env::var("PATH").unwrap_or_else(|_| "".to_owned());
+    let user_path = std::env::var("PATH").unwrap_or_else(|_| String::new());
     let plist_content = plist_content.replace("{{{PATH}}}", &user_path);
 
     std::fs::write(plist_file.clone(), plist_content).expect("Unable to write plist file");
@@ -81,14 +81,14 @@ pub fn register() -> Result<()> {
   info!("reloading espanso launchctl entry");
 
   if let Err(err) = Command::new("launchctl")
-    .args(&["unload", "-w", plist_file.to_str().unwrap_or_default()])
+    .args(["unload", "-w", plist_file.to_str().unwrap_or_default()])
     .output()
   {
     warn!("unload command failed: {}", err);
   }
 
   let res = Command::new("launchctl")
-    .args(&["load", "-w", plist_file.to_str().unwrap_or_default()])
+    .args(["load", "-w", plist_file.to_str().unwrap_or_default()])
     .status();
 
   if let Ok(status) = res {
@@ -116,7 +116,7 @@ pub fn unregister() -> Result<()> {
   let plist_file = agents_dir.join(SERVICE_PLIST_FILE_NAME);
   if plist_file.exists() {
     let _res = Command::new("launchctl")
-      .args(&["unload", "-w", plist_file.to_str().unwrap_or_default()])
+      .args(["unload", "-w", plist_file.to_str().unwrap_or_default()])
       .output();
 
     std::fs::remove_file(&plist_file)?;
@@ -154,7 +154,7 @@ pub fn start_service() -> Result<()> {
   }
 
   let res = Command::new("launchctl")
-    .args(&["start", "com.federicoterzi.espanso"])
+    .args(["start", "com.federicoterzi.espanso"])
     .status();
 
   if let Ok(status) = res {
diff --git a/espanso/src/cli/service/mod.rs b/espanso/src/cli/service/mod.rs
index fe6a9214..0c607cf4 100644
--- a/espanso/src/cli/service/mod.rs
+++ b/espanso/src/cli/service/mod.rs
@@ -76,16 +76,14 @@ fn service_main(args: CliModuleArgs) -> i32 {
     if let Err(err) = register() {
       error_eprintln!("unable to register service: {}", err);
       return SERVICE_FAILURE;
-    } else {
-      info_println!("service registered correctly!");
     }
+    info_println!("service registered correctly!");
   } else if cli_args.subcommand_matches("unregister").is_some() {
     if let Err(err) = unregister() {
       error_eprintln!("unable to unregister service: {}", err);
       return SERVICE_FAILURE;
-    } else {
-      info_println!("service unregistered correctly!");
     }
+    info_println!("service unregistered correctly!");
   } else if cli_args.subcommand_matches("check").is_some() {
     if is_registered() {
       info_println!("registered as a service");
diff --git a/espanso/src/cli/service/stop.rs b/espanso/src/cli/service/stop.rs
index 9024416c..92f9be33 100644
--- a/espanso/src/cli/service/stop.rs
+++ b/espanso/src/cli/service/stop.rs
@@ -111,10 +111,10 @@ fn forcefully_terminate_espanso() {
 
       if cfg!(target_os = "windows") {
         let _ = Command::new("taskkill")
-          .args(&["/pid", &str_pid, "/f"])
+          .args(["/pid", &str_pid, "/f"])
           .output();
       } else {
-        let _ = Command::new("kill").args(&["-9", &str_pid]).output();
+        let _ = Command::new("kill").args(["-9", &str_pid]).output();
       }
     }
   }
diff --git a/espanso/src/cli/service/unix.rs b/espanso/src/cli/service/unix.rs
index 327001a9..66a08dab 100644
--- a/espanso/src/cli/service/unix.rs
+++ b/espanso/src/cli/service/unix.rs
@@ -58,8 +58,8 @@ pub fn fork_daemon(paths_overrides: &PathsOverrides) -> Result<()> {
 
 pub fn spawn_launcher(paths_overrides: &PathsOverrides) -> Result<()> {
   let espanso_exe_path = std::env::current_exe()?;
-  let mut command = std::process::Command::new(&espanso_exe_path.to_string_lossy().to_string());
-  command.args(&["launcher"]);
+  let mut command = std::process::Command::new(espanso_exe_path.to_string_lossy().to_string());
+  command.args(["launcher"]);
   command.with_paths_overrides(paths_overrides);
 
   let mut child = command.spawn()?;
diff --git a/espanso/src/cli/service/win.rs b/espanso/src/cli/service/win.rs
index f41b3832..a49c513d 100644
--- a/espanso/src/cli/service/win.rs
+++ b/espanso/src/cli/service/win.rs
@@ -96,8 +96,8 @@ pub fn start_service() -> Result<()> {
   let current_path = std::env::current_exe().expect("unable to get exec path");
 
   Command::new(current_path)
-    .args(&["launcher"])
-    .creation_flags(0x08000008) // CREATE_NO_WINDOW + DETACHED_PROCESS
+    .args(["launcher"])
+    .creation_flags(0x0800_0008) // CREATE_NO_WINDOW + DETACHED_PROCESS
     .spawn()?;
 
   Ok(())
diff --git a/espanso/src/cli/worker/engine/caches/app_info_provider.rs b/espanso/src/cli/worker/engine/caches/app_info_provider.rs
index a0184ff3..7dab84f1 100644
--- a/espanso/src/cli/worker/engine/caches/app_info_provider.rs
+++ b/espanso/src/cli/worker/engine/caches/app_info_provider.rs
@@ -28,7 +28,7 @@ pub struct CachedAppInfoProvider<'a> {
   app_info_provider: &'a dyn AppInfoProvider,
   caching_interval: Duration,
 
-  _cached_info: RefCell<Option<(Instant, AppInfo)>>,
+  cached_info: RefCell<Option<(Instant, AppInfo)>>,
 }
 
 impl<'a> CachedAppInfoProvider<'a> {
@@ -36,14 +36,14 @@ impl<'a> CachedAppInfoProvider<'a> {
     Self {
       app_info_provider,
       caching_interval,
-      _cached_info: RefCell::new(None),
+      cached_info: RefCell::new(None),
     }
   }
 }
 
 impl<'a> AppInfoProvider for CachedAppInfoProvider<'a> {
   fn get_info(&self) -> espanso_info::AppInfo {
-    let mut cached_info = self._cached_info.borrow_mut();
+    let mut cached_info = self.cached_info.borrow_mut();
     if let Some((instant, cached_value)) = &*cached_info {
       if instant.elapsed() < self.caching_interval {
         // Return cached config
diff --git a/espanso/src/cli/worker/engine/dispatch/executor/event_injector.rs b/espanso/src/cli/worker/engine/dispatch/executor/event_injector.rs
index 5741b30e..e495e3d6 100644
--- a/espanso/src/cli/worker/engine/dispatch/executor/event_injector.rs
+++ b/espanso/src/cli/worker/engine/dispatch/executor/event_injector.rs
@@ -76,7 +76,7 @@ impl<'a> TextInjector for EventInjectorAdapter<'a> {
       if i > 0 {
         self
           .injector
-          .send_keys(&[espanso_inject::keys::Key::Enter], injection_options)?
+          .send_keys(&[espanso_inject::keys::Key::Enter], injection_options)?;
       }
 
       self.injector.send_string(line, injection_options)?;
diff --git a/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs b/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs
index 4a1f911e..5fb7ba8a 100644
--- a/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs
+++ b/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs
@@ -40,7 +40,7 @@ impl SecureInputManager for SecureInputManagerAdapter {
   fn launch_secure_input_autofix(&self) -> anyhow::Result<()> {
     let espanso_path = std::env::current_exe()?;
     let child = std::process::Command::new(espanso_path)
-      .args(&["workaround", "secure-input"])
+      .args(["workaround", "secure-input"])
       .stdout(Stdio::piped())
       .spawn()
       .context("unable to spawn workaround process")?;
diff --git a/espanso/src/cli/worker/engine/funnel/key_state.rs b/espanso/src/cli/worker/engine/funnel/key_state.rs
index 8b50a977..ad296e22 100644
--- a/espanso/src/cli/worker/engine/funnel/key_state.rs
+++ b/espanso/src/cli/worker/engine/funnel/key_state.rs
@@ -110,7 +110,7 @@ impl KeyStatus {
   }
 
   fn release(&mut self) {
-    self.pressed_at = None
+    self.pressed_at = None;
   }
 
   fn press(&mut self) {
diff --git a/espanso/src/cli/worker/engine/funnel/modifier.rs b/espanso/src/cli/worker/engine/funnel/modifier.rs
index 5be0ff26..5e895632 100644
--- a/espanso/src/cli/worker/engine/funnel/modifier.rs
+++ b/espanso/src/cli/worker/engine/funnel/modifier.rs
@@ -136,7 +136,7 @@ impl ModifierStatus {
   }
 
   fn release(&mut self) {
-    self.pressed_at = None
+    self.pressed_at = None;
   }
 
   fn press(&mut self) {
@@ -178,7 +178,7 @@ impl espanso_engine::process::ModifierStateProvider for ModifierStateStore {
           Modifier::Meta => {
             is_meta_down = true;
           }
-          _ => {}
+          Modifier::Shift => {}
         }
       }
     }
diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs
index a30262f6..1a98b62d 100644
--- a/espanso/src/cli/worker/engine/mod.rs
+++ b/espanso/src/cli/worker/engine/mod.rs
@@ -21,6 +21,7 @@ use std::thread::JoinHandle;
 
 use anyhow::Result;
 use crossbeam::channel::Receiver;
+use espanso_clipboard::ClipboardOptions;
 use espanso_config::{config::ConfigStore, matches::store::MatchStore};
 use espanso_detect::SourceCreationOptions;
 use espanso_engine::event::{EventType, ExitMode};
@@ -182,7 +183,7 @@ pub fn initialize_and_spawn(
         ..Default::default()
       })
       .expect("failed to initialize injector module"); // TODO: handle the options
-      let clipboard = espanso_clipboard::get_clipboard(Default::default())
+      let clipboard = espanso_clipboard::get_clipboard(ClipboardOptions::default())
         .expect("failed to initialize clipboard module"); // TODO: handle options
 
       let clipboard_adapter = ClipboardAdapter::new(&*clipboard, &config_manager);
@@ -282,7 +283,7 @@ pub fn initialize_and_spawn(
           notification_manager.notify_config_reloaded(true);
         }
         Some(flag) if flag == WORKER_START_REASON_KEYBOARD_LAYOUT_CHANGED => {
-          notification_manager.notify_keyboard_layout_reloaded()
+          notification_manager.notify_keyboard_layout_reloaded();
         }
         _ => {
           notification_manager.notify_start();
diff --git a/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs b/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs
index 71cfa706..e1f90cd1 100644
--- a/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/matcher/convert.rs
@@ -60,7 +60,7 @@ impl<'a> MatchConverter<'a> {
     // First convert configuration (user-defined) matches
     for m in match_set.matches {
       if let MatchCause::Trigger(cause) = &m.cause {
-        for trigger in cause.triggers.iter() {
+        for trigger in &cause.triggers {
           matches.push(RollingMatch::from_string(
             m.id,
             trigger,
@@ -69,19 +69,19 @@ impl<'a> MatchConverter<'a> {
               left_word: cause.left_word,
               right_word: cause.right_word,
             },
-          ))
+          ));
         }
       }
     }
 
     // Then convert built-in ones
     for m in self.builtin_matches {
-      for trigger in m.triggers.iter() {
+      for trigger in &m.triggers {
         matches.push(RollingMatch::from_string(
           m.id,
           trigger,
           &StringMatchOptions::default(),
-        ))
+        ));
       }
     }
 
@@ -95,7 +95,7 @@ impl<'a> MatchConverter<'a> {
 
     for m in match_set.matches {
       if let MatchCause::Regex(cause) = &m.cause {
-        matches.push(RegexMatch::new(m.id, &cause.regex))
+        matches.push(RegexMatch::new(m.id, &cause.regex));
       }
     }
 
diff --git a/espanso/src/cli/worker/engine/process/middleware/render/extension/form.rs b/espanso/src/cli/worker/engine/process/middleware/render/extension/form.rs
index 16da24f0..b30bd2cc 100644
--- a/espanso/src/cli/worker/engine/process/middleware/render/extension/form.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/render/extension/form.rs
@@ -85,7 +85,7 @@ fn convert_fields(fields: &Params) -> HashMap<String, FormField> {
           multiline: params
             .get("multiline")
             .and_then(|val| val.as_bool())
-            .cloned()
+            .copied()
             .unwrap_or(false),
         }),
       }
@@ -109,7 +109,7 @@ fn extract_values(value: &Value, trim_string_values: Option<&Value>) -> Option<V
     Value::Array(values) => Some(
       values
         .iter()
-        .flat_map(|choice| choice.as_string())
+        .filter_map(|choice| choice.as_string())
         .cloned()
         .collect(),
     ),
@@ -119,10 +119,10 @@ fn extract_values(value: &Value, trim_string_values: Option<&Value>) -> Option<V
         .filter_map(|line| {
           if trim_string_values {
             let trimmed_line = line.trim();
-            if !trimmed_line.is_empty() {
-              Some(trimmed_line)
-            } else {
+            if trimmed_line.is_empty() {
               None
+            } else {
+              Some(trimmed_line)
             }
           } else {
             Some(line)
diff --git a/espanso/src/cli/worker/engine/process/middleware/render/mod.rs b/espanso/src/cli/worker/engine/process/middleware/render/mod.rs
index 9b5d7ae3..fbd3ef9f 100644
--- a/espanso/src/cli/worker/engine/process/middleware/render/mod.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/render/mod.rs
@@ -85,7 +85,7 @@ fn generate_global_vars_map(config_provider: &dyn ConfigProvider) -> HashMap<i32
   let mut global_vars_map = HashMap::new();
 
   for (_, match_set) in config_provider.configs() {
-    for var in match_set.global_vars.iter() {
+    for var in &match_set.global_vars {
       global_vars_map
         .entry(var.id)
         .or_insert_with(|| convert_var((*var).clone()));
@@ -104,13 +104,13 @@ fn generate_context<'a>(
   let mut templates = Vec::new();
   let mut global_vars = Vec::new();
 
-  for m in match_set.matches.iter() {
+  for m in &match_set.matches {
     if let Some(Some(template)) = template_map.get(&m.id) {
       templates.push(template);
     }
   }
 
-  for var in match_set.global_vars.iter() {
+  for var in &match_set.global_vars {
     if let Some(var) = global_vars_map.get(&var.id) {
       global_vars.push(var);
     }
@@ -202,7 +202,7 @@ impl<'a> Renderer<'a> for RendererAdapter<'a> {
         .or_insert_with(|| generate_context(&match_set, &self.template_map, &self.global_vars_map));
 
       let raw_match = self.match_provider.get(match_id);
-      let propagate_case = raw_match.map(is_propagate_case).unwrap_or(false);
+      let propagate_case = raw_match.is_some_and(is_propagate_case);
       let preferred_uppercasing_style = raw_match.and_then(extract_uppercasing_style);
 
       let options = RenderOptions {
@@ -216,7 +216,9 @@ impl<'a> Renderer<'a> for RendererAdapter<'a> {
       };
 
       // If some trigger vars are specified, augment the template with them
-      let augmented_template = if !trigger_vars.is_empty() {
+      let augmented_template = if trigger_vars.is_empty() {
+        None
+      } else {
         let mut augmented = template.clone();
         for (name, value) in trigger_vars {
           let mut params = espanso_render::Params::new();
@@ -230,11 +232,9 @@ impl<'a> Renderer<'a> for RendererAdapter<'a> {
               inject_vars: false,
               ..Default::default()
             },
-          )
+          );
         }
         Some(augmented)
-      } else {
-        None
       };
 
       let template = if let Some(augmented) = augmented_template.as_ref() {
diff --git a/espanso/src/cli/worker/match_cache.rs b/espanso/src/cli/worker/match_cache.rs
index e143436a..c29b109b 100644
--- a/espanso/src/cli/worker/match_cache.rs
+++ b/espanso/src/cli/worker/match_cache.rs
@@ -52,7 +52,7 @@ impl<'a> MatchCache<'a> {
 
 impl<'a> super::engine::process::middleware::render::MatchProvider<'a> for MatchCache<'a> {
   fn matches(&self) -> Vec<&'a Match> {
-    self.cache.iter().map(|(_, m)| *m).collect()
+    self.cache.values().copied().collect()
   }
 
   fn get(&self, id: i32) -> Option<&'a Match> {
diff --git a/espanso/src/cli/worker/ui/notification.rs b/espanso/src/cli/worker/ui/notification.rs
index 9d18479e..54364a15 100644
--- a/espanso/src/cli/worker/ui/notification.rs
+++ b/espanso/src/cli/worker/ui/notification.rs
@@ -63,9 +63,9 @@ impl<'a> espanso_engine::process::NotificationManager for NotificationManager<'a
     }
 
     if enabled {
-      self.notify("Espanso enabled!")
+      self.notify("Espanso enabled!");
     } else {
-      self.notify("Espanso disabled!")
+      self.notify("Espanso disabled!");
     }
   }
 
diff --git a/espanso/src/cli/worker/ui/welcome.rs b/espanso/src/cli/worker/ui/welcome.rs
index d37c46ea..1a80db3e 100644
--- a/espanso/src/cli/worker/ui/welcome.rs
+++ b/espanso/src/cli/worker/ui/welcome.rs
@@ -20,8 +20,8 @@
 #[cfg(feature = "modulo")]
 pub fn show_welcome_screen() {
   let espanso_exe_path = std::env::current_exe().expect("unable to determine executable path");
-  let mut command = std::process::Command::new(&espanso_exe_path.to_string_lossy().to_string());
-  command.args(&["modulo", "welcome"]);
+  let mut command = std::process::Command::new(espanso_exe_path.to_string_lossy().to_string());
+  command.args(["modulo", "welcome"]);
 
   command.spawn().expect("unable to show welcome screen");
 }
diff --git a/espanso/src/exit_code.rs b/espanso/src/exit_code.rs
index c3bd78ac..7c5ca02b 100644
--- a/espanso/src/exit_code.rs
+++ b/espanso/src/exit_code.rs
@@ -97,19 +97,16 @@ pub fn configure_custom_panic_hook() {
       },
     };
 
-    match info.location() {
-      Some(location) => {
-        error_eprintln!(
-          "ERROR: '{}' panicked at '{}': {}:{}",
-          thread,
-          msg,
-          location.file(),
-          location.line(),
-        );
-      }
-      None => {
-        error_eprintln!("ERROR: '{}' panicked at '{}'", thread, msg);
-      }
+    if let Some(location) = info.location() {
+      error_eprintln!(
+        "ERROR: '{}' panicked at '{}': {}:{}",
+        thread,
+        msg,
+        location.file(),
+        location.line(),
+      );
+    } else {
+      error_eprintln!("ERROR: '{}' panicked at '{}'", thread, msg);
     }
 
     let exit_code = CURRENT_PANIC_EXIT_CODE.lock().unwrap();
diff --git a/espanso/src/gui/modulo/manager.rs b/espanso/src/gui/modulo/manager.rs
index a860e5db..72ad6d61 100644
--- a/espanso/src/gui/modulo/manager.rs
+++ b/espanso/src/gui/modulo/manager.rs
@@ -59,7 +59,7 @@ impl ModuloManager {
         Ok(mut child) => {
           if let Some(stdin) = child.stdin.as_mut() {
             match stdin.write_all(body.as_bytes()) {
-              Ok(_) => Ok(()),
+              Ok(()) => Ok(()),
               Err(error) => Err(ModuloError::Error(error).into()),
             }
           } else {
@@ -93,7 +93,7 @@ impl ModuloManager {
         Ok(mut child) => {
           if let Some(stdin) = child.stdin.as_mut() {
             match stdin.write_all(body.as_bytes()) {
-              Ok(_) => {
+              Ok(()) => {
                 // Get the output
                 match child.wait_with_output() {
                   Ok(child_output) => {
@@ -109,13 +109,13 @@ impl ModuloManager {
                       error!(
                         "modulo exited with non-zero status code: {:?}",
                         child_output.status.code()
-                      )
+                      );
                     }
 
-                    if !output.trim().is_empty() {
-                      Ok(output.to_string())
-                    } else {
+                    if output.trim().is_empty() {
                       Err(ModuloError::EmptyOutput.into())
+                    } else {
+                      Ok(output.to_string())
                     }
                   }
                   Err(error) => Err(ModuloError::Error(error).into()),
diff --git a/espanso/src/gui/modulo/search.rs b/espanso/src/gui/modulo/search.rs
index d8d0bac5..b533e430 100644
--- a/espanso/src/gui/modulo/search.rs
+++ b/espanso/src/gui/modulo/search.rs
@@ -111,7 +111,7 @@ fn convert_items(items: &[SearchItem]) -> Vec<ModuloSearchItemConfig> {
         item
           .additional_search_terms
           .iter()
-          .map(|term| term.as_str())
+          .map(String::as_str)
           .collect()
       },
       is_builtin: item.is_builtin,
diff --git a/espanso/src/ipc.rs b/espanso/src/ipc.rs
index 5e8d3b4e..17b15cc8 100644
--- a/espanso/src/ipc.rs
+++ b/espanso/src/ipc.rs
@@ -54,10 +54,10 @@ pub fn create_ipc_client_to_worker(runtime_dir: &Path) -> Result<impl IPCClient<
 }
 
 fn create_ipc_server(runtime_dir: &Path, name: &str) -> Result<impl IPCServer<IPCEvent>> {
-  espanso_ipc::server(&format!("espanso{}", name), runtime_dir)
+  espanso_ipc::server(&format!("espanso{name}"), runtime_dir)
 }
 
 fn create_ipc_client(runtime_dir: &Path, target_process: &str) -> Result<impl IPCClient<IPCEvent>> {
-  let client = espanso_ipc::client(&format!("espanso{}", target_process), runtime_dir)?;
+  let client = espanso_ipc::client(&format!("espanso{target_process}"), runtime_dir)?;
   Ok(client)
 }
diff --git a/espanso/src/lock.rs b/espanso/src/lock.rs
index ee2dc2e1..906f8ba7 100644
--- a/espanso/src/lock.rs
+++ b/espanso/src/lock.rs
@@ -36,7 +36,7 @@ impl Lock {
   }
 
   fn acquire(runtime_dir: &Path, name: &str) -> Option<Lock> {
-    let lock_file_path = runtime_dir.join(format!("{}.lock", name));
+    let lock_file_path = runtime_dir.join(format!("{name}.lock"));
     let lock_file = OpenOptions::new()
       .read(true)
       .write(true)
diff --git a/espanso/src/patch/config_store.rs b/espanso/src/patch/config_store.rs
index 169c4aaa..3df46688 100644
--- a/espanso/src/patch/config_store.rs
+++ b/espanso/src/patch/config_store.rs
@@ -66,7 +66,7 @@ impl ConfigStore for PatchedConfigStore {
     self.config_store.default()
   }
 
-  fn active<'f>(&'f self, app: &espanso_config::config::AppProperties) -> Arc<dyn Config> {
+  fn active(&self, app: &espanso_config::config::AppProperties) -> Arc<dyn Config> {
     let active_config = self.config_store.active(app);
 
     if !active_config.apply_patch() {
diff --git a/espanso/src/patch/patches/macros.rs b/espanso/src/patch/patches/macros.rs
index c89c4ac8..ce20f337 100644
--- a/espanso/src/patch/patches/macros.rs
+++ b/espanso/src/patch/patches/macros.rs
@@ -71,7 +71,7 @@ macro_rules! generate_patchable_config {
           self.base.match_paths()
         }
 
-        fn is_match<'b>(&self, app: &AppProperties<'b>) -> bool {
+        fn is_match(&self, app: &AppProperties) -> bool {
           self.base.is_match(app)
         }
 
diff --git a/espanso/src/path/linux.rs b/espanso/src/path/linux.rs
index 29a27c27..65d6c00e 100644
--- a/espanso/src/path/linux.rs
+++ b/espanso/src/path/linux.rs
@@ -35,7 +35,7 @@ pub fn add_espanso_to_path(_: bool) -> Result<()> {
 
   let target_link_path = target_link_dir.join("espanso");
 
-  if let Err(error) = std::os::unix::fs::symlink(&exec_path, &target_link_path) {
+  if let Err(error) = std::os::unix::fs::symlink(exec_path, target_link_path) {
     return Err(PathError::SymlinkError(error).into());
   }
 
diff --git a/espanso/src/path/macos.rs b/espanso/src/path/macos.rs
index cd0f7c21..5e24fee0 100644
--- a/espanso/src/path/macos.rs
+++ b/espanso/src/path/macos.rs
@@ -89,7 +89,7 @@ fn create_dir_and_link_with_applescript(exec_path: &Path, target_link_path: &Pat
   );
 
   let mut child = std::process::Command::new("osascript")
-    .args(&["-e", &params])
+    .args(["-e", &params])
     .spawn()?;
 
   let result = child.wait()?;
@@ -120,7 +120,7 @@ pub fn remove_espanso_from_path(prompt_when_necessary: bool) -> Result<()> {
           );
 
           let mut child = std::process::Command::new("osascript")
-            .args(&["-e", &params])
+            .args(["-e", &params])
             .spawn()?;
 
           let result = child.wait()?;
diff --git a/espanso/src/preferences/default.rs b/espanso/src/preferences/default.rs
index d04cccab..250cd72b 100644
--- a/espanso/src/preferences/default.rs
+++ b/espanso/src/preferences/default.rs
@@ -51,7 +51,7 @@ impl<KVSType: KVS> DefaultPreferences<KVSType> {
     self
       .kvs
       .set(key, value)
-      .unwrap_or_else(|_| panic!("unable to write preference for key {}", key))
+      .unwrap_or_else(|_| panic!("unable to write preference for key {}", key));
   }
 }
 
diff --git a/espanso/src/util.rs b/espanso/src/util.rs
index 42602f5d..3461fccd 100644
--- a/espanso/src/util.rs
+++ b/espanso/src/util.rs
@@ -26,7 +26,7 @@ pub fn set_command_flags(command: &mut Command) {
   use std::os::windows::process::CommandExt;
   // Avoid showing the shell window
   // See: https://github.com/federico-terzi/espanso/issues/249
-  command.creation_flags(0x08000000);
+  command.creation_flags(0x0800_0000);
 }
 
 #[cfg(not(target_os = "windows"))]
@@ -38,7 +38,7 @@ pub fn set_command_flags(_: &mut Command) {
 pub fn attach_console() {
   // When using the windows subsystem we loose the terminal output.
   // Therefore we try to attach to the current console if available.
-  unsafe { winapi::um::wincon::AttachConsole(0xFFFFFFFF) };
+  unsafe { winapi::um::wincon::AttachConsole(0xFFFF_FFFF) };
 }
 
 #[cfg(not(target_os = "windows"))]
diff --git a/snapcraft.yaml b/snapcraft.yaml
index 3a3ece22..3a1e7bca 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -1,5 +1,5 @@
 name: espanso
-version: 2.1.8
+version: 2.2.0
 summary: A Cross-platform Text Expander written in Rust
 description: |
   espanso is a Cross-platform, Text Expander written in Rust.


Test patch: