console.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. # -*- coding: utf-8 -*-
  2. # *****************************************************************************
  3. # Copyright (C) 2003-2006 Gary Bishop.
  4. # Copyright (C) 2006-2020 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
  5. # Copyright (C) 2020 Bassem Girgis. <brgirgis@gmail.com>
  6. #
  7. # Distributed under the terms of the BSD License. The full license is in
  8. # the file COPYING, distributed as part of this software.
  9. # *****************************************************************************
  10. from .event import Event
  11. """Cursor control and color for the Windows console.
  12. This was modeled after the C extension of the same name by Fredrik Lundh.
  13. """
  14. # primitive debug printing that won't interfere with the screen
  15. import os
  16. import re
  17. import sys
  18. import traceback
  19. import pyreadline3.unicode_helper as unicode_helper
  20. from pyreadline3.console.ansi import AnsiState, AnsiWriter
  21. from pyreadline3.keysyms import KeyPress, make_KeyPress
  22. from pyreadline3.logger import log
  23. from pyreadline3.unicode_helper import ensure_str, ensure_unicode
  24. try:
  25. import ctypes.util
  26. from ctypes import *
  27. from ctypes.wintypes import *
  28. from _ctypes import call_function
  29. except ImportError:
  30. raise ImportError("You need ctypes to run this code")
  31. def nolog(string):
  32. pass
  33. log = nolog
  34. # some constants we need
  35. STD_INPUT_HANDLE = -10
  36. STD_OUTPUT_HANDLE = -11
  37. ENABLE_WINDOW_INPUT = 0x0008
  38. ENABLE_MOUSE_INPUT = 0x0010
  39. ENABLE_PROCESSED_INPUT = 0x0001
  40. WHITE = 0x7
  41. BLACK = 0
  42. MENU_EVENT = 0x0008
  43. KEY_EVENT = 0x0001
  44. MOUSE_MOVED = 0x0001
  45. MOUSE_EVENT = 0x0002
  46. WINDOW_BUFFER_SIZE_EVENT = 0x0004
  47. FOCUS_EVENT = 0x0010
  48. MENU_EVENT = 0x0008
  49. VK_SHIFT = 0x10
  50. VK_CONTROL = 0x11
  51. VK_MENU = 0x12
  52. GENERIC_READ = int(0x80000000)
  53. GENERIC_WRITE = 0x40000000
  54. # Windows structures we'll need later
  55. class COORD(Structure):
  56. _fields_ = [("X", c_short), ("Y", c_short)]
  57. class SMALL_RECT(Structure):
  58. _fields_ = [
  59. ("Left", c_short),
  60. ("Top", c_short),
  61. ("Right", c_short),
  62. ("Bottom", c_short),
  63. ]
  64. class CONSOLE_SCREEN_BUFFER_INFO(Structure):
  65. _fields_ = [
  66. ("dwSize", COORD),
  67. ("dwCursorPosition", COORD),
  68. ("wAttributes", c_short),
  69. ("srWindow", SMALL_RECT),
  70. ("dwMaximumWindowSize", COORD),
  71. ]
  72. class CHAR_UNION(Union):
  73. _fields_ = [("UnicodeChar", c_wchar), ("AsciiChar", c_char)]
  74. class CHAR_INFO(Structure):
  75. _fields_ = [("Char", CHAR_UNION), ("Attributes", c_short)]
  76. class KEY_EVENT_RECORD(Structure):
  77. _fields_ = [
  78. ("bKeyDown", c_byte),
  79. ("pad2", c_byte),
  80. ("pad1", c_short),
  81. ("wRepeatCount", c_short),
  82. ("wVirtualKeyCode", c_short),
  83. ("wVirtualScanCode", c_short),
  84. ("uChar", CHAR_UNION),
  85. ("dwControlKeyState", c_int),
  86. ]
  87. class MOUSE_EVENT_RECORD(Structure):
  88. _fields_ = [
  89. ("dwMousePosition", COORD),
  90. ("dwButtonState", c_int),
  91. ("dwControlKeyState", c_int),
  92. ("dwEventFlags", c_int),
  93. ]
  94. class WINDOW_BUFFER_SIZE_RECORD(Structure):
  95. _fields_ = [("dwSize", COORD)]
  96. class MENU_EVENT_RECORD(Structure):
  97. _fields_ = [("dwCommandId", c_uint)]
  98. class FOCUS_EVENT_RECORD(Structure):
  99. _fields_ = [("bSetFocus", c_byte)]
  100. class INPUT_UNION(Union):
  101. _fields_ = [
  102. ("KeyEvent", KEY_EVENT_RECORD),
  103. ("MouseEvent", MOUSE_EVENT_RECORD),
  104. ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
  105. ("MenuEvent", MENU_EVENT_RECORD),
  106. ("FocusEvent", FOCUS_EVENT_RECORD),
  107. ]
  108. class INPUT_RECORD(Structure):
  109. _fields_ = [("EventType", c_short), ("Event", INPUT_UNION)]
  110. class CONSOLE_CURSOR_INFO(Structure):
  111. _fields_ = [("dwSize", c_int), ("bVisible", c_byte)]
  112. # I didn't want to have to individually import these so I made a list, they are
  113. # added to the Console class later in this file.
  114. funcs = [
  115. "AllocConsole",
  116. "CreateConsoleScreenBuffer",
  117. "FillConsoleOutputAttribute",
  118. "FillConsoleOutputCharacterW",
  119. "FreeConsole",
  120. "GetConsoleCursorInfo",
  121. "GetConsoleMode",
  122. "GetConsoleScreenBufferInfo",
  123. "GetConsoleTitleW",
  124. "GetProcAddress",
  125. "GetStdHandle",
  126. "PeekConsoleInputW",
  127. "ReadConsoleInputW",
  128. "ScrollConsoleScreenBufferW",
  129. "SetConsoleActiveScreenBuffer",
  130. "SetConsoleCursorInfo",
  131. "SetConsoleCursorPosition",
  132. "SetConsoleMode",
  133. "SetConsoleScreenBufferSize",
  134. "SetConsoleTextAttribute",
  135. "SetConsoleTitleW",
  136. "SetConsoleWindowInfo",
  137. "WriteConsoleW",
  138. "WriteConsoleOutputCharacterW",
  139. "WriteFile",
  140. ]
  141. # I don't want events for these keys, they are just a bother for my application
  142. key_modifiers = {
  143. VK_SHIFT: 1,
  144. VK_CONTROL: 1,
  145. VK_MENU: 1, # alt key
  146. 0x5B: 1, # windows key
  147. }
  148. def split_block(text, size=1000):
  149. return [text[start : start + size] for start in range(0, len(text), size)]
  150. class Console(object):
  151. """Console driver for Windows."""
  152. def __init__(self, newbuffer=0):
  153. """Initialize the Console object.
  154. newbuffer=1 will allocate a new buffer so the old content will be restored
  155. on exit.
  156. """
  157. # Do I need the following line? It causes a console to be created whenever
  158. # readline is imported into a pythonw application which seems wrong. Things
  159. # seem to work without it...
  160. # self.AllocConsole()
  161. if newbuffer:
  162. self.hout = self.CreateConsoleScreenBuffer(
  163. GENERIC_READ | GENERIC_WRITE, 0, None, 1, None
  164. )
  165. self.SetConsoleActiveScreenBuffer(self.hout)
  166. else:
  167. self.hout = self.GetStdHandle(STD_OUTPUT_HANDLE)
  168. self.hin = self.GetStdHandle(STD_INPUT_HANDLE)
  169. self.inmode = DWORD(0)
  170. self.GetConsoleMode(self.hin, byref(self.inmode))
  171. self.SetConsoleMode(self.hin, 0xF)
  172. info = CONSOLE_SCREEN_BUFFER_INFO()
  173. self.GetConsoleScreenBufferInfo(self.hout, byref(info))
  174. self.attr = info.wAttributes
  175. self.saveattr = info.wAttributes # remember the initial colors
  176. self.defaultstate = AnsiState()
  177. self.defaultstate.winattr = info.wAttributes
  178. self.ansiwriter = AnsiWriter(self.defaultstate)
  179. background = self.attr & 0xF0
  180. for escape in self.escape_to_color:
  181. if self.escape_to_color[escape] is not None:
  182. self.escape_to_color[escape] |= background
  183. log("initial attr=%x" % self.attr)
  184. self.softspace = 0 # this is for using it as a file-like object
  185. self.serial = 0
  186. self.pythondll = ctypes.pythonapi
  187. self.inputHookPtr = c_void_p.from_address(
  188. addressof(self.pythondll.PyOS_InputHook)
  189. ).value
  190. self.pythondll.PyMem_RawMalloc.restype = c_size_t
  191. self.pythondll.PyMem_RawMalloc.argtypes = [c_size_t]
  192. setattr(Console, "PyMem_Malloc", self.pythondll.PyMem_RawMalloc)
  193. def __del__(self):
  194. """Cleanup the console when finished."""
  195. # I don't think this ever gets called
  196. self.SetConsoleTextAttribute(self.hout, self.saveattr)
  197. self.SetConsoleMode(self.hin, self.inmode)
  198. self.FreeConsole()
  199. def _get_top_bot(self):
  200. info = CONSOLE_SCREEN_BUFFER_INFO()
  201. self.GetConsoleScreenBufferInfo(self.hout, byref(info))
  202. rect = info.srWindow
  203. top = rect.Top
  204. bot = rect.Bottom
  205. return top, bot
  206. def fixcoord(self, x, y):
  207. """Return a long with x and y packed inside,
  208. also handle negative x and y."""
  209. if x < 0 or y < 0:
  210. info = CONSOLE_SCREEN_BUFFER_INFO()
  211. self.GetConsoleScreenBufferInfo(self.hout, byref(info))
  212. if x < 0:
  213. x = info.srWindow.Right - x
  214. y = info.srWindow.Bottom + y
  215. # this is a hack! ctypes won't pass structures but COORD is
  216. # just like a long, so this works.
  217. return c_int(y << 16 | x)
  218. def pos(self, x=None, y=None):
  219. """Move or query the window cursor."""
  220. if x is None:
  221. info = CONSOLE_SCREEN_BUFFER_INFO()
  222. self.GetConsoleScreenBufferInfo(self.hout, byref(info))
  223. return (info.dwCursorPosition.X, info.dwCursorPosition.Y)
  224. else:
  225. return self.SetConsoleCursorPosition(self.hout, self.fixcoord(x, y))
  226. def home(self):
  227. """Move to home."""
  228. self.pos(0, 0)
  229. # Map ANSI color escape sequences into Windows Console Attributes
  230. terminal_escape = re.compile("(\001?\033\\[[0-9;]+m\002?)")
  231. escape_parts = re.compile("\001?\033\\[([0-9;]+)m\002?")
  232. escape_to_color = {
  233. "0;30": 0x0, # black
  234. "0;31": 0x4, # red
  235. "0;32": 0x2, # green
  236. "0;33": 0x4 + 0x2, # brown?
  237. "0;34": 0x1, # blue
  238. "0;35": 0x1 + 0x4, # purple
  239. "0;36": 0x2 + 0x4, # cyan
  240. "0;37": 0x1 + 0x2 + 0x4, # grey
  241. "1;30": 0x1 + 0x2 + 0x4, # dark gray
  242. "1;31": 0x4 + 0x8, # red
  243. "1;32": 0x2 + 0x8, # light green
  244. "1;33": 0x4 + 0x2 + 0x8, # yellow
  245. "1;34": 0x1 + 0x8, # light blue
  246. "1;35": 0x1 + 0x4 + 0x8, # light purple
  247. "1;36": 0x1 + 0x2 + 0x8, # light cyan
  248. "1;37": 0x1 + 0x2 + 0x4 + 0x8, # white
  249. "0": None,
  250. }
  251. # This pattern should match all characters that change the cursor position differently
  252. # than a normal character.
  253. motion_char_re = re.compile("([\n\r\t\010\007])")
  254. def write_scrolling(self, text, attr=None):
  255. """write text at current cursor position while watching for scrolling.
  256. If the window scrolls because you are at the bottom of the screen
  257. buffer, all positions that you are storing will be shifted by the
  258. scroll amount. For example, I remember the cursor position of the
  259. prompt so that I can redraw the line but if the window scrolls,
  260. the remembered position is off.
  261. This variant of write tries to keep track of the cursor position
  262. so that it will know when the screen buffer is scrolled. It
  263. returns the number of lines that the buffer scrolled.
  264. """
  265. text = ensure_unicode(text)
  266. x, y = self.pos()
  267. w, h = self.size()
  268. scroll = 0 # the result
  269. # split the string into ordinary characters and funny characters
  270. chunks = self.motion_char_re.split(text)
  271. for chunk in chunks:
  272. n = self.write_color(chunk, attr)
  273. if len(chunk) == 1: # the funny characters will be alone
  274. if chunk[0] == "\n": # newline
  275. x = 0
  276. y += 1
  277. elif chunk[0] == "\r": # carriage return
  278. x = 0
  279. elif chunk[0] == "\t": # tab
  280. x = 8 * (int(x / 8) + 1)
  281. if x > w: # newline
  282. x -= w
  283. y += 1
  284. elif chunk[0] == "\007": # bell
  285. pass
  286. elif chunk[0] == "\010":
  287. x -= 1
  288. if x < 0:
  289. y -= 1 # backed up 1 line
  290. else: # ordinary character
  291. x += 1
  292. if x == w: # wrap
  293. x = 0
  294. y += 1
  295. if y == h: # scroll
  296. scroll += 1
  297. y = h - 1
  298. else: # chunk of ordinary characters
  299. x += n
  300. l = int(x / w) # lines we advanced
  301. x = x % w # new x value
  302. y += l
  303. if y >= h: # scroll
  304. scroll += y - h + 1
  305. y = h - 1
  306. return scroll
  307. def write_color(self, text, attr=None):
  308. text = ensure_unicode(text)
  309. n, res = self.ansiwriter.write_color(text, attr)
  310. junk = DWORD(0)
  311. for attr, chunk in res:
  312. log("console.attr:%s" % (attr))
  313. log("console.chunk:%s" % (chunk))
  314. self.SetConsoleTextAttribute(self.hout, attr.winattr)
  315. for short_chunk in split_block(chunk):
  316. self.WriteConsoleW(
  317. self.hout, short_chunk, len(short_chunk), byref(junk), None
  318. )
  319. return n
  320. def write_plain(self, text, attr=None):
  321. """write text at current cursor position."""
  322. text = ensure_unicode(text)
  323. log('write("%s", %s)' % (text, attr))
  324. if attr is None:
  325. attr = self.attr
  326. junk = DWORD(0)
  327. self.SetConsoleTextAttribute(self.hout, attr)
  328. for short_chunk in split_block(chunk):
  329. self.WriteConsoleW(
  330. self.hout,
  331. ensure_unicode(short_chunk),
  332. len(short_chunk),
  333. byref(junk),
  334. None,
  335. )
  336. return len(text)
  337. # This function must be used to ensure functioning with EMACS
  338. # Emacs sets the EMACS environment variable
  339. if "EMACS" in os.environ:
  340. def write_color(self, text, attr=None):
  341. text = ensure_str(text)
  342. junk = DWORD(0)
  343. self.WriteFile(self.hout, text, len(text), byref(junk), None)
  344. return len(text)
  345. write_plain = write_color
  346. # make this class look like a file object
  347. def write(self, text):
  348. text = ensure_unicode(text)
  349. log('write("%s")' % text)
  350. return self.write_color(text)
  351. # write = write_scrolling
  352. def isatty(self):
  353. return True
  354. def flush(self):
  355. pass
  356. def page(self, attr=None, fill=" "):
  357. """Fill the entire screen."""
  358. if attr is None:
  359. attr = self.attr
  360. if len(fill) != 1:
  361. raise ValueError
  362. info = CONSOLE_SCREEN_BUFFER_INFO()
  363. self.GetConsoleScreenBufferInfo(self.hout, byref(info))
  364. if info.dwCursorPosition.X != 0 or info.dwCursorPosition.Y != 0:
  365. self.SetConsoleCursorPosition(self.hout, self.fixcoord(0, 0))
  366. w = info.dwSize.X
  367. n = DWORD(0)
  368. for y in range(info.dwSize.Y):
  369. self.FillConsoleOutputAttribute(
  370. self.hout, attr, w, self.fixcoord(0, y), byref(n)
  371. )
  372. self.FillConsoleOutputCharacterW(
  373. self.hout, ord(fill[0]), w, self.fixcoord(0, y), byref(n)
  374. )
  375. self.attr = attr
  376. def text(self, x, y, text, attr=None):
  377. """Write text at the given position."""
  378. if attr is None:
  379. attr = self.attr
  380. pos = self.fixcoord(x, y)
  381. n = DWORD(0)
  382. self.WriteConsoleOutputCharacterW(self.hout, text, len(text), pos, byref(n))
  383. self.FillConsoleOutputAttribute(self.hout, attr, n, pos, byref(n))
  384. def clear_to_end_of_window(self):
  385. top, bot = self._get_top_bot()
  386. pos = self.pos()
  387. w, h = self.size()
  388. self.rectangle((pos[0], pos[1], w, pos[1] + 1))
  389. if pos[1] < bot:
  390. self.rectangle((0, pos[1] + 1, w, bot + 1))
  391. def rectangle(self, rect, attr=None, fill=" "):
  392. """Fill Rectangle."""
  393. x0, y0, x1, y1 = rect
  394. n = DWORD(0)
  395. if attr is None:
  396. attr = self.attr
  397. for y in range(y0, y1):
  398. pos = self.fixcoord(x0, y)
  399. self.FillConsoleOutputAttribute(self.hout, attr, x1 - x0, pos, byref(n))
  400. self.FillConsoleOutputCharacterW(
  401. self.hout, ord(fill[0]), x1 - x0, pos, byref(n)
  402. )
  403. def scroll(self, rect, dx, dy, attr=None, fill=" "):
  404. """Scroll a rectangle."""
  405. if attr is None:
  406. attr = self.attr
  407. x0, y0, x1, y1 = rect
  408. source = SMALL_RECT(x0, y0, x1 - 1, y1 - 1)
  409. dest = self.fixcoord(x0 + dx, y0 + dy)
  410. style = CHAR_INFO()
  411. style.Char.AsciiChar = ensure_str(fill[0])
  412. style.Attributes = attr
  413. return self.ScrollConsoleScreenBufferW(
  414. self.hout, byref(source), byref(source), dest, byref(style)
  415. )
  416. def scroll_window(self, lines):
  417. """Scroll the window by the indicated number of lines."""
  418. info = CONSOLE_SCREEN_BUFFER_INFO()
  419. self.GetConsoleScreenBufferInfo(self.hout, byref(info))
  420. rect = info.srWindow
  421. log("sw: rtop=%d rbot=%d" % (rect.Top, rect.Bottom))
  422. top = rect.Top + lines
  423. bot = rect.Bottom + lines
  424. h = bot - top
  425. maxbot = info.dwSize.Y - 1
  426. if top < 0:
  427. top = 0
  428. bot = h
  429. if bot > maxbot:
  430. bot = maxbot
  431. top = bot - h
  432. nrect = SMALL_RECT()
  433. nrect.Top = top
  434. nrect.Bottom = bot
  435. nrect.Left = rect.Left
  436. nrect.Right = rect.Right
  437. log("sn: top=%d bot=%d" % (top, bot))
  438. r = self.SetConsoleWindowInfo(self.hout, True, byref(nrect))
  439. log("r=%d" % r)
  440. def get(self):
  441. """Get next event from queue."""
  442. inputHookFunc = c_void_p.from_address(self.inputHookPtr).value
  443. Cevent = INPUT_RECORD()
  444. count = DWORD(0)
  445. while True:
  446. if inputHookFunc:
  447. call_function(inputHookFunc, ())
  448. status = self.ReadConsoleInputW(self.hin, byref(Cevent), 1, byref(count))
  449. if status and count.value == 1:
  450. e = event(self, Cevent)
  451. return e
  452. def getkeypress(self):
  453. """Return next key press event from the queue, ignoring others."""
  454. while True:
  455. e = self.get()
  456. if e.type == "KeyPress" and e.keycode not in key_modifiers:
  457. log("console.getkeypress %s" % e)
  458. if e.keyinfo.keyname == "next":
  459. self.scroll_window(12)
  460. elif e.keyinfo.keyname == "prior":
  461. self.scroll_window(-12)
  462. else:
  463. return e
  464. elif (e.type == "KeyRelease") and (
  465. e.keyinfo == KeyPress("S", False, True, False, "S")
  466. or e.keyinfo == KeyPress("C", False, True, False, "C")
  467. ):
  468. log("getKeypress:%s,%s,%s" % (e.keyinfo, e.keycode, e.type))
  469. return e
  470. def getchar(self):
  471. """Get next character from queue."""
  472. Cevent = INPUT_RECORD()
  473. count = DWORD(0)
  474. while True:
  475. status = self.ReadConsoleInputW(self.hin, byref(Cevent), 1, byref(count))
  476. if (
  477. status
  478. and (count.value == 1)
  479. and (Cevent.EventType == 1)
  480. and Cevent.Event.KeyEvent.bKeyDown
  481. ):
  482. sym = keysym(Cevent.Event.KeyEvent.wVirtualKeyCode)
  483. if len(sym) == 0:
  484. sym = Cevent.Event.KeyEvent.uChar.AsciiChar
  485. return sym
  486. def peek(self):
  487. """Check event queue."""
  488. Cevent = INPUT_RECORD()
  489. count = DWORD(0)
  490. status = self.PeekConsoleInputW(self.hin, byref(Cevent), 1, byref(count))
  491. if status and count == 1:
  492. return event(self, Cevent)
  493. def title(self, txt=None):
  494. """Set/get title."""
  495. if txt:
  496. self.SetConsoleTitleW(txt)
  497. else:
  498. buffer = create_unicode_buffer(200)
  499. n = self.GetConsoleTitleW(buffer, 200)
  500. if n > 0:
  501. return buffer.value[:n]
  502. def size(self, width=None, height=None):
  503. """Set/get window size."""
  504. info = CONSOLE_SCREEN_BUFFER_INFO()
  505. status = self.GetConsoleScreenBufferInfo(self.hout, byref(info))
  506. if not status:
  507. return None
  508. if width is not None and height is not None:
  509. wmin = info.srWindow.Right - info.srWindow.Left + 1
  510. hmin = info.srWindow.Bottom - info.srWindow.Top + 1
  511. # print wmin, hmin
  512. width = max(width, wmin)
  513. height = max(height, hmin)
  514. # print width, height
  515. self.SetConsoleScreenBufferSize(self.hout, self.fixcoord(width, height))
  516. else:
  517. return (info.dwSize.X, info.dwSize.Y)
  518. def cursor(self, visible=None, size=None):
  519. """Set cursor on or off."""
  520. info = CONSOLE_CURSOR_INFO()
  521. if self.GetConsoleCursorInfo(self.hout, byref(info)):
  522. if visible is not None:
  523. info.bVisible = visible
  524. if size is not None:
  525. info.dwSize = size
  526. self.SetConsoleCursorInfo(self.hout, byref(info))
  527. def bell(self):
  528. self.write("\007")
  529. def next_serial(self):
  530. """Get next event serial number."""
  531. self.serial += 1
  532. return self.serial
  533. # add the functions from the dll to the class
  534. for func in funcs:
  535. setattr(Console, func, getattr(windll.kernel32, func))
  536. _strncpy = ctypes.windll.kernel32.lstrcpynA
  537. _strncpy.restype = c_char_p
  538. _strncpy.argtypes = [c_char_p, c_char_p, c_size_t]
  539. LPVOID = c_void_p
  540. LPCVOID = c_void_p
  541. FARPROC = c_void_p
  542. LPDWORD = POINTER(DWORD)
  543. Console.AllocConsole.restype = BOOL
  544. Console.AllocConsole.argtypes = [] # void
  545. Console.CreateConsoleScreenBuffer.restype = HANDLE
  546. Console.CreateConsoleScreenBuffer.argtypes = [
  547. DWORD,
  548. DWORD,
  549. c_void_p,
  550. DWORD,
  551. LPVOID,
  552. ] # DWORD, DWORD, SECURITY_ATTRIBUTES*, DWORD, LPVOID
  553. Console.FillConsoleOutputAttribute.restype = BOOL
  554. Console.FillConsoleOutputAttribute.argtypes = [
  555. HANDLE,
  556. WORD,
  557. DWORD,
  558. c_int,
  559. LPDWORD,
  560. ] # HANDLE, WORD, DWORD, COORD, LPDWORD
  561. Console.FillConsoleOutputCharacterW.restype = BOOL
  562. Console.FillConsoleOutputCharacterW.argtypes = [
  563. HANDLE,
  564. c_ushort,
  565. DWORD,
  566. c_int,
  567. LPDWORD,
  568. ] # HANDLE, TCHAR, DWORD, COORD, LPDWORD
  569. Console.FreeConsole.restype = BOOL
  570. Console.FreeConsole.argtypes = [] # void
  571. Console.GetConsoleCursorInfo.restype = BOOL
  572. Console.GetConsoleCursorInfo.argtypes = [
  573. HANDLE,
  574. c_void_p,
  575. ] # HANDLE, PCONSOLE_CURSOR_INFO
  576. Console.GetConsoleMode.restype = BOOL
  577. Console.GetConsoleMode.argtypes = [HANDLE, LPDWORD] # HANDLE, LPDWORD
  578. Console.GetConsoleScreenBufferInfo.restype = BOOL
  579. Console.GetConsoleScreenBufferInfo.argtypes = [
  580. HANDLE,
  581. c_void_p,
  582. ] # HANDLE, PCONSOLE_SCREEN_BUFFER_INFO
  583. Console.GetConsoleTitleW.restype = DWORD
  584. Console.GetConsoleTitleW.argtypes = [c_wchar_p, DWORD] # LPTSTR , DWORD
  585. Console.GetProcAddress.restype = FARPROC
  586. Console.GetProcAddress.argtypes = [HMODULE, c_char_p] # HMODULE , LPCSTR
  587. Console.GetStdHandle.restype = HANDLE
  588. Console.GetStdHandle.argtypes = [DWORD]
  589. Console.PeekConsoleInputW.restype = BOOL
  590. # HANDLE, PINPUT_RECORD, DWORD, LPDWORD
  591. Console.PeekConsoleInputW.argtypes = [HANDLE, c_void_p, DWORD, LPDWORD]
  592. Console.ReadConsoleInputW.restype = BOOL
  593. # HANDLE, PINPUT_RECORD, DWORD, LPDWORD
  594. Console.ReadConsoleInputW.argtypes = [HANDLE, c_void_p, DWORD, LPDWORD]
  595. Console.ScrollConsoleScreenBufferW.restype = BOOL
  596. Console.ScrollConsoleScreenBufferW.argtypes = [
  597. HANDLE,
  598. c_void_p,
  599. c_void_p,
  600. c_int,
  601. c_void_p,
  602. ] # HANDLE, SMALL_RECT*, SMALL_RECT*, COORD, LPDWORD
  603. Console.SetConsoleActiveScreenBuffer.restype = BOOL
  604. Console.SetConsoleActiveScreenBuffer.argtypes = [HANDLE] # HANDLE
  605. Console.SetConsoleCursorInfo.restype = BOOL
  606. Console.SetConsoleCursorInfo.argtypes = [
  607. HANDLE,
  608. c_void_p,
  609. ] # HANDLE, CONSOLE_CURSOR_INFO*
  610. Console.SetConsoleCursorPosition.restype = BOOL
  611. Console.SetConsoleCursorPosition.argtypes = [HANDLE, c_int] # HANDLE, COORD
  612. Console.SetConsoleMode.restype = BOOL
  613. Console.SetConsoleMode.argtypes = [HANDLE, DWORD] # HANDLE, DWORD
  614. Console.SetConsoleScreenBufferSize.restype = BOOL
  615. Console.SetConsoleScreenBufferSize.argtypes = [HANDLE, c_int] # HANDLE, COORD
  616. Console.SetConsoleTextAttribute.restype = BOOL
  617. Console.SetConsoleTextAttribute.argtypes = [HANDLE, WORD] # HANDLE, WORD
  618. Console.SetConsoleTitleW.restype = BOOL
  619. Console.SetConsoleTitleW.argtypes = [c_wchar_p] # LPCTSTR
  620. Console.SetConsoleWindowInfo.restype = BOOL
  621. Console.SetConsoleWindowInfo.argtypes = [
  622. HANDLE,
  623. BOOL,
  624. c_void_p,
  625. ] # HANDLE, BOOL, SMALL_RECT*
  626. Console.WriteConsoleW.restype = BOOL
  627. # HANDLE, VOID*, DWORD, LPDWORD, LPVOID
  628. Console.WriteConsoleW.argtypes = [HANDLE, c_void_p, DWORD, LPDWORD, LPVOID]
  629. Console.WriteConsoleOutputCharacterW.restype = BOOL
  630. Console.WriteConsoleOutputCharacterW.argtypes = [
  631. HANDLE,
  632. c_wchar_p,
  633. DWORD,
  634. c_int,
  635. LPDWORD,
  636. ] # HANDLE, LPCTSTR, DWORD, COORD, LPDWORD
  637. Console.WriteFile.restype = BOOL
  638. # HANDLE, LPCVOID , DWORD, LPDWORD , LPOVERLAPPED
  639. Console.WriteFile.argtypes = [HANDLE, LPCVOID, DWORD, LPDWORD, c_void_p]
  640. VkKeyScan = windll.user32.VkKeyScanA
  641. class event(Event):
  642. """Represent events from the console."""
  643. def __init__(self, console, input):
  644. """Initialize an event from the Windows input structure."""
  645. self.type = "??"
  646. self.serial = console.next_serial()
  647. self.width = 0
  648. self.height = 0
  649. self.x = 0
  650. self.y = 0
  651. self.char = ""
  652. self.keycode = 0
  653. self.keysym = "??"
  654. # a tuple with (control, meta, shift, keycode) for dispatch
  655. self.keyinfo = None
  656. self.width = None
  657. if input.EventType == KEY_EVENT:
  658. if input.Event.KeyEvent.bKeyDown:
  659. self.type = "KeyPress"
  660. else:
  661. self.type = "KeyRelease"
  662. self.char = input.Event.KeyEvent.uChar.UnicodeChar
  663. self.keycode = input.Event.KeyEvent.wVirtualKeyCode
  664. self.state = input.Event.KeyEvent.dwControlKeyState
  665. self.keyinfo = make_KeyPress(self.char, self.state, self.keycode)
  666. elif input.EventType == MOUSE_EVENT:
  667. if input.Event.MouseEvent.dwEventFlags & MOUSE_MOVED:
  668. self.type = "Motion"
  669. else:
  670. self.type = "Button"
  671. self.x = input.Event.MouseEvent.dwMousePosition.X
  672. self.y = input.Event.MouseEvent.dwMousePosition.Y
  673. self.state = input.Event.MouseEvent.dwButtonState
  674. elif input.EventType == WINDOW_BUFFER_SIZE_EVENT:
  675. self.type = "Configure"
  676. self.width = input.Event.WindowBufferSizeEvent.dwSize.X
  677. self.height = input.Event.WindowBufferSizeEvent.dwSize.Y
  678. elif input.EventType == FOCUS_EVENT:
  679. if input.Event.FocusEvent.bSetFocus:
  680. self.type = "FocusIn"
  681. else:
  682. self.type = "FocusOut"
  683. elif input.EventType == MENU_EVENT:
  684. self.type = "Menu"
  685. self.state = input.Event.MenuEvent.dwCommandId
  686. def getconsole(buffer=1):
  687. """Get a console handle.
  688. If buffer is non-zero, a new console buffer is allocated and
  689. installed. Otherwise, this returns a handle to the current
  690. console buffer"""
  691. c = Console(buffer)
  692. return c
  693. # The following code uses ctypes to allow a Python callable to
  694. # substitute for GNU readline within the Python interpreter. Calling
  695. # raw_input or other functions that do input, inside your callable
  696. # might be a bad idea, then again, it might work.
  697. # The Python callable can raise EOFError or KeyboardInterrupt and
  698. # these will be translated into the appropriate outputs from readline
  699. # so that they will then be translated back!
  700. # If the Python callable raises any other exception, a traceback will
  701. # be printed and readline will appear to return an empty line.
  702. # I use ctypes to create a C-callable from a Python wrapper that
  703. # handles the exceptions and gets the result into the right form.
  704. # the type for our C-callable wrapper
  705. HOOKFUNC23 = CFUNCTYPE(c_char_p, c_void_p, c_void_p, c_char_p)
  706. readline_hook = None # the python hook goes here
  707. readline_ref = None # reference to the c-callable to keep it alive
  708. def hook_wrapper_23(stdin, stdout, prompt):
  709. """Wrap a Python readline so it behaves like GNU readline."""
  710. try:
  711. # call the Python hook
  712. res = ensure_str(readline_hook(prompt))
  713. # make sure it returned the right sort of thing
  714. if res and not isinstance(res, bytes):
  715. raise TypeError("readline must return a string.")
  716. except KeyboardInterrupt:
  717. # GNU readline returns 0 on keyboard interrupt
  718. return 0
  719. except EOFError:
  720. # It returns an empty string on EOF
  721. res = ensure_str("")
  722. except BaseException:
  723. print("Readline internal error", file=sys.stderr)
  724. traceback.print_exc()
  725. res = ensure_str("\n")
  726. # we have to make a copy because the caller expects to free the result
  727. n = len(res)
  728. p = Console.PyMem_Malloc(n + 1)
  729. _strncpy(cast(p, c_char_p), res, n + 1)
  730. return p
  731. def install_readline(hook):
  732. """Set up things for the interpreter to call
  733. our function like GNU readline."""
  734. global readline_hook, readline_ref
  735. # save the hook so the wrapper can call it
  736. readline_hook = hook
  737. # get the address of PyOS_ReadlineFunctionPointer so we can update it
  738. PyOS_RFP = c_void_p.from_address(
  739. Console.GetProcAddress(
  740. sys.dllhandle, "PyOS_ReadlineFunctionPointer".encode("ascii")
  741. )
  742. )
  743. # save a reference to the generated C-callable so it doesn't go away
  744. readline_ref = HOOKFUNC23(hook_wrapper_23)
  745. # get the address of the function
  746. func_start = c_void_p.from_address(addressof(readline_ref)).value
  747. # write the function address into PyOS_ReadlineFunctionPointer
  748. PyOS_RFP.value = func_start
  749. if __name__ == "__main__":
  750. import sys
  751. import time
  752. def p(char):
  753. return chr(VkKeyScan(ord(char)) & 0xFF)
  754. c = Console(0)
  755. sys.stdout = c
  756. sys.stderr = c
  757. c.page()
  758. print(p("d"), p("D"))
  759. c.pos(5, 10)
  760. c.write("hi there")
  761. print("some printed output")
  762. for i in range(10):
  763. q = c.getkeypress()
  764. print(q)
  765. del c