Draw inverted monochrome cursor pixels so the text caret is visible

This commit is contained in:
Viktor Liu
2026-08-03 10:07:53 +02:00
parent 3d4137ae19
commit 4984e8efef
2 changed files with 42 additions and 5 deletions

View File

@@ -43,6 +43,7 @@ var (
procCreateJobObjectW = kernel32.NewProc("CreateJobObjectW")
procSetInformationJobObject = kernel32.NewProc("SetInformationJobObject")
procAssignProcessToJobObject = kernel32.NewProc("AssignProcessToJobObject")
procIsProcessInJob = kernel32.NewProc("IsProcessInJob")
procSetTokenInformation = advapi32.NewProc("SetTokenInformation")
procCreateEnvironmentBlock = userenv.NewProc("CreateEnvironmentBlock")
procDestroyEnvironmentBlock = userenv.NewProc("DestroyEnvironmentBlock")
@@ -323,7 +324,13 @@ func spawnAgentInSession(sessionID uint32, socketPath, authToken string, jobHand
if jobHandle != 0 {
r, _, e := procAssignProcessToJobObject.Call(uintptr(jobHandle), uintptr(pi.Process))
if r == 0 {
log.Warnf("assign agent to job object: %v (orphan possible on service crash)", e)
// Report the job state of both ends: assignment is refused when the
// child already belongs to a job that will not accept ours, which is
// the difference between a breakaway that did not happen and a
// rights problem on the handles.
log.Warnf("assign agent to job object: %v (orphan possible on service crash); "+
"daemon in a job: %s, agent in a job: %s",
e, describeInJob(windows.CurrentProcess()), describeInJob(pi.Process))
}
}
@@ -404,6 +411,21 @@ func newSessionManager() *sessionManager {
return m
}
// describeInJob reports whether a process belongs to any job object, for the
// diagnostic on a failed assignment. Returns the error text when the query
// itself fails, since that is equally informative there.
func describeInJob(h windows.Handle) string {
var inJob int32
r, _, e := procIsProcessInJob.Call(uintptr(h), 0, uintptr(unsafe.Pointer(&inJob)))
if r == 0 {
return fmt.Sprintf("unknown (%v)", e)
}
if inJob != 0 {
return "yes"
}
return "no"
}
// createKillOnCloseJob returns a Job Object configured so that closing its
// handle (process exit or explicit Close) terminates every process assigned
// to it. Used to keep orphaned vnc-agent processes from outliving the service.

View File

@@ -337,15 +337,30 @@ func decodeMonoCursor(hbmMask windows.Handle) (*image.RGBA, error) {
img := image.NewRGBA(image.Rect(0, 0, int(w), int(h)))
for y := int32(0); y < h; y++ {
for x := int32(0); x < w; x++ {
and := data[(y*w+x)*4]
xor := data[((y+h)*w+x)*4]
and := data[(y*w+x)*4] != 0
xor := data[((y+h)*w+x)*4] != 0
di := (y*w + x) * 4
if and != 0 {
// The two halves of the mask encode four states:
//
// AND=0 XOR=0 black
// AND=0 XOR=1 white
// AND=1 XOR=0 transparent
// AND=1 XOR=1 inverts whatever is on screen
//
// The cursor pseudo-encoding has no inversion, and the text
// caret (IDC_IBEAM) is drawn almost entirely from inverted
// pixels, which is how it stays legible over both light and dark
// text. Reading them as transparent left it invisible while
// every other stock cursor rendered, so they are drawn black
// here: that is what the other VNC servers settle on, and text
// fields are light far more often than not.
if and && !xor {
img.Pix[di+3] = 0
continue
}
c := byte(0)
if xor != 0 {
if !and && xor {
c = 255
}
img.Pix[di+0] = c