pyOS-kernel 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyos/__init__.py +22 -0
- pyos/boot/__init__.py +3 -0
- pyos/boot/bootloader.asm +174 -0
- pyos/builder.py +177 -0
- pyos/cli.py +286 -0
- pyos/compiler/__init__.py +8 -0
- pyos/compiler/assembler.py +180 -0
- pyos/compiler/codegen.py +238 -0
- pyos/drivers/__init__.py +8 -0
- pyos/drivers/keyboard.py +319 -0
- pyos/drivers/screen.py +310 -0
- pyos/emulator.py +195 -0
- pyos/interrupts/__init__.py +8 -0
- pyos/interrupts/handler.py +237 -0
- pyos/interrupts/idt.py +165 -0
- pyos/kernel.py +288 -0
- pyos/memory/__init__.py +8 -0
- pyos/memory/gdt.py +295 -0
- pyos/memory/manager.py +397 -0
- pyos/syscalls/__init__.py +7 -0
- pyos/syscalls/handler.py +179 -0
- pyos_kernel-0.1.0.dist-info/METADATA +323 -0
- pyos_kernel-0.1.0.dist-info/RECORD +27 -0
- pyos_kernel-0.1.0.dist-info/WHEEL +5 -0
- pyos_kernel-0.1.0.dist-info/entry_points.txt +2 -0
- pyos_kernel-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyos_kernel-0.1.0.dist-info/top_level.txt +1 -0
pyos/drivers/keyboard.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS Keyboard Driver - PS/2 Keyboard Support
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Callable, Dict
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class KeyCode(Enum):
|
|
11
|
+
"""Keyboard scan codes"""
|
|
12
|
+
ESC = 0x01
|
|
13
|
+
KEY_1 = 0x02
|
|
14
|
+
KEY_2 = 0x03
|
|
15
|
+
KEY_3 = 0x04
|
|
16
|
+
KEY_4 = 0x05
|
|
17
|
+
KEY_5 = 0x06
|
|
18
|
+
KEY_6 = 0x07
|
|
19
|
+
KEY_7 = 0x08
|
|
20
|
+
KEY_8 = 0x09
|
|
21
|
+
KEY_9 = 0x0A
|
|
22
|
+
KEY_0 = 0x0B
|
|
23
|
+
MINUS = 0x0C
|
|
24
|
+
EQUALS = 0x0D
|
|
25
|
+
BACKSPACE = 0x0E
|
|
26
|
+
TAB = 0x0F
|
|
27
|
+
KEY_Q = 0x10
|
|
28
|
+
KEY_W = 0x11
|
|
29
|
+
KEY_E = 0x12
|
|
30
|
+
KEY_R = 0x13
|
|
31
|
+
KEY_T = 0x14
|
|
32
|
+
KEY_Y = 0x15
|
|
33
|
+
KEY_U = 0x16
|
|
34
|
+
KEY_I = 0x17
|
|
35
|
+
KEY_O = 0x18
|
|
36
|
+
KEY_P = 0x19
|
|
37
|
+
LEFT_BRACKET = 0x1A
|
|
38
|
+
RIGHT_BRACKET = 0x1B
|
|
39
|
+
ENTER = 0x1C
|
|
40
|
+
LEFT_CTRL = 0x1D
|
|
41
|
+
KEY_A = 0x1E
|
|
42
|
+
KEY_S = 0x1F
|
|
43
|
+
KEY_D = 0x20
|
|
44
|
+
KEY_F = 0x21
|
|
45
|
+
KEY_G = 0x22
|
|
46
|
+
KEY_H = 0x23
|
|
47
|
+
KEY_J = 0x24
|
|
48
|
+
KEY_K = 0x25
|
|
49
|
+
KEY_L = 0x26
|
|
50
|
+
SEMICOLON = 0x27
|
|
51
|
+
QUOTE = 0x28
|
|
52
|
+
BACKTICK = 0x29
|
|
53
|
+
LEFT_SHIFT = 0x2A
|
|
54
|
+
BACKSLASH = 0x2B
|
|
55
|
+
KEY_Z = 0x2C
|
|
56
|
+
KEY_X = 0x2D
|
|
57
|
+
KEY_C = 0x2E
|
|
58
|
+
KEY_V = 0x2F
|
|
59
|
+
KEY_B = 0x30
|
|
60
|
+
KEY_N = 0x31
|
|
61
|
+
KEY_M = 0x32
|
|
62
|
+
COMMA = 0x33
|
|
63
|
+
PERIOD = 0x34
|
|
64
|
+
SLASH = 0x35
|
|
65
|
+
RIGHT_SHIFT = 0x36
|
|
66
|
+
KEYPAD_STAR = 0x37
|
|
67
|
+
LEFT_ALT = 0x38
|
|
68
|
+
SPACE = 0x39
|
|
69
|
+
CAPS_LOCK = 0x3A
|
|
70
|
+
F1 = 0x3B
|
|
71
|
+
F2 = 0x3C
|
|
72
|
+
F3 = 0x3D
|
|
73
|
+
F4 = 0x3E
|
|
74
|
+
F5 = 0x3F
|
|
75
|
+
F6 = 0x40
|
|
76
|
+
F7 = 0x41
|
|
77
|
+
F8 = 0x42
|
|
78
|
+
F9 = 0x43
|
|
79
|
+
F10 = 0x44
|
|
80
|
+
F11 = 0x57
|
|
81
|
+
F12 = 0x58
|
|
82
|
+
UP = 0x48
|
|
83
|
+
DOWN = 0x50
|
|
84
|
+
LEFT = 0x4B
|
|
85
|
+
RIGHT = 0x4D
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Scancode to character mapping (US layout)
|
|
89
|
+
SCANCODE_TO_CHAR: Dict[int, str] = {
|
|
90
|
+
0x02: '1', 0x03: '2', 0x04: '3', 0x05: '4', 0x06: '5',
|
|
91
|
+
0x07: '6', 0x08: '7', 0x09: '8', 0x0A: '9', 0x0B: '0',
|
|
92
|
+
0x0C: '-', 0x0D: '=',
|
|
93
|
+
0x10: 'q', 0x11: 'w', 0x12: 'e', 0x13: 'r', 0x14: 't',
|
|
94
|
+
0x15: 'y', 0x16: 'u', 0x17: 'i', 0x18: 'o', 0x19: 'p',
|
|
95
|
+
0x1A: '[', 0x1B: ']',
|
|
96
|
+
0x1E: 'a', 0x1F: 's', 0x20: 'd', 0x21: 'f', 0x22: 'g',
|
|
97
|
+
0x23: 'h', 0x24: 'j', 0x25: 'k', 0x26: 'l',
|
|
98
|
+
0x27: ';', 0x28: "'", 0x29: '`',
|
|
99
|
+
0x2B: '\\',
|
|
100
|
+
0x2C: 'z', 0x2D: 'x', 0x2E: 'c', 0x2F: 'v', 0x30: 'b',
|
|
101
|
+
0x31: 'n', 0x32: 'm',
|
|
102
|
+
0x33: ',', 0x34: '.', 0x35: '/',
|
|
103
|
+
0x39: ' ',
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
SCANCODE_TO_CHAR_SHIFT: Dict[int, str] = {
|
|
107
|
+
0x02: '!', 0x03: '@', 0x04: '#', 0x05: '$', 0x06: '%',
|
|
108
|
+
0x07: '^', 0x08: '&', 0x09: '*', 0x0A: '(', 0x0B: ')',
|
|
109
|
+
0x0C: '_', 0x0D: '+',
|
|
110
|
+
0x10: 'Q', 0x11: 'W', 0x12: 'E', 0x13: 'R', 0x14: 'T',
|
|
111
|
+
0x15: 'Y', 0x16: 'U', 0x17: 'I', 0x18: 'O', 0x19: 'P',
|
|
112
|
+
0x1A: '{', 0x1B: '}',
|
|
113
|
+
0x1E: 'A', 0x1F: 'S', 0x20: 'D', 0x21: 'F', 0x22: 'G',
|
|
114
|
+
0x23: 'H', 0x24: 'J', 0x25: 'K', 0x26: 'L',
|
|
115
|
+
0x27: ':', 0x28: '"', 0x29: '~',
|
|
116
|
+
0x2B: '|',
|
|
117
|
+
0x2C: 'Z', 0x2D: 'X', 0x2E: 'C', 0x2F: 'V', 0x30: 'B',
|
|
118
|
+
0x31: 'N', 0x32: 'M',
|
|
119
|
+
0x33: '<', 0x34: '>', 0x35: '?',
|
|
120
|
+
0x39: ' ',
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class KeyEvent:
|
|
126
|
+
"""Represents a keyboard event"""
|
|
127
|
+
scancode: int
|
|
128
|
+
char: str
|
|
129
|
+
pressed: bool # True for key down, False for key up
|
|
130
|
+
shift: bool
|
|
131
|
+
ctrl: bool
|
|
132
|
+
alt: bool
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Keyboard:
|
|
136
|
+
"""
|
|
137
|
+
PS/2 Keyboard Driver.
|
|
138
|
+
|
|
139
|
+
Provides methods to read keyboard input.
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
@kernel.on_keypress
|
|
143
|
+
def handle_key(key):
|
|
144
|
+
Screen.print(f"You pressed: {key.char}")
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
_handlers: list = []
|
|
148
|
+
_buffer: list = []
|
|
149
|
+
_shift_pressed: bool = False
|
|
150
|
+
_ctrl_pressed: bool = False
|
|
151
|
+
_alt_pressed: bool = False
|
|
152
|
+
_operations: list = []
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def read_key(cls) -> Optional[KeyEvent]:
|
|
156
|
+
"""
|
|
157
|
+
Read a single key from the keyboard buffer.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
KeyEvent if a key is available, None otherwise.
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
key = Keyboard.read_key()
|
|
164
|
+
if key:
|
|
165
|
+
Screen.print(key.char)
|
|
166
|
+
"""
|
|
167
|
+
cls._operations.append({"type": "read_key"})
|
|
168
|
+
if cls._buffer:
|
|
169
|
+
return cls._buffer.pop(0)
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def read_char(cls) -> Optional[str]:
|
|
174
|
+
"""
|
|
175
|
+
Read a single character from keyboard.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Character string if available, None otherwise.
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
char = Keyboard.read_char()
|
|
182
|
+
"""
|
|
183
|
+
cls._operations.append({"type": "read_char"})
|
|
184
|
+
key = cls.read_key()
|
|
185
|
+
if key and key.char:
|
|
186
|
+
return key.char
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def read_line(cls) -> str:
|
|
191
|
+
"""
|
|
192
|
+
Read a line of input (until Enter is pressed).
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
The input string.
|
|
196
|
+
|
|
197
|
+
Example:
|
|
198
|
+
name = Keyboard.read_line()
|
|
199
|
+
Screen.print(f"Hello, {name}!")
|
|
200
|
+
"""
|
|
201
|
+
cls._operations.append({"type": "read_line"})
|
|
202
|
+
return "" # Placeholder - actual implementation in ASM
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def wait_key(cls) -> KeyEvent:
|
|
206
|
+
"""
|
|
207
|
+
Wait for a key press (blocking).
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
The KeyEvent when a key is pressed.
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
Screen.print("Press any key...")
|
|
214
|
+
key = Keyboard.wait_key()
|
|
215
|
+
"""
|
|
216
|
+
cls._operations.append({"type": "wait_key"})
|
|
217
|
+
return KeyEvent(0, '', True, False, False, False)
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def is_key_pressed(cls, key: KeyCode) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
Check if a specific key is currently pressed.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
key: The KeyCode to check
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
True if the key is pressed.
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
if Keyboard.is_key_pressed(KeyCode.SPACE):
|
|
232
|
+
Screen.print("Space is pressed!")
|
|
233
|
+
"""
|
|
234
|
+
cls._operations.append({
|
|
235
|
+
"type": "is_key_pressed",
|
|
236
|
+
"keycode": key.value,
|
|
237
|
+
})
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def is_shift_pressed(cls) -> bool:
|
|
242
|
+
"""Check if Shift key is pressed."""
|
|
243
|
+
return cls._shift_pressed
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def is_ctrl_pressed(cls) -> bool:
|
|
247
|
+
"""Check if Ctrl key is pressed."""
|
|
248
|
+
return cls._ctrl_pressed
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def is_alt_pressed(cls) -> bool:
|
|
252
|
+
"""Check if Alt key is pressed."""
|
|
253
|
+
return cls._alt_pressed
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def clear_buffer(cls) -> None:
|
|
257
|
+
"""Clear the keyboard buffer."""
|
|
258
|
+
cls._buffer.clear()
|
|
259
|
+
cls._operations.append({"type": "clear_buffer"})
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def set_repeat_rate(cls, delay_ms: int = 500, rate_cps: int = 10) -> None:
|
|
263
|
+
"""
|
|
264
|
+
Set keyboard repeat rate.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
delay_ms: Delay before repeat starts (ms)
|
|
268
|
+
rate_cps: Characters per second when repeating
|
|
269
|
+
"""
|
|
270
|
+
cls._operations.append({
|
|
271
|
+
"type": "set_repeat_rate",
|
|
272
|
+
"delay": delay_ms,
|
|
273
|
+
"rate": rate_cps,
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def _process_scancode(cls, scancode: int) -> Optional[KeyEvent]:
|
|
278
|
+
"""Process a raw scancode (used internally)."""
|
|
279
|
+
pressed = (scancode & 0x80) == 0
|
|
280
|
+
code = scancode & 0x7F
|
|
281
|
+
|
|
282
|
+
# Handle modifier keys
|
|
283
|
+
if code == KeyCode.LEFT_SHIFT.value or code == KeyCode.RIGHT_SHIFT.value:
|
|
284
|
+
cls._shift_pressed = pressed
|
|
285
|
+
return None
|
|
286
|
+
elif code == KeyCode.LEFT_CTRL.value:
|
|
287
|
+
cls._ctrl_pressed = pressed
|
|
288
|
+
return None
|
|
289
|
+
elif code == KeyCode.LEFT_ALT.value:
|
|
290
|
+
cls._alt_pressed = pressed
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
# Get character
|
|
294
|
+
char_map = SCANCODE_TO_CHAR_SHIFT if cls._shift_pressed else SCANCODE_TO_CHAR
|
|
295
|
+
char = char_map.get(code, '')
|
|
296
|
+
|
|
297
|
+
return KeyEvent(
|
|
298
|
+
scancode=code,
|
|
299
|
+
char=char,
|
|
300
|
+
pressed=pressed,
|
|
301
|
+
shift=cls._shift_pressed,
|
|
302
|
+
ctrl=cls._ctrl_pressed,
|
|
303
|
+
alt=cls._alt_pressed,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def _reset(cls) -> None:
|
|
308
|
+
"""Reset keyboard state (used internally)."""
|
|
309
|
+
cls._handlers = []
|
|
310
|
+
cls._buffer = []
|
|
311
|
+
cls._shift_pressed = False
|
|
312
|
+
cls._ctrl_pressed = False
|
|
313
|
+
cls._alt_pressed = False
|
|
314
|
+
cls._operations = []
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def _get_operations(cls) -> list:
|
|
318
|
+
"""Get all recorded operations (used by compiler)."""
|
|
319
|
+
return cls._operations.copy()
|
pyos/drivers/screen.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS Screen Driver - VGA Text Mode
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Color(Enum):
|
|
11
|
+
"""VGA Text Mode Colors"""
|
|
12
|
+
BLACK = 0
|
|
13
|
+
BLUE = 1
|
|
14
|
+
GREEN = 2
|
|
15
|
+
CYAN = 3
|
|
16
|
+
RED = 4
|
|
17
|
+
MAGENTA = 5
|
|
18
|
+
BROWN = 6
|
|
19
|
+
LIGHT_GRAY = 7
|
|
20
|
+
DARK_GRAY = 8
|
|
21
|
+
LIGHT_BLUE = 9
|
|
22
|
+
LIGHT_GREEN = 10
|
|
23
|
+
LIGHT_CYAN = 11
|
|
24
|
+
LIGHT_RED = 12
|
|
25
|
+
LIGHT_MAGENTA = 13
|
|
26
|
+
YELLOW = 14
|
|
27
|
+
WHITE = 15
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Color name mapping
|
|
31
|
+
COLOR_NAMES = {
|
|
32
|
+
"black": Color.BLACK,
|
|
33
|
+
"blue": Color.BLUE,
|
|
34
|
+
"green": Color.GREEN,
|
|
35
|
+
"cyan": Color.CYAN,
|
|
36
|
+
"red": Color.RED,
|
|
37
|
+
"magenta": Color.MAGENTA,
|
|
38
|
+
"brown": Color.BROWN,
|
|
39
|
+
"light_gray": Color.LIGHT_GRAY,
|
|
40
|
+
"gray": Color.LIGHT_GRAY,
|
|
41
|
+
"dark_gray": Color.DARK_GRAY,
|
|
42
|
+
"light_blue": Color.LIGHT_BLUE,
|
|
43
|
+
"light_green": Color.LIGHT_GREEN,
|
|
44
|
+
"light_cyan": Color.LIGHT_CYAN,
|
|
45
|
+
"light_red": Color.LIGHT_RED,
|
|
46
|
+
"light_magenta": Color.LIGHT_MAGENTA,
|
|
47
|
+
"yellow": Color.YELLOW,
|
|
48
|
+
"white": Color.WHITE,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class ScreenConfig:
|
|
54
|
+
"""Screen configuration"""
|
|
55
|
+
width: int = 80
|
|
56
|
+
height: int = 25
|
|
57
|
+
vga_address: int = 0xB8000
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Screen:
|
|
61
|
+
"""
|
|
62
|
+
VGA Text Mode Screen Driver.
|
|
63
|
+
|
|
64
|
+
Provides methods to write text to the screen with colors.
|
|
65
|
+
All methods are class methods for easy access.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
Screen.clear()
|
|
69
|
+
Screen.set_color("green", "black")
|
|
70
|
+
Screen.print("Hello World!")
|
|
71
|
+
Screen.print_at("Position text", row=5, col=10)
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
_config = ScreenConfig()
|
|
75
|
+
_cursor_row: int = 0
|
|
76
|
+
_cursor_col: int = 0
|
|
77
|
+
_foreground: Color = Color.WHITE
|
|
78
|
+
_background: Color = Color.BLACK
|
|
79
|
+
|
|
80
|
+
# Internal buffer for compilation
|
|
81
|
+
_operations: list = []
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def clear(cls) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Clear the entire screen.
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
Screen.clear()
|
|
90
|
+
"""
|
|
91
|
+
cls._cursor_row = 0
|
|
92
|
+
cls._cursor_col = 0
|
|
93
|
+
cls._operations.append({
|
|
94
|
+
"type": "clear",
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def print(
|
|
99
|
+
cls,
|
|
100
|
+
text: str,
|
|
101
|
+
row: Optional[int] = None,
|
|
102
|
+
col: Optional[int] = None,
|
|
103
|
+
color: Optional[str] = None,
|
|
104
|
+
background: Optional[str] = None,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Print text to the screen.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
text: The text to print
|
|
111
|
+
row: Row position (optional, uses cursor if not specified)
|
|
112
|
+
col: Column position (optional, uses cursor if not specified)
|
|
113
|
+
color: Foreground color name (optional)
|
|
114
|
+
background: Background color name (optional)
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
Screen.print("Hello World!")
|
|
118
|
+
Screen.print("Colored text", color="green")
|
|
119
|
+
Screen.print("At position", row=5, col=10)
|
|
120
|
+
"""
|
|
121
|
+
if row is not None:
|
|
122
|
+
cls._cursor_row = row
|
|
123
|
+
if col is not None:
|
|
124
|
+
cls._cursor_col = col
|
|
125
|
+
|
|
126
|
+
fg = cls._foreground
|
|
127
|
+
bg = cls._background
|
|
128
|
+
|
|
129
|
+
if color is not None:
|
|
130
|
+
fg = COLOR_NAMES.get(color.lower(), cls._foreground)
|
|
131
|
+
if background is not None:
|
|
132
|
+
bg = COLOR_NAMES.get(background.lower(), cls._background)
|
|
133
|
+
|
|
134
|
+
cls._operations.append({
|
|
135
|
+
"type": "print",
|
|
136
|
+
"text": text,
|
|
137
|
+
"row": cls._cursor_row,
|
|
138
|
+
"col": cls._cursor_col,
|
|
139
|
+
"foreground": fg.value,
|
|
140
|
+
"background": bg.value,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
# Move cursor to next line
|
|
144
|
+
cls._cursor_row += 1
|
|
145
|
+
cls._cursor_col = 0
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def print_at(
|
|
149
|
+
cls,
|
|
150
|
+
text: str,
|
|
151
|
+
row: int,
|
|
152
|
+
col: int,
|
|
153
|
+
color: Optional[str] = None,
|
|
154
|
+
background: Optional[str] = None,
|
|
155
|
+
) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Print text at a specific position.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
text: The text to print
|
|
161
|
+
row: Row position (0-24)
|
|
162
|
+
col: Column position (0-79)
|
|
163
|
+
color: Foreground color name (optional)
|
|
164
|
+
background: Background color name (optional)
|
|
165
|
+
|
|
166
|
+
Example:
|
|
167
|
+
Screen.print_at("Hello", row=10, col=35)
|
|
168
|
+
"""
|
|
169
|
+
cls.print(text, row=row, col=col, color=color, background=background)
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def print_char(
|
|
173
|
+
cls,
|
|
174
|
+
char: str,
|
|
175
|
+
row: int,
|
|
176
|
+
col: int,
|
|
177
|
+
color: Optional[str] = None,
|
|
178
|
+
background: Optional[str] = None,
|
|
179
|
+
) -> None:
|
|
180
|
+
"""
|
|
181
|
+
Print a single character at a specific position.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
char: Single character to print
|
|
185
|
+
row: Row position
|
|
186
|
+
col: Column position
|
|
187
|
+
color: Foreground color (optional)
|
|
188
|
+
background: Background color (optional)
|
|
189
|
+
"""
|
|
190
|
+
cls._operations.append({
|
|
191
|
+
"type": "print_char",
|
|
192
|
+
"char": char[0] if char else ' ',
|
|
193
|
+
"row": row,
|
|
194
|
+
"col": col,
|
|
195
|
+
"foreground": COLOR_NAMES.get(color, cls._foreground).value if color else cls._foreground.value,
|
|
196
|
+
"background": COLOR_NAMES.get(background, cls._background).value if background else cls._background.value,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def set_color(cls, foreground: str, background: str = "black") -> None:
|
|
201
|
+
"""
|
|
202
|
+
Set the default text colors.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
foreground: Foreground color name
|
|
206
|
+
background: Background color name (default: black)
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
Screen.set_color("green", "black")
|
|
210
|
+
Screen.set_color("white", "blue")
|
|
211
|
+
"""
|
|
212
|
+
cls._foreground = COLOR_NAMES.get(foreground.lower(), Color.WHITE)
|
|
213
|
+
cls._background = COLOR_NAMES.get(background.lower(), Color.BLACK)
|
|
214
|
+
|
|
215
|
+
cls._operations.append({
|
|
216
|
+
"type": "set_color",
|
|
217
|
+
"foreground": cls._foreground.value,
|
|
218
|
+
"background": cls._background.value,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def set_cursor(cls, row: int, col: int) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Set the cursor position.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
row: Row position (0-24)
|
|
228
|
+
col: Column position (0-79)
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
Screen.set_cursor(10, 40)
|
|
232
|
+
"""
|
|
233
|
+
cls._cursor_row = max(0, min(row, cls._config.height - 1))
|
|
234
|
+
cls._cursor_col = max(0, min(col, cls._config.width - 1))
|
|
235
|
+
|
|
236
|
+
cls._operations.append({
|
|
237
|
+
"type": "set_cursor",
|
|
238
|
+
"row": cls._cursor_row,
|
|
239
|
+
"col": cls._cursor_col,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def get_cursor(cls) -> Tuple[int, int]:
|
|
244
|
+
"""
|
|
245
|
+
Get the current cursor position.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Tuple of (row, col)
|
|
249
|
+
"""
|
|
250
|
+
return (cls._cursor_row, cls._cursor_col)
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def scroll_up(cls, lines: int = 1) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Scroll the screen up by a number of lines.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
lines: Number of lines to scroll (default: 1)
|
|
259
|
+
"""
|
|
260
|
+
cls._operations.append({
|
|
261
|
+
"type": "scroll_up",
|
|
262
|
+
"lines": lines,
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def scroll_down(cls, lines: int = 1) -> None:
|
|
267
|
+
"""
|
|
268
|
+
Scroll the screen down by a number of lines.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
lines: Number of lines to scroll (default: 1)
|
|
272
|
+
"""
|
|
273
|
+
cls._operations.append({
|
|
274
|
+
"type": "scroll_down",
|
|
275
|
+
"lines": lines,
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
@classmethod
|
|
279
|
+
def enable_cursor(cls) -> None:
|
|
280
|
+
"""Enable the hardware cursor."""
|
|
281
|
+
cls._operations.append({"type": "enable_cursor"})
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def disable_cursor(cls) -> None:
|
|
285
|
+
"""Disable the hardware cursor."""
|
|
286
|
+
cls._operations.append({"type": "disable_cursor"})
|
|
287
|
+
|
|
288
|
+
@classmethod
|
|
289
|
+
def get_width(cls) -> int:
|
|
290
|
+
"""Get screen width in characters."""
|
|
291
|
+
return cls._config.width
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def get_height(cls) -> int:
|
|
295
|
+
"""Get screen height in characters."""
|
|
296
|
+
return cls._config.height
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def _reset(cls) -> None:
|
|
300
|
+
"""Reset screen state (used internally)."""
|
|
301
|
+
cls._cursor_row = 0
|
|
302
|
+
cls._cursor_col = 0
|
|
303
|
+
cls._foreground = Color.WHITE
|
|
304
|
+
cls._background = Color.BLACK
|
|
305
|
+
cls._operations = []
|
|
306
|
+
|
|
307
|
+
@classmethod
|
|
308
|
+
def _get_operations(cls) -> list:
|
|
309
|
+
"""Get all recorded operations (used by compiler)."""
|
|
310
|
+
return cls._operations.copy()
|