SchOS/kernel/dev/mouse.c
Nexoniarz eafdddefbc mouse: clamp the pointer to the screen that exists, not to 800x600
kernel/dev/mouse.c held the pointer inside literal 800x600 bounds. That
was correct while 800x600 was the only mode the loader could set, which
it was until the resolution became a boot-time choice. At 1280x800 the
pointer simply stopped two thirds of the way across and would not go
further -- which reads as the cursor drifting away from the host's rather
than as a clamp.

vga.c grows vga_get_width/vga_get_height for it. The mode is chosen at
boot now, so nothing above the framebuffer may hardcode its size, and
these are the first two callers that needed to ask.

Verified that the system still builds and boots at 1280x800, and that
bootcheck is 108 [ok] and schcom-test passes. I did not manage to
photograph the pointer past the old limit in the time available -- there
is no reader for /dev/mouse in the image and the screendump attempt did
not resolve, so the fix rests on inspection rather than on a picture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 05:46:12 +02:00

224 lines
No EOL
4.5 KiB
C

#include "types.h"
#include "defs.h"
#include "irq.h"
#include "x86.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#define KBD_S_STAT 0x64
#define KBD_S_DATA 0x60
#define KBD_C_WRITE 0xD4
#define MOUSE_ACK 0xFA
struct {
struct spinlock lock;
int x;
int y;
uint8 buttons;
} mouse_state;
static void
mouse_wait(int type)
{
int timeout = 100000;
if(type == 0){
while(timeout-- && !(inb(KBD_S_STAT) & 0x01));
} else {
while(timeout-- && (inb(KBD_S_STAT) & 0x02));
}
}
static void
mouse_write(uchar data)
{
mouse_wait(1);
outb(KBD_S_STAT, KBD_C_WRITE);
mouse_wait(1);
outb(KBD_S_DATA, data);
}
static uchar
mouse_read(void)
{
mouse_wait(0);
return inb(KBD_S_DATA);
}
static void
mouse_irq(void *arg)
{
(void)arg;
mouseintr();
}
// Move the pointer. Caller holds mouse_state.lock.
//
// Positive dy is *down*, matching both the screen and USB HID. The PS/2
// packet reports the opposite, which is why its caller negates first --
// there is one pointer and two protocols disagreeing about which way is up,
// and the conversion belongs at the edge rather than in here.
static void
mouse_move_locked(int dx, int dy)
{
mouse_state.x += dx;
mouse_state.y += dy;
// Clamp to the screen that actually exists. These were the literals 800 and
// 600, which was correct while that was the only mode the loader could set.
// It is a boot-time choice now (press R), and at 1280x800 the pointer simply
// stopped two thirds of the way across -- which reads as the cursor drifting
// away from the host's, rather than as a clamp.
int maxx = (int)vga_get_width();
int maxy = (int)vga_get_height();
if(maxx <= 0) maxx = 800;
if(maxy <= 0) maxy = 600;
if(mouse_state.x < 0) mouse_state.x = 0;
if(mouse_state.y < 0) mouse_state.y = 0;
if(mouse_state.x >= maxx) mouse_state.x = maxx - 1;
if(mouse_state.y >= maxy) mouse_state.y = maxy - 1;
}
// The same pointer, moved by something that is not the PS/2 port. USB HID
// uses this; anything else that produces motion events should too.
void
mouse_inject(int dx, int dy, int buttons)
{
acquire(&mouse_state.lock);
mouse_state.buttons = buttons;
mouse_move_locked(dx, dy);
release(&mouse_state.lock);
input_mouse(dx, dy, buttons);
}
void
mouseintr(void)
{
static uchar buf[3];
static int phase = 0;
uchar status;
status = inb(KBD_S_STAT);
if(!(status & 0x01) || !(status & 0x20)){
return;
}
uchar data = inb(KBD_S_DATA);
acquire(&mouse_state.lock);
if(phase == 0 && !(data & 0x08)){
release(&mouse_state.lock);
return;
}
buf[phase++] = data;
if(phase == 3){
phase = 0;
mouse_state.buttons = buf[0] & 0x07;
int dx = (buf[0] & 0x10) ? (int)buf[1] - 256 : buf[1];
int dy = (buf[0] & 0x20) ? (int)buf[2] - 256 : buf[2];
// The overflow bits mean the delta saturated and is meaningless; moving
// by it would throw the pointer across the screen.
if(!(buf[0] & 0x40) && !(buf[0] & 0x80))
mouse_move_locked(dx, -dy);
else
dx = dy = 0;
release(&mouse_state.lock);
// Outside the lock: input_report takes its own, and holding two at once
// is how a lock ordering rule gets invented by accident.
input_mouse(dx, -dy, buf[0] & 0x07);
return;
}
release(&mouse_state.lock);
}
int get_mouse_x(void) {
int x;
acquire(&mouse_state.lock);
x = mouse_state.x;
release(&mouse_state.lock);
return x;
}
int get_mouse_y(void) {
int y;
acquire(&mouse_state.lock);
y = mouse_state.y;
release(&mouse_state.lock);
return y;
}
uint8 get_mouse_buttons(void) {
uint8 b;
acquire(&mouse_state.lock);
b = mouse_state.buttons;
release(&mouse_state.lock);
return b;
}
int
mouseread(struct inode *ip, uint off, char *dst, int n)
{
struct mouse_data m;
int size = sizeof(struct mouse_data);
if(n < size) {
return -1;
}
m.x = get_mouse_x();
m.y = get_mouse_y();
m.buttons = get_mouse_buttons();
memmove(dst, &m, size);
return size;
}
void
mouseinit(void)
{
initlock(&mouse_state.lock, "mouse");
mouse_state.x = 400;
mouse_state.y = 300;
while(inb(KBD_S_STAT) & 0x01)
inb(KBD_S_DATA);
mouse_wait(1);
outb(KBD_S_STAT, 0xA8);
mouse_wait(1);
outb(KBD_S_STAT, 0x20);
mouse_wait(0);
uchar cb = inb(KBD_S_DATA);
cb |= 0x02;
cb |= 0x01;
cb &= ~0x20;
mouse_wait(1);
outb(KBD_S_STAT, 0x60);
mouse_wait(1);
outb(KBD_S_DATA, cb);
mouse_write(0xF6);
mouse_read();
mouse_write(0xF4);
mouse_read();
irq_register(IRQ_MOUSE, mouse_irq, 0, "ps2-mouse", 0);
}