SuperMapper Keymapper for mouse and keyboard mobile users
Key RequiredUniversal

SuperMapper Keymapper for mouse and keyboard mobile users

Universal Scripttazgamerz7867tazgamerz7867 6.4K views 380 executesUpdated Aug 8, 2025
Unrated
00
Lua Script•634 lines (23465 chars)
-- Keymapper Finale v5 - Superman Save Edition
-- By Taz + ChatGPT
-- TEMPORARY: Resets on re-run but supports saving/loading to workspace.KeymapperSaves
-- Paste into mobile executor (Delta/Arceus X) and run.

-- Services
local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local HttpService = game:GetService("HttpService")
local LocalPlayer = Players.LocalPlayer
local VIM = game:GetService("VirtualInputManager") -- may error in some environments; handled.
local Camera = workspace.CurrentCamera

-- ---------------------- Self-clean previous instance ----------------------
local EXISTING = LocalPlayer:FindFirstChild("PlayerGui") and LocalPlayer.PlayerGui:FindFirstChild("KeymapperGUI")
if EXISTING then
    -- attempt to clean up
    pcall(function() EXISTING:Destroy() end)
end
-- Also clear any previous saves folder to avoid duplicates only if we want fresh (we keep saves)
-- create saves folder if not present
local savesFolder = workspace:FindFirstChild("KeymapperSaves")
if not savesFolder then
    savesFolder = Instance.new("Folder")
    savesFolder.Name = "KeymapperSaves"
    savesFolder.Parent = workspace
end

-- Connection management table for safe disconnect on reload
local connections = {}

local function Connect(signal, fn)
    local c = signal:Connect(fn)
    table.insert(connections, c)
    return c
end

-- If any previous global connections table existed, try to disconnect them (best-effort)
if _G.__KeymapperConnections then
    for _,c in ipairs(_G.__KeymapperConnections) do
        pcall(function() c:Disconnect() end)
    end
end
_G.__KeymapperConnections = connections

-- ---------------------- GUI Setup ----------------------
local ScreenGui = Instance.new("ScreenGui")
ScreenGui.Name = "KeymapperGUI"
ScreenGui.ResetOnSpawn = false
ScreenGui.Parent = LocalPlayer:WaitForChild("PlayerGui")

local function makeFrame(parent, size, pos, bg)
    local f = Instance.new("Frame")
    f.Size = size
    f.Position = pos
    f.BackgroundColor3 = bg or Color3.fromRGB(50,50,50)
    f.BackgroundTransparency = 0.35
    f.BorderSizePixel = 0
    f.Active = true
    f.Draggable = true
    f.Parent = parent
    return f
end

local function makeButton(parent, text, size, pos)
    local b = Instance.new("TextButton")
    b.Size = size
    b.Position = pos
    b.BackgroundColor3 = Color3.fromRGB(30,30,30)
    b.BackgroundTransparency = 0.45
    b.TextColor3 = Color3.fromRGB(255,255,255)
    b.Text = text
    b.Font = Enum.Font.SourceSansBold
    b.TextSize = 14
    b.Active = true
    b.Draggable = true
    b.Parent = parent
    return b
end

local function makeLabel(parent, text, size, pos)
    local l = Instance.new("TextLabel")
    l.Size = size
    l.Position = pos
    l.BackgroundTransparency = 1
    l.TextColor3 = Color3.fromRGB(230,230,230)
    l.Text = text
    l.Font = Enum.Font.SourceSans
    l.TextSize = 13
    l.Parent = parent
    return l
end

-- main menu frame
local menu = makeFrame(ScreenGui, UDim2.new(0,220,0,380), UDim2.new(0,0,0,90))
menu.Name = "MainMenu"

local title = makeLabel(menu, "Keymapper Finale v5", UDim2.new(0,1,0,0), UDim2.new(0,10,0,6))
title.Font = Enum.Font.GothamBold
title.TextSize = 16

-- mode state
local mode = "Keymap" -- "Keymap" or "WithoutKeymap"
local shiftlock = false
local keyMappings = {} -- mapKey: {btn = Instance, type="KEY"/"LMB"/"RMB", key = enum}
local activeKeys = {} -- keys currently held (for continuous sends)

