Skip to content
Open
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
9 changes: 8 additions & 1 deletion src/BizHawk.Client.Common/ArgParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ public static class ArgParser
Description = "pairs in the format `k1:v1;k2:v2` (mind your shell escape sequences); if the value is `true`/`false` it's interpreted as a boolean, if it's a valid 32-bit signed integer e.g. `-1234` it's interpreted as such, if it's a valid 32-bit float e.g. `12.34` it's interpreted as such, else it's interpreted as a string",
};

private static readonly Option<string?> OptionWMClass = new ("--wmclass")
{
Description = "set a custom WM_CLASS for this Bizhawk initiation, Linux only.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a CLI flag? When should a user be changing it? If it's for vendoring, we already have VersionInfo.CustomBuildString.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial reasoning for looking into adding WM_CLASS was to help improve BizHawk's integration with the DE (I use KDE). By defining a WM_CLASS we can hint StartupWMClass for example in .desktop files.
In allowing WM_CLASS to be set in a CLI arg it is then feasible to distinguish between different installs of Bizhawk (for example, a Kaizo Ironmon setup and a regular Bizhawk install)

This comment was marked as resolved.

This comment was marked as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I should really get around to making a textbox to set the window title thing. Then you could pull from that, and your .desktop files could pass --config. Or maybe it could use the rom name and you wouldn't need any extra flags?

};

static ArgParser()
{
OptionAVDumpFrameList.Description = $"comma-separated list of integers, indices of frames which should be included in the A/V dump (encoding); implies `{OptionAVDumpEndAtFrame.Name}=<end>` where `<end>` is the highest frame listed";
Expand Down Expand Up @@ -162,6 +167,7 @@ private static RootCommand GetRootCommand()
root.Add(/* --url-post */ OptionHTTPClientURIPOST);
root.Add(/* --userdata */ OptionUserdataUnparsedPairs);
root.Add(/* --version */ OptionQueryAppVersion);
root.Add(/* --wmclass */ OptionWMClass);

return root;
}
Expand Down Expand Up @@ -268,7 +274,8 @@ private static void EnsureConsole()
openExtToolDll: result.GetValue(OptionOpenExternalTool),
socketProtocol: result.GetValue(OptionSocketServerUseUDP) ? ProtocolType.Udp : ProtocolType.Tcp,
userdataUnparsedPairs: userdataUnparsedPairs,
cmdRom: result.GetValue(ArgumentRomFilePath)
cmdRom: result.GetValue(ArgumentRomFilePath),
wmClassName: result.GetValue(OptionWMClass)
);
return null;
}
Expand Down
6 changes: 5 additions & 1 deletion src/BizHawk.Client.Common/ParsedCLIFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public readonly struct ParsedCLIFlags

public readonly string? cmdRom;

public readonly string? wmClassName;

public ParsedCLIFlags(
int? cmdLoadSlot,
string? cmdLoadState,
Expand All @@ -73,7 +75,8 @@ public ParsedCLIFlags(
string? openExtToolDll,
ProtocolType socketProtocol,
IReadOnlyList<(string Key, string Value)>? userdataUnparsedPairs,
string? cmdRom)
string? cmdRom,
string? wmClassName)
{
this.cmdLoadSlot = cmdLoadSlot;
this.cmdLoadState = cmdLoadState;
Expand All @@ -97,6 +100,7 @@ public ParsedCLIFlags(
SocketProtocol = socketProtocol;
UserdataUnparsedPairs = userdataUnparsedPairs;
this.cmdRom = cmdRom;
this.wmClassName = wmClassName;
}
}
}
7 changes: 7 additions & 0 deletions src/BizHawk.Client.EmuHawk/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,13 @@ private void MainForm_Load(object sender, EventArgs e)
else Console.WriteLine($"requested ext. tool dll {requestedExtToolDll} could not be loaded");
}

//Code to set WM_CLASS
if(OSTailoredCode.CurrentOS==OSTailoredCode.DistinctOS.Linux)
{
string wmclass = _argParser.wmClassName is not null ? _argParser.wmClassName : "BizHawk";
XlibImports.SetWMClass(_x11Display, wmclass);
}

#if DEBUG
AddDebugMenu();
#endif
Expand Down
56 changes: 56 additions & 0 deletions src/BizHawk.Common/LSB/XlibImports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -715,5 +715,61 @@ public struct XkbDescRec
[DllImport(XLIB)]
[return: MarshalAs(UnmanagedType.SysUInt)]
public static extern Keysym XkbKeycodeToKeysym(IntPtr display, uint keycode, int group, int level);

[DllImport(XLIB)]
public static extern IntPtr XAllocClassHint();

[DllImport(XLIB)]
public static extern Status XSetClassHint(IntPtr display, IntPtr window, IntPtr classHint);

[DllImport(XLIB)]
public static extern Status XGetClassHint(IntPtr display, IntPtr window, out IntPtr classHint);

public struct XClassHint
{
public IntPtr res_name;
public IntPtr res_class;
}

[DllImport(XLIB)]
public static extern Status XQueryTree(IntPtr display, IntPtr window, out IntPtr rootReturn, out IntPtr parentReturn, out IntPtr[] childrenReturn, out int childCount);
[DllImport(XLIB)]
public static extern Status XFetchName(IntPtr display, IntPtr window, out string returnedName);

public static void SetWMClass(IntPtr x11Display, string className)
{
//Establish reference to xDisplay
if(x11Display == IntPtr.Zero)
{
x11Display = XOpenDisplay(null);
}
//Setup the ClassHint
var wmSet = new XClassHint{res_name = Marshal.StringToCoTaskMemAnsi("BizHawk"), res_class = Marshal.StringToCoTaskMemAnsi(className)};
IntPtr point = XAllocClassHint();
Marshal.StructureToPtr(wmSet, point, true);

//To find the window we must query twice, once to get the array length and the second to populate
XQueryTree(x11Display, XDefaultRootWindow(x11Display), out _, out _, out _, out int windowCount);
IntPtr[] windowList = new IntPtr[windowCount];
XQueryTree(x11Display, XDefaultRootWindow(x11Display), out _, out _, out windowList, out windowCount);
for(int windowIterator = 0; windowIterator < windowCount; windowIterator++)
{
XFetchName(x11Display, windowList[windowIterator], out string name);
if(name?.Contains("BizHawk") == true)
{
//Interogate if a WM_CLASS is already set
XGetClassHint(x11Display, windowList[windowIterator], out var classHint);
if(classHint == IntPtr.Zero)
{
//Assign+Retrieve - doesn't seem to set properly without retrieving it after)
XSetClassHint(x11Display, windowList[windowIterator], point);
XGetClassHint(x11Display, windowList[windowIterator],out _);
}
//Reset classHint after checking, doesn't seem to overwrite otherwise.
classHint = IntPtr.Zero;
}
}
_ = XFlush(x11Display);
}
}
}
Loading