Skip to content

Instantly share code, notes, and snippets.

@MineRobber9000
Last active January 11, 2026 03:10
Show Gist options
  • Select an option

  • Save MineRobber9000/6886835b990fca3bb1dddeb3ae794a49 to your computer and use it in GitHub Desktop.

Select an option

Save MineRobber9000/6886835b990fca3bb1dddeb3ae794a49 to your computer and use it in GitHub Desktop.
Mirror a ComputerCraft computer screen to a monitor
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe, 2025 minerobber
--
-- SPDX-License-Identifier: LicenseRef-CCPL
local function printUsage()
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <name> <program> <arguments>")
return
end-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
--
-- SPDX-License-Identifier: LicenseRef-CCPL
local function printUsage()
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage:")
print(" " .. programName .. " <name> <program> <arguments>")
--print(" " .. programName .. " scale <name> <scale>")
return
end
local tArgs = { ... }
if #tArgs < 2 then
if tArgs[1]=="define" then
-- shell completion
local completion = require("cc.shell.completion")
shell.setCompletionFunction(shell.getRunningProgram(), completion.build(
{ completion.peripheral, true },
{ completion.programWithArgs, 3, many=true }
))
-- settings
settings.define("mirror.fill_character", {
description = "The character that fills background of the monitor outside the terminal window.";
default = "\127";
type = "string";
})
settings.define("mirror.fill_fg", {
description = "The color to fill the foreground with, as a hex color.";
default = "0";
type = "string";
})
settings.define("mirror.fill_bg", {
description = "The color to fill the background with, as a hex color.";
default = "f";
type = "string";
})
return
end
printUsage()
return
end
local sName = tArgs[1]
if peripheral.getType(sName) ~= "monitor" then
print("No monitor named " .. sName)
return
end
local termWidth, termHeight = term.getSize()
peripheral.call(sName, "setTextScale", 1)
local monWidth, monHeight = peripheral.call(sName, "getSize")
local monScale = math.min(math.floor((monWidth / termWidth) * 2) / 2, math.floor((monHeight / termHeight) * 2) / 2)
peripheral.call(sName, "setTextScale", monScale)
monWidth, monHeight = peripheral.call(sName, "getSize")
local sProgram = tArgs[2]
local sPath = shell.resolveProgram(sProgram)
if sPath == nil then
print("No such program: " .. sProgram)
return
end
print("Mirroring " .. sProgram .. " on monitor " .. sName .. "...")
sleep(3)
local monOffsetX = math.floor((monWidth - termWidth)/2)
local monOffsetY = math.floor((monHeight - termHeight)/2)
local previousTerm
local redirect = setmetatable({
setCursorPos = function(x, y)
if not previousTerm then error("attempt to set cursor position before redirect") end
peripheral.call(sName, "setCursorPos", x+monOffsetX, y+monOffsetY)
return previousTerm.setCursorPos(x, y)
end;
},{
__index=function(_, k)
return function(...)
if not previousTerm then return term[k](...) end
peripheral.call(sName, k, ...)
return previousTerm[k](...)
end
end;
})
for y=1,monHeight do
peripheral.call(sName, "setCursorPos", 1, y)
peripheral.call(sName, "blit", settings.get("mirror.fill_character","\127"):sub(1,1):rep(monWidth), settings.get("mirror.fill_fg","0"):sub(1,1):rep(monWidth), settings.get("mirror.fill_bg","f"):rep(monWidth))
end
local win = window.create(redirect, 1, 1, termWidth, termHeight, false)
previousTerm = term.redirect(win)
win.setVisible(true)
term.clear()
term.setCursorPos(1,1)
local co = coroutine.create(function()
(shell.execute or shell.run)(sProgram, table.unpack(tArgs, 3))
end)
local function resume(...)
local ok, param = coroutine.resume(co, ...)
if not ok then
printError(param)
end
return param
end
local timers = {}
local ok, param = pcall(function()
local sFilter = resume()
while coroutine.status(co) ~= "dead" do
local tEvent = table.pack(os.pullEventRaw())
if sFilter == nil or tEvent[1] == sFilter or tEvent[1] == "terminate" then
sFilter = resume(table.unpack(tEvent, 1, tEvent.n))
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_click") then
if tEvent[1] == "monitor_touch" and tEvent[2] == sName then
timers[os.startTimer(0.1)] = { tEvent[3], tEvent[4] }
sFilter = resume("mouse_click", 1, table.unpack(tEvent, 3, tEvent.n))
end
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "term_resize") then
if tEvent[1] == "monitor_resize" and tEvent[2] == sName then
sFilter = resume("term_resize")
end
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_up") then
if tEvent[1] == "timer" and timers[tEvent[2]] then
sFilter = resume("mouse_up", 1, table.unpack(timers[tEvent[2]], 1, 2))
timers[tEvent[2]] = nil
end
end
end
end)
term.redirect(previousTerm)
term.clear()
term.setCursorPos(1,1)
if not ok then
printError(param)
end
local tArgs = { ... }
if #tArgs < 2 then
if tArgs[1]=="define" then
-- shell completion
local completion = require("cc.shell.completion")
shell.setCompletionFunction(shell.getRunningProgram(), completion.build(
{ completion.peripheral, true },
{ completion.programWithArgs, 3, many=true }
))
-- settings
settings.define("mirror.fill_character", {
description = "The character that fills background of the monitor outside the terminal window.";
default = "\127";
type = "string";
})
settings.define("mirror.fill_fg", {
description = "The color to fill the foreground with, as a hex color.";
default = "0";
type = "string";
})
settings.define("mirror.fill_bg", {
description = "The color to fill the background with, as a hex color.";
default = "f";
type = "string";
})
return
end
printUsage()
return
end
local sName = tArgs[1]
if peripheral.getType(sName) ~= "monitor" then
print("No monitor named " .. sName)
return
end
local termWidth, termHeight = term.getSize()
local monWidth, monHeight = peripheral.call(sName, "getSize")
if monWidth<termWidth or monHeight<termHeight then
print("Monitor " .. sName .. " too small")
return
end
local sProgram = tArgs[2]
local sPath = shell.resolveProgram(sProgram)
if sPath == nil then
print("No such program: " .. sProgram)
return
end
print("Mirroring " .. sProgram .. " on monitor " .. sName .. "...")
sleep(3)
local monOffsetX = math.floor((monWidth - termWidth)/2)
local monOffsetY = math.floor((monHeight - termHeight)/2)
local previousTerm
local redirect = setmetatable({
setCursorPos = function(x, y)
if not previousTerm then error("attempt to set cursor position before redirect") end
peripheral.call(sName, "setCursorPos", x+monOffsetX, y+monOffsetY)
return previousTerm.setCursorPos(x, y)
end;
},{
__index=function(_, k)
return function(...)
if not previousTerm then return term[k](...) end
peripheral.call(sName, k, ...)
return previousTerm[k](...)
end
end;
})
for y=1,monHeight do
peripheral.call(sName, "setCursorPos", 1, y)
peripheral.call(sName, "blit", settings.get("mirror.fill_character","\127"):sub(1,1):rep(monWidth), settings.get("mirror.fill_fg","0"):sub(1,1):rep(monWidth), settings.get("mirror.fill_bg","f"):rep(monWidth))
end
local win = window.create(redirect, 1, 1, termWidth, termHeight, false)
previousTerm = term.redirect(win)
win.setVisible(true)
term.clear()
term.setCursorPos(1,1)
local co = coroutine.create(function()
(shell.execute or shell.run)(sProgram, table.unpack(tArgs, 3))
end)
local function resume(...)
local ok, param = coroutine.resume(co, ...)
if not ok then
printError(param)
end
return param
end
local timers = {}
local ok, param = pcall(function()
local sFilter = resume()
while coroutine.status(co) ~= "dead" do
local tEvent = table.pack(os.pullEventRaw())
if sFilter == nil or tEvent[1] == sFilter or tEvent[1] == "terminate" then
sFilter = resume(table.unpack(tEvent, 1, tEvent.n))
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_click") then
if tEvent[1] == "monitor_touch" and tEvent[2] == sName then
timers[os.startTimer(0.1)] = { tEvent[3], tEvent[4] }
sFilter = resume("mouse_click", 1, table.unpack(tEvent, 3, tEvent.n))
end
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "term_resize") then
if tEvent[1] == "monitor_resize" and tEvent[2] == sName then
sFilter = resume("term_resize")
end
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_up") then
if tEvent[1] == "timer" and timers[tEvent[2]] then
sFilter = resume("mouse_up", 1, table.unpack(timers[tEvent[2]], 1, 2))
timers[tEvent[2]] = nil
end
end
end
end)
term.redirect(previousTerm)
term.clear()
term.setCursorPos(1,1)
if not ok then
printError(param)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment