diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0c1175f..d108e45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,8 +13,32 @@ env: CARGO_TERM_COLOR: always jobs: + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.88.0 + override: true + components: rustfmt, clippy + + - name: Check formatting + run: cargo fmt -- --check + + - name: Run clippy + run: cargo clippy -- -D warnings + + - name: Run tests + run: cargo test + build: name: Build Multi-Platform + needs: lint runs-on: ubuntu-latest strategy: matrix: @@ -29,7 +53,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.88.0 override: true target: ${{ matrix.target }} @@ -57,9 +81,41 @@ jobs: retention-days: 7 if-no-files-found: error + build-deb: + name: Build Debian Package + needs: lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.88.0 + override: true + + - name: Install Just + run: cargo install just + + - name: Install dpkg-dev + run: sudo apt-get update && sudo apt-get install -y dpkg-dev + + - name: Build .deb package + run: just build-deb + + - name: Upload .deb Artifact + uses: actions/upload-artifact@v4 + with: + name: tuitype-deb + path: releases/*.deb + retention-days: 7 + if-no-files-found: error + publish: name: Publish Release - needs: build + needs: [build, build-deb] runs-on: ubuntu-latest permissions: contents: write @@ -98,11 +154,18 @@ jobs: name: tuitype-x86_64-unknown-linux-gnu path: artifacts/linux + - name: Download Debian Package + uses: actions/download-artifact@v4 + with: + name: tuitype-deb + path: artifacts/deb + - name: Prepare Release Files run: | mkdir -p release cp artifacts/windows/tuitype-x64.exe release/ cp artifacts/linux/tuitype-linux-x64 release/ + cp artifacts/deb/*.deb release/ chmod +x release/tuitype-linux-x64 - name: Create Release and Upload Assets @@ -117,4 +180,5 @@ jobs: See the full changelog: https://github.com/${{ github.repository }}/compare/${{ env.TAG_NAME }}~1...${{ env.TAG_NAME }} files: | release/tuitype-x64.exe - release/tuitype-linux-x64 \ No newline at end of file + release/tuitype-linux-x64 + release/*.deb \ No newline at end of file diff --git a/.gitignore b/.gitignore index d833684..2263f41 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target /debug /test -**.vscode** \ No newline at end of file +**.vscode** +/releases +/dist \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5033cc9..8e5aead 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,7 +608,7 @@ dependencies = [ [[package]] name = "tuitype" -version = "0.1.6" +version = "0.1.7" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 873b574..9b2206a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tuitype" -version = "0.1.6" -edition = "2021" +version = "0.1.7" +edition = "2024" description = "A terminal-based typing test application similar to MonkeyType" authors = ["RobbyV2"] license = "MIT" diff --git a/build_release.sh b/build_release.sh deleted file mode 100644 index 91b2810..0000000 --- a/build_release.sh +++ /dev/null @@ -1,151 +0,0 @@ -#!/bin/bash -# Build script for tuitype - builds for multiple platforms - -# Exit immediately if a command exits with a non-zero status -set -e - -# Build directory -RELEASE_DIR="./releases" -mkdir -p "$RELEASE_DIR" - -# Output version -VERSION=$(grep '^version =' Cargo.toml | cut -d '"' -f 2) -echo "Building tuitype v$VERSION" - -# Ensure all dependencies are available -echo "Updating dependencies..." -cargo update - -# -------------------------------- -# Native platform builds -# -------------------------------- - -# Linux x86_64 -echo "Building for Linux (x86_64)..." -cross build --release --target x86_64-unknown-linux-gnu -mkdir -p "$RELEASE_DIR/linux-x86_64" -cp target/x86_64-unknown-linux-gnu/release/tuitype "$RELEASE_DIR/linux-x86_64/" -tar -czf "$RELEASE_DIR/tuitype-v$VERSION-linux-x86_64.tar.gz" -C "$RELEASE_DIR/linux-x86_64" tuitype - -# macOS x86_64 -echo "Building for macOS (x86_64)..." -cargo build --release --target x86_64-apple-darwin -mkdir -p "$RELEASE_DIR/macos-x86_64" -cp target/x86_64-apple-darwin/release/tuitype "$RELEASE_DIR/macos-x86_64/" -tar -czf "$RELEASE_DIR/tuitype-v$VERSION-macos-x86_64.tar.gz" -C "$RELEASE_DIR/macos-x86_64" tuitype - -# macOS ARM (Apple Silicon) -echo "Building for macOS (ARM)..." -cargo build --release --target aarch64-apple-darwin -mkdir -p "$RELEASE_DIR/macos-arm" -cp target/aarch64-apple-darwin/release/tuitype "$RELEASE_DIR/macos-arm/" -tar -czf "$RELEASE_DIR/tuitype-v$VERSION-macos-arm.tar.gz" -C "$RELEASE_DIR/macos-arm" tuitype - -# Windows x86_64 -echo "Building for Windows (x86_64)..." -cargo build --release --target x86_64-pc-windows-msvc -mkdir -p "$RELEASE_DIR/windows-x86_64" -cp target/x86_64-pc-windows-msvc/release/tuitype.exe "$RELEASE_DIR/windows-x86_64/" -pushd "$RELEASE_DIR/windows-x86_64" -zip -r "../tuitype-v$VERSION-windows-x86_64.zip" tuitype.exe -popd - -# -------------------------------- -# WebAssembly builds -# -------------------------------- - -# WASI (WebAssembly System Interface) -echo "Building for WASI (WebAssembly)..." -cargo build --release --target wasm32-wasi -mkdir -p "$RELEASE_DIR/wasm-wasi" -cp target/wasm32-wasi/release/tuitype.wasm "$RELEASE_DIR/wasm-wasi/" -tar -czf "$RELEASE_DIR/tuitype-v$VERSION-wasi.tar.gz" -C "$RELEASE_DIR/wasm-wasi" tuitype.wasm - -# Web (using wasm-bindgen) -echo "Building for Web (WebAssembly)..." -cargo build --release --target wasm32-unknown-unknown - -# Check if wasm-bindgen-cli is installed -if command -v wasm-bindgen &> /dev/null; then - mkdir -p "$RELEASE_DIR/wasm-web" - wasm-bindgen --target web \ - --out-dir "$RELEASE_DIR/wasm-web" \ - ./target/wasm32-unknown-unknown/release/tuitype.wasm - - # Create simple HTML example - cat > "$RELEASE_DIR/wasm-web/index.html" < - - - - TuiType - Web Demo - - - -

TuiType - Web Demo

- -
-
-
-
WPM: 0
-
Accuracy: 100%
-
Time: 0s
-
-
- - - - -EOL - - # Create a zip with web files - pushd "$RELEASE_DIR/wasm-web" - zip -r "../tuitype-v$VERSION-web.zip" * - popd -else - echo "Warning: wasm-bindgen-cli not found, skipping Web build packaging" - echo "Install with: cargo install wasm-bindgen-cli" -fi - -echo "All builds completed!" -echo "Release files are available in the $RELEASE_DIR directory:" -ls -la "$RELEASE_DIR"/*.{tar.gz,zip} \ No newline at end of file diff --git a/justfile b/justfile index 4daea4a..007bcce 100644 --- a/justfile +++ b/justfile @@ -62,9 +62,156 @@ update: deps: cargo tree -# Build for multiple platforms (using the existing build script) +# Build for multiple platforms (consolidated from build_release.sh) build-multi: - ./build_release.sh + #!/usr/bin/env bash + set -e + + # Build directory + RELEASE_DIR="./releases" + mkdir -p "$RELEASE_DIR" + + # Output version + VERSION=$(grep '^version =' Cargo.toml | cut -d '"' -f 2) + echo "Building tuitype v$VERSION" + + # Ensure all dependencies are available + echo "Updating dependencies..." + cargo update + + # -------------------------------- + # Native platform builds + # -------------------------------- + + # Linux x86_64 + echo "Building for Linux (x86_64)..." + cross build --release --target x86_64-unknown-linux-gnu + mkdir -p "$RELEASE_DIR/linux-x86_64" + cp target/x86_64-unknown-linux-gnu/release/tuitype "$RELEASE_DIR/linux-x86_64/" + tar -czf "$RELEASE_DIR/tuitype-v$VERSION-linux-x86_64.tar.gz" -C "$RELEASE_DIR/linux-x86_64" tuitype + + # macOS x86_64 + echo "Building for macOS (x86_64)..." + cargo build --release --target x86_64-apple-darwin + mkdir -p "$RELEASE_DIR/macos-x86_64" + cp target/x86_64-apple-darwin/release/tuitype "$RELEASE_DIR/macos-x86_64/" + tar -czf "$RELEASE_DIR/tuitype-v$VERSION-macos-x86_64.tar.gz" -C "$RELEASE_DIR/macos-x86_64" tuitype + + # macOS ARM (Apple Silicon) + echo "Building for macOS (ARM)..." + cargo build --release --target aarch64-apple-darwin + mkdir -p "$RELEASE_DIR/macos-arm" + cp target/aarch64-apple-darwin/release/tuitype "$RELEASE_DIR/macos-arm/" + tar -czf "$RELEASE_DIR/tuitype-v$VERSION-macos-arm.tar.gz" -C "$RELEASE_DIR/macos-arm" tuitype + + # Windows x86_64 + echo "Building for Windows (x86_64)..." + cargo build --release --target x86_64-pc-windows-msvc + mkdir -p "$RELEASE_DIR/windows-x86_64" + cp target/x86_64-pc-windows-msvc/release/tuitype.exe "$RELEASE_DIR/windows-x86_64/" + pushd "$RELEASE_DIR/windows-x86_64" + zip -r "../tuitype-v$VERSION-windows-x86_64.zip" tuitype.exe + popd + + # -------------------------------- + # WebAssembly builds + # -------------------------------- + + # WASI (WebAssembly System Interface) + echo "Building for WASI (WebAssembly)..." + cargo build --release --target wasm32-wasi + mkdir -p "$RELEASE_DIR/wasm-wasi" + cp target/wasm32-wasi/release/tuitype.wasm "$RELEASE_DIR/wasm-wasi/" + tar -czf "$RELEASE_DIR/tuitype-v$VERSION-wasi.tar.gz" -C "$RELEASE_DIR/wasm-wasi" tuitype.wasm + + # Web (using wasm-bindgen) + echo "Building for Web (WebAssembly)..." + cargo build --release --target wasm32-unknown-unknown + + # Check if wasm-bindgen-cli is installed + if command -v wasm-bindgen &> /dev/null; then + mkdir -p "$RELEASE_DIR/wasm-web" + wasm-bindgen --target web \ + --out-dir "$RELEASE_DIR/wasm-web" \ + ./target/wasm32-unknown-unknown/release/tuitype.wasm + + # Create simple HTML example + cat > "$RELEASE_DIR/wasm-web/index.html" <<'EOL' + + + + + TuiType - Web Demo + + + +

TuiType - Web Demo

+ +
+
+
+
WPM: 0
+
Accuracy: 100%
+
Time: 0s
+
+
+ + + + + EOL + + # Create a zip with web files + pushd "$RELEASE_DIR/wasm-web" + zip -r "../tuitype-v$VERSION-web.zip" * + popd + else + echo "Warning: wasm-bindgen-cli not found, skipping Web build packaging" + echo "Install with: cargo install wasm-bindgen-cli" + fi + + echo "All builds completed!" + echo "Release files are available in the $RELEASE_DIR directory:" + ls -la "$RELEASE_DIR"/*.{tar.gz,zip} # Show project info info: @@ -84,5 +231,198 @@ dev: fmt check test # Release workflow - all checks plus release build release: ci build-release +# Build a .deb package from the binary +build-deb: + #!/usr/bin/env bash + set -e + + # Check if dpkg-deb is available + if ! command -v dpkg-deb &> /dev/null; then + echo "Error: dpkg-deb is not available. Please install dpkg-dev package." + echo "On Ubuntu/Debian: sudo apt-get install dpkg-dev" + exit 1 + fi + + # Get version from Cargo.toml + VERSION=$(grep '^version =' Cargo.toml | cut -d '"' -f 2) + PACKAGE_NAME="tuitype" + DEB_DIR="./dist/deb" + PACKAGE_DIR="$DEB_DIR/${PACKAGE_NAME}_${VERSION}_amd64" + + echo "Building .deb package for tuitype v$VERSION" + + # Clean up previous builds + rm -rf "$DEB_DIR" + + # Create directory structure + mkdir -p "$PACKAGE_DIR/DEBIAN" + mkdir -p "$PACKAGE_DIR/usr/bin" + mkdir -p "$PACKAGE_DIR/usr/share/doc/$PACKAGE_NAME" + mkdir -p "$PACKAGE_DIR/usr/share/man/man1" + + # Build the binary + echo "Building release binary..." + cargo build --release + + # Copy binary + cp target/release/tuitype "$PACKAGE_DIR/usr/bin/" + chmod 755 "$PACKAGE_DIR/usr/bin/tuitype" + + # Create control file + cat > "$PACKAGE_DIR/DEBIAN/control" <= 2.17) + Maintainer: RobbyV2 + Description: A terminal-based typing test application + TuiType is a terminal-based typing test application similar to MonkeyType. + It provides an interactive typing experience directly in your terminal + with customizable settings and detailed statistics. + Homepage: https://github.com/RobbyV2/TuiType + EOF + + # Create postinst script + cat > "$PACKAGE_DIR/DEBIAN/postinst" <<'EOF' + #!/bin/bash + set -e + + # Update man database if available + if command -v mandb > /dev/null 2>&1; then + mandb -q || true + fi + + echo "TuiType has been installed successfully!" + echo "Run 'tuitype' to start the application." + + exit 0 + EOF + chmod 755 "$PACKAGE_DIR/DEBIAN/postinst" + + # Create prerm script + cat > "$PACKAGE_DIR/DEBIAN/prerm" <<'EOF' + #!/bin/bash + set -e + + # Nothing to do before removal + exit 0 + EOF + chmod 755 "$PACKAGE_DIR/DEBIAN/prerm" + + # Create postrm script + cat > "$PACKAGE_DIR/DEBIAN/postrm" <<'EOF' + #!/bin/bash + set -e + + case "$1" in + remove|purge) + # Remove any user config directories if purging + if [ "$1" = "purge" ]; then + echo "Purging TuiType configuration files..." + rm -rf /home/*/.config/tuitype || true + rm -rf /root/.config/tuitype || true + fi + ;; + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + # Nothing to do + ;; + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; + esac + + exit 0 + EOF + chmod 755 "$PACKAGE_DIR/DEBIAN/postrm" + + # Create copyright file + cat > "$PACKAGE_DIR/usr/share/doc/$PACKAGE_NAME/copyright" < + Source: https://github.com/RobbyV2/TuiType + + Files: * + Copyright: 2025 RobbyV2 + License: MIT + + License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + EOF + + # Create simple man page + cat > "$PACKAGE_DIR/usr/share/man/man1/tuitype.1" <<'EOF' + .TH TUITYPE 1 "2025" "TuiType" "User Commands" + .SH NAME + tuitype \- terminal-based typing test application + .SH SYNOPSIS + .B tuitype + .SH DESCRIPTION + TuiType is a terminal-based typing test application similar to MonkeyType. + It provides an interactive typing experience directly in your terminal + with customizable settings and detailed statistics. + .SH OPTIONS + No command line options are currently supported. + .SH FILES + .TP + .I ~/.config/tuitype/ + User configuration directory + .SH AUTHOR + Written by RobbyV2. + .SH "REPORTING BUGS" + Report bugs to: https://github.com/RobbyV2/TuiType/issues + .SH COPYRIGHT + Copyright \(co 2025 RobbyV2. + License MIT: . + This is free software: you are free to change and redistribute it. + There is NO WARRANTY, to the extent permitted by law. + EOF + + # Compress man page + gzip -9n "$PACKAGE_DIR/usr/share/man/man1/tuitype.1" + + # Create changelog + cat > "$PACKAGE_DIR/usr/share/doc/$PACKAGE_NAME/changelog.Debian" < $(date -R) + EOF + gzip -9n "$PACKAGE_DIR/usr/share/doc/$PACKAGE_NAME/changelog.Debian" + + # Build the .deb package + echo "Building .deb package..." + dpkg-deb --build "$PACKAGE_DIR" + + # Move to releases directory + mkdir -p ./releases + mv "$DEB_DIR/${PACKAGE_NAME}_${VERSION}_amd64.deb" "./releases/" + + echo "Successfully created: ./releases/${PACKAGE_NAME}_${VERSION}_amd64.deb" + echo "" + echo "To install: sudo dpkg -i ./releases/${PACKAGE_NAME}_${VERSION}_amd64.deb" + echo "To remove: sudo apt remove $PACKAGE_NAME" + echo "To purge: sudo apt purge $PACKAGE_NAME" + # Quick check - just build and clippy (fastest feedback) quick: check clippy \ No newline at end of file diff --git a/src/input/mod.rs b/src/input/mod.rs index b1c3b53..007fb97 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,9 +1,11 @@ use crate::AppResult; use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}; use std::collections::HashMap; -use std::env; use std::time::{Duration, Instant}; +#[cfg(target_os = "windows")] +use std::env; + #[derive(Debug, Clone, Copy)] pub enum Event { Tick, @@ -12,15 +14,13 @@ pub enum Event { Resize(u16, u16), } -#[derive(Debug, Clone, Copy)] -#[derive(Default)] +#[derive(Debug, Clone, Copy, Default)] struct KeyState { last_press: Option, last_release: Option, is_held: bool, } - impl KeyState { fn should_process_key(&mut self, now: Instant, kind: KeyEventKind) -> bool { match kind { @@ -115,7 +115,7 @@ impl InputHandler { #[cfg(target_arch = "wasm32")] pub mod wasm { use super::*; - use wasm_bindgen::{prelude::*, JsCast}; + use wasm_bindgen::{JsCast, prelude::*}; use web_sys::{KeyboardEvent, MouseEvent as WebMouseEvent}; pub struct WasmInputHandler { diff --git a/src/main.rs b/src/main.rs index 4057202..fbbdae8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,18 +2,18 @@ use anyhow::Result; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture}, execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; -use ratatui::{backend::CrosstermBackend, Terminal}; +use ratatui::{Terminal, backend::CrosstermBackend}; use std::{ io::{self}, time::Duration, }; use tuitype::{ + AppResult, config::Config, input::{Event, InputHandler}, - ui::{render, App}, - AppResult, + ui::{App, render}, }; fn main() -> Result<()> { diff --git a/src/ui/draw.rs b/src/ui/draw.rs index 5097a34..27e5acc 100644 --- a/src/ui/draw.rs +++ b/src/ui/draw.rs @@ -1,11 +1,11 @@ use crate::ui::{App, MenuState, WarningState}; use ratatui::{ + Frame, layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, symbols, text::{Line, Span, Text}, widgets::{Block, BorderType, Borders, Chart, Dataset, Gauge, Paragraph, Wrap}, - Frame, }; pub fn render(app: &App, frame: &mut Frame) -> anyhow::Result<()> { @@ -406,18 +406,8 @@ fn draw_test_complete_new(app: &App, frame: &mut Frame, area: Rect) { } ); - let width = area - .width - .saturating_sub(10) - .min(60) - .max(20) - .min(area.width); - let height = area - .height - .saturating_sub(2) - .min(15) - .max(3) - .min(area.height); + let width = area.width.saturating_sub(10).clamp(20, 60).min(area.width); + let height = area.height.saturating_sub(2).clamp(3, 15).min(area.height); if width < 15 || height < 3 { let text = "Test Complete\nPress ENTER to restart"; @@ -1447,12 +1437,7 @@ fn draw_warning(app: &App, frame: &mut Frame, area: Rect) { } ); - let width = area - .width - .saturating_sub(10) - .min(80) - .max(30) - .min(area.width); + let width = area.width.saturating_sub(10).clamp(30, 80).min(area.width); let height = 10.min(area.height.saturating_sub(4)).min(area.height); if width < 30 || height < 5 { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index fc6a134..5cb17f6 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,16 +1,16 @@ mod draw; mod themes; +use crate::AppResult; use crate::config::{Config, Difficulty, TestMode, ThemeConfig}; use crate::input::Event; use crate::stats::TypingStats; use crate::text::TextSource; -use crate::AppResult; use std::collections::HashMap; use std::time::Instant; pub use draw::render; -pub use themes::{get_theme, ThemeType}; +pub use themes::{ThemeType, get_theme}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum MenuState { @@ -208,13 +208,13 @@ impl App { }; if !matches!(self.config.test_mode, TestMode::Timed(_)) - && !self.can_change_settings("test_mode") { - self.set_repeat_mode_warning( - "Test mode cannot be changed while Repeat Mode is active." - .to_string(), - ); - return Ok(()); - } + && !self.can_change_settings("test_mode") + { + self.set_repeat_mode_warning( + "Test mode cannot be changed while Repeat Mode is active.".to_string(), + ); + return Ok(()); + } self.config.test_mode = TestMode::Timed(seconds); self.time_remaining = Some(seconds); @@ -254,8 +254,7 @@ impl App { } } else if !self.can_change_settings("test_mode") { self.set_repeat_mode_warning( - "Word count cannot be changed while Repeat Mode is active." - .to_string(), + "Word count cannot be changed while Repeat Mode is active.".to_string(), ); return Ok(()); } @@ -532,10 +531,9 @@ impl App { pub fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) -> AppResult<()> { use crossterm::event::{KeyCode, KeyModifiers}; - if self.warning_state != WarningState::None - && self.handle_warning(key_event) { - return Ok(()); - } + if self.warning_state != WarningState::None && self.handle_warning(key_event) { + return Ok(()); + } let now = Instant::now(); let key_code = key_event.code;