7

Let's say that I have an input("> "), and if you try to input a lowercase "Hello, world!" it will look like this:

> HELLO WORLD!
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
ignacy
  • 71
  • 3
  • I don't think that's possible. I believe `input()` just uses the terminal's standard input stream and consequently will just show exactly whatever was typed in. – ch4rl1e97 May 28 '23 at 16:50
  • @ch4rl1e97 It's certainly possible, it's just a bit of work and unintuitive so I don't blame you for saying its not. You have to change the terminal settings to turn off echoing, then handle the character input yourself (if interested, I did this for UNIX in [my answer below](https://stackoverflow.com/a/76357381/13376511)). It's a bit of reinventing the wheel, but nevertheless possible. – Michael M. May 29 '23 at 19:24

4 Answers4

5

No, there isn't a practical way to do this

It might be possible to edit the function somewhere deep in the source code, but that would be messy. You'd be best off writing a custom input function, using something like curses or keyboard to deal with the IO.

3

You could somewhat emulate this with pyautogui.

import pyautogui, ctypes
        
def upper_input(msg:str='write something: '):
    if not ctypes.WinDLL("User32.dll").GetKeyState(0x14):
        pyautogui.press('capslock')
    input(msg)  
    pyautogui.press('capslock')  
    
upper_input()   
OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
1

Here is a version that uses sys.stdout and pynput.

IMO, it's more useful to have a function that can be configured to output uppercase, as-well-as lowercase, capitalized, title or any other string method. It may also be useful to pass everything through a regex, to restrict input.

import sys, re, operator
from pynput      import keyboard
from dataclasses import dataclass, field

#commands / print only
BACKSPACE = 8
ENTER     = 13
L_ARROW   = 37
R_ARROW   = 39

#ranges to "keep" from
ALL    = tuple((9, *range(32, 127))) #all accepted characters range
ALL_AE = (9,32)                      #AttributeError accepted characters range (tab,space,np_dot)
ALL_TE = tuple((46, *range(48,58)))  #TypeError accepted characters range (0-9)

#numpad vk range
NPVK     = range(96,106)
NPVK_OFS = 48
#numpad decimal
DCVK     = 110
DCVK_OFS = 64


@dataclass
class _data:
    input:list[str] = field(default_factory=list[str])
    caret:int       = 0
    
    #conveniences
    
    @property
    def output(self) -> str: 
        return "".join(self.input)
    
    @property
    def xput(self) -> list[str]: 
        return (self.input or [None]*(self.caret+1))
    
    def i(self, func:callable, d:int=0) -> int:
        return (-1, self.caret+d)[func(self.caret,len(self.input))]