-- UI elements
local modeBtn = makeButton(menu, "Mode: Keymap", UDim2.new(0,200,0,36), UDim2.new(0,10,0,34))
local spawnMappedBtn = makeButton(menu, "Spawn Mapped Btn", UDim2.new(0,200,0,36), UDim2.new(0,10,0,80))
local spawnLMBBtn = makeButton(menu, "Spawn LMB Btn", UDim2.new(0,200,0,36), UDim2.new(0,10,0,126))
local spawnRMBBtn = makeButton(menu, "Spawn RMB Btn", UDim2.new(0,200,0,36), UDim2.new(0,10,0,172))
local shiftBtn = makeButton(menu, "Shiftlock: OFF", UDim2.new(0,200,0,36), UDim2.new(0,10,0,218))
local saveBtn = makeButton(menu, "Save Setup", UDim2.new(0,200,0,36), UDim2.new(0,10,0,264))
local loadBtn = makeButton(menu, "Load Setup", UDim2.new(0,200,0,36), UDim2.new(0,10,0,310))
local hideBtn = makeButton(menu, "Hide Menu", UDim2.new(0,200,0,36), UDim2.new(0,10,0,356))

-- floating show menu button
local showBtn = makeButton(ScreenGui, "Show Menu", UDim2.new(0,120,0,36), UDim2.new(0,10,0,10))
showBtn.Visible = false

-- small prompt frame for inputting save name & confirmations
local promptFrame = makeFrame(ScreenGui, UDim2.new(0,300,0,140), UDim2.new(0,120,0,200))
promptFrame.Visible = false
local promptLabel = makeLabel(promptFrame, "Prompt", UDim2.new(0,1,0,0), UDim2.new(0,10,0,8))
promptLabel.TextSize = 15
local textBox = Instance.new("TextBox", promptFrame)
textBox.Size = UDim2.new(0,280,0,32)
textBox.Position = UDim2.new(0,10,0,40)
textBox.BackgroundTransparency = 0.5
textBox.Text = ""
textBox.ClearTextOnFocus = false
textBox.TextColor3 = Color3.fromRGB(255,255,255)
textBox.PlaceholderText = "Type slot name..."
textBox.Font = Enum.Font.SourceSans
textBox.TextSize = 14

local promptConfirm = makeButton(promptFrame, "OK", UDim2.new(0,120,0,32), UDim2.new(0,10,0,82))
local promptCancel = makeButton(promptFrame, "Cancel", UDim2.new(0,120,0,32), UDim2.new(0,150,0,82))

-- small warning display
local function showWarning(msg, t)
    t = t or 4
    local warn = makeFrame(ScreenGui, UDim2.new(0,360,0,60), UDim2.new(0.5,-180,0,50))
    local label = makeLabel(warn, "⚠️  "..msg, UDim2.new(0,1,0,1), UDim2.new(0,10,0,8))
    warn.BackgroundTransparency = 0.25
    delay(t, function() pcall(function() warn:Destroy() end) end)
end

-- ---------------------- Helper functions ----------------------
local function serializeUDim2(u)
    return {X = u.X.Scale, XS = u.X.Offset, Y = u.Y.Scale, YS = u.Y.Offset}
end
local function deserializeUDim2(t)
    return UDim2.new(t.X or 0, t.XS or 0, t.Y or 0, t.YS or 0)
end

local function serializeButtonData()
    local out = {}
    for k,v in pairs(keyMappings) do
        local keyIdentifier
        if typeof(k) == "EnumItem" then
            -- Mouse buttons stored as string "MouseButton1"/"MouseButton2"
            keyIdentifier = tostring(k)
        else
            keyIdentifier = tostring(k.Value) -- KeyCode value number
        end
        out[#out+1] = {
            key = keyIdentifier,
            type = v.type,
            pos = serializeUDim2(v.btn.Position),
            size = serializeUDim2(v.btn.Size),
            text = v.btn.Text
        }
    end
    return out
end

