Fixing a dead line on my laptop screen with xrandr
A thin line appeared across the very top of my laptop screen. Full width, one edge to the other, always there.
The first thing I wanted to know was whether it was software or hardware, because those have very different fixes. So I put different colored backgrounds behind it and watched what the line did.
- On white it was dim, almost invisible.
- On black, blue and red it was bright, with a green and red fringe.
- It never disappeared on any background.
That last point is the one that matters. A line that changes color with the content but never goes away is not content being drawn wrong, it is the panel or the cable failing. Content bleed would vanish on at least one background.
This also killed the obvious idea. My first instinct had been to paint a black bar over those rows and hide the line under it. But black is the background where the line is at its brightest. Masking would have made it more visible, not less.
The trick: move the picture instead of hiding the line
If I cannot paint over the bad rows, I can stop using them. X11 lets you apply an arbitrary transformation matrix to an output, and a translation is a perfectly valid transformation:
xrandr --output eDP --transform 1,0,0,0,1,-25,0,0,1 --filter nearest
That middle -25 shifts the entire image down by 25 pixels. The bad rows are still bad, but nothing is drawn on them any more, so there is nothing there to be wrong. I started at -6 and walked it up until the line was gone.
Two things worth knowing before you copy that line.
My output is named eDP, not eDP-1. Run plain xrandr first and read the real name, otherwise you get a quiet warning: output eDP-1 not found; ignoring and nothing happens, which is exactly what happened to me on the first try.
--filter nearest matters. Without it the image gets resampled and everything looks slightly soft. With it, pixels land on pixels.
Reset any time with:
xrandr --output eDP --transform none
And if a re-apply throws BadMatch (invalid parameter attributes) on RRSetScreenSize, reset to none first, then apply. Re-applying a transform on top of an existing one is what triggers it.
The part that took the longest
The picture had moved, but the mouse cursor had not. The screen is still 1080 rows tall as far as X is concerned, so the pointer could still walk up into the dead strip and vanish under it.
I tried three things.
xinput coordinate transformation matrix. This is the standard answer you find for confining a pointer to part of a screen:
xinput set-prop "DEVICE" "Coordinate Transformation Matrix" 1 0 0 0 0.976 0.023 0 0 1
It did nothing useful. That matrix remaps an absolute input range onto a region of the screen. A normal mouse and most laptop touchpads report relative motion, so all the matrix does there is scale the deltas. It changes your pointer speed, it does not fence the pointer in. Worth knowing, because a lot of advice online repeats this without the caveat.
XFixes pointer barriers. These are the real mechanism, an invisible wall at a given line that blocks pointer motion crossing it in a chosen direction. About thirty lines of ctypes against libXfixes and it worked, immediately and properly.
Then a few minutes later my cursor got stuck and could not leave a band at the bottom of the screen. Cinnamon uses barriers itself for hot corners, and mine were fighting with its. I killed the process and got my cursor back, but a fix that occasionally traps your pointer is not a fix.
Polling and warping the pointer back. Not elegant. Check where the cursor is every 20ms, and if it is inside the dead strip, move it back out:
libX11.XQueryPointer(dpy, root, ...)
if ry.value < TOP:
libX11.XWarpPointer(dpy, 0, root, 0, 0, 0, 0, rx.value, TOP)
libX11.XFlush(dpy)
0.4% CPU, 12MB of RAM, and the worst thing that can happen is a tiny stutter at that edge. Nothing can trap the pointer, because nothing is holding it, something is just nudging it.
And then I turned that part off
Here is the detail I did not see coming. With a transform applied, X moves the image but keeps drawing the cursor untransformed. So the cursor's coordinates and the content's coordinates are 25 pixels out of step with each other.
Which means the rows I was blocking to keep the cursor out of the black strip were the same rows that now held the top of my toolbar. I could not click the top 25 pixels of my own panel. The pointer kept sliding away from the window buttons.
Cursor briefly invisible in a black strip is cosmetic. Not being able to hit the close button is not. So the clamp is off, and the script only does the transform now.
The final version is two files, in this gist: a script that takes the pixel count as its argument, plus the clamp daemon kept around in case I ever want it back.
Making it survive a reboot is a .desktop file in ~/.config/autostart/:
[Desktop Entry]
Type=Application
Exec=/home/YOU/screen-fix.sh 25
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=Monitor line fix
Be honest about what this is
This is a workaround, not a repair. The panel is still broken and the fault is spreading, which is what these faults do. Mine needed 6 pixels when I started and 25 by the end of the same session, and at one point showed up along the bottom edge too. A single line usually becomes several over a few months.
Before you settle for shifting your screen: open and close the lid slowly and watch the line. If it flickers or changes, the eDP cable is loose rather than the panel being dead, and reseating it at both ends can fix the whole thing for free. Press gently near the top bezel and see if the line reacts. And if the machine is still under warranty, this is a straightforward panel defect claim, so go make it.
I am buying time until then. 25 pixels of it.