You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
824 lines
30 KiB
824 lines
30 KiB
# -*- coding: utf-8 -*-
|
|
#*****************************************************************************
|
|
# Copyright (C) 2003-2006 Gary Bishop.
|
|
# Copyright (C) 2006 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
|
|
#
|
|
# Distributed under the terms of the BSD License. The full license is in
|
|
# the file COPYING, distributed as part of this software.
|
|
#*****************************************************************************
|
|
from __future__ import print_function, unicode_literals, absolute_import
|
|
'''Cursor control and color for the Windows console.
|
|
|
|
This was modeled after the C extension of the same name by Fredrik Lundh.
|
|
'''
|
|
|
|
# primitive debug printing that won't interfere with the screen
|
|
|
|
import sys,os
|
|
import traceback
|
|
import re
|
|
|
|
import pyreadline.unicode_helper as unicode_helper
|
|
|
|
from pyreadline.logger import log
|
|
from pyreadline.unicode_helper import ensure_unicode, ensure_str
|
|
from pyreadline.keysyms import make_KeyPress, KeyPress
|
|
from pyreadline.console.ansi import AnsiState, AnsiWriter
|
|
|
|
try:
|
|
import ctypes.util
|
|
from ctypes import *
|
|
from _ctypes import call_function
|
|
from ctypes.wintypes import *
|
|
except ImportError:
|
|
raise ImportError("You need ctypes to run this code")
|
|
|
|
if sys.version_info < (2, 6):
|
|
bytes = str
|
|
|
|
def nolog(string):
|
|
pass
|
|
|
|
log = nolog
|
|
|
|
|
|
# some constants we need
|
|
STD_INPUT_HANDLE = -10
|
|
STD_OUTPUT_HANDLE = -11
|
|
ENABLE_WINDOW_INPUT = 0x0008
|
|
ENABLE_MOUSE_INPUT = 0x0010
|
|
ENABLE_PROCESSED_INPUT = 0x0001
|
|
WHITE = 0x7
|
|
BLACK = 0
|
|
MENU_EVENT = 0x0008
|
|
KEY_EVENT = 0x0001
|
|
MOUSE_MOVED = 0x0001
|
|
MOUSE_EVENT = 0x0002
|
|
WINDOW_BUFFER_SIZE_EVENT = 0x0004
|
|
FOCUS_EVENT = 0x0010
|
|
MENU_EVENT = 0x0008
|
|
VK_SHIFT = 0x10
|
|
VK_CONTROL = 0x11
|
|
VK_MENU = 0x12
|
|
GENERIC_READ = int(0x80000000)
|
|
GENERIC_WRITE = 0x40000000
|
|
|
|
# Windows structures we'll need later
|
|
class COORD(Structure):
|
|
_fields_ = [("X", c_short),
|
|
("Y", c_short)]
|
|
|
|
class SMALL_RECT(Structure):
|
|
_fields_ = [("Left", c_short),
|
|
("Top", c_short),
|
|
("Right", c_short),
|
|
("Bottom", c_short)]
|
|
|
|
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
|
|
_fields_ = [("dwSize", COORD),
|
|
("dwCursorPosition", COORD),
|
|
("wAttributes", c_short),
|
|
("srWindow", SMALL_RECT),
|
|
("dwMaximumWindowSize", COORD)]
|
|
|
|
class CHAR_UNION(Union):
|
|
_fields_ = [("UnicodeChar", c_wchar),
|
|
("AsciiChar", c_char)]
|
|
|
|
class CHAR_INFO(Structure):
|
|
_fields_ = [("Char", CHAR_UNION),
|
|
("Attributes", c_short)]
|
|
|
|
class KEY_EVENT_RECORD(Structure):
|
|
_fields_ = [("bKeyDown", c_byte),
|
|
("pad2", c_byte),
|
|
('pad1', c_short),
|
|
("wRepeatCount", c_short),
|
|
("wVirtualKeyCode", c_short),
|
|
("wVirtualScanCode", c_short),
|
|
("uChar", CHAR_UNION),
|
|
("dwControlKeyState", c_int)]
|
|
|
|
class MOUSE_EVENT_RECORD(Structure):
|
|
_fields_ = [("dwMousePosition", COORD),
|
|
("dwButtonState", c_int),
|
|
("dwControlKeyState", c_int),
|
|
("dwEventFlags", c_int)]
|
|
|
|
class WINDOW_BUFFER_SIZE_RECORD(Structure):
|
|
_fields_ = [("dwSize", COORD)]
|
|
|
|
class MENU_EVENT_RECORD(Structure):
|
|
_fields_ = [("dwCommandId", c_uint)]
|
|
|
|
class FOCUS_EVENT_RECORD(Structure):
|
|
_fields_ = [("bSetFocus", c_byte)]
|
|
|
|
class INPUT_UNION(Union):
|
|
_fields_ = [("KeyEvent", KEY_EVENT_RECORD),
|
|
("MouseEvent", MOUSE_EVENT_RECORD),
|
|
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
|
|
("MenuEvent", MENU_EVENT_RECORD),
|
|
("FocusEvent", FOCUS_EVENT_RECORD)]
|
|
|
|
class INPUT_RECORD(Structure):
|
|
_fields_ = [("EventType", c_short),
|
|
("Event", INPUT_UNION)]
|
|
|
|
class CONSOLE_CURSOR_INFO(Structure):
|
|
_fields_ = [("dwSize", c_int),
|
|
("bVisible", c_byte)]
|
|
|
|
|
|
# I didn't want to have to individually import these so I made a list, they are
|
|
# added to the Console class later in this file.
|
|
|
|
funcs = [
|
|
'AllocConsole',
|
|
'CreateConsoleScreenBuffer',
|
|
'FillConsoleOutputAttribute',
|
|
'FillConsoleOutputCharacterW',
|
|
'FreeConsole',
|
|
'GetConsoleCursorInfo',
|
|
'GetConsoleMode',
|
|
'GetConsoleScreenBufferInfo',
|
|
'GetConsoleTitleW',
|
|
'GetProcAddress',
|
|
'GetStdHandle',
|
|
'PeekConsoleInputW',
|
|
'ReadConsoleInputW',
|
|
'ScrollConsoleScreenBufferW',
|
|
'SetConsoleActiveScreenBuffer',
|
|
'SetConsoleCursorInfo',
|
|
'SetConsoleCursorPosition',
|
|
'SetConsoleMode',
|
|
'SetConsoleScreenBufferSize',
|
|
'SetConsoleTextAttribute',
|
|
'SetConsoleTitleW',
|
|
'SetConsoleWindowInfo',
|
|
'WriteConsoleW',
|
|
'WriteConsoleOutputCharacterW',
|
|
'WriteFile',
|
|
]
|
|
|
|
# I don't want events for these keys, they are just a bother for my application
|
|
key_modifiers = { VK_SHIFT : 1,
|
|
VK_CONTROL : 1,
|
|
VK_MENU : 1, # alt key
|
|
0x5b : 1, # windows key
|
|
}
|
|
|
|
def split_block(text, size=1000):
|
|
return [text[start:start + size] for start in range(0, len(text), size)]
|
|
|
|
|
|
|
|
class Console(object):
|
|
'''Console driver for Windows.
|
|
|
|
'''
|
|
|
|
def __init__(self, newbuffer=0):
|
|
'''Initialize the Console object.
|
|
|
|
newbuffer=1 will allocate a new buffer so the old content will be restored
|
|
on exit.
|
|
'''
|
|
#Do I need the following line? It causes a console to be created whenever
|
|
#readline is imported into a pythonw application which seems wrong. Things
|
|
#seem to work without it...
|
|
#self.AllocConsole()
|
|
|
|
if newbuffer:
|
|
self.hout = self.CreateConsoleScreenBuffer(
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0, None, 1, None)
|
|
self.SetConsoleActiveScreenBuffer(self.hout)
|
|
else:
|
|
self.hout = self.GetStdHandle(STD_OUTPUT_HANDLE)
|
|
|
|
self.hin = self.GetStdHandle(STD_INPUT_HANDLE)
|
|
self.inmode = DWORD(0)
|
|
self.GetConsoleMode(self.hin, byref(self.inmode))
|
|
self.SetConsoleMode(self.hin, 0xf)
|
|
info = CONSOLE_SCREEN_BUFFER_INFO()
|
|
self.GetConsoleScreenBufferInfo(self.hout, byref(info))
|
|
self.attr = info.wAttributes
|
|
self.saveattr = info.wAttributes # remember the initial colors
|
|
self.defaultstate = AnsiState()
|
|
self.defaultstate.winattr = info.wAttributes
|
|
self.ansiwriter = AnsiWriter(self.defaultstate)
|
|
|
|
background = self.attr & 0xf0
|
|
for escape in self.escape_to_color:
|
|
if self.escape_to_color[escape] is not None:
|
|
self.escape_to_color[escape] |= background
|
|
log('initial attr=%x' % self.attr)
|
|
self.softspace = 0 # this is for using it as a file-like object
|
|
self.serial = 0
|
|
|
|
self.pythondll = ctypes.pythonapi
|
|
self.inputHookPtr = \
|
|
c_void_p.from_address(addressof(self.pythondll.PyOS_InputHook)).value
|
|
|
|
if sys.version_info[:2] > (3, 4):
|
|
self.pythondll.PyMem_RawMalloc.restype = c_size_t
|
|
self.pythondll.PyMem_Malloc.argtypes = [c_size_t]
|
|
setattr(Console, 'PyMem_Malloc', self.pythondll.PyMem_RawMalloc)
|
|
else:
|
|
self.pythondll.PyMem_Malloc.restype = c_size_t
|
|
self.pythondll.PyMem_Malloc.argtypes = [c_size_t]
|
|
setattr(Console, 'PyMem_Malloc', self.pythondll.PyMem_Malloc)
|
|
|
|
|
|
|
|
|
|
def __del__(self):
|
|
'''Cleanup the console when finished.'''
|
|
# I don't think this ever gets called
|
|
self.SetConsoleTextAttribute(self.hout, self.saveattr)
|
|
self.SetConsoleMode(self.hin, self.inmode)
|
|
self.FreeConsole()
|
|
|
|
def _get_top_bot(self):
|
|
info = CONSOLE_SCREEN_BUFFER_INFO()
|
|
self.GetConsoleScreenBufferInfo(self.hout, byref(info))
|
|
rect = info.srWindow
|
|
top = rect.Top
|
|
bot = rect.Bottom
|
|
return top,bot
|
|
|
|
def fixcoord(self, x, y):
|
|
'''Return a long with x and y packed inside,
|
|
also handle negative x and y.'''
|
|
if x < 0 or y < 0:
|
|
info = CONSOLE_SCREEN_BUFFER_INFO()
|
|
self.GetConsoleScreenBufferInfo(self.hout, byref(info))
|
|
if x < 0:
|
|
x = info.srWindow.Right - x
|
|
y = info.srWindow.Bottom + y
|
|
|
|
# this is a hack! ctypes won't pass structures but COORD is
|
|
# just like a long, so this works.
|
|
return c_int(y << 16 | x)
|
|
|
|
def pos(self, x=None, y=None):
|
|
'''Move or query the window cursor.'''
|
|
if x is None:
|
|
info = CONSOLE_SCREEN_BUFFER_INFO()
|
|
self.GetConsoleScreenBufferInfo(self.hout, byref(info))
|
|
return (info.dwCursorPosition.X, info.dwCursorPosition.Y)
|
|
else:
|
|
return self.SetConsoleCursorPosition(self.hout,
|
|
self.fixcoord(x, y))
|
|
|
|
def home(self):
|
|
'''Move to home.'''
|
|
self.pos(0, 0)
|
|
|
|
# Map ANSI color escape sequences into Windows Console Attributes
|
|
|
|
terminal_escape = re.compile('(\001?\033\\[[0-9;]+m\002?)')
|
|
escape_parts = re.compile('\001?\033\\[([0-9;]+)m\002?')
|
|
escape_to_color = { '0;30': 0x0, #black
|
|
'0;31': 0x4, #red
|
|
'0;32': 0x2, #green
|
|
'0;33': 0x4+0x2, #brown?
|
|
'0;34': 0x1, #blue
|
|
'0;35': 0x1+0x4, #purple
|
|
'0;36': 0x2+0x4, #cyan
|
|
'0;37': 0x1+0x2+0x4, #grey
|
|
'1;30': 0x1+0x2+0x4, #dark gray
|
|
'1;31': 0x4+0x8, #red
|
|
'1;32': 0x2+0x8, #light green
|
|
'1;33': 0x4+0x2+0x8, #yellow
|
|
'1;34': 0x1+0x8, #light blue
|
|
'1;35': 0x1+0x4+0x8, #light purple
|
|
'1;36': 0x1+0x2+0x8, #light cyan
|
|
'1;37': 0x1+0x2+0x4+0x8, #white
|
|
'0': None,
|
|
}
|
|
|
|
# This pattern should match all characters that change the cursor position differently
|
|
# than a normal character.
|
|
motion_char_re = re.compile('([\n\r\t\010\007])')
|
|
|
|
def write_scrolling(self, text, attr=None):
|
|
'''write text at current cursor position while watching for scrolling.
|
|
|
|
If the window scrolls because you are at the bottom of the screen
|
|
buffer, all positions that you are storing will be shifted by the
|
|
scroll amount. For example, I remember the cursor position of the
|
|
prompt so that I can redraw the line but if the window scrolls,
|
|
the remembered position is off.
|
|
|
|
This variant of write tries to keep track of the cursor position
|
|
so that it will know when the screen buffer is scrolled. It
|
|
returns the number of lines that the buffer scrolled.
|
|
|
|
'''
|
|
text = ensure_unicode(text)
|
|
x, y = self.pos()
|
|
w, h = self.size()
|
|
scroll = 0 # the result
|
|
# split the string into ordinary characters and funny characters
|
|
chunks = self.motion_char_re.split(text)
|
|
for chunk in chunks:
|
|
n = self.write_color(chunk, attr)
|
|
if len(chunk) == 1: # the funny characters will be alone
|
|
if chunk[0] == '\n': # newline
|
|
x = 0
|
|
y += 1
|
|
elif chunk[0] == '\r': # carriage return
|
|
x = 0
|
|
elif chunk[0] == '\t': # tab
|
|
x = 8 * (int(x / 8) + 1)
|
|
if x > w: # newline
|
|
x -= w
|
|
y += 1
|
|
elif chunk[0] == '\007': # bell
|
|
pass
|
|
elif chunk[0] == '\010':
|
|
x -= 1
|
|
if x < 0:
|
|
y -= 1 # backed up 1 line
|
|
else: # ordinary character
|
|
x += 1
|
|
if x == w: # wrap
|
|
x = 0
|
|
y += 1
|
|
if y == h: # scroll
|
|
scroll += 1
|
|
y = h - 1
|
|
else: # chunk of ordinary characters
|
|
x += n
|
|
l = int(x / w) # lines we advanced
|
|
x = x % w # new x value
|
|
y += l
|
|
if y >= h: # scroll
|
|
scroll += y - h + 1
|
|
y = h - 1
|
|
return scroll
|
|
|
|
def write_color(self, text, attr=None):
|
|
text = ensure_unicode(text)
|
|
n, res= self.ansiwriter.write_color(text, attr)
|
|
junk = DWORD(0)
|
|
for attr,chunk in res:
|
|
log("console.attr:%s"%(attr))
|
|
log("console.chunk:%s"%(chunk))
|
|
self.SetConsoleTextAttribute(self.hout, attr.winattr)
|
|
for short_chunk in split_block(chunk):
|
|
self.WriteConsoleW(self.hout, short_chunk,
|
|
len(short_chunk), byref(junk), None)
|
|
return n
|
|
|
|
def write_plain(self, text, attr=None):
|
|
'''write text at current cursor position.'''
|
|
text = ensure_unicode(text)
|
|
log('write("%s", %s)' %(text, attr))
|
|
if attr is None:
|
|
attr = self.attr
|
|
junk = DWORD(0)
|
|
self.SetConsoleTextAttribute(self.hout, attr)
|
|
for short_chunk in split_block(chunk):
|
|
self.WriteConsoleW(self.hout, ensure_unicode(short_chunk),
|
|
len(short_chunk), byref(junk), None)
|
|
return len(text)
|
|
|
|
#This function must be used to ensure functioning with EMACS
|
|
#Emacs sets the EMACS environment variable
|
|
if "EMACS" in os.environ:
|
|
def write_color(self, text, attr=None):
|
|
text = ensure_str(text)
|
|
junk = DWORD(0)
|
|
self.WriteFile(self.hout, text, len(text), byref(junk), None)
|
|
return len(text)
|
|
write_plain = write_color
|
|
|
|
# make this class look like a file object
|
|
def write(self, text):
|
|
text = ensure_unicode(text)
|
|
log('write("%s")' % text)
|
|
return self.write_color(text)
|
|
|
|
#write = write_scrolling
|
|
|
|
def isatty(self):
|
|
return True
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
def page(self, attr=None, fill=' '):
|
|
'''Fill the entire screen.'''
|
|
if attr is None:
|
|
attr = self.attr
|
|
if len(fill) != 1:
|
|
raise ValueError
|
|
info = CONSOLE_SCREEN_BUFFER_INFO()
|
|
self.GetConsoleScreenBufferInfo(self.hout, byref(info))
|
|
if info.dwCursorPosition.X != 0 or info.dwCursorPosition.Y != 0:
|
|
self.SetConsoleCursorPosition(self.hout, self.fixcoord(0, 0))
|
|
|
|
w = info.dwSize.X
|
|
n = DWORD(0)
|
|
for y in range(info.dwSize.Y):
|
|
self.FillConsoleOutputAttribute(self.hout, attr,
|
|
w, self.fixcoord(0, y), byref(n))
|
|
self.FillConsoleOutputCharacterW(self.hout, ord(fill[0]),
|
|
w, self.fixcoord(0, y), byref(n))
|
|
|
|
self.attr = attr
|
|
|
|
def text(self, x, y, text, attr=None):
|
|
'''Write text at the given position.'''
|
|
if attr is None:
|
|
attr = self.attr
|
|
|
|
pos = self.fixcoord(x, y)
|
|
n = DWORD(0)
|
|
self.WriteConsoleOutputCharacterW(self.hout, text,
|
|
len(text), pos, byref(n))
|
|
self.FillConsoleOutputAttribute(self.hout, attr, n, pos, byref(n))
|
|
|
|
def clear_to_end_of_window(self):
|
|
top, bot = self._get_top_bot()
|
|
pos = self.pos()
|
|
w, h = self.size()
|
|
self.rectangle( (pos[0], pos[1], w, pos[1] + 1))
|
|
if pos[1] < bot:
|
|
self.rectangle((0, pos[1] + 1, w, bot + 1))
|
|
|
|
def rectangle(self, rect, attr=None, fill=' '):
|
|
'''Fill Rectangle.'''
|
|
x0, y0, x1, y1 = rect
|
|
n = DWORD(0)
|
|
if attr is None:
|
|
attr = self.attr
|
|
for y in range(y0, y1):
|
|
pos = self.fixcoord(x0, y)
|
|
self.FillConsoleOutputAttribute(self.hout, attr, x1 - x0,
|
|
pos, byref(n))
|
|
self.FillConsoleOutputCharacterW(self.hout, ord(fill[0]), x1 - x0,
|
|
pos, byref(n))
|
|
|
|
def scroll(self, rect, dx, dy, attr=None, fill=' '):
|
|
'''Scroll a rectangle.'''
|
|
if attr is None:
|
|
attr = self.attr
|
|
x0, y0, x1, y1 = rect
|
|
source = SMALL_RECT(x0, y0, x1 - 1, y1 - 1)
|
|
dest = self.fixcoord(x0 + dx, y0 + dy)
|
|
style = CHAR_INFO()
|
|
style.Char.AsciiChar = ensure_str(fill[0])
|
|
style.Attributes = attr
|
|
|
|
return self.ScrollConsoleScreenBufferW(self.hout, byref(source),
|
|
byref(source), dest, byref(style))
|
|
|
|
def scroll_window(self, lines):
|
|
'''Scroll the window by the indicated number of lines.'''
|
|
info = CONSOLE_SCREEN_BUFFER_INFO()
|
|
self.GetConsoleScreenBufferInfo(self.hout, byref(info))
|
|
rect = info.srWindow
|
|
log('sw: rtop=%d rbot=%d' % (rect.Top, rect.Bottom))
|
|
top = rect.Top + lines
|
|
bot = rect.Bottom + lines
|
|
h = bot - top
|
|
maxbot = info.dwSize.Y-1
|
|
if top < 0:
|
|
top = 0
|
|
bot = h
|
|
if bot > maxbot:
|
|
bot = maxbot
|
|
top = bot - h
|
|
|
|
nrect = SMALL_RECT()
|
|
nrect.Top = top
|
|
nrect.Bottom = bot
|
|
nrect.Left = rect.Left
|
|
nrect.Right = rect.Right
|
|
log('sn: top=%d bot=%d' % (top, bot))
|
|
r=self.SetConsoleWindowInfo(self.hout, True, byref(nrect))
|
|
log('r=%d' % r)
|
|
|
|
def get(self):
|
|
'''Get next event from queue.'''
|
|
inputHookFunc = c_void_p.from_address(self.inputHookPtr).value
|
|
|
|
Cevent = INPUT_RECORD()
|
|
count = DWORD(0)
|
|
while 1:
|
|
if inputHookFunc:
|
|
call_function(inputHookFunc, ())
|
|
status = self.ReadConsoleInputW(self.hin,
|
|
byref(Cevent), 1, byref(count))
|
|
if status and count.value == 1:
|
|
e = event(self, Cevent)
|
|
return e
|
|
|
|
def getkeypress(self):
|
|
'''Return next key press event from the queue, ignoring others.'''
|
|
while 1:
|
|
e = self.get()
|
|
if e.type == 'KeyPress' and e.keycode not in key_modifiers:
|
|
log("console.getkeypress %s"%e)
|
|
if e.keyinfo.keyname == 'next':
|
|
self.scroll_window(12)
|
|
elif e.keyinfo.keyname == 'prior':
|
|
self.scroll_window(-12)
|
|
else:
|
|
return e
|
|
elif ((e.type == 'KeyRelease') and
|
|
(e.keyinfo == KeyPress('S', False, True, False, 'S'))):
|
|
log("getKeypress:%s,%s,%s"%(e.keyinfo, e.keycode, e.type))
|
|
return e
|
|
|
|
|
|
def getchar(self):
|
|
'''Get next character from queue.'''
|
|
|
|
Cevent = INPUT_RECORD()
|
|
count = DWORD(0)
|
|
while 1:
|
|
status = self.ReadConsoleInputW(self.hin,
|
|
byref(Cevent), 1, byref(count))
|
|
if (status and
|
|
(count.value == 1) and
|
|
(Cevent.EventType == 1) and
|
|
Cevent.Event.KeyEvent.bKeyDown):
|
|
sym = keysym(Cevent.Event.KeyEvent.wVirtualKeyCode)
|
|
if len(sym) == 0:
|
|
sym = Cevent.Event.KeyEvent.uChar.AsciiChar
|
|
return sym
|
|
|
|
def peek(self):
|
|
'''Check event queue.'''
|
|
Cevent = INPUT_RECORD()
|
|
count = DWORD(0)
|
|
status = self.PeekConsoleInputW(self.hin,
|
|
byref(Cevent), 1, byref(count))
|
|
if status and count == 1:
|
|
return event(self, Cevent)
|
|
|
|
def title(self, txt=None):
|
|
'''Set/get title.'''
|
|
if txt:
|
|
self.SetConsoleTitleW(txt)
|
|
else:
|
|
buffer = create_unicode_buffer(200)
|
|
n = self.GetConsoleTitleW(buffer, 200)
|
|
if n > 0:
|
|
return buffer.value[:n]
|
|
|
|
def size(self, width=None, height=None):
|
|
'''Set/get window size.'''
|
|
info = CONSOLE_SCREEN_BUFFER_INFO()
|
|
status = self.GetConsoleScreenBufferInfo(self.hout, byref(info))
|
|
if not status:
|
|
return None
|
|
if width is not None and height is not None:
|
|
wmin = info.srWindow.Right - info.srWindow.Left + 1
|
|
hmin = info.srWindow.Bottom - info.srWindow.Top + 1
|
|
#print wmin, hmin
|
|
width = max(width, wmin)
|
|
height = max(height, hmin)
|
|
#print width, height
|
|
self.SetConsoleScreenBufferSize(self.hout,
|
|
self.fixcoord(width, height))
|
|
else:
|
|
return (info.dwSize.X, info.dwSize.Y)
|
|
|
|
def cursor(self, visible=None, size=None):
|
|
'''Set cursor on or off.'''
|
|
info = CONSOLE_CURSOR_INFO()
|
|
if self.GetConsoleCursorInfo(self.hout, byref(info)):
|
|
if visible is not None:
|
|
info.bVisible = visible
|
|
if size is not None:
|
|
info.dwSize = size
|
|
self.SetConsoleCursorInfo(self.hout, byref(info))
|
|
|
|
def bell(self):
|
|
self.write('\007')
|
|
|
|
def next_serial(self):
|
|
'''Get next event serial number.'''
|
|
self.serial += 1
|
|
return self.serial
|
|
|
|
# add the functions from the dll to the class
|
|
for func in funcs:
|
|
setattr(Console, func, getattr(windll.kernel32, func))
|
|
|
|
|
|
_strncpy = ctypes.windll.kernel32.lstrcpynA
|
|
_strncpy.restype = c_char_p
|
|
_strncpy.argtypes = [c_char_p, c_char_p, c_size_t]
|
|
|
|
|
|
LPVOID = c_void_p
|
|
LPCVOID = c_void_p
|
|
FARPROC = c_void_p
|
|
LPDWORD = POINTER(DWORD)
|
|
|
|
Console.AllocConsole.restype = BOOL
|
|
Console.AllocConsole.argtypes = [] #void
|
|
Console.CreateConsoleScreenBuffer.restype = HANDLE
|
|
Console.CreateConsoleScreenBuffer.argtypes = [DWORD, DWORD, c_void_p, DWORD, LPVOID] #DWORD, DWORD, SECURITY_ATTRIBUTES*, DWORD, LPVOID
|
|
Console.FillConsoleOutputAttribute.restype = BOOL
|
|
Console.FillConsoleOutputAttribute.argtypes = [HANDLE, WORD, DWORD, c_int, LPDWORD] #HANDLE, WORD, DWORD, COORD, LPDWORD
|
|
Console.FillConsoleOutputCharacterW.restype = BOOL
|
|
Console.FillConsoleOutputCharacterW.argtypes = [HANDLE, c_ushort, DWORD, c_int, LPDWORD] #HANDLE, TCHAR, DWORD, COORD, LPDWORD
|
|
Console.FreeConsole.restype = BOOL
|
|
Console.FreeConsole.argtypes = [] #void
|
|
Console.GetConsoleCursorInfo.restype = BOOL
|
|
Console.GetConsoleCursorInfo.argtypes = [HANDLE, c_void_p] #HANDLE, PCONSOLE_CURSOR_INFO
|
|
Console.GetConsoleMode.restype = BOOL
|
|
Console.GetConsoleMode.argtypes = [HANDLE, LPDWORD] #HANDLE, LPDWORD
|
|
Console.GetConsoleScreenBufferInfo.restype = BOOL
|
|
Console.GetConsoleScreenBufferInfo.argtypes = [HANDLE, c_void_p] #HANDLE, PCONSOLE_SCREEN_BUFFER_INFO
|
|
Console.GetConsoleTitleW.restype = DWORD
|
|
Console.GetConsoleTitleW.argtypes = [c_wchar_p, DWORD] #LPTSTR , DWORD
|
|
Console.GetProcAddress.restype = FARPROC
|
|
Console.GetProcAddress.argtypes = [HMODULE, c_char_p] #HMODULE , LPCSTR
|
|
Console.GetStdHandle.restype = HANDLE
|
|
Console.GetStdHandle.argtypes = [DWORD]
|
|
Console.PeekConsoleInputW.restype = BOOL
|
|
Console.PeekConsoleInputW.argtypes = [HANDLE, c_void_p, DWORD, LPDWORD] #HANDLE, PINPUT_RECORD, DWORD, LPDWORD
|
|
Console.ReadConsoleInputW.restype = BOOL
|
|
Console.ReadConsoleInputW.argtypes = [HANDLE, c_void_p, DWORD, LPDWORD] #HANDLE, PINPUT_RECORD, DWORD, LPDWORD
|
|
Console.ScrollConsoleScreenBufferW.restype = BOOL
|
|
Console.ScrollConsoleScreenBufferW.argtypes = [HANDLE, c_void_p, c_void_p, c_int, c_void_p] #HANDLE, SMALL_RECT*, SMALL_RECT*, COORD, LPDWORD
|
|
Console.SetConsoleActiveScreenBuffer.restype = BOOL
|
|
Console.SetConsoleActiveScreenBuffer.argtypes = [HANDLE] #HANDLE
|
|
Console.SetConsoleCursorInfo.restype = BOOL
|
|
Console.SetConsoleCursorInfo.argtypes = [HANDLE, c_void_p] #HANDLE, CONSOLE_CURSOR_INFO*
|
|
Console.SetConsoleCursorPosition.restype = BOOL
|
|
Console.SetConsoleCursorPosition.argtypes = [HANDLE, c_int] #HANDLE, COORD
|
|
Console.SetConsoleMode.restype = BOOL
|
|
Console.SetConsoleMode.argtypes = [HANDLE, DWORD] #HANDLE, DWORD
|
|
Console.SetConsoleScreenBufferSize.restype = BOOL
|
|
Console.SetConsoleScreenBufferSize.argtypes = [HANDLE, c_int] #HANDLE, COORD
|
|
Console.SetConsoleTextAttribute.restype = BOOL
|
|
Console.SetConsoleTextAttribute.argtypes = [HANDLE, WORD] #HANDLE, WORD
|
|
Console.SetConsoleTitleW.restype = BOOL
|
|
Console.SetConsoleTitleW.argtypes = [c_wchar_p] #LPCTSTR
|
|
Console.SetConsoleWindowInfo.restype = BOOL
|
|
Console.SetConsoleWindowInfo.argtypes = [HANDLE, BOOL, c_void_p] #HANDLE, BOOL, SMALL_RECT*
|
|
Console.WriteConsoleW.restype = BOOL
|
|
Console.WriteConsoleW.argtypes = [HANDLE, c_void_p, DWORD, LPDWORD, LPVOID] #HANDLE, VOID*, DWORD, LPDWORD, LPVOID
|
|
Console.WriteConsoleOutputCharacterW.restype = BOOL
|
|
Console.WriteConsoleOutputCharacterW.argtypes = [HANDLE, c_wchar_p, DWORD, c_int, LPDWORD] #HANDLE, LPCTSTR, DWORD, COORD, LPDWORD
|
|
Console.WriteFile.restype = BOOL
|
|
Console.WriteFile.argtypes = [HANDLE, LPCVOID, DWORD, LPDWORD, c_void_p] #HANDLE, LPCVOID , DWORD, LPDWORD , LPOVERLAPPED
|
|
|
|
|
|
|
|
from .event import Event
|
|
|
|
VkKeyScan = windll.user32.VkKeyScanA
|
|
|
|
|
|
class event(Event):
|
|
'''Represent events from the console.'''
|
|
def __init__(self, console, input):
|
|
'''Initialize an event from the Windows input structure.'''
|
|
self.type = '??'
|
|
self.serial = console.next_serial()
|
|
self.width = 0
|
|
self.height = 0
|
|
self.x = 0
|
|
self.y = 0
|
|
self.char = ''
|
|
self.keycode = 0
|
|
self.keysym = '??'
|
|
self.keyinfo = None # a tuple with (control, meta, shift, keycode) for dispatch
|
|
self.width = None
|
|
|
|
if input.EventType == KEY_EVENT:
|
|
if input.Event.KeyEvent.bKeyDown:
|
|
self.type = "KeyPress"
|
|
else:
|
|
self.type = "KeyRelease"
|
|
self.char = input.Event.KeyEvent.uChar.UnicodeChar
|
|
self.keycode = input.Event.KeyEvent.wVirtualKeyCode
|
|
self.state = input.Event.KeyEvent.dwControlKeyState
|
|
self.keyinfo = make_KeyPress(self.char,self.state,self.keycode)
|
|
|
|
elif input.EventType == MOUSE_EVENT:
|
|
if input.Event.MouseEvent.dwEventFlags & MOUSE_MOVED:
|
|
self.type = "Motion"
|
|
else:
|
|
self.type = "Button"
|
|
self.x = input.Event.MouseEvent.dwMousePosition.X
|
|
self.y = input.Event.MouseEvent.dwMousePosition.Y
|
|
self.state = input.Event.MouseEvent.dwButtonState
|
|
elif input.EventType == WINDOW_BUFFER_SIZE_EVENT:
|
|
self.type = "Configure"
|
|
self.width = input.Event.WindowBufferSizeEvent.dwSize.X
|
|
self.height = input.Event.WindowBufferSizeEvent.dwSize.Y
|
|
elif input.EventType == FOCUS_EVENT:
|
|
if input.Event.FocusEvent.bSetFocus:
|
|
self.type = "FocusIn"
|
|
else:
|
|
self.type = "FocusOut"
|
|
elif input.EventType == MENU_EVENT:
|
|
self.type = "Menu"
|
|
self.state = input.Event.MenuEvent.dwCommandId
|
|
|
|
|
|
def getconsole(buffer=1):
|
|
"""Get a console handle.
|
|
|
|
If buffer is non-zero, a new console buffer is allocated and
|
|
installed. Otherwise, this returns a handle to the current
|
|
console buffer"""
|
|
|
|
c = Console(buffer)
|
|
|
|
return c
|
|
|
|
# The following code uses ctypes to allow a Python callable to
|
|
# substitute for GNU readline within the Python interpreter. Calling
|
|
# raw_input or other functions that do input, inside your callable
|
|
# might be a bad idea, then again, it might work.
|
|
|
|
# The Python callable can raise EOFError or KeyboardInterrupt and
|
|
# these will be translated into the appropriate outputs from readline
|
|
# so that they will then be translated back!
|
|
|
|
# If the Python callable raises any other exception, a traceback will
|
|
# be printed and readline will appear to return an empty line.
|
|
|
|
# I use ctypes to create a C-callable from a Python wrapper that
|
|
# handles the exceptions and gets the result into the right form.
|
|
|
|
# the type for our C-callable wrapper
|
|
HOOKFUNC23 = CFUNCTYPE(c_char_p, c_void_p, c_void_p, c_char_p)
|
|
|
|
readline_hook = None # the python hook goes here
|
|
readline_ref = None # reference to the c-callable to keep it alive
|
|
|
|
def hook_wrapper_23(stdin, stdout, prompt):
|
|
'''Wrap a Python readline so it behaves like GNU readline.'''
|
|
try:
|
|
# call the Python hook
|
|
res = ensure_str(readline_hook(prompt))
|
|
# make sure it returned the right sort of thing
|
|
if res and not isinstance(res, bytes):
|
|
raise TypeError('readline must return a string.')
|
|
except KeyboardInterrupt:
|
|
# GNU readline returns 0 on keyboard interrupt
|
|
return 0
|
|
except EOFError:
|
|
# It returns an empty string on EOF
|
|
res = ensure_str('')
|
|
except:
|
|
print('Readline internal error', file=sys.stderr)
|
|
traceback.print_exc()
|
|
res = ensure_str('\n')
|
|
# we have to make a copy because the caller expects to free the result
|
|
n = len(res)
|
|
p = Console.PyMem_Malloc(n + 1)
|
|
_strncpy(cast(p, c_char_p), res, n + 1)
|
|
return p
|
|
|
|
|
|
def install_readline(hook):
|
|
'''Set up things for the interpreter to call
|
|
our function like GNU readline.'''
|
|
global readline_hook, readline_ref
|
|
# save the hook so the wrapper can call it
|
|
readline_hook = hook
|
|
# get the address of PyOS_ReadlineFunctionPointer so we can update it
|
|
PyOS_RFP = c_void_p.from_address(Console.GetProcAddress(sys.dllhandle,
|
|
"PyOS_ReadlineFunctionPointer".encode('ascii')))
|
|
# save a reference to the generated C-callable so it doesn't go away
|
|
readline_ref = HOOKFUNC23(hook_wrapper_23)
|
|
# get the address of the function
|
|
func_start = c_void_p.from_address(addressof(readline_ref)).value
|
|
# write the function address into PyOS_ReadlineFunctionPointer
|
|
PyOS_RFP.value = func_start
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import time, sys
|
|
|
|
|
|
def p(char):
|
|
return chr(VkKeyScan(ord(char)) & 0xff)
|
|
|
|
c = Console(0)
|
|
sys.stdout = c
|
|
sys.stderr = c
|
|
c.page()
|
|
print(p("d"), p("D"))
|
|
c.pos(5, 10)
|
|
c.write('hi there')
|
|
print('some printed output')
|
|
for i in range(10):
|
|
q = c.getkeypress()
|
|
print(q)
|
|
del c
|