""" INPUT 2
    *all arguments are optional
    @prompt     - the user prompt
    @fmt        - str.lower, str.upper, str.title... or any similar function reference
    @expr/flags - `re.compile(@expr, @flags).match(@fmt(input_data))`
    @req        - `re.compile(@req, @flags).match(final_data)`
    
    supports: 
    * backspace
    * numpad
    * arrow keys (locked to span of input)
    
    
    ! home, end, pgup, pgdn, ins, del, prt sc & pause, do nothing
"""
def input2(prompt:str='', fmt:callable=None, expr:str=None, flags:int=0, req:str=None) -> str:
    #default
    fmt = fmt or (lambda c: c)

    #for matching `fmt` results
    match = re.compile(fr'{expr or r"."}|[\b\n]$', flags).match
    
    #for matching final results, upon submission
    req = False if not req else re.compile(fr'{req}', flags).match

    #print prompt
    sys.stdout.write(prompt)
    sys.stdout.flush()

    #input storage, caret position
    d = _data()

    #keypress handler
    def emulator(key) -> bool:
        #get and test ordinal, one way or another
        try:
            i  = ord(key.char)
            ok = i in ALL
        except AttributeError:
            i  = key.value.vk 
            ok = i in ALL_AE + (BACKSPACE, ENTER)
        except TypeError:
            i  = key.vk 
            #reformat numpad vk to number ord
            i -= NPVK_OFS * (i in NPVK)
            #reformat numpad decimal vk to dot ord
            i -= DCVK_OFS * (i == DCVK) 
            ok = i in ALL_TE
            
        if ok:
            #get character 
            c = chr(i)
            e = False #assume enter is not being pressed
    
            #character filtering / storage
            if t := (i in ALL): 
                #copy
                tmp = d.input[:]
                
                #modify
                if d.caret >= len(tmp): tmp.append(c)
                else                  : tmp[d.caret] = c
                    
                #format
                tmp = fmt(''.join(tmp))
    
                #check
                if not match(tmp): return True
                
                #update
                d.input = list(tmp)
    
            #backspace        
            elif i==BACKSPACE: 
                #accomodate for maximum tab space and/or character to be popped
                n = sum(3*(c=='\t') for c in d.input)+1 
                
                #adjust console if d.input was modified
                if d.xput.pop(d.i(operator.le, -1)):
                    L = len(d.input)
                    
                    #blank line
                    blank = chr(0)*(len(prompt)+L+n) 
                    
                    #update caret
                    d.caret -= 1  
                    
                    sys.stdout.write(f'\r{blank}\r')         #erase line
                    sys.stdout.write(f'{prompt}{d.output}')  #reprint line
                    sys.stdout.write('\b'*(L-d.caret))       #place caret
                    sys.stdout.flush()
                    
                return True
                
            #enter with required
            elif (e := (i==ENTER)) and req:
                if not req(d.output): return True
    
            #decide the proper character to print
            tmp = d.xput[d.i(operator.lt)]
            tmp = ('\n', tmp or '')[(not e) & t]
            
            #update caret
            d.caret += 1
            
            #print character
            sys.stdout.write(tmp)
            sys.stdout.flush()
            
            #quit on enter
            return not e
            
        #arrow keys
        elif i in (L_ARROW, R_ARROW):
            r = i==R_ARROW
            if -r < d.caret <= (len(d.input)-r):
                tmp = d.xput[d.i(operator.lt)]
                tmp = ('\b',tmp or '')[r]
                
                d.caret += (-1,1)[r]
                
                sys.stdout.write(tmp)
                sys.stdout.flush()
                
        return True
        
    #listen for keys
    with keyboard.Listener(on_press=emulator) as listener:
        listener.join()
     
    return d.output



if __name__ == '__main__':
    strawman = input2('Full Name: ', str.upper, r'^[a-z ]+$', re.I, r'^[a-z]{2,} [a-z]{2,}$')
    print(strawman)

repository: input2 (github)

OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
1

In response to TheTridentGuy's answer, here is that (slightly) impractical way:

If you're restricting yourself to UNIX, then you can use the built-in termios and tty modules to put the terminal in cbreak mode. From there, you can uppercase each individual character you get and use print() and sys.stdout.flush(). You'll also want to register a function with atexit to restore the terminal then unregister at the end of your function, just to make sure the terminal settings are always restored (if you don't do this, it'll leave your terminal in a weird mode and make other parts of your program not work). Part of this idea was inspired by this answer:

import atexit
import sys
import select
import tty
import termios


def upper_input(prompt):
    # get current terminal settings
    old_settings = termios.tcgetattr(sys.stdin)

    # Returns true if there's data for us to read using select
    def isData():
        return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

    # restores the terminal settings to what they were
    def restoreSettings():
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

    # print the prompt
    print(prompt, end='')
    sys.stdout.flush()

    try:
        # change the terminal settings
        tty.setcbreak(sys.stdin.fileno())
        atexit.register(restoreSettings)

        # keep looping until we get a newline, adding each character to a
        # growing string and printing out each character we get as uppercase
        s = ''
        while 1:
            if isData():
                c = sys.stdin.read(1)
                print(c.upper(), end='')
                sys.stdout.flush()

                if c == '\n':
                    break
                s += c.upper()
    finally:
        # restore the terminal settings
        restoreSettings()
        atexit.unregister(restoreSettings)
    return s


inp = upper_input('> ')
print(f'Hi, {inp}')

You could probably port this to Windows using something similar, but I don't have a Windows machine.

Michael M.
  • 10,486
  • 9
  • 18
  • 34