Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions app/src/main/runtime/display/XServerDisplayActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2713,6 +2713,7 @@ this, shortcut, isCloudSyncEnabledForShortcut(),
extractGraphicsDriverFiles();
resolveAudioDriver();
changeWineAudioDriver();
applyColorCursorRegistry();

try {
if (steamFuture != null) steamFuture.get();
Expand Down Expand Up @@ -8306,6 +8307,8 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException {
overrideEnvVars.clear();
}

applyColorCursors();

environment = new XEnvironment(this, imageFs);
environment.addComponent(
new SysVSharedMemoryComponent(
Expand Down Expand Up @@ -14118,6 +14121,24 @@ private void resolveAudioDriver() {
this, R.string.directaudio_unavailable, android.widget.Toast.LENGTH_LONG).show());
}

private void applyColorCursors() {
if (waylandMode || gamescopeMode) return;
String toggle = envVars.get("BANNER_X11_RENDER");
if (toggle != null && (toggle.equals("0") || toggle.equalsIgnoreCase("false"))) return;
xServer.enableRenderCursors();
}

private void applyColorCursorRegistry() {
if (waylandMode) return;
File userRegFile = new File(imageFs.getRootDir(), ImageFs.WINEPREFIX + "/user.reg");
if (!userRegFile.exists()) return;
try (WineRegistryEditor registryEditor = new WineRegistryEditor(userRegFile)) {
registryEditor.setStringValue("Software\\Wine\\X11 Driver", "ClientSideWithRender", "N");
} catch (Exception e) {
Log.w(TAG, "Failed to disable client-side RENDER", e);
}
}

private void changeWineAudioDriver() {
if (!audioDriver.equals(container.getExtra("audioDriver"))) {
File rootDir = imageFs.getRootDir();
Expand Down
103 changes: 103 additions & 0 deletions app/src/main/runtime/display/xserver/CursorManager.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.winlator.cmod.runtime.display.xserver;

import android.util.SparseArray;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

public class CursorManager extends XResourceManager {
Expand Down Expand Up @@ -36,6 +38,107 @@ public Cursor createCursor(int id, short x, short y, Pixmap sourcePixmap, Pixmap
return cursor;
}

private static final String[] FALLBACK_ARROW = {
"X ",
"XX ",
"X.X ",
"X..X ",
"X...X ",
"X....X ",
"X.....X ",
"X......X ",
"X.......X ",
"X........X ",
"X.....XXXXX",
"X..X..X ",
"X.X X..X ",
"XX X..X ",
"X X..X ",
" X..X ",
" XX "
};

public Cursor createFallbackArrowCursor(int id) {
if (cursors.indexOfKey(id) >= 0) return null;
short width = (short) FALLBACK_ARROW[0].length();
short height = (short) FALLBACK_ARROW.length;
ByteBuffer pixels =
ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.LITTLE_ENDIAN);
IntBuffer data = pixels.asIntBuffer();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
char c = FALLBACK_ARROW[y].charAt(x);
data.put(y * width + x, c == 'X' ? 0xff000000 : c == '.' ? 0xffffffff : 0);
}
}
return addArgbCursor(id, 0, 0, width, height, pixels);
}

/**
* libXcursor hands over premultiplied ARGB32 pixels, while cursor drawables are blended with
* straight alpha, so the alpha is divided back out here.
*/
public Cursor createArgbCursor(
int id, int hotX, int hotY, short width, short height, ByteBuffer argb) {
if (cursors.indexOfKey(id) >= 0) return null;
if (width <= 0 || height <= 0 || argb.capacity() < width * height * 4) return null;

ByteBuffer pixels =
ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.LITTLE_ENDIAN);
IntBuffer src = argb.duplicate().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
IntBuffer dst = pixels.asIntBuffer();
boolean visible = false;
for (int i = 0; i < width * height; i++) {
int pixel = src.get(i);
int alpha = pixel >>> 24;
if (alpha == 0) {
dst.put(i, 0);
continue;
}
visible = true;
dst.put(i, alpha == 0xff ? pixel : (alpha << 24) | unpremultiply(pixel, alpha));
}
Cursor cursor = addArgbCursor(id, hotX, hotY, width, height, pixels);
// A fully transparent cursor is how games hide the pointer, so it must not become an arrow.
cursor.setVisible(visible);
return cursor;
}

private static int unpremultiply(int pixel, int alpha) {
int red = Math.min(0xff, (((pixel >> 16) & 0xff) * 0xff + alpha - 1) / alpha);
int green = Math.min(0xff, (((pixel >> 8) & 0xff) * 0xff + alpha - 1) / alpha);
int blue = Math.min(0xff, ((pixel & 0xff) * 0xff + alpha - 1) / alpha);
return (red << 16) | (green << 8) | blue;
}

private Cursor addArgbCursor(
int id, int hotX, int hotY, short width, short height, ByteBuffer pixels) {
Drawable drawable =
drawableManager.createDrawable(IDGenerator.generate(), width, height, (byte) 32);
drawable.drawImage(
(short) 0,
(short) 0,
(short) 0,
(short) 0,
width,
height,
(byte) 32,
pixels,
width,
height);
Cursor cursor =
new Cursor(
id,
Math.max(0, Math.min(hotX, width - 1)),
Math.max(0, Math.min(hotY, height - 1)),
drawable,
null,
null);
cursors.put(id, cursor);
triggerOnCreateResourceListener(cursor);
return cursor;
}

public void freeCursor(int id) {
triggerOnFreeResourceListener(cursors.get(id));
cursors.remove(id);
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/runtime/display/xserver/XServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.winlator.cmod.runtime.display.xserver.extensions.Extension;
import com.winlator.cmod.runtime.display.xserver.extensions.MITSHMExtension;
import com.winlator.cmod.runtime.display.xserver.extensions.PresentExtension;
import com.winlator.cmod.runtime.display.xserver.extensions.RenderExtension;
import com.winlator.cmod.runtime.display.xserver.extensions.SyncExtension;
import com.winlator.cmod.runtime.display.xserver.extensions.XInput2Extension;
import com.winlator.cmod.shared.android.CursorLocker;
Expand Down Expand Up @@ -392,6 +393,14 @@ private void registerExtension(Extension ext, int[] nextEventId, int[] nextError
extensions.put(ext.getMajorOpcode(), ext);
}

public void enableRenderCursors() {
extensions.put(RenderExtension.MAJOR_OPCODE, new RenderExtension(this));
}

public boolean isRenderCursorsEnabled() {
return extensions.get(RenderExtension.MAJOR_OPCODE) != null;
}

private void setupExtensions() {
int[] nextEventId = {64};
int[] nextErrorId = {128};
Expand Down
Loading
Loading