Quitting a loop by pressing 'q'

I’ve got a long (possibly infinite) loop. For illustration:

function dosomething()
    for i=1:100
        println("doing something $i")
        sleep(1)
    end
end

dosomething()

Suppose I want to quit the loop early, by pressing ‘q’ on the keyboard (just ‘q’, no RET). The program is running in the terminal / REPL. How can this be accomplished? (Searched a bit for answers but they were from distant eras or specific to Gtk). It would be nice to have a general answer, but one that works on Linux is also enough. Also, it is best the Terminal isn’t dead if I don’t press ‘q’ or Julia crashes or I press ^C.

What objects to Ctrl+c (^C) ?

julia> function dosomething()
           for i=1:100
               println("doing something $i")
               sleep(1)
           end
       end
dosomething (generic function with 1 method)

julia> dosomething()
doing something 1
^CERROR: InterruptException:
Stacktrace:
 [1] poptask(W::Base.IntrusiveLinkedListSynchronized{Task})
   @ Base ./task.jl:974
 [2] wait()
   @ Base ./task.jl:983
 [3] wait(c::Base.GenericCondition{Base.Threads.SpinLock}; first::Bool)
   @ Base ./condition.jl:130
 [4] wait
   @ ./condition.jl:125 [inlined]
 [5] _trywait(t::Timer)
   @ Base ./asyncevent.jl:138
 [6] wait
   @ ./asyncevent.jl:155 [inlined]
 [7] sleep
   @ ./asyncevent.jl:240 [inlined]
 [8] dosomething()
   @ Main ./REPL[1]:4
 [9] top-level scope
   @ REPL[2]:1

julia> dosomething()
doing something 1
doing something 2
^CERROR: InterruptException:
Stacktrace:
 [1] poptask(W::Base.IntrusiveLinkedListSynchronized{Task})
   @ Base ./task.jl:974
 [2] wait()
   @ Base ./task.jl:983
 [3] wait(c::Base.GenericCondition{Base.Threads.SpinLock}; first::Bool)
   @ Base ./condition.jl:130
 [4] wait
   @ ./condition.jl:125 [inlined]
 [5] _trywait(t::Timer)
   @ Base ./asyncevent.jl:138
 [6] wait
   @ ./asyncevent.jl:155 [inlined]
 [7] sleep
   @ ./asyncevent.jl:240 [inlined]
 [8] dosomething()
   @ Main ./REPL[1]:4
 [9] top-level scope
   @ REPL[2]:1

Ctrl-C is nice. But ‘q’ is what I was looking for.

if readline() =="q" break end might work but will probably require pressing enter.

But that wouldn’t interrupt a very lengthy computation, right?

What @Dan apparently wants, is an interrupt but with q instead of Strg+c.

You could run it as a async task, some similar questions here Keyboard input monitoring - #6 by cjdoris, How to Detect Key Down Events? - #2 by albheim

4 Likes

These similar questions seem more to the point. I’ll try them and post an answer if my attempts are sufficiently successful.

UPDATE: Unfortunately, both answers don’t seem to work for me:

  • Keyboard input monitoring… uses terminal without definition.
  • How to Detect Key Down Events… doesn’t work either.

I’m using 1.9.2 on Linux. Any more suggestions? (hopefully with code).

The second link (“How to detect key down events”) works for me on Linux with 1.9.3. Do you get an error or just no response?
I see this, I pressed ‘w’ and then ‘q’:

julia> includet("keypress.jl")

julia> main()
Doing some long calculation
Doing some long calculation
Doing some long calculation
w is not a recognized command
Doing some long calculation
Doing some long calculation
quitting

Edit:

I just realized you said 1.9.2, it works on 1.9.2 for me as well.

1 Like

It works for me now too. Perhaps my session was wonky from previous attempts.
I see the same output as you do.

This is basically not possible for security reasons (on at least macOS), but you can do it sort of, still, at least as root/superuser.

This has historically been possible on all platforms, but unlike reading from the keyboard, waiting until pressing enter, which is possible (through use of C library), it’s not possible to portably monitor the keyboard, with C, and thus neither any other language.

But since the julia runtime is different for each platform, I actually thought maybe Julia could provide the non-portable code for this, i.e. provide a portable API for Julia (source) code apps (which would call the non-portable platform APIs). And maybe that it should. This could also be done through a package, but I thought an argument could be made for Julia to do it, for discoverability, to avert also that people would use non-portable e.g. Windows APIs, and tie their Julia (otherwise portable) source code to one platform.

For now I don’t know of any Julia package to do this but you can use others, e.g. this Python package I found, through PythonCall.jl. There are basically no great disadvantages (this is not speed-critical). It’s
LGPLv3-licenced which is good for basically everything, unless you have some “GPLv2-only code” to use with.

You might look at how pygame handles this to steal some ideas.

[There is a port of it, to Julia, GameZero.jl, available. Both use the SDL C library, and it likely handles, but not for terminals, rather (full-screen) windows?]

https://pynput.readthedocs.io/en/latest/limitations.html

Recent versions of macOS restrict monitoring of the keyboard for security reasons. For that reason, one of the following must be true:

  • The process must run as root.
  • Your application must be white listed under Enable access for assistive devices. Note that this might require that you package your application, since otherwise the entire Python installation must be white listed.
  • On versions after Mojave, you may also need to whitelist your terminal application if running your script from a terminal.

Please note that this does not apply to monitoring of the mouse or trackpad.

You can see there details for Linux and Windows. Obviously monitoring the keyboard is possible somehow, e.g. for games or simply for word processors, i.e. GUI apps, and while games may not be the traditional GUI-type, I assume they use the same platform GUI APIs, mostly. I.e. they function as if they were in a windows, but they can in many or most cases be full-screen. Then I don’t really see a security issue. It is I think about when you have many apps on screen (or even hidden with no visible window), why should all be able to monitor the keyboard? A keylogger is a known security risk, monitoring and logging e.g. all your passwords.

Since there is no risk, nor forbidden to monitor the mouse, maybe that is good enough for you? Ideally you would have a button on screen (requires Gtk.jl or other GUI toolkit?) to press. It’s likely no what you want that you simply press a button on the mouse and it triggers something in your app whereever the mouse pointer is located, in or out of your window.

At least in some Linux GUIs you can type into a window only when it has focus, and only when the pointer is over your window. It seems like less of a security risk, also would be annoying if you couldn’t type anything elsewhere without a q…

1 Like

This is too obtrusive for my taste. If the other solution wasn’t working, maybe I would have forged with this one, or given up on this UX. But the other solutions seems to be sufficient.

1 Like

Yes, readline requires pressing ENTER. I see the linked answer from the marked solution post using the built-into-Julia REPL.TerminalMenus.terminal doesn’t. It’s great that this is possible, and I confirmed on Linux; and when I think about it, e.g. for Julia’s REPL it must respond somehow, it e.g. echos q to the screen. I’m actually not sure how it’s implemented, for terminals (by it not Julia?), or e.g console windows in Windows. Possibly you can monitor the q, but not prevent it from appearing in the window (unlike what you would want for games). The security issue probably doesn’t apply when echoed to the screen (nor for full-screen), or otherwise it would contradict my other answer. I would like to know if there are some security or portability issues with that solution. At least I see there’s a potential issue with the undocumented REPL.Terminals.raw! function, and the possibility of error("unable to switch to raw mode") .