aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md28
-rw-r--r--cpp/libchelleport.cpp33
-rw-r--r--include/libchelleport.h12
-rw-r--r--src/Chelleport.hs116
-rw-r--r--src/Chelleport/Config.hs3
-rw-r--r--src/Chelleport/Context.hs17
-rw-r--r--src/Chelleport/Control.hs43
-rw-r--r--src/Chelleport/Draw.hs9
-rw-r--r--src/Chelleport/OCR.hs18
-rw-r--r--src/Chelleport/Types.hs5
-rw-r--r--src/Chelleport/Utils.hs6
-rw-r--r--src/Chelleport/View.hs19
12 files changed, 186 insertions, 123 deletions
diff --git a/README.md b/README.md
index 6cb9778..e26a626 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,19 @@
# Chelleport
Control your mouse pointer with your keyboard
-> Note: So far it only supports Linux running X11 display server with a compositor, because that's what I use. Might support more if there's interest.
+> Note: So far it only supports Linux running X11 display server with a compositor, because that's what I use. Might look into supporting more systems if there is interest.
https://github.com/user-attachments/assets/93ddc1ff-6cbe-4be4-9507-d68de880212a
+## Features
+- **Text search mode**: Pressing `<c-s>` puts you in search mode which uses OCR to find words on the screen that you can search and move your cursor to.
+- **Labelled hints mode**: This is the default mode. It shows a grid on the screen with 2 keys for each cell. You can move to any cell by pressing the keys shown.
+- **Click**: Pressing `space` left clicks at current mouse position. Holding `shift` key left clicks and show the grid again.
+- **Select text/Drag-n-drop**: Pressing `Ctrl+V` starts dragging/selecting/holding down left mouse button. Press `space` to stop dragging. Or press `Ctrl+V` again to stop dragging and show the grid again.
+- **Double click**: Pressing `2` followed by `space` will click twice. Any digit key followed by `space` will click that many times.
+- **Right click**: Pressing `minus` key right clicks at current mouse position. Holding `shift` key right clicks and shows the grid again.
+- **Granular movement**: Once you match with a label on the screen, you can use `hjkl` keys to move your cursor. Holding `shift` key will use bigger steps for movements. You can also repeat movement by pressing a digit before the movement. Eg: `5k` moves 5 small steps up. `5K` moves 5 big steps up.
+
## Install
- Clone the repo and build it yourself: `cabal build chelleport`
@@ -12,16 +21,15 @@ https://github.com/user-attachments/assets/93ddc1ff-6cbe-4be4-9507-d68de880212a
## Usage
-- Use [sxhkd](https://github.com/baskerville/sxhkd), [shotkey](https://github.com/phenax/shotkey), your window manager or any other key binding manager to set up a keybinding for `chelleport`
+Use [sxhkd](https://github.com/baskerville/sxhkd), [shotkey](https://github.com/phenax/shotkey), your window manager or any other key binding manager to set up a keybinding for `chelleport`.
+
+### Hints mode (default. `<c-h>` to switch to hints mode)
- With the grid open, type any of the key sequences shown on the grid to move the pointer there
-- Once there, you can now use `hjkl` keys to make smaller movements. Hold `shift` to move in bigger increments.
+- Once a match is found, you can now use `hjkl` keys to make smaller movements. Hold `shift` to move in bigger increments.
- Press `space` to click
-
-## Features
-- **Click**: Pressing `space` left clicks at current mouse position. Holding `shift` key left clicks and show the grid again.
-- **Select text/Drag-n-drop**: Pressing `Ctrl+V` starts dragging/selecting/holding down left mouse button. Press `space` to stop dragging. Or press `Ctrl+V` again to stop dragging and show the grid again.
-- **Double click**: Pressing `2` followed by `space` will click twice. Any digit key followed by `space` will click that many times.
-- **Right click**: Pressing `minus` key right clicks at current mouse position. Holding `shift` key right clicks and shows the grid again.
-- **Granular movement**: Once you match with a label on the screen, you can use `hjkl` keys to move your cursor. Holding `shift` key will use bigger steps for movements. You can also repeat movement by pressing a digit before the movement. Eg: `5k` moves 5 small steps up. `5K` moves 5 big steps up.
+### Search mode (`<c-s>` to switch to search mode)
+- Words that are recognized by OCR will be highlighted
+- Type the characters in one of the words to move the cursor to it
+- Press `<c-n>` & `<c-p>` to go to next/previous match respectively
diff --git a/cpp/libchelleport.cpp b/cpp/libchelleport.cpp
index 345a454..4ec3599 100644
--- a/cpp/libchelleport.cpp
+++ b/cpp/libchelleport.cpp
@@ -10,25 +10,16 @@
#include "../include/libchelleport.h"
-std::vector<OCRMatch> extractTextCoordinates(const char *imagePath);
-
-#define CONFIDENCE_THRESHOLD 30.
-#define MIN_CHARACTER_COUNT 2
-const tesseract::PageIteratorLevel RESULT_ITER_MODE = tesseract::RIL_WORD;
-
OCRMatch *findWordCoordinates(const char *image_path, int *size) {
- auto boxes = extractTextCoordinates(image_path);
- static OCRMatch *ptr = new OCRMatch[boxes.size()];
- std::copy(boxes.begin(), boxes.end(), ptr);
+ auto matches = extractTextCoordinates(image_path);
+
+ static OCRMatch *ptr = new OCRMatch[matches.size()];
+ std::copy(matches.begin(), matches.end(), ptr);
- // for (const auto &box : boxes) {
- // std::cout << box.text << "\n\n";
- // std::cout << "Text: " << box.text << "\nPosition: (" << box.startX << ","
- // << box.startY << ") -> (" << box.endX << "," << box.endY << ")"
- // << "\n\n";
- // }
+ // for (const auto &match : matches)
+ // showMatch(match);
- *size = boxes.size();
+ *size = matches.size();
return ptr;
}
@@ -52,16 +43,16 @@ std::vector<OCRMatch> extractTextCoordinates(const char *imagePath) {
tesseract::ResultIterator *iterator = tesseract->GetIterator();
auto level = RESULT_ITER_MODE;
+ int x1, y1, x2, y2;
if (iterator != 0) {
do {
float conf = iterator->Confidence(level);
const char *word = iterator->GetUTF8Text(level);
- int x1, y1, x2, y2;
- iterator->BoundingBox(level, &x1, &y1, &x2, &y2);
if (conf > CONFIDENCE_THRESHOLD && word != nullptr &&
strlen(word) >= MIN_CHARACTER_COUNT) {
+ iterator->BoundingBox(level, &x1, &y1, &x2, &y2);
results.push_back(OCRMatch{x1, y1, x2, y2, word});
}
} while (iterator->Next(level));
@@ -74,3 +65,9 @@ std::vector<OCRMatch> extractTextCoordinates(const char *imagePath) {
return results;
}
+
+void showMatch(const OCRMatch &match) {
+ std::cout << "Text: " << match.text << "; Position: (" << match.startX << ","
+ << match.startY << ") -> (" << match.endX << "," << match.endY
+ << ")" << "\n\n";
+}
diff --git a/include/libchelleport.h b/include/libchelleport.h
index adb9f5d..ef693cb 100644
--- a/include/libchelleport.h
+++ b/include/libchelleport.h
@@ -1,3 +1,6 @@
+#include <tesseract/publictypes.h>
+#include <vector>
+
// NOTE: Remember to update size and alignment in ocr hs module on change
struct OCRMatch {
int startX, startY;
@@ -5,6 +8,15 @@ struct OCRMatch {
const char *text;
};
+#define CONFIDENCE_THRESHOLD 25.
+#define MIN_CHARACTER_COUNT 2
+
+const tesseract::PageIteratorLevel RESULT_ITER_MODE = tesseract::RIL_WORD;
+
extern "C" {
OCRMatch *findWordCoordinates(const char *image_path, /* returns */ int *size);
}
+
+std::vector<OCRMatch> extractTextCoordinates(const char *imagePath);
+
+void showMatch(const OCRMatch &match);
diff --git a/src/Chelleport.hs b/src/Chelleport.hs
index 14c181a..d8f74ae 100644
--- a/src/Chelleport.hs
+++ b/src/Chelleport.hs
@@ -4,19 +4,22 @@ import Chelleport.AppShell (MonadAppShell (hideWindow, showWindow, shutdownApp),
import Chelleport.Context (initializeContext)
import Chelleport.Control
( MonadControl (clickMouseButton, getMousePointerPosition, moveMousePointer, pressMouseButton, releaseMouseButton),
+ anyAlphanumeric,
+ anyDigit,
+ checkKey,
+ ctrl,
directionalIncrement,
eventToKeycode,
- isKeyPressWith,
- isKeyPressed,
- isKeyReleaseWith,
- withCtrl,
- withShift,
+ key,
+ pressed,
+ released,
+ shift,
)
-import Chelleport.Draw (MonadDraw (windowPosition), cellSize)
-import Chelleport.KeySequence (findMatchPosition, generateGrid, isKeycodeDigit, isValidKey, keycodeToInt, nextChars, toKeyChar)
-import Chelleport.OCR (MonadOCR, getWordsOnScreen)
+import Chelleport.Draw (MonadDraw (windowPosition, windowSize), cellSize)
+import Chelleport.KeySequence (findMatchPosition, generateGrid, keycodeToInt, nextChars, toKeyChar)
+import Chelleport.OCR (MonadOCR (captureScreenshot), getWordsInImage)
import Chelleport.Types
-import Chelleport.Utils (cIntToInt, clamp, intToCInt, isEmpty, isNotEmpty, itemAt)
+import Chelleport.Utils (cIntToInt, clamp, intToCInt, isEmpty, itemAt, (<||>))
import qualified Chelleport.View
import Control.Monad (forM_)
import Control.Monad.IO.Class (MonadIO)
@@ -54,46 +57,37 @@ eventHandler state event =
case SDL.eventPayload event of
SDL.QuitEvent -> Just ShutdownApp
SDL.KeyboardEvent ev
- -- Escape
- | isKeyPressWith ev SDL.KeycodeEscape ->
- Just ShutdownApp
- -- minus / underscore
- | isKeyPressWith ev SDL.KeycodeMinus || isKeyPressWith ev SDL.KeycodeUnderscore ->
- if withShift ev
+ -- Esc: Quit
+ | checkKey [key SDL.KeycodeEscape, pressed] ev -> Just ShutdownApp
+ -- <C-s>: Enable search mode
+ | checkKey [ctrl, key SDL.KeycodeS, pressed] ev -> Just $ SetMode defaultSearchMode
+ -- <C-h>: Enable hints mode
+ | checkKey [ctrl, key SDL.KeycodeH, pressed] ev -> Just $ SetMode defaultHintsMode
+ -- <C-n>, <C-p>: Search increment next/prev
+ | checkKey [ctrl, key SDL.KeycodeN, pressed] ev -> Just $ IncrementHighlightIndex (stateRepetition state)
+ | checkKey [ctrl, key SDL.KeycodeP, pressed] ev -> Just $ IncrementHighlightIndex (-1 * stateRepetition state)
+ -- Space / Shift+Space : Left click/chain left click
+ | checkKey [key SDL.KeycodeSpace, pressed] ev ->
+ if shift ev
+ then Just $ ChainMouseClick LeftClick
+ else Just $ TriggerMouseClick LeftClick
+ -- Backspace: Reset keys
+ | checkKey [key SDL.KeycodeBackspace, pressed] ev -> Just ResetKeys
+ -- <C-v>: Toggle mouse dragging
+ | checkKey [ctrl, key SDL.KeycodeV, pressed] ev -> Just MouseDragToggle
+ -- minus / underscore: Right click/chain right click
+ | checkKey [key SDL.KeycodeMinus <||> key SDL.KeycodeUnderscore, pressed] ev ->
+ if shift ev
then Just $ ChainMouseClick RightClick
else Just $ TriggerMouseClick RightClick
- -- 0-9
- | isKeycodeDigit (eventToKeycode ev) ->
+ -- 0-9: Repetition digit
+ | checkKey [anyDigit, pressed] ev ->
Just $ UpdateRepetition (fromMaybe 0 $ keycodeToInt $ eventToKeycode ev)
- -- Enable search mode
- | withCtrl ev && isKeyPressWith ev SDL.KeycodeS ->
- Just $ SetMode defaultSearchMode
- -- Enable hints mode
- | withCtrl ev && isKeyPressWith ev SDL.KeycodeH ->
- Just $ SetMode defaultHintsMode
- -- Search increment next/prev
- | withCtrl ev && isKeyPressWith ev SDL.KeycodeN -> Just $ IncrementHighlightIndex (stateRepetition state)
- | withCtrl ev && isKeyPressWith ev SDL.KeycodeP -> Just $ IncrementHighlightIndex (-1 * stateRepetition state)
- -- Space / Shift+Space
- | isKeyPressWith ev SDL.KeycodeSpace ->
- if withShift ev
- then Just $ ChainMouseClick LeftClick
- else Just $ TriggerMouseClick LeftClick
- -- Tab / Backspace
- | isKeyPressWith ev SDL.KeycodeTab || isKeyPressWith ev SDL.KeycodeBackspace ->
- Just ResetKeys
- -- Ctrl + V
- | withCtrl ev && isKeyPressWith ev SDL.KeycodeV ->
- Just MouseDragToggle
-- A-Z
- | isKeyPressed ev && isValidKey (eventToKeycode ev) ->
- Just $ HandleKeyInput $ eventToKeycode ev
- -- Shift press
- | isKeyPressWith ev SDL.KeycodeLShift || isKeyPressWith ev SDL.KeycodeRShift ->
- Just $ UpdateShiftState True
- -- Shift release
- | isKeyReleaseWith ev SDL.KeycodeLShift || isKeyReleaseWith ev SDL.KeycodeRShift ->
- Just $ UpdateShiftState False
+ | checkKey [anyAlphanumeric, pressed] ev -> Just $ HandleKeyInput $ eventToKeycode ev
+ -- Shift press/release: Toggle shift mode
+ | checkKey [pressed, key SDL.KeycodeRShift <||> key SDL.KeycodeLShift] ev -> Just $ UpdateShiftState True
+ | checkKey [released, key SDL.KeycodeRShift <||> key SDL.KeycodeLShift] ev -> Just $ UpdateShiftState False
_ -> Nothing
wordPosition :: (MonadDraw m) => OCRMatch -> m (Int, Int)
@@ -107,13 +101,17 @@ update state (SetMode mode) = do
case mode of
ModeHints -> pure (state {stateMode = mode}, Nothing)
ModeSearch {} -> do
- wordsOnScreen <- getWordsOnScreen
+ pos <- windowPosition
+ size <- windowSize
+ screenshot <- hideWindow >> captureScreenshot pos size <* showWindow
+
+ wordsOnScreen <- getWordsInImage screenshot
let updatedMode = mode {searchWords = wordsOnScreen, searchFilteredWords = wordsOnScreen}
pure (state {stateMode = updatedMode}, Nothing)
-- HINTS MODE: Act on key inputs
-update state@(State {stateMode = ModeHints}) (HandleKeyInput key) = do
- case (toKeyChar key, validChars) of
+update state@(State {stateMode = ModeHints}) (HandleKeyInput keycode) = do
+ case (toKeyChar keycode, validChars) of
(Just keyChar, Just validChars')
| stateIsMatched state && keyChar `elem` ("HJKL" :: String) -> do
incr <- incrementValue
@@ -141,8 +139,8 @@ update state@(State {stateMode = ModeHints}) (HandleKeyInput key) = do
else pure (wcell `div` 16, hcell `div` 16)
-- SEARCH MODE: Act on key inputs
-update state@(State {stateMode = ModeSearch {searchWords, searchInputText}}) (HandleKeyInput key) = do
- case toKeyChar key of
+update state@(State {stateMode = ModeSearch {searchWords, searchInputText}}) (HandleKeyInput keycode) = do
+ case toKeyChar keycode of
Just keyChar -> do
let searchText = searchInputText ++ [toLower keyChar]
let matches = filterMatches searchText
@@ -193,7 +191,19 @@ update state (MoveMousePosition (x, y)) = do
-- Reset entered key sequence and state
update state ResetKeys = do
- pure (state {stateKeySequence = [], stateIsMatched = False, stateRepetition = 1}, Nothing)
+ pure
+ ( state
+ { stateKeySequence = [],
+ stateIsMatched = False,
+ stateRepetition = 1,
+ stateMode = resetMode (stateMode state)
+ },
+ Nothing
+ )
+ where
+ resetMode mode@ModeHints = mode
+ resetMode (ModeSearch {searchWords}) =
+ defaultSearchMode {searchWords = searchWords, searchFilteredWords = searchWords}
-- Trigger click
update state (TriggerMouseClick btn) = do
@@ -241,5 +251,5 @@ update state (UpdateRepetition count) = do
pure (state {stateRepetition = count}, Nothing)
-- Set/unset whether shift is pressed
-update state (UpdateShiftState shift) =
- pure (state {stateIsShiftPressed = shift}, Nothing)
+update state (UpdateShiftState shiftPressed) =
+ pure (state {stateIsShiftPressed = shiftPressed}, Nothing)
diff --git a/src/Chelleport/Config.hs b/src/Chelleport/Config.hs
index d8b4132..b75abc0 100644
--- a/src/Chelleport/Config.hs
+++ b/src/Chelleport/Config.hs
@@ -36,6 +36,3 @@ colorFineGrainGrid = SDL.V4 55 52 65 100
windowOpacity :: CFloat
windowOpacity = 0.5
-
-fontSize :: Int
-fontSize = 24
diff --git a/src/Chelleport/Context.hs b/src/Chelleport/Context.hs
index 30d8516..3109e05 100644
--- a/src/Chelleport/Context.hs
+++ b/src/Chelleport/Context.hs
@@ -19,7 +19,7 @@ initializeContext = do
window <- initializeWindow
renderer <- initializeRenderer window
- font <- loadFont
+ (fontSm, fontLg) <- loadFonts
display <- X11.openDisplay ""
@@ -27,18 +27,21 @@ initializeContext = do
DrawContext
{ ctxWindow = window,
ctxRenderer = renderer,
- ctxFont = font,
+ ctxFontLarge = fontLg,
+ ctxFontSmall = fontSm,
ctxX11Display = display
}
rawFontData :: ByteString
rawFontData = $(embedFileRelative "./static/font.ttf")
-loadFont :: IO TTF.Font
-loadFont = do
- font <- TTF.decode rawFontData fontSize
- TTF.setStyle font [TTF.Bold]
- pure font
+loadFonts :: IO (TTF.Font, TTF.Font)
+loadFonts = do
+ fontSm <- TTF.decode rawFontData 14
+ TTF.setStyle fontSm [TTF.Bold]
+ fontLg <- TTF.decode rawFontData 24
+ TTF.setStyle fontLg [TTF.Bold]
+ pure (fontSm, fontLg)
initializeRenderer :: SDL.Window -> IO SDL.Renderer
initializeRenderer window = do
diff --git a/src/Chelleport/Control.hs b/src/Chelleport/Control.hs
index f7a9c66..48bdcb2 100644
--- a/src/Chelleport/Control.hs
+++ b/src/Chelleport/Control.hs
@@ -1,7 +1,8 @@
module Chelleport.Control where
+import Chelleport.KeySequence (isKeycodeDigit, isValidKey)
import Chelleport.Types
-import Chelleport.Utils (cIntToInt)
+import Chelleport.Utils
import Control.Concurrent (threadDelay)
import Control.Monad (unless)
import Control.Monad.IO.Class (MonadIO (liftIO))
@@ -63,31 +64,35 @@ instance (MonadIO m) => MonadControl (AppM m) where
xSimulateButtonEvent display X11.button1 False 0
X11.sync display False
-isKeyPressed :: SDL.KeyboardEventData -> Bool
-isKeyPressed = (SDL.Pressed ==) . SDL.keyboardEventKeyMotion
-
-isKeyRelease :: SDL.KeyboardEventData -> Bool
-isKeyRelease = (SDL.Released ==) . SDL.keyboardEventKeyMotion
-
eventToKeycode :: SDL.KeyboardEventData -> SDL.Keycode
eventToKeycode = SDL.keysymKeycode . SDL.keyboardEventKeysym
-isKeyPressWith :: SDL.KeyboardEventData -> SDL.Keycode -> Bool
-isKeyPressWith keyboardEvent keyCode =
- isKeyPressed keyboardEvent && eventToKeycode keyboardEvent == keyCode
-
-isKeyReleaseWith :: SDL.KeyboardEventData -> SDL.Keycode -> Bool
-isKeyReleaseWith keyboardEvent keyCode =
- isKeyRelease keyboardEvent && eventToKeycode keyboardEvent == keyCode
-
keyModifier :: SDL.KeyboardEventData -> SDL.KeyModifier
keyModifier = SDL.keysymModifier . SDL.keyboardEventKeysym
-withShift :: SDL.KeyboardEventData -> Bool
-withShift ev = SDL.keyModifierLeftShift (keyModifier ev) || SDL.keyModifierRightShift (keyModifier ev)
+checkKey :: [SDL.KeyboardEventData -> Bool] -> SDL.KeyboardEventData -> Bool
+checkKey = (<&&>)
+
+pressed :: SDL.KeyboardEventData -> Bool
+pressed = (SDL.Pressed ==) . SDL.keyboardEventKeyMotion
+
+released :: SDL.KeyboardEventData -> Bool
+released = (SDL.Released ==) . SDL.keyboardEventKeyMotion
+
+key :: SDL.Keycode -> SDL.KeyboardEventData -> Bool
+key keycode = (keycode ==) . eventToKeycode
+
+ctrl :: SDL.KeyboardEventData -> Bool
+ctrl ev = SDL.keyModifierLeftCtrl (keyModifier ev) || SDL.keyModifierRightCtrl (keyModifier ev)
+
+shift :: SDL.KeyboardEventData -> Bool
+shift ev = SDL.keyModifierLeftShift (keyModifier ev) || SDL.keyModifierRightShift (keyModifier ev)
+
+anyDigit :: SDL.KeyboardEventData -> Bool
+anyDigit = isKeycodeDigit . eventToKeycode
-withCtrl :: SDL.KeyboardEventData -> Bool
-withCtrl ev = SDL.keyModifierLeftCtrl (keyModifier ev) || SDL.keyModifierRightCtrl (keyModifier ev)
+anyAlphanumeric :: SDL.KeyboardEventData -> Bool
+anyAlphanumeric = isValidKey . eventToKeycode
directionalIncrement :: (CInt, CInt) -> Char -> (Int, Int)
directionalIncrement (incX, incY) = \case
diff --git a/src/Chelleport/Draw.hs b/src/Chelleport/Draw.hs
index 3720ba0..60944a3 100644
--- a/src/Chelleport/Draw.hs
+++ b/src/Chelleport/Draw.hs
@@ -12,7 +12,7 @@ import qualified SDL.Font as TTF
class (Monad m) => MonadDraw m where
drawLine :: (CInt, CInt) -> (CInt, CInt) -> m ()
- drawText :: (CInt, CInt) -> Color -> Text -> m (CInt, CInt)
+ drawText :: (CInt, CInt) -> Color -> FontSize -> Text -> m (CInt, CInt)
drawCircle :: Int -> (CInt, CInt) -> m ()
fillRect :: (CInt, CInt) -> (CInt, CInt) -> m ()
setDrawColor :: Color -> m ()
@@ -33,8 +33,11 @@ instance (MonadIO m) => MonadDraw (AppM m) where
let rect = SDL.Rectangle (SDL.P $ SDL.V2 x y) (SDL.V2 w h)
SDL.fillRect renderer (Just rect)
- drawText (x, y) color text = do
- DrawContext {ctxRenderer = renderer, ctxFont = font} <- ask
+ drawText (x, y) color size text = do
+ DrawContext {ctxRenderer = renderer, ctxFontSmall, ctxFontLarge} <- ask
+ let font = case size of
+ FontSM -> ctxFontSmall
+ FontLG -> ctxFontLarge
surface <- TTF.blended font color text
texture <- SDL.createTextureFromSurface renderer surface
SDL.freeSurface surface
diff --git a/src/Chelleport/OCR.hs b/src/Chelleport/OCR.hs
index f6bc5b9..ef9dc9e 100644
--- a/src/Chelleport/OCR.hs
+++ b/src/Chelleport/OCR.hs
@@ -1,6 +1,7 @@
module Chelleport.OCR (MonadOCR (..)) where
import Chelleport.Types
+import Control.Concurrent (threadDelay)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.RWS (MonadReader (ask))
import qualified Data.ByteString as BS
@@ -9,7 +10,6 @@ import Foreign.C (CInt, CString, newCString)
import GHC.IO.Handle.FD (withFile)
import GHC.IO.IOMode (IOMode (WriteMode))
import qualified Graphics.X11 as X11
-import qualified SDL
import System.Directory (removeFile)
import System.IO (hPutStrLn)
import System.IO.Temp (emptySystemTempFile)
@@ -18,16 +18,20 @@ foreign import ccall unsafe "libchelleport.h findWordCoordinates"
c_getAllWordCoordinates :: CString -> Ptr CInt -> IO (Ptr OCRMatch)
class (Monad m) => MonadOCR m where
- getWordsOnScreen :: m [OCRMatch]
+ captureScreenshot :: (CInt, CInt) -> (CInt, CInt) -> m FilePath
+ getWordsInImage :: FilePath -> m [OCRMatch]
instance (MonadIO m) => MonadOCR (AppM m) where
- getWordsOnScreen = do
+ captureScreenshot (x, y) (width, height) = do
ctx <- ask
- SDL.V2 width height <- SDL.get . SDL.windowSize . ctxWindow $ ctx
- SDL.V2 x y <- SDL.getWindowAbsolutePosition . ctxWindow $ ctx
liftIO $ do
- imgFilePath <- liftIO $ createTemporaryScreenshot ctx (x, y) (width, height)
- findWordCoordinates imgFilePath <* removeFile imgFilePath
+ threadDelay 20_000
+ path <- createTemporaryScreenshot ctx (x, y) (width, height)
+ threadDelay 20_000
+ pure path
+
+ getWordsInImage filePath = do
+ liftIO $ findWordCoordinates filePath <* removeFile filePath
findWordCoordinates :: String -> IO [OCRMatch]
findWordCoordinates imgPath = alloca $ \sizePtr -> do
diff --git a/src/Chelleport/Types.hs b/src/Chelleport/Types.hs
index 43d063f..59ed43d 100644
--- a/src/Chelleport/Types.hs
+++ b/src/Chelleport/Types.hs
@@ -79,10 +79,13 @@ data AppAction
| IncrementHighlightIndex Int
deriving (Show, Eq)
+data FontSize = FontSM | FontLG deriving (Show, Eq)
+
data DrawContext = DrawContext
{ ctxWindow :: SDL.Window,
ctxRenderer :: SDL.Renderer,
- ctxFont :: TTF.Font,
+ ctxFontSmall :: TTF.Font,
+ ctxFontLarge :: TTF.Font,
ctxX11Display :: X11.Display
}
diff --git a/src/Chelleport/Utils.hs b/src/Chelleport/Utils.hs
index 9423e5d..3f0dd73 100644
--- a/src/Chelleport/Utils.hs
+++ b/src/Chelleport/Utils.hs
@@ -43,3 +43,9 @@ itemAt (_ : xs) i = itemAt xs (i - 1)
clamp :: (Integral a) => (a, a) -> a -> a
clamp (low, high) n = max low (min high n)
+
+(<&&>) :: [a -> Bool] -> a -> Bool
+(<&&>) preds ev = all (\p -> p ev) preds
+
+(<||>) :: (a -> Bool) -> (a -> Bool) -> a -> Bool
+(<||>) p1 p2 x = p1 x || p2 x
diff --git a/src/Chelleport/View.hs b/src/Chelleport/View.hs
index c2e0ff8..202c636 100644
--- a/src/Chelleport/View.hs
+++ b/src/Chelleport/View.hs
@@ -15,13 +15,28 @@ render state = case stateMode state of
ModeSearch {searchFilteredWords, searchHighlightedIndex} ->
renderSearchView state searchFilteredWords searchHighlightedIndex
+getSearchText :: State -> String
+getSearchText state = case stateMode state of
+ ModeHints -> ""
+ ModeSearch {searchInputText, searchFilteredWords, searchHighlightedIndex} ->
+ "Searching (" ++ matchCount ++ "): " ++ searchInputText
+ where
+ matchCount
+ | isEmpty searchFilteredWords = "0/0"
+ | otherwise = show (searchHighlightedIndex + 1) ++ "/" ++ show (length searchFilteredWords)
+
renderSearchView :: (MonadDraw m) => State -> [OCRMatch] -> Int -> m ()
renderSearchView state matches highlightedIndex = do
renderGridLines state
+
forM_ (zip [0 ..] matches) $ \(index, OCRMatch {matchStartX, matchStartY, matchEndX, matchEndY}) -> do
setDrawColor $ if highlightedIndex == index then colorAccent else colorLightGray
fillRectVertices (matchStartX, matchStartY) (matchEndX, matchEndY)
+ (w, h) <- windowSize
+ drawText (w `div` 2, h `div` 2) colorAccent FontSM (Text.pack $ getSearchText state)
+ pure ()
+
renderHintsView :: (MonadDraw m) => State -> m ()
renderHintsView state = do
renderGridLines state
@@ -50,12 +65,12 @@ renderKeySequence keySequence cell (px, py) = do
previousTextWidth <-
if isNotEmpty matched
- then fst <$> drawText (px, py) colorLightGray (Text.pack matched)
+ then fst <$> drawText (px, py) colorLightGray FontLG (Text.pack matched)
else pure 0
when (isNotEmpty remaining) $ case textColor of
Just color -> do
- void $ drawText (px + previousTextWidth, py) color $ Text.pack remaining
+ void $ drawText (px + previousTextWidth, py) color FontLG $ Text.pack remaining
Nothing -> pure ()
pure isVisible