## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
128 lines
3.6 KiB
C++
128 lines
3.6 KiB
C++
#pragma once
|
|
#include <GfxRenderer.h>
|
|
#include <InputManager.h>
|
|
|
|
#include <functional>
|
|
#include <string>
|
|
|
|
#include "../Activity.h"
|
|
|
|
/**
|
|
* Reusable keyboard entry activity for text input.
|
|
* Can be started from any activity that needs text entry.
|
|
*
|
|
* Usage:
|
|
* 1. Create a KeyboardEntryActivity instance
|
|
* 2. Set callbacks with setOnComplete() and setOnCancel()
|
|
* 3. Call onEnter() to start the activity
|
|
* 4. Call loop() in your main loop
|
|
* 5. When complete or cancelled, callbacks will be invoked
|
|
*/
|
|
class KeyboardEntryActivity : public Activity {
|
|
public:
|
|
// Callback types
|
|
using OnCompleteCallback = std::function<void(const std::string&)>;
|
|
using OnCancelCallback = std::function<void()>;
|
|
|
|
/**
|
|
* Constructor
|
|
* @param renderer Reference to the GfxRenderer for drawing
|
|
* @param inputManager Reference to InputManager for handling input
|
|
* @param title Title to display above the keyboard
|
|
* @param initialText Initial text to show in the input field
|
|
* @param maxLength Maximum length of input text (0 for unlimited)
|
|
* @param isPassword If true, display asterisks instead of actual characters
|
|
*/
|
|
KeyboardEntryActivity(GfxRenderer& renderer, InputManager& inputManager, const std::string& title = "Enter Text",
|
|
const std::string& initialText = "", size_t maxLength = 0, bool isPassword = false);
|
|
|
|
/**
|
|
* Handle button input. Call this in your main loop.
|
|
* @return true if input was handled, false otherwise
|
|
*/
|
|
bool handleInput();
|
|
|
|
/**
|
|
* Render the keyboard at the specified Y position.
|
|
* @param startY Y-coordinate where keyboard rendering starts (default 10)
|
|
*/
|
|
void render(int startY = 10) const;
|
|
|
|
/**
|
|
* Get the current text entered by the user.
|
|
*/
|
|
const std::string& getText() const { return text; }
|
|
|
|
/**
|
|
* Set the current text.
|
|
*/
|
|
void setText(const std::string& newText);
|
|
|
|
/**
|
|
* Check if the user has completed text entry (pressed OK on Done).
|
|
*/
|
|
bool isComplete() const { return complete; }
|
|
|
|
/**
|
|
* Check if the user has cancelled text entry.
|
|
*/
|
|
bool isCancelled() const { return cancelled; }
|
|
|
|
/**
|
|
* Reset the keyboard state for reuse.
|
|
*/
|
|
void reset(const std::string& newTitle = "", const std::string& newInitialText = "");
|
|
|
|
/**
|
|
* Set callback for when input is complete.
|
|
*/
|
|
void setOnComplete(OnCompleteCallback callback) { onComplete = callback; }
|
|
|
|
/**
|
|
* Set callback for when input is cancelled.
|
|
*/
|
|
void setOnCancel(OnCancelCallback callback) { onCancel = callback; }
|
|
|
|
// Activity overrides
|
|
void onEnter() override;
|
|
void onExit() override;
|
|
void loop() override;
|
|
|
|
private:
|
|
std::string title;
|
|
std::string text;
|
|
size_t maxLength;
|
|
bool isPassword;
|
|
|
|
// Keyboard state
|
|
int selectedRow = 0;
|
|
int selectedCol = 0;
|
|
bool shiftActive = false;
|
|
bool complete = false;
|
|
bool cancelled = false;
|
|
|
|
// Callbacks
|
|
OnCompleteCallback onComplete;
|
|
OnCancelCallback onCancel;
|
|
|
|
// Keyboard layout
|
|
static constexpr int NUM_ROWS = 5;
|
|
static constexpr int KEYS_PER_ROW = 13; // Max keys per row (rows 0 and 1 have 13 keys)
|
|
static const char* const keyboard[NUM_ROWS];
|
|
static const char* const keyboardShift[NUM_ROWS];
|
|
|
|
// Special key positions (bottom row)
|
|
static constexpr int SHIFT_ROW = 4;
|
|
static constexpr int SHIFT_COL = 0;
|
|
static constexpr int SPACE_ROW = 4;
|
|
static constexpr int SPACE_COL = 2;
|
|
static constexpr int BACKSPACE_ROW = 4;
|
|
static constexpr int BACKSPACE_COL = 7;
|
|
static constexpr int DONE_ROW = 4;
|
|
static constexpr int DONE_COL = 9;
|
|
|
|
char getSelectedChar() const;
|
|
void handleKeyPress();
|
|
int getRowLength(int row) const;
|
|
};
|