local function clearCurrentMappedButtons()
    for _,v in pairs(keyMappings) do
        if v.btn and v.btn.Parent then
            pcall(function() v.btn:Destroy() end)
        end
    end
    keyMappings = {}
end

-- create mapped button instance (returns the btn)
local function createMappedButtonInstance(labelText, pos, size)
    local btn = Instance.new("TextButton")
    btn.Size = size or UDim2.new(0,50,0,50)
    btn.Position = pos or UDim2.new(0.5,-25,0.5,-25)
    btn.BackgroundColor3 = Color3.fromRGB(100,100,255)
    btn.BackgroundTransparency = 0.5
    btn.TextColor3 = Color3.fromRGB(255,255,255)
    btn.Text = labelText or "?"
    btn.Font = Enum.Font.Gotham
    btn.TextSize = 14
    btn.Active = true
    btn.Draggable = true
    btn.Parent = ScreenGui
    return btn
end

-- save to workspace folder as StringValue
local function saveSlot(slotName)
    if not slotName or slotName == "" then return end
    local data = {
        mode = mode,
        shiftlock = shiftlock,
        mappings = serializeButtonData()
    }
    local json = HttpService:JSONEncode(data)
    -- create or overwrite StringValue under workspace.KeymapperSaves
    local sv = savesFolder:FindFirstChild(slotName)
    if sv then
        sv.Value = json
    else
        local s = Instance.new("StringValue")
        s.Name = slotName
        s.Value = json
        s.Parent = savesFolder
    end
    showWarning("Saved slot '"..slotName.."'", 3)
end

local function listSlots()
    local t = {}
    for _,v in ipairs(savesFolder:GetChildren()) do
        if v:IsA("StringValue") then
            table.insert(t, v.Name)
        end
    end
    return t
end

local function loadSlot(slotName)
    local sv = savesFolder:FindFirstChild(slotName)
    if not sv then
        showWarning("Slot not found: "..tostring(slotName), 3)
        return
    end
    local ok, data = pcall(function() return HttpService:JSONDecode(sv.Value) end)
    if not ok or not data then
        showWarning("Failed to decode slot data.",3)
        return
    end

    -- confirm overwrite (we show a quick warning and wait for confirm via prompt)
    -- we'll ask user via promptFrame; reuse prompt
    promptLabel.Text = "Loading will overwrite current setup.\nType YES to confirm."
    textBox.Text = ""
    promptFrame.Visible = true
    promptConfirm.Visible = true
    promptCancel.Visible = true

    local connConfirm
    connConfirm = Connect(promptConfirm.MouseButton1Click, function()
        if textBox.Text:upper() == "YES" then
            -- proceed to load
            clearCurrentMappedButtons()
            mode = data.mode or "Keymap"
            modeBtn.Text = "Mode: "..(mode == "Keymap" and "Keymap" or "Without Keymap")
            shiftlock = data.shiftlock or false
            shiftBtn.Text = "Shiftlock: "..(shiftlock and "ON" or "OFF")
            -- create mapped buttons
            for _,m in ipairs(data.mappings or {}) do
                local pos = deserializeUDim2(m.pos)
                local size = deserializeUDim2(m.size)
                local b = createMappedButtonInstance(m.text or "?", pos, size)
                local keyId = m.key
                if string.find(keyId, "Mouse") then
                    -- mouse mapping stored as "Enum.UserInputType.MouseButton1"
                    if keyId:find("MouseButton1") then
                        keyMappings[Enum.UserInputType.MouseButton1] = {btn = b, type = "LMB"}
                    elseif keyId:find("MouseButton2") then
                        keyMappings[Enum.UserInputType.MouseButton2] = {btn = b, type = "RMB"}
                    end
                else
                    -- key code stored as number string
                    local num = tonumber(keyId)
                    if num then
                        local kc = Enum.KeyCode:GetEnumItems()[1] -- dummy
                        -- we need to find KeyCode by value; brute force
                        for _,e in ipairs(Enum.KeyCode:GetEnumItems()) do
                            if e.Value == num then
                                kc = e
                                break
                            end
                        end
                        keyMappings[kc] = {btn = b, type = "KEY", key = kc}
                    end
                end
            end
            promptFrame.Visible = false
            showWarning("Loaded slot '"..slotName.."'", 3)
        else
            showWarning("Load canceled.",2)
            promptFrame.Visible = false
        end
        pcall(function() connConfirm:Disconnect() end)
    end)
    Connect(promptCancel.MouseButton1Click, function()
        promptFrame.Visible = false
        pcall(function() connConfirm:Disconnect() end)
    end)
end

-- ---------------------- Fail Detection for VIM availability ----------------------
local function checkVIMAvailable()
    local ok, err = pcall(function()
        -- try a harmless call
        VIM:SendKeyEvent(true, Enum.KeyCode.Unknown, false, game)
        -- and mouse
        VIM:SendMouseButtonEvent(0,0,0,true,game,1)
    end)
    return ok
end

if not checkVIMAvailable() then
    showWarning("⚠️ VirtualInputManager not available. You need a real keymapper if this doesn't work.", 6)
end

-- ---------------------- Create mapped buttons handlers ----------------------
-- When user clicks spawnMappedBtn, we create a button that listens for next keyboard press to bind
spawnMappedBtn.MouseButton1Click:Connect(function()
    local b = createMappedButtonInstance("?", UDim2.new(0.5,-25,0.5,-25), nil)
    local listening = true
    local connA, connB
    connA = Connect(b.MouseButton1Click, function()
        b.Text = "Press Key..."
        listening = true
    end)
    connB = Connect(UIS.InputBegan, function(input, gp)
        if listening and input.UserInputType == Enum.UserInputType.Keyboard then
            local kc = input.KeyCode
            b.Text = kc.Name
            keyMappings[kc] = {btn = b, type = "KEY", key = kc}
            listening = false
        end
    end)
end)

-- spawn LMB
spawnLMBBtn.MouseButton1Click:Connect(function()
    local b = createMappedButtonInstance("LMB", UDim2.new(0.6,0,0.5,-25), nil)
    keyMappings[Enum.UserInputType.MouseButton1] = {btn = b, type = "LMB"}
end)
-- spawn RMB
spawnRMBBtn.MouseButton1Click:Connect(function()
    local b = createMappedButtonInstance("RMB", UDim2.new(0.7,0,0.5,-25), nil)
    keyMappings[Enum.UserInputType.MouseButton2] = {btn = b, type = "RMB"}
end)

-- shiftlock toggle
shiftBtn.MouseButton1Click:Connect(function()
    shiftlock = not shiftlock
    shiftBtn.Text = "Shiftlock: " .. (shiftlock and "ON" or "OFF")
    LocalPlayer.DevEnableMouseLock = shiftlock
    if shiftlock then
        UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
        UIS.MouseIconEnabled = false
    else
        UIS.MouseBehavior = Enum.MouseBehavior.Default
        UIS.MouseIconEnabled = true
    end
end)

-- Hide/Show menu
hideBtn.MouseButton1Click:Connect(function()
    menu.Visible = false
    showBtn.Visible = true
end)
showBtn.MouseButton1Click:Connect(function()
    menu.Visible = true
    showBtn.Visible = false
end)

-- Save button: prompt for slot name
saveBtn.MouseButton1Click:Connect(function()
    promptLabel.Text = "Enter slot name to save:"
    textBox.Text = ""
    promptFrame.Visible = true
    local conn
    conn = Connect(promptConfirm.MouseButton1Click, function()
        local name = textBox.Text or ""
        if name == "" then
            showWarning("Slot name cannot be empty", 2)
        else
            saveSlot(name)
            promptFrame.Visible = false
        end
        pcall(function() conn:Disconnect() end)
    end)
    Connect(promptCancel.MouseButton1Click, function()
        promptFrame.Visible = false
        pcall(function() conn:Disconnect() end)
    end)
end)

-- Load button: show list of slots in prompt and ask to type slot name
loadBtn.MouseButton1Click:Connect(function()
    local slots = listSlots()
    if #slots == 0 then
        showWarning("No save slots found.", 3)
        return
    end
    promptLabel.Text = "Available slots:\n- "..table.concat(slots, "\n- ").."\n\nType slot name to load:"
    textBox.Text = ""
    promptFrame.Visible = true
    local conn
    conn = Connect(promptConfirm.MouseButton1Click, function()
        local name = textBox.Text or ""
        if name == "" then
            showWarning("Type a slot name to load",2)
        else
            promptFrame.Visible = false
            loadSlot(name)
        end
        pcall(function() conn:Disconnect() end)
    end)
    Connect(promptCancel.MouseButton1Click, function()
        promptFrame.Visible = false
        pcall(function() conn:Disconnect() end)
    end)
end)

-- ---------------------- Input Handling (multi-key + hold support) ----------------------
-- We'll track activeKeys and spawn coroutines that send events while held.
local sendIntervals = 0.08 -- seconds between repeated forced events while holding

local function sendKeyOnce(keycode)
    local ok, err = pcall(function()
        VIM:SendKeyEvent(true, keycode, false, game)
        task.wait(0.03)
        VIM:SendKeyEvent(false, keycode, false, game)
    end)
    if not ok then
        -- VIM failed; warn once
        showWarning("Our key sending is blocked. You might need a real keymapper.", 4)
    end
end

local function sendMouseClickAtButton(btn, mouseButtonType)
    local success, err = pcall(function()
        local pos = btn.AbsolutePosition + (btn.AbsoluteSize/2)
        VIM:SendMouseButtonEvent(pos.X, pos.Y, mouseButtonType, true, game, 1)
        task.wait(0.02)
        VIM:SendMouseButtonEvent(pos.X, pos.Y, mouseButtonType, false, game, 1)
    end)
    if not success then
        showWarning("Mouse send blocked. You might need a real keymapper.", 4)
    end
end

-- spawn a loop to repeatedly send while key held (for WithoutKeymap Mode)
local holdingLoops = {} -- map KeyCode -> coroutine handle
local function startHoldingKey(kc)
    if holdingLoops[kc] then return end
    local co = coroutine.create(function()
        while activeKeys[kc] do
            sendKeyOnce(kc)
            task.wait(sendIntervals)
        end
        holdingLoops[kc] = nil
    end)
    holdingLoops[kc] = co
    coroutine.resume(co)
end
local function stopHoldingKey(kc)
    activeKeys[kc] = nil
    -- coroutine will exit automatically
end

-- InputBegan: mark active, handle according to mode
Connect(UIS.InputBegan, function(input, gp)
    local uit = input.UserInputType
    if uit == Enum.UserInputType.Keyboard then
        local kc = input.KeyCode
        -- mark active
        activeKeys[kc] = true
        -- if WithoutKeymap: force-send (and start repeat while held)
        if mode == "WithoutKeymap" then
            -- start continuous sending while held
            startHoldingKey(kc)
        end

        -- if Keymap mode and this key is mapped to a UI button, simulate mouse tap
        if mode == "Keymap" then
            local map = keyMappings[kc]
            if map and map.btn and map.type == "KEY" then
                spawn(function()
                    sendMouseClickAtButton(map.btn, 0)
                end)
            end
        end
    elseif uit == Enum.UserInputType.MouseButton1 or uit == Enum.UserInputType.MouseButton2 then
        -- mouse buttons: treat as user press; if mapped in Keymap mode, trigger mapped on-screen button
        if mode == "Keymap" then
            local map = keyMappings[uit]
            if map and map.btn then
                local mb = (uit == Enum.UserInputType.MouseButton2) and 1 or 0
                spawn(function() sendMouseClickAtButton(map.btn, mb) end)
            end
        else
            -- WithoutKeymap: we can try to force a mouse event for every mouse click (non-blocking)
            local ok, err = pcall(function()
                -- send a raw click (at current center of screen)
                local x = Camera.ViewportSize.X/2
                local y = Camera.ViewportSize.Y/2
                local mb = (uit == Enum.UserInputType.MouseButton2) and 1 or 0
                VIM:SendMouseButtonEvent(x,y,mb,true,game,1)
                task.wait(0.02)
                VIM:SendMouseButtonEvent(x,y,mb,false,game,1)
            end)
            if not ok then
                -- ignore silently or show warn
            end
        end
    end
end)

-- InputEnded: stop holding
Connect(UIS.InputEnded, function(input, gp)
    if input.UserInputType == Enum.UserInputType.Keyboard then
        local kc = input.KeyCode
        stopHoldingKey(kc)
    end
end)

-- ---------------------- Mode Switch & showing/hiding mapped buttons on mode change ----------------------
modeBtn.MouseButton1Click:Connect(function()
    if mode == "Keymap" then
        mode = "WithoutKeymap"
        modeBtn.Text = "Mode: WithoutKeymap"
        -- hide mapped buttons visually (but keep data)
        for k,v in pairs(keyMappings) do
            if v.btn then v.btn.Visible = false end
        end
    else
        mode = "Keymap"
        modeBtn.Text = "Mode: Keymap"
        for k,v in pairs(keyMappings) do
            if v.btn then v.btn.Visible = true end
        end
    end
end)

-- ---------------------- Persistence: when re-run this script will have removed prior GUI and listeners ----------------------
-- Add small help label
local help = makeLabel(menu, "Save slots stored in workspace.KeymapperSaves\nUse Save/Load with custom names.\nType YES to confirm loads.", UDim2.new(0,1,0,80), UDim2.new(0,10,0,400))
help.TextSize = 11

-- ---------------------- Click-to-edit mapped buttons (allow rebind & delete) ----------------------
-- When user clicks a mapped on-screen button, open a tiny context menu
local function attachMappedButtonContext(btn, keyRep)
    -- show two small buttons near the btn: Rebind, Delete
    btn.MouseButton2Click:Connect(function()
        -- Right click menu (if supported)
        -- For simplicity, just prompt for rebind or deletion via prompt
        promptLabel.Text = "Type NEW key name to rebind or DELETE to remove."
        textBox.Text = ""
        promptFrame.Visible = true
        local connC
        connC = Connect(promptConfirm.MouseButton1Click, function()
            local v = textBox.Text
            if v == "" then
                showWarning("No input.",2)
            elseif v:upper() == "DELETE" then
                -- remove mapping entry
                for k,m in pairs(keyMappings) do
                    if m.btn == btn then
                        keyMappings[k] = nil
                        break
                    end
                end
                pcall(function() btn:Destroy() end)
                promptFrame.Visible = false
            else
                -- try to find KeyCode by name
                local found = nil
                for _,kc in ipairs(Enum.KeyCode:GetEnumItems()) do
                    if kc.Name:upper() == v:upper() then found = kc break end
                end
                if found then
                    -- find previous mapping and update keyMappings
                    for k,m in pairs(keyMappings) do
                        if m.btn == btn then
                            keyMappings[k] = nil
                            keyMappings[found] = {btn = btn, type = "KEY", key = found}
                            btn.Text = found.Name
                            break
                        end
                    end
                    promptFrame.Visible = false
                else
                    showWarning("Key not found: "..v, 2)
                end
            end
            pcall(function() connC:Disconnect() end)
        end)
        Connect(promptCancel.MouseButton1Click, function()
            promptFrame.Visible = false
            pcall(function() connC:Disconnect() end)
        end)
    end)
end

-- Hook creation so context menus attach
-- Whenever a mapped UI button is created we should attach context. We'll attach for existing ones too.
for k,v in pairs(keyMappings) do
    if v.btn then attachMappedButtonContext(v.btn, k) end
end

-- Also attach when createMappedButtonInstance used earlier in load; hook creation: override that function? Instead we will periodically ensure attachments
Connect(RunService.Heartbeat, function()
    for k,v in pairs(keyMappings) do
        if v and v.btn and not v._contextAttached then
            pcall(function() attachMappedButtonContext(v.btn, k) end)
            v._contextAttached = true
        end
    end
    -- ensure shiftlock remains active when set
    if shiftlock then
        UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
        UIS.MouseIconEnabled = false
    end
end)

-- ---------------------- Final message ----------------------
showWarning("Keymapper Finale v5 loaded. Use Save to create slots. Run again to replace.", 5)

-- End of script

Features & Information

has many stuff even has warning save slots etc!

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts