diff options
Diffstat (limited to '')
| -rw-r--r-- | cpp/libchelleport.cpp | 10 | ||||
| -rw-r--r-- | flake.nix | 2 | ||||
| -rw-r--r-- | include/libchelleport.h | 2 | ||||
| -rw-r--r-- | specs/Specs/AppStateUpdateSpec.hs | 56 | ||||
| -rw-r--r-- | specs/Specs/ViewSpec.hs | 11 | ||||
| -rw-r--r-- | specs/TestUtils.hs | 7 | ||||
| -rw-r--r-- | src/Chelleport.hs | 91 | ||||
| -rw-r--r-- | src/Chelleport/AppShell.hs | 9 | ||||
| -rw-r--r-- | src/Chelleport/Control.hs | 4 | ||||
| -rw-r--r-- | src/Chelleport/Draw.hs | 9 | ||||
| -rw-r--r-- | src/Chelleport/OCR.hs | 40 | ||||
| -rw-r--r-- | src/Chelleport/Types.hs | 29 | ||||
| -rw-r--r-- | src/Chelleport/View.hs | 14 |
13 files changed, 159 insertions, 125 deletions
diff --git a/cpp/libchelleport.cpp b/cpp/libchelleport.cpp index 8f2e9f2..345a454 100644 --- a/cpp/libchelleport.cpp +++ b/cpp/libchelleport.cpp @@ -13,6 +13,8 @@ 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); @@ -49,7 +51,7 @@ std::vector<OCRMatch> extractTextCoordinates(const char *imagePath) { tesseract->Recognize(0); tesseract::ResultIterator *iterator = tesseract->GetIterator(); - tesseract::PageIteratorLevel level = tesseract::RIL_TEXTLINE; + auto level = RESULT_ITER_MODE; if (iterator != 0) { do { @@ -58,9 +60,9 @@ std::vector<OCRMatch> extractTextCoordinates(const char *imagePath) { int x1, y1, x2, y2; iterator->BoundingBox(level, &x1, &y1, &x2, &y2); - if (conf > CONFIDENCE_THRESHOLD && word != nullptr && strlen(word) >= 2) { - OCRMatch box{x1, y1, x2, y2, word}; - results.push_back(box); + if (conf > CONFIDENCE_THRESHOLD && word != nullptr && + strlen(word) >= MIN_CHARACTER_COUNT) { + results.push_back(OCRMatch{x1, y1, x2, y2, word}); } } while (iterator->Next(level)); } @@ -26,6 +26,7 @@ }); otherFiles = [ { source = ./static; target = "static"; } + { source = ./include; target = "include"; } ]; configurationFlags = [ "--ghc-options=-O2" @@ -37,7 +38,6 @@ SDL2_ttf tesseract leptonica - imagemagick gcc pkg-config diff --git a/include/libchelleport.h b/include/libchelleport.h index 8a9a17e..adb9f5d 100644 --- a/include/libchelleport.h +++ b/include/libchelleport.h @@ -1,5 +1,3 @@ -#include <string> - // NOTE: Remember to update size and alignment in ocr hs module on change struct OCRMatch { int startX, startY; diff --git a/specs/Specs/AppStateUpdateSpec.hs b/specs/Specs/AppStateUpdateSpec.hs index e54f50f..861264d 100644 --- a/specs/Specs/AppStateUpdateSpec.hs +++ b/specs/Specs/AppStateUpdateSpec.hs @@ -2,7 +2,7 @@ module Specs.AppStateUpdateSpec where import Chelleport (initialState, update) import Chelleport.Types -import Chelleport.Utils (intToCInt, uniq) +import Chelleport.Utils (uniq) import Control.Monad (join) import qualified SDL import Test.Hspec @@ -12,32 +12,23 @@ test :: SpecWith () test = do describe "#initialState" $ do it "returns the initial state of the app" $ do - (initState, _) <- runWithMocks initialState + ((initState, _), _) <- runWithMocks initialState stateKeySequence initState `shouldBe` [] stateIsMatched initState `shouldBe` False stateIsShiftPressed initState `shouldBe` False it "returns grid with 16x9 key sequences" $ do - (initState, _) <- runWithMocks initialState + ((initState, _), _) <- runWithMocks initialState length (stateGrid initState) `shouldBe` 9 stateGrid initState `shouldSatisfy` all ((== 16) . length) stateGrid initState `shouldSatisfy` all (all ((== 2) . length)) it "returns grid with all unique key sequences" $ do - (initState, _) <- runWithMocks initialState + ((initState, _), _) <- runWithMocks initialState join (stateGrid initState) `shouldBe` uniq (join $ stateGrid initState) describe "#update" $ do - let defaultState = - State - { stateKeySequence = [], - stateIsShiftPressed = False, - stateIsMatched = False, - stateGrid = [["ABC", "DEF"], ["DJK", "JKL"]], - stateRepetition = 1, - stateIsDragging = False, - stateMode = ModeHints - } + let defaultState = defaultAppState {stateGrid = [["ABC", "DEF"], ["DJK", "JKL"]]} context "with action HandleKeyInput" $ do context "when there are no matches" $ do @@ -63,9 +54,9 @@ test = do ((nextState, _), _) <- runWithMocks $ update currentState $ HandleKeyInput SDL.KeycodeF nextState `shouldBe` currentState {stateKeySequence = "DEF", stateIsMatched = True} - it "continues with MoveMousePosition action" $ do + it "continues with MoveMousePosition action at center of matched cell" $ do ((_, action), _) <- runWithMocks $ update currentState $ HandleKeyInput SDL.KeycodeF - action `shouldBe` Just (MoveMousePosition (0, 1)) + action `shouldBe` Just (MoveMousePosition (1640, 370)) context "with action TriggerMouseClick" $ do let currentState = defaultState @@ -185,16 +176,10 @@ test = do context "with action MoveMousePosition" $ do let currentState = defaultState - let rows = intToCInt $ length $ stateGrid currentState - let columns = intToCInt $ length $ head $ stateGrid currentState - -- TODO: Test with inline mocked values - it "moves mouse pointer to center of cell of given coordinates" $ do - (_, mock) <- runWithMocks $ update currentState $ MoveMousePosition (0, 0) - mock - `shouldHaveCalled` Mock_moveMousePointer - (mockWindowOffsetX + mockWindowWidth `div` columns `div` 2) - (mockWindowOffsetY + mockWindowHeight `div` rows `div` 2) + it "moves mouse pointer to the given coordinates" $ do + (_, mock) <- runWithMocks $ update currentState $ MoveMousePosition (23, 320) + mock `shouldHaveCalled` Mock_moveMousePointer 23 320 it "does not continue or update state" $ do (result, _) <- runWithMocks $ update currentState $ MoveMousePosition (0, 0) @@ -211,28 +196,27 @@ test = do context "with action IncrementMouseCursor" $ do let currentState = defaultState - -- TODO: Test with inline mocked values - it "increments mouse position relative to current position" $ do - (_, mock) <- runWithMocks $ update currentState $ IncrementMouseCursor (10, -5) - mock `shouldHaveCalled` Mock_moveMousePointer 52 37 + it "continues with MoveMousePosition" $ do + ((_, action), _) <- runWithMocks $ update currentState $ IncrementMouseCursor (10, -5) + action `shouldBe` Just (MoveMousePosition (52, 37)) - it "does not continue or update state" $ do - (result, _) <- runWithMocks $ update currentState $ IncrementMouseCursor (0, 0) - result `shouldBe` (currentState, Nothing) + it "does update state" $ do + ((state, _), _) <- runWithMocks $ update currentState $ IncrementMouseCursor (10, -5) + state `shouldBe` currentState context "when repetition is more than 1" $ do let currentState = defaultState {stateRepetition = 5} it "multiplies increment" $ do - (_, mock) <- runWithMocks $ update currentState $ IncrementMouseCursor (10, -5) - mock `shouldHaveCalled` Mock_moveMousePointer 92 17 + ((_, action), _) <- runWithMocks $ update currentState $ IncrementMouseCursor (10, -5) + action `shouldBe` Just (MoveMousePosition (92, 17)) context "when repetition is 0" $ do let currentState = defaultState {stateRepetition = 0} it "increments just once" $ do - (_, mock) <- runWithMocks $ update currentState $ IncrementMouseCursor (10, -5) - mock `shouldHaveCalled` Mock_moveMousePointer 52 37 + ((_, action), _) <- runWithMocks $ update currentState $ IncrementMouseCursor (10, -5) + action `shouldBe` Just (MoveMousePosition (52, 37)) context "with action ShutdownApp" $ do let currentState = defaultState diff --git a/specs/Specs/ViewSpec.hs b/specs/Specs/ViewSpec.hs index b8418d6..39e7fea 100644 --- a/specs/Specs/ViewSpec.hs +++ b/specs/Specs/ViewSpec.hs @@ -8,16 +8,7 @@ import TestUtils test :: SpecWith () test = do - let defaultState = - State - { stateKeySequence = [], - stateIsShiftPressed = False, - stateIsMatched = False, - stateGrid = [["ABC", "DEF"], ["DJK", "JKL"]], - stateRepetition = 1, - stateIsDragging = False, - stateMode = ModeHints - } + let defaultState = defaultAppState {stateGrid = [["ABC", "DEF"], ["DJK", "JKL"]]} let drawTextCalls = filter (\case Mock_drawText {} -> True; _ -> False) . calls describe "#render" $ do diff --git a/specs/TestUtils.hs b/specs/TestUtils.hs index c98899d..76a185d 100644 --- a/specs/TestUtils.hs +++ b/specs/TestUtils.hs @@ -3,6 +3,7 @@ module TestUtils where import Chelleport.AppShell (MonadAppShell (..)) import Chelleport.Control (MonadControl (..)) import Chelleport.Draw (MonadDraw (..)) +import Chelleport.OCR (MonadOCR (..)) import Control.Monad (void) import Control.Monad.IO.Class (MonadIO) import Control.Monad.State (MonadState (state), StateT (runStateT)) @@ -11,7 +12,7 @@ import Foreign.C (CInt) import Mock import Test.Hspec -$(generateMock [''MonadDraw, ''MonadControl, ''MonadAppShell]) +$(generateMock [''MonadDraw, ''MonadControl, ''MonadAppShell, ''MonadOCR]) newtype MockCalls = MockCalls {calls :: [Call]} deriving (Show) @@ -53,6 +54,7 @@ instance (MonadIO m) => MonadControl (TestM m) where instance (MonadIO m) => MonadDraw (TestM m) where drawLine p1 p2 = registerMockCall $ Mock_drawLine p1 p2 + fillRect p size = registerMockCall $ Mock_fillRect p size drawText p color text = (fromIntegral $ mockTextWidth * Text.length text, 0) <$ registerMockCall (Mock_drawText p color text) drawCircle radius p = registerMockCall $ Mock_drawCircle radius p setDrawColor color = registerMockCall $ Mock_setDrawColor color @@ -63,3 +65,6 @@ instance (MonadIO m) => MonadAppShell (TestM m) where hideWindow = registerMockCall Mock_hideWindow showWindow = registerMockCall Mock_showWindow shutdownApp = registerMockCall Mock_shutdownApp + +instance (MonadIO m) => MonadOCR (TestM m) where + getWordsOnScreen = [] <$ registerMockCall Mock_getWordsOnScreen diff --git a/src/Chelleport.hs b/src/Chelleport.hs index 96c9fb6..4b44dd1 100644 --- a/src/Chelleport.hs +++ b/src/Chelleport.hs @@ -14,23 +14,18 @@ import Chelleport.Control ) import Chelleport.Draw (MonadDraw (windowPosition), cellSize) import Chelleport.KeySequence (findMatchPosition, generateGrid, isKeycodeDigit, isValidKey, keycodeToInt, nextChars, toKeyChar) +import Chelleport.OCR (MonadOCR, getWordsOnScreen) import Chelleport.Types -import Chelleport.Utils (intToCInt) +import Chelleport.Utils (cIntToInt, intToCInt, isEmpty, isNotEmpty) import qualified Chelleport.View import Control.Monad (forM_) import Control.Monad.IO.Class (MonadIO) import Control.Monad.Reader (ReaderT (runReaderT)) +import Data.Char (toLower) +import Data.List (isInfixOf) import Data.Maybe (fromMaybe, isJust) import qualified SDL --- run :: IO () --- run = do --- ctx <- initializeContext --- benchmark "ocr" $ do --- res <- (`runReaderT` ctx) . runAppM $ getWordsOnScreen --- print $ "---" ++ show (length res) --- pure () - run :: IO () run = do ctx <- initializeContext @@ -45,19 +40,10 @@ run = do runAppWithCtx :: (MonadIO m) => DrawContext -> AppM m x -> m x runAppWithCtx ctx = (`runReaderT` ctx) . runAppM -initialState :: (Monad m) => m State +initialState :: (Monad m) => m (State, Maybe AppAction) initialState = do let cells = fromMaybe (pure undefined) $ generateGrid 0 (rows, columns) hintKeys - pure $ - State - { stateGrid = cells, - stateKeySequence = [], - stateIsMatched = False, - stateIsShiftPressed = False, - stateIsDragging = False, - stateRepetition = 1, - stateMode = ModeSearch - } + pure (defaultAppState {stateGrid = cells}, Just $ SetMode defaultHintsMode) where rows = 9 columns = 16 @@ -79,6 +65,12 @@ eventHandler event = -- 0-9 | isKeycodeDigit (eventToKeycode 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 -- Space / Shift+Space | isKeyPressWith ev SDL.KeycodeSpace -> if withShift ev @@ -101,9 +93,18 @@ eventHandler event = Just $ UpdateShiftState False _ -> Nothing -update :: (MonadAppShell m, MonadDraw m, MonadControl m) => State -> AppAction -> m (State, Maybe AppAction) --- Act on key inputs -update state (HandleKeyInput key) = do +update :: (MonadAppShell m, MonadDraw m, MonadControl m, MonadOCR m) => State -> AppAction -> m (State, Maybe AppAction) +-- Set mode +update state (SetMode mode) = do + case mode of + ModeHints -> pure (state {stateMode = mode}, Nothing) + ModeSearch {} -> do + wordsOnScreen <- getWordsOnScreen + 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 (Just keyChar, Just validChars') | stateIsMatched state && keyChar `elem` ("HJKL" :: String) -> do @@ -114,35 +115,51 @@ update state (HandleKeyInput key) = do let newKeySequence = stateKeySequence state ++ [keyChar] let matchPosition = findMatchPosition newKeySequence $ stateGrid state let state' = state {stateKeySequence = newKeySequence, stateIsMatched = isJust matchPosition} - pure (state', MoveMousePosition <$> matchPosition) + action <- maybe (pure Nothing) (fmap (Just . MoveMousePosition) . getPosition) matchPosition + pure (state', action) _ -> pure (state, Nothing) where validChars = nextChars (stateKeySequence state) (stateGrid state) + getPosition (row, col) = do + (wcell, hcell) <- cellSize state + let x = (wcell `div` 2) + wcell * intToCInt col + let y = (hcell `div` 2) + hcell * intToCInt row + (winx, winy) <- windowPosition + pure (cIntToInt $ winx + x, cIntToInt $ winy + y) incrementValue = do (wcell, hcell) <- cellSize state if stateIsShiftPressed state then pure (wcell `div` 4, hcell `div` 4) 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 + Just keyChar -> do + let searchText = searchInputText ++ [toLower keyChar] + let matches = filterMatches searchText + let highlightedWord = if isNotEmpty matches then Just $ head matches else Nothing + let updatedMode = (stateMode state) {searchInputText = searchText, searchFilteredWords = matches} + pure (state {stateMode = updatedMode}, MoveMousePosition . wordPosition <$> highlightedWord) + _ -> do + pure (state, Nothing) + where + wordPosition w = (cIntToInt $ matchStartX w, cIntToInt $ matchStartY w) + filterMatches text + | isEmpty text = searchWords + | otherwise = filter (isInfixOf text . map toLower . matchText) searchWords + -- Move mouse incrementally update state (IncrementMouseCursor (incX, incY)) = do (curX, curY) <- getMousePointerPosition - let count = intToCInt $ case stateRepetition state of 0 -> 1; n -> n - moveMousePointer (curX + count * intToCInt incX) (curY + count * intToCInt incY) - pure (state {stateRepetition = 1}, Nothing) + let count = case stateRepetition state of 0 -> 1; n -> n + let pos = (cIntToInt curX + count * incX, cIntToInt curY + count * incY) + pure (state {stateRepetition = 1}, Just $ MoveMousePosition pos) -- Move mouse to given position -update state (MoveMousePosition (row, col)) = do - (x, y) <- getPosition - moveMousePointer x y +update state (MoveMousePosition (x, y)) = do + moveMousePointer (intToCInt x) (intToCInt y) pure (state, Nothing) - where - getPosition = do - (wcell, hcell) <- cellSize state - let x = (wcell `div` 2) + wcell * intToCInt col - let y = (hcell `div` 2) + hcell * intToCInt row - (winx, winy) <- windowPosition - pure (winx + x, winy + y) -- Reset entered key sequence and state update state ResetKeys = do diff --git a/src/Chelleport/AppShell.hs b/src/Chelleport/AppShell.hs index ba31982..2e95a85 100644 --- a/src/Chelleport/AppShell.hs +++ b/src/Chelleport/AppShell.hs @@ -35,18 +35,19 @@ type EventHandler state appAction = state -> SDL.Event -> Maybe appAction type View m state = state -> m () -type Initializer m state = m state +type Initializer m state appAction = m (state, Maybe appAction) setupAppShell :: (MonadIO m) => DrawContext -> - Initializer m state -> + Initializer m state appAction -> Update m state appAction -> EventHandler state appAction -> View m state -> m () -setupAppShell (DrawContext {ctxRenderer = renderer}) getInitState update eventHandler draw = - getInitState >>= appLoop +setupAppShell (DrawContext {ctxRenderer = renderer}) getInitState update eventHandler draw = do + state <- getInitState >>= evalUpdateResult + appLoop state where appLoop currentState = do SDL.rendererDrawColor renderer $= colorBackground diff --git a/src/Chelleport/Control.hs b/src/Chelleport/Control.hs index 80be6f8..f7a9c66 100644 --- a/src/Chelleport/Control.hs +++ b/src/Chelleport/Control.hs @@ -64,10 +64,10 @@ instance (MonadIO m) => MonadControl (AppM m) where X11.sync display False isKeyPressed :: SDL.KeyboardEventData -> Bool -isKeyPressed = (== SDL.Pressed) . SDL.keyboardEventKeyMotion +isKeyPressed = (SDL.Pressed ==) . SDL.keyboardEventKeyMotion isKeyRelease :: SDL.KeyboardEventData -> Bool -isKeyRelease = (== SDL.Released) . SDL.keyboardEventKeyMotion +isKeyRelease = (SDL.Released ==) . SDL.keyboardEventKeyMotion eventToKeycode :: SDL.KeyboardEventData -> SDL.Keycode eventToKeycode = SDL.keysymKeycode . SDL.keyboardEventKeysym diff --git a/src/Chelleport/Draw.hs b/src/Chelleport/Draw.hs index 3f67848..3720ba0 100644 --- a/src/Chelleport/Draw.hs +++ b/src/Chelleport/Draw.hs @@ -14,6 +14,7 @@ class (Monad m) => MonadDraw m where drawLine :: (CInt, CInt) -> (CInt, CInt) -> m () drawText :: (CInt, CInt) -> Color -> Text -> m (CInt, CInt) drawCircle :: Int -> (CInt, CInt) -> m () + fillRect :: (CInt, CInt) -> (CInt, CInt) -> m () setDrawColor :: Color -> m () windowSize :: m (CInt, CInt) windowPosition :: m (CInt, CInt) @@ -27,6 +28,11 @@ instance (MonadIO m) => MonadDraw (AppM m) where renderer <- asks ctxRenderer SDL.rendererDrawColor renderer $= color + fillRect (x, y) (w, h) = do + renderer <- asks ctxRenderer + 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 surface <- TTF.blended font color text @@ -65,6 +71,9 @@ instance (MonadIO m) => MonadDraw (AppM m) where SDL.V2 x y <- asks ctxWindow >>= SDL.getWindowAbsolutePosition pure (x, y) +fillRectVertices :: (MonadDraw m) => (CInt, CInt) -> (CInt, CInt) -> m () +fillRectVertices (x1, y1) (x2, y2) = fillRect (x1, y1) (x2 - x1, y2 - y1) + cellSize :: (MonadDraw m) => State -> m (CInt, CInt) cellSize (State {stateGrid}) = do (width, height) <- windowSize diff --git a/src/Chelleport/OCR.hs b/src/Chelleport/OCR.hs index 496b6a0..f6bc5b9 100644 --- a/src/Chelleport/OCR.hs +++ b/src/Chelleport/OCR.hs @@ -1,8 +1,8 @@ -module Chelleport.OCR (getWordsOnScreen) where +module Chelleport.OCR (MonadOCR (..)) where import Chelleport.Types import Control.Monad.IO.Class (MonadIO (liftIO)) -import Control.Monad.RWS (asks) +import Control.Monad.RWS (MonadReader (ask)) import qualified Data.ByteString as BS import Foreign (Bits (shiftR), Ptr, Storable (peek, pokeByteOff), alloca, allocaBytes, peekArray, (.&.)) import Foreign.C (CInt, CString, newCString) @@ -15,48 +15,39 @@ import System.IO (hPutStrLn) import System.IO.Temp (emptySystemTempFile) foreign import ccall unsafe "libchelleport.h findWordCoordinates" - c_findWordCoordinates :: CString -> Ptr CInt -> IO (Ptr OCRMatch) + c_getAllWordCoordinates :: CString -> Ptr CInt -> IO (Ptr OCRMatch) class (Monad m) => MonadOCR m where getWordsOnScreen :: m [OCRMatch] instance (MonadIO m) => MonadOCR (AppM m) where getWordsOnScreen = do - SDL.V2 width height <- asks ctxWindow >>= SDL.get . SDL.windowSize - SDL.V2 x y <- asks ctxWindow >>= SDL.getWindowAbsolutePosition + 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 (x, y) (width, height) + imgFilePath <- liftIO $ createTemporaryScreenshot ctx (x, y) (width, height) findWordCoordinates imgFilePath <* removeFile imgFilePath findWordCoordinates :: String -> IO [OCRMatch] findWordCoordinates imgPath = alloca $ \sizePtr -> do imgPathC <- newCString imgPath - arrayPtr <- c_findWordCoordinates imgPathC sizePtr + arrayPtr <- c_getAllWordCoordinates imgPathC sizePtr size <- peek sizePtr peekArray (fromIntegral size) arrayPtr -createTemporaryScreenshot :: (CInt, CInt) -> (CInt, CInt) -> IO String -createTemporaryScreenshot offset size = do +createTemporaryScreenshot :: DrawContext -> (CInt, CInt) -> (CInt, CInt) -> IO String +createTemporaryScreenshot ctx offset size = do tmpFilePath <- emptySystemTempFile "chelleport-screenshot.png" - screenshot tmpFilePath offset size + screenshot ctx tmpFilePath offset size pure tmpFilePath -screenshot :: String -> (CInt, CInt) -> (CInt, CInt) -> IO () -screenshot filename (offsetX, offsetY) (width, height) = do - dpy <- X11.openDisplay "" - root <- X11.rootWindow dpy (X11.defaultScreen dpy) +screenshot :: DrawContext -> String -> (CInt, CInt) -> (CInt, CInt) -> IO () +screenshot (DrawContext {ctxX11Display = display}) filename (offsetX, offsetY) (width, height) = do + root <- X11.rootWindow display (X11.defaultScreen display) - image <- - X11.getImage - dpy - root - offsetX - offsetY - (fromIntegral width) - (fromIntegral height) - (fromIntegral X11.allPlanes_aux) - X11.zPixmap + image <- X11.getImage display root offsetX offsetY (fromIntegral width) (fromIntegral height) (fromIntegral X11.allPlanes_aux) X11.zPixmap allocaBytes (fromIntegral $ width * height * 3) $ \ptr -> do let getPixel :: CInt -> CInt -> IO () @@ -74,7 +65,6 @@ screenshot filename (offsetX, offsetY) (width, height) = do savePPMFile filename (fromIntegral width) (fromIntegral height) rgbData X11.destroyImage image - X11.closeDisplay dpy savePPMFile :: FilePath -> Int -> Int -> BS.ByteString -> IO () savePPMFile path width height rgbData = withFile path WriteMode $ \h -> do diff --git a/src/Chelleport/Types.hs b/src/Chelleport/Types.hs index cb580e0..9894e54 100644 --- a/src/Chelleport/Types.hs +++ b/src/Chelleport/Types.hs @@ -17,9 +17,21 @@ type KeySequence = [Char] type KeyGrid = [[Cell]] -data Mode = ModeHints | ModeSearch +data Mode + = ModeHints + | ModeSearch + { searchWords :: [OCRMatch], + searchFilteredWords :: [OCRMatch], + searchInputText :: String + } deriving (Show, Eq) +defaultSearchMode :: Mode +defaultSearchMode = ModeSearch {searchWords = [], searchFilteredWords = [], searchInputText = ""} + +defaultHintsMode :: Mode +defaultHintsMode = ModeHints + data State = State { stateGrid :: KeyGrid, stateKeySequence :: KeySequence, @@ -31,6 +43,18 @@ data State = State } deriving (Show, Eq) +defaultAppState :: State +defaultAppState = + State + { stateGrid = [], + stateKeySequence = "", + stateIsMatched = False, + stateIsShiftPressed = False, + stateIsDragging = False, + stateRepetition = 1, + stateMode = ModeHints + } + data AppAction = ChainMouseClick MouseButtonType | HandleKeyInput SDL.Keycode @@ -44,6 +68,7 @@ data AppAction | TriggerMouseClick MouseButtonType | UpdateShiftState Bool | UpdateRepetition Int + | SetMode Mode deriving (Show, Eq) data DrawContext = DrawContext @@ -66,7 +91,7 @@ data OCRMatch = OCRMatch matchEndY :: !CInt, matchText :: !String } - deriving (Show) + deriving (Show, Eq) instance Storable OCRMatch where sizeOf _ = 4 * sizeOf (undefined :: CInt) + sizeOf (undefined :: Ptr CChar) diff --git a/src/Chelleport/View.hs b/src/Chelleport/View.hs index fe06fd7..1a4f1f8 100644 --- a/src/Chelleport/View.hs +++ b/src/Chelleport/View.hs @@ -10,7 +10,19 @@ import qualified Data.Text as Text import Foreign.C (CInt) render :: (MonadDraw m) => State -> m () -render state = do +render state = case stateMode state of + ModeHints -> renderHintsView state + ModeSearch {searchFilteredWords} -> renderSearchView state searchFilteredWords + +renderSearchView :: (MonadDraw m) => State -> [OCRMatch] -> m () +renderSearchView state matches = do + renderGridLines state + setDrawColor colorWhite + forM_ matches $ \(OCRMatch {matchStartX, matchStartY, matchEndX, matchEndY}) -> do + fillRectVertices (matchStartX, matchStartY) (matchEndX, matchEndY) + +renderHintsView :: (MonadDraw m) => State -> m () +renderHintsView state = do renderGridLines state (wcell, hcell) <- cellSize state |
