简体   繁体   中英

GTK: How do I grab keyboard input for a dialog/splash window, so that keyinput works out of window region?

I noticed that when my mouse is out of the dialog area, the keyboard input stops working.

This is detrimental since I want this small app to grab keyboard, so that I could handle it through keyboard without having to move my mouse.

I tried: windowSetKeepAbove , windowSetSkipPagerHint , windowSetSkipTaskbarHint , and windowPresentWithTime . I still could not focus in the window. None of these seem to work.

Also tried Seat.grab function, it gave me GDK_GRAB_NOT_VIEWABLE . But I am running this after calling showAll on the main window. Why is it not viewable?

I am so confused now. Any help would be appreciated.

EDIT: It is written in gi-gtk binding of haskell, but I don't think the language would be relevant - it is pretty much 1-1 binding to the gtk library itself. (Eg windowSetTypeHint corresponds to Gtk.Window.set_type_hint )

Here is the close-to-minimal reproducible example. (I guess things like windowSetPosition could have culled out, but it should not affect much. onWidgetKeyPressEvent is to hook into key press event)

{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE LambdaCase #-}

module Main where

import Control.Monad
import Data.Foldable
import Data.Text qualified as T
import GI.Gdk qualified as Gdk
import GI.Gio.Objects qualified as Gio
import GI.Gtk qualified as Gtk
import System.Exit

main :: IO ()
main = do
  -- Does not care crashing here
  Just app <- Gtk.applicationNew (Just $ T.pack "test.program") []
  Gio.onApplicationActivate app (activating app)
  status <- Gio.applicationRun app Nothing
  when (status /= 0) $ exitWith (ExitFailure $ fromIntegral status)
  where
    activating :: Gtk.Application -> IO ()
    activating app = do
      window <- Gtk.applicationWindowNew app >>= Gtk.toWindow
      Gtk.windowSetTitle window (T.pack "Test Program")
      Gtk.windowSetDefaultSize window 560 140
      Gtk.windowSetTypeHint window Gdk.WindowTypeHintDialog
      Gtk.windowSetPosition window Gtk.WindowPositionCenterAlways
      Gtk.windowSetKeepAbove window True
      Gtk.windowSetSkipPagerHint window True
      Gtk.windowSetSkipTaskbarHint window True
      Gtk.onWidgetKeyPressEvent window $
        Gdk.getEventKeyKeyval >=> \case
          Gdk.KEY_Escape -> True <$ Gtk.windowClose window
          _ -> pure False

      Gtk.widgetShowAll window

      screen <- Gtk.windowGetScreen window
      gdkWins <- Gdk.screenGetToplevelWindows screen
      seat <- Gdk.screenGetDisplay screen >>= Gdk.displayGetDefaultSeat
      event <- Gtk.getCurrentEvent
      putStrLn "Finding window"
      filterM (fmap (Gdk.WindowStateAbove `elem`) . Gdk.windowGetState) gdkWins
        >>= traverse_
          ( \win -> do
              putStrLn "Window found"
              Gdk.windowShow win
              stat <- Gdk.seatGrab seat win [Gdk.SeatCapabilitiesAll] True (Nothing @Gdk.Cursor) event Nothing
              print stat
          )

      pure ()

I know, horrible hack, but I don't know other ways to get Gdk.Window . Searched through the gtk library , could not find the way to take Gdk.Window out of Gtk.Window .

Still, it turns out that this hack have found the gdk window.

Running with eg cabal run prints:

Finding window
Window found
GrabStatusNotViewable

So I got: GDK_GRAB_NOT_VIEWABLE somehow. It turns out that later on when eg focus event is fired, grab works normally. But I want to grab the mouse/keyboard earlier.

It turns out that the window needs to be mapped to grab the seat. I suspect that showAll does not immediately map the window, so calling seatGrab there leads to an error.

Hence, the better way would be grabbing the focus at Widget's map-event . It conveniently carries the Gdk window field in its event struct, which I could use to grab the seat. In haskell, getEventAnyWindow gets the gdk window from the EventAny struct.

A haskell snippet setting window to grab the focus:

-- | Grab the screen on window map.
windowGrabOnMap :: MonadIO m => Window -> m ()
windowGrabOnMap window = do
  afterWidgetMapEvent window $
    Gdk.getEventAnyWindow >=> \case
      Nothing -> pure False
      Just win -> do
        event <- getCurrentEvent
        seat <- Gdk.windowGetDisplay win >>= Gdk.displayGetDefaultSeat
        Gdk.seatGrab seat win [Gdk.SeatCapabilitiesAll] True (Nothing @Gdk.Cursor) event Nothing
        pure False
  pure ()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM