4 Commits

Author SHA1 Message Date
TheWhiteDog9487 6f0f58a715 🧹 cleanup: 清理代码 2026-09-15 11:39:18 +08:00
TheWhiteDog9487 bce136188a 🧩 feat: 支持显示Mod文件路径 2026-09-15 11:33:48 +08:00
TheWhiteDog9487 863bcfd0e2 🧩 feat: 支持从线程调用栈进行强力搜索 2026-09-15 11:31:32 +08:00
TheWhiteDog9487 e0af7c05eb 📖 docs: 修复英文README图片不显示的问题 2026-09-07 10:34:09 +08:00
12 changed files with 127 additions and 18 deletions
+2 -2
View File
@@ -20,8 +20,8 @@ Display hover text on the keybind modification button to show which mod provides
By default, to avoid disturbing users, information that is not useful to ordinary players is not shown.
If you need it, you can enable additional features in the config screen.
![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-14.png)
![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-32.png)
![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-14.png?raw=true)
![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-32.png?raw=true)
# Important Notes
**This mod cannot guarantee 100% accuracy!**
+10
View File
@@ -52,6 +52,16 @@ dependencies {
implementation("maven.modrinth:fuuu3xnx:bvFby61J")
// https://modrinth.com/mod/controlling/versions?l=fabric
implementation("maven.modrinth:xv94TkTM:rAp2jljs")
// https://modrinth.com/mod/notenoughcrashes/versions?l=fabric
implementation("maven.modrinth:yM94ont6:3oHxOFPT")
// https://modrinth.com/mod/scoreboard-overhaul/versions
implementation("maven.modrinth:2YIQkF2v:HI1qkixZ")
// https://modrinth.com/plugin/plasmo-voice/versions?l=fabric
implementation("maven.modrinth:1bZhdhsH:I0T9OQcy")
// https://modrinth.com/mod/first-person-model/versions?l=fabric
implementation("maven.modrinth:H5XMjpHi:6sgz2HEq")
// https://modrinth.com/mod/entityculling/versions?l=fabric
implementation("maven.modrinth:NNAgCjsB:iiF6U3Ne")
}
tasks.processResources {
+1 -1
View File
@@ -14,7 +14,7 @@ loom_version=1.17-SNAPSHOT
fabric_kotlin_version=1.13.13+kotlin.2.4.10
# Mod Properties
version=1.0.0
version=1.1.0
group=xyz.thewhitedog9487
OutputFileBasename=WhoProvideThisKeybind
@@ -3,13 +3,9 @@ package xyz.thewhitedog9487.client.mixin;
import xyz.thewhitedog9487.client.MiscellaneousKt;
import xyz.thewhitedog9487.client.SettingsKt;
import xyz.thewhitedog9487.client.WhoProvideThisKeybindModClient;
import xyz.thewhitedog9487.client.ModInfo;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.options.controls.KeyBindsList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -0,0 +1,48 @@
package xyz.thewhitedog9487.client.mixin;
import com.mojang.blaze3d.platform.InputConstants;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.client.KeyMapping;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import xyz.thewhitedog9487.client.MiscellaneousKt;
import xyz.thewhitedog9487.client.ModInfo;
import java.util.Arrays;
import java.util.Optional;
@Mixin(KeyMapping.class)
public class KeyMappingMixin {
@Inject(method = "<init>(Ljava/lang/String;Lcom/mojang/blaze3d/platform/InputConstants$Type;ILnet/minecraft/client/KeyMapping$Category;I)V",
at = @At("TAIL"))
void DiscoverStackTrace(String name, InputConstants.Type type, int value, KeyMapping.Category category, int order, CallbackInfo ci) {
var StackTraceCLassNames = Arrays.stream(Thread.currentThread().getStackTrace())
.map(StackTraceElement::getClassName)
.toList();
for (String ClassName : StackTraceCLassNames) {
if (IsFrameworkClass(ClassName)) continue;
for (String PartOfClassName : ClassName.split("\\.")) {
Optional<ModContainer> CandidateModContainer = FabricLoader.getInstance().getModContainer(PartOfClassName);
if (CandidateModContainer.isPresent() && CandidateModContainer.get() != MiscellaneousKt.getVanillaMinecraftContainer()) {
var ModInfo = new ModInfo(CandidateModContainer.get());
MiscellaneousKt.getKeyMappingToMod().put((KeyMapping) (Object) this, ModInfo);
return; } } } }
@Unique
boolean IsFrameworkClass(@NotNull String ClassName) {
return ClassName.startsWith("java.")
|| ClassName.startsWith("jdk.")
|| ClassName.startsWith("sun.")
|| ClassName.startsWith("com.sun.")
|| ClassName.startsWith("kotlin.")
|| ClassName.startsWith("net.minecraft")
|| ClassName.startsWith("net.fabricmc")
|| ClassName.startsWith("org.spongepowered.")
// ↓ 第三方Mod兼容条目
|| ClassName.startsWith("dev.tr7zw.transition"); } }
@@ -7,17 +7,22 @@ import net.minecraft.client.KeyMapping
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.Tooltip
import net.minecraft.network.chat.Component
import kotlin.io.path.pathString
val VanillaMinecraftContainer: ModContainer = FabricLoader
.getInstance()
.getModContainer("minecraft")
.get()
val KeyMappingToMod: HashMap<KeyMapping, ModInfo> = HashMap()
val KeyMapping.Provider: ModInfo get() {
if (SettingsInstance.SearchInStackTrace) {
KeyMappingToMod[this]?.let { return it } }
val KeyBindTranslationKey = name
val CandidateModIds = KeyBindTranslationKey.split(".")
if (CandidateModIds.size == 1){
WhoProvideThisKeybindModClient.ClientLogger.warn("这是个什么东西,按键绑定ID只有一个部分,ID: $KeyBindTranslationKey")
return ModInfo(null) }
for (CandidateModId in CandidateModIds) {
val ModContainer = FabricLoader.getInstance().getModContainer(CandidateModId)
@@ -29,7 +34,8 @@ data class ModInfo(val ModContainerInstance: ModContainer?) {
val ID: String = ModContainerInstance?.metadata?.id ?: Component.translatable("special_provider_UNKNOWN").string
val MetaData: ModMetadata? = ModContainerInstance?.metadata
val DisplayName: String = MetaData?.name ?: Component.translatable("special_provider_UNKNOWN").string
val HumanFriendlyName: Component = Component.translatableWithFallback("modmenu.nameTranslation." + (MetaData?.id ?: "unknown"), DisplayName) }
val HumanFriendlyName: Component = Component.translatableWithFallback("modmenu.nameTranslation." + (MetaData?.id ?: "unknown"), DisplayName)
val FilePath: String = ModContainerInstance?.origin?.paths?.joinToString("\n") { it.pathString } ?: Component.translatable("special_string_UNKNOWN").string }
fun Button.SetNewTooltip(Key: KeyMapping) {
if (SettingsInstance.ModEnabled == false) return
@@ -57,4 +63,9 @@ fun Button.SetNewTooltip(Key: KeyMapping) {
.append(Component.translatable("tooltip.key_id",
Component.literal(Key.name))) }
// 第四行:文件路径
if (SettingsInstance.ShowFilePath) {
NewTooltip.append(Component.literal("\n"))
.append(Component.translatable("tooltip.mod_file_path", Mod.FilePath)) }
setTooltip(Tooltip.create(NewTooltip)) }
@@ -44,6 +44,20 @@ class ModMenuEntryPoint: ModMenuApi {
.binding( false, { SettingsInstance.ShowDebugInfo }, { SettingsInstance.ShowDebugInfo = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.option {
Option.createBuilder<Boolean>()
.name(Component.translatable("option.SearchInStackTrace"))
.description { OptionDescription.of(Component.translatable("option.SearchInStackTrace.description") ) }
.binding( false, { SettingsInstance.SearchInStackTrace }, { SettingsInstance.SearchInStackTrace = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.option {
Option.createBuilder<Boolean>()
.name(Component.translatable("option.ShowFilePath"))
.description { OptionDescription.of(Component.translatable("option.ShowFilePath.description") ) }
.binding( false, { SettingsInstance.ShowFilePath }, { SettingsInstance.ShowFilePath = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.build() }
.build()
.generateScreen(ParentScreen) } }
@@ -7,7 +7,6 @@ import net.fabricmc.loader.api.FabricLoader
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
import java.nio.file.StandardWatchEventKinds.*
const val CurrentConfigurationVersionCode: Long = 0
class Settings {
@@ -57,11 +56,30 @@ class Settings {
取值范围:true false
默认值:false
""")
var ShowDebugInfo = false }
var ShowDebugInfo = false
@SerialEntry(comment =
"""
是否注入KeyMapping构造函数,在每次注册按键绑定时查询线程的调用栈信息来进行强力匹配
警告:在部分情况下可能产生更多误报,请按需选择
类型:布尔值
取值范围:true false
默认值:false
""")
var SearchInStackTrace = false
@SerialEntry(comment =
"""
是否在悬停信息中显示Mod的文件路径
类型:布尔值
取值范围:true false
默认值:false
""")
var ShowFilePath = false }
var SettingsHandler: ConfigClassHandler<Settings>? = null
val ConfigFilePath = FabricLoader
val ConfigFilePath: Path = FabricLoader
.getInstance()
.configDir
.resolve(WhoProvideThisKeybindModClient.FriendlyModID + ".json5")
@@ -77,7 +95,7 @@ fun RegisterConfigFileReloadWatcher() {
val ConfigDirectory = ConfigFilePath.parent
try {
val WatchService = FileSystems.getDefault().newWatchService()
ConfigDirectory.register(WatchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
ConfigDirectory.register(WatchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE)
while (!Thread.currentThread().isInterrupted) {
val WatchKey = WatchService.take()
var ShouldReload = false
@@ -95,7 +113,7 @@ fun RegisterConfigFileReloadWatcher() {
WhoProvideThisKeybindModClient.ClientLogger.info("检测到配置文件变更,已重新加载设置。") }
if (!WatchKey.reset()) {
break } }
} catch (e: InterruptedException) {
} catch (_: InterruptedException) {
Thread.currentThread().interrupt()
} catch (e: IOException) {
throw RuntimeException(e) } } }
@@ -1,13 +1,14 @@
package xyz.thewhitedog9487.client
import net.fabricmc.api.ClientModInitializer
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import xyz.thewhitedog9487.client.Event.RegisterClientLifecycleEvents
object WhoProvideThisKeybindModClient : ClientModInitializer {
const val ModID: String = "whoprovidethiskeybind"
const val FriendlyModID = "WhoProvideThisKeybind"
val ClientLogger = LoggerFactory.getLogger(FriendlyModID)
val ClientLogger: Logger = LoggerFactory.getLogger(FriendlyModID)
override fun onInitializeClient() {
RegisterClientLifecycleEvents() } }
@@ -3,7 +3,8 @@
"package": "xyz.thewhitedog9487.client.mixin",
"compatibilityLevel": "JAVA_25",
"client": [
"KeyBindsListKeyEntryMixin"
"KeyBindsListKeyEntryMixin",
"KeyMappingMixin"
],
"injectors": {
"defaultRequire": 1
@@ -13,8 +13,13 @@
"option.ShowKeybindID.description": "Display the keybind's own ID in the tooltip",
"option.ShowDebugInfo": "Show Debug Info",
"option.ShowDebugInfo.description": "This information is used to troubleshoot issues related to this mod; it generally does not need to be enabled",
"option.ShowFilePath": "Show File Path",
"option.ShowFilePath.description": "Display the mod file path in the information",
"option.SearchInStackTrace": "Enable aggressive search via stack trace lookup",
"option.SearchInStackTrace.description": "Injects into the KeyMapping class constructor and queries the current thread's call stack each time a keybind is registered for aggressive matching\nWarning: In some cases, this may produce more false positives; enable as needed",
"tooltip.provided_by": "Provided by: %s",
"tooltip.provider_mod_id": "Provider Mod ID: %s",
"tooltip.key_id": "Keybind ID: %s",
"special_provider_UNKNOWN": "Unknown"
"tooltip.mod_file_path": "Mod File Path: %s",
"special_string_UNKNOWN": "Unknown"
}
@@ -13,8 +13,13 @@
"option.ShowKeybindID.description": "在提示信息中显示该按键本身的ID",
"option.ShowDebugInfo": "是否显示调试信息",
"option.ShowDebugInfo.description": "这些信息用于出现和本Mod相关的问题时进行排查,一般情况下不需要开启",
"option.ShowFilePath": "是否显示文件路径",
"option.ShowFilePath.description": "在信息中显示Mod的文件路径",
"option.SearchInStackTrace": "是否要通过查询堆栈信息来进行强力搜索",
"option.SearchInStackTrace.description": "注入KeyMapping类的构造函数,在每次注册按键绑定时查询线程的调用栈信息来进行强力匹配\n警告:部分情况下可能产生更多误报,请按需选择",
"tooltip.provided_by": "提供者:%s",
"tooltip.provider_mod_id": "提供者ID%s",
"tooltip.key_id": "键位ID%s",
"special_provider_UNKNOWN": "未知"
"tooltip.mod_file_path": "Mod文件路径:%s",
"special_string_UNKNOWN": "未知"
}