2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
34 /*#include <sys/farptr.h>*/
36 #ifndef wake_up_wait_list
37 void u_name(wake_up_wait_list
)(struct list
*wait_list
, mutex_t
*mutex_to_lock
, bool can_allocate_memory
);
38 void c_name(wake_up_wait_list
)(struct list
*wait_list
, mutex_t
*mutex_to_lock
, bool can_allocate_memory
);
41 static bool have_ntvdm
;
42 static int dos_charset
;
43 static struct console_read_packet pkt
;
45 static struct list packet_wait_list
;
46 static mutex_t packet_mutex
;
48 static int8_t dos_mouse_initialized
;
49 static uint8_t dos_mouse_n_buttons
;
50 static unsigned short dos_mouse_last_x
;
51 static unsigned short dos_mouse_last_y
;
52 static unsigned short dos_mouse_last_buttons
;
54 static struct console_read_packet
*mouse_events
;
55 size_t n_mouse_events
;
57 static bool dos_mouse_init(void)
61 if (dos_mouse_initialized
)
62 return dos_mouse_initialized
== 1;
64 memset(&r
, 0, sizeof(__dpmi_regs
));
66 if (r
.x
.ax
!= 0xffff) {
67 dos_mouse_initialized
= -1;
70 dos_mouse_n_buttons
= r
.x
.bx
== 3 ? 3 : 2;
71 dos_mouse_initialized
= 1;
73 memset(&r
, 0, sizeof(__dpmi_regs
));
76 r
.x
.dx
= ScreenCols() * 8 - 1;
79 memset(&r
, 0, sizeof(__dpmi_regs
));
82 r
.x
.dx
= ScreenRows() * 8 - 1;
87 dos_mouse_last_x
= r
.x
.cx
/ 8;
88 dos_mouse_last_y
= r
.x
.dx
/ 8;
89 dos_mouse_last_buttons
= r
.x
.bx
;
94 static void dos_enqueue_mouse_packet(unsigned short x
, unsigned short y
, unsigned short buttons
)
97 struct console_read_packet mouse_packet
;
100 memset(&mouse_packet
, 0, sizeof(struct console_read_packet
));
101 mouse_packet
.type
= 2;
102 mouse_packet
.u
.m
.x
= x
;
103 mouse_packet
.u
.m
.y
= y
;
104 mouse_packet
.u
.m
.prev_buttons
= dos_mouse_last_buttons
;
105 mouse_packet
.u
.m
.buttons
= buttons
;
107 if (n_mouse_events
) {
108 struct console_read_packet
*last
= &mouse_events
[n_mouse_events
- 1];
109 if (last
->u
.m
.buttons
== last
->u
.m
.prev_buttons
)
113 if (unlikely(!array_add_mayfail(struct console_read_packet
, &mouse_events
, &n_mouse_events
, mouse_packet
, &errp
, &sink
))) {
118 dos_mouse_last_x
= x
;
119 dos_mouse_last_y
= y
;
120 dos_mouse_last_buttons
= buttons
;
123 static void dos_mouse_poll(void)
129 for (i
= 0; i
< dos_mouse_n_buttons
; i
++) {
130 memset(&r
, 0, sizeof(__dpmi_regs
));
131 r
.x
.ax
= !(dos_mouse_last_buttons
& (1 << i
)) ? 5 : 6;
133 __dpmi_int(0x33, &r
);
135 dos_enqueue_mouse_packet(r
.x
.cx
/ 8, r
.x
.dx
/ 8, dos_mouse_last_buttons
^ (1 << i
));
138 memset(&r
, 0, sizeof(__dpmi_regs
));
140 __dpmi_int(0x33, &r
);
144 if (x
!= dos_mouse_last_x
|| y
!= dos_mouse_last_y
)
145 dos_enqueue_mouse_packet(x
, y
, dos_mouse_last_buttons
);
148 static void dos_mouse_set_visibility(bool visible
)
150 if (dos_mouse_initialized
== 1) {
152 memset(&r
, 0, sizeof(__dpmi_regs
));
153 r
.x
.ax
= visible
? 1 : 2;
154 __dpmi_int(0x33, &r
);
158 bool dos_poll_devices(void)
160 if (dos_mouse_initialized
== 1) {
164 if (!list_is_empty(&packet_wait_list
) && !have_pkt
) {
167 if (dos_mouse_init() && n_mouse_events
) {
168 mutex_lock(&packet_mutex
);
169 memcpy(&pkt
, mouse_events
, sizeof(struct console_read_packet
));
171 memmove(mouse_events
, mouse_events
+ 1, (n_mouse_events
- 1) * sizeof(struct console_read_packet
));
173 call(wake_up_wait_list
)(&packet_wait_list
, &packet_mutex
, true);
177 if (!bioskey(0x11)) {
179 * This is very strange. It fixes some lockups in NTVDM
180 * if we are holding a key for a few minutes
183 mutex_lock(&packet_mutex
);
184 call(wake_up_wait_list
)(&packet_wait_list
, &packet_mutex
, true);
189 /*uint16_t head, tail;
191 head = _farpeekw(0x40, 0x1a);
192 tail = _farpeekw(0x40, 0x1c);
195 mutex_lock(&packet_mutex);
196 call(wake_up_wait_list)(&packet_wait_list, &packet_mutex, true);
199 k = _farpeekw(0x40, head);
201 if (head == _farpeekw(0x40, 0x82))
202 head = _farpeekw(0x40, 0x80);
203 _farpokew(0x40, 0x1a, head);
206 /*debug("bioskey: %04x", k);*/
207 mutex_lock(&packet_mutex
);
208 memset(&pkt
, 0, sizeof(struct console_read_packet
));
210 pkt
.u
.k
.vkey
= (k
>> 8) & 0xff;
211 pkt
.u
.k
.key
= k
& 0xff;
212 pkt
.u
.k
.cp
= dos_charset
;
214 call(wake_up_wait_list
)(&packet_wait_list
, &packet_mutex
, true);
230 void dos_wait_on_packet(mutex_t
**mutex_to_lock
, struct list
*list_entry
)
232 *mutex_to_lock
= &packet_mutex
;
233 mutex_lock(&packet_mutex
);
234 list_add(&packet_wait_list
, list_entry
);
235 if (unlikely(have_pkt
)) {
238 mutex_unlock(&packet_mutex
);
242 call(wake_up_wait_list
)(&packet_wait_list
, &packet_mutex
, true);
245 ssize_t
os_read_console_packet(handle_t attr_unused h
, struct console_read_packet
*result
, ajla_error_t attr_unused
*err
)
248 memcpy(result
, &pkt
, sizeof(struct console_read_packet
));
252 return OS_RW_WOULDBLOCK
;
255 bool os_write_console_packet(handle_t attr_unused h
, struct console_write_packet attr_unused
*packet
, ajla_error_t
*err
)
257 bool mouse_hidden
= false;
258 uint8_t *buffer
= NULL
;
259 size_t buffer_chars
= 0;
260 if (unlikely(!isatty(h
))) {
261 fatal_mayfail(error_ajla(EC_SYNC
, AJLA_ERROR_INVALID_OPERATION
), err
, "attempting to use packet console on non-console");
266 switch (packet
->type
) {
274 dos_mouse_set_visibility(false);
279 n_chars
= packet
->u
.c
.n_chars
;
280 if (!buffer
|| n_chars
> buffer_chars
) {
281 if (unlikely(buffer
!= NULL
))
283 buffer_chars
= maximum(maximum(n_chars
, buffer_chars
* 2), 80);
284 buffer
= mem_alloc_mayfail(uint8_t *, buffer_chars
* 2, err
);
285 if (unlikely(!buffer
))
288 for (i
= 0; i
< n_chars
; i
++) {
289 buffer
[i
* 2] = packet
->u
.c
.data
[i
* 2];
290 buffer
[i
* 2 + 1] = packet
->u
.c
.data
[i
* 2 + 1];
293 if (unlikely(!puttext(x
+ 1, y
+ 1, x
+ n_chars
, y
+ 1, buffer
))) {
294 fatal_mayfail(error_ajla(EC_SYNC
, AJLA_ERROR_INVALID_OPERATION
), err
, "puttext failed");
298 packet
= cast_ptr(struct console_write_packet
*, &packet
->u
.c
.data
[packet
->u
.c
.n_chars
* 2]);
302 gotoxy(packet
->u
.p
.x
+ 1, packet
->u
.p
.y
+ 1);
303 packet
= cast_ptr(struct console_write_packet
*, &packet
->u
.p
.end
);
307 _setcursortype(packet
->u
.v
.v
? _NORMALCURSOR
: _NOCURSOR
);
308 packet
= cast_ptr(struct console_write_packet
*, &packet
->u
.v
.end
);
312 internal(file_line
, "os_write_console_packet: invalid type %d", (int)packet
->type
);
317 dos_mouse_set_visibility(true);
318 if (unlikely(buffer
!= NULL
))
324 dos_mouse_set_visibility(true);
325 if (unlikely(buffer
!= NULL
))
330 bool os_drives(char **drives
, size_t *drives_l
, ajla_error_t
*err
)
332 unsigned cd
, n_drvs
, i
, j
;
333 if (unlikely(!array_init_mayfail(char, drives
, drives_l
, err
)))
336 for (i
= 0; i
< 26; i
++) {
337 char str
[4] = " :\\";
339 _dos_setdrive(i
+ 1, &n_drvs
);
344 if (unlikely(!array_add_multiple_mayfail(char, drives
, drives_l
, str
, 4, NULL
, err
))) {
345 _dos_setdrive(cd
, &n_drvs
);
349 _dos_setdrive(cd
, &n_drvs
);
357 static void dos_revert_handles(int old_std_handles
[3])
360 for (i
= 0; i
< 3; i
++) {
361 if (old_std_handles
[i
] >= 0) {
363 if (unlikely(old_std_handles
[i
] == i
))
365 EINTR_LOOP(r
, dup2(old_std_handles
[i
], i
));
368 fatal("can't copy file descriptor %d to %d: %s", old_std_handles
[i
], i
, error_decode(error_from_errno(EC_SYSCALL
, er
)));
370 EINTR_LOOP(r
, close(old_std_handles
[i
]));
375 struct proc_handle
*os_proc_spawn(dir_handle_t wd
, const char *path
, size_t n_handles
, handle_t
*src
, int *target
, char * const args
[], char *envc
, ajla_error_t
*err
)
378 struct proc_handle
*ph
;
382 int old_std_handles
[3];
385 if (unlikely(!array_init_mayfail(char *, &env
, &env_l
, err
)))
388 if (unlikely(!array_add_mayfail(char *, &env
, &env_l
, envc
, NULL
, err
)))
390 envc
= strchr(envc
, 0) + 1;
392 if (unlikely(!array_add_mayfail(char *, &env
, &env_l
, NULL
, NULL
, err
)))
395 ph
= mem_alloc_mayfail(struct proc_handle
*, sizeof(struct proc_handle
), err
);
401 if (unlikely(!os_set_cwd(wd
, err
))) {
407 old_std_handles
[0] = old_std_handles
[1] = old_std_handles
[2] = -1;
408 for (i
= 0; i
< n_handles
; i
++) {
412 if (unlikely(t
>= 3)) {
413 fatal_mayfail(error_ajla(EC_SYNC
, AJLA_ERROR_NOT_SUPPORTED
), err
, "only the first three handles can be redirected");
416 if (old_std_handles
[t
] != -1) {
417 fatal_mayfail(error_ajla(EC_SYNC
, AJLA_ERROR_NOT_SUPPORTED
), err
, "redirecting a handle multiple times");
420 if (likely(s
== t
)) {
421 old_std_handles
[t
] = s
;
424 EINTR_LOOP(saved_t
, dup(t
));
425 if (unlikely(saved_t
== -1)) {
426 ajla_error_t e
= error_from_errno(EC_SYSCALL
, errno
);
427 fatal_mayfail(e
, err
, "can't save handle %d: %s", t
, error_decode(e
));
430 old_std_handles
[t
] = saved_t
;
431 if (s
< 3 && unlikely(old_std_handles
[s
] >= 0))
432 s
= old_std_handles
[s
];
433 EINTR_LOOP(r
, dup2(s
, t
));
434 if (unlikely(r
== -1)) {
435 ajla_error_t e
= error_from_errno(EC_SYSCALL
, errno
);
436 fatal_mayfail(e
, err
, "can't copy handle %d to %d: %s", s
, t
, error_decode(e
));
438 os_set_original_cwd();
439 dos_revert_handles(old_std_handles
);
446 EINTR_LOOP(r
, spawnve(P_WAIT
, path
, args
, env
));
450 os_set_original_cwd();
451 dos_revert_handles(old_std_handles
);
455 ajla_error_t e
= error_from_errno(EC_SYSCALL
, er
);
456 fatal_mayfail(e
, err
, "can't spawn process '%s': %s", path
, error_decode(e
));
464 void os_proc_free_handle(struct proc_handle
*ph
)
469 bool os_proc_register_wait(struct proc_handle
*ph
, mutex_t attr_unused
**mutex_to_lock
, struct list attr_unused
*list_entry
, int *status
)
471 *status
= ph
->status
;
475 void os_proc_check_all(void)
479 dir_handle_t
os_dir_root(ajla_error_t
*err
)
481 char *d
= str_dup(" :\\", -1, err
);
484 d
[0] = getdisk() + 'A';
492 dos_mouse_initialized
= 0;
493 array_init(struct console_read_packet
, &mouse_events
, &n_mouse_events
);
495 memset(&r
, 0, sizeof(__dpmi_regs
));
497 __dpmi_int(0x21, &r
);
498 have_ntvdm
= r
.h
.bl
== 0x5 && r
.h
.bh
== 0x32;
500 memset(&r
, 0, sizeof(__dpmi_regs
));
502 __dpmi_int(0x21, &r
);
503 if (!(r
.x
.flags
& 1)) {
504 dos_charset
= r
.x
.bx
;
510 list_init(&packet_wait_list
);
511 mutex_init(&packet_mutex
);
516 ajla_assert_lo(list_is_empty(&packet_wait_list
), (file_line
, "dos_done: packet_wait_list is not empty"));
517 mutex_done(&packet_mutex
);
518 mem_free(mouse_events
);