Fix accessible output window, revert Tab/Ctrl+Space, sync readme keys
- output_functions.xml: merge NotepadSmartScroll's Win32/Alien-based smart-scroll logic in directly (was a separate plugin). Fixed the caret-restore bug: gating the restore on scroll position was wrong since the notepad tracks near the bottom during normal reading; now gated on whether the caret was literally at the end of the old text. Added a jump-to-end-on-focus toggle (Ctrl+Alt+J) with a 0.2s polling timer to catch focus landing on the output notepad by any means. - Reverted the Tab/Ctrl+Space rebinding - removed switch_to_output and trigger_word_completion along with their accelerators. - cosmic rage.MCL: dropped the dangling NotepadSmartScroll.xml include (file no longer exists, was reintroduced by an auto-save). - readme.md: documented the actual current CosmicRage.xml keys (several had drifted out of sync - Alt+F12 is now stop-sounds not restart, Alt+Delete/Ctrl+S/Ctrl+F1-3/Alt+F3/Alt+Shift+H/F11 were undocumented) and added the output_functions.xml accessible-output keybindings, which weren't documented at all before.
This commit is contained in:
BIN
cosmic rage/alien/core.dll
Normal file
BIN
cosmic rage/alien/core.dll
Normal file
Binary file not shown.
BIN
cosmic rage/alien/struct.dll
Normal file
BIN
cosmic rage/alien/struct.dll
Normal file
Binary file not shown.
245
cosmic rage/lua/alien.lua
Normal file
245
cosmic rage/lua/alien.lua
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
|
||||||
|
|
||||||
|
local core = require "alien.core"
|
||||||
|
local io = require "io"
|
||||||
|
|
||||||
|
local pairs, ipairs = pairs, ipairs
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local error = error
|
||||||
|
local pcall = pcall
|
||||||
|
local type = type
|
||||||
|
local rawset = rawset
|
||||||
|
local unpack = unpack
|
||||||
|
local math = math
|
||||||
|
local print = print
|
||||||
|
|
||||||
|
module "alien"
|
||||||
|
|
||||||
|
loaded = {}
|
||||||
|
|
||||||
|
local load_library, find_library = {}, {}
|
||||||
|
|
||||||
|
local function find_library_helper(libname, opt)
|
||||||
|
local expr = '/[^() ]*lib' .. libname .. '\\.so[^() ]*'
|
||||||
|
local cmd = '/sbin/ldconfig ' .. opt ..
|
||||||
|
' 2>/dev/null | egrep -o "' .. expr .. '"'
|
||||||
|
local pipe = io.popen(cmd)
|
||||||
|
if pipe then
|
||||||
|
local res = pipe:read()
|
||||||
|
pipe:close()
|
||||||
|
return res and res:match("([^%s]*)")
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function find_library.linux(libname)
|
||||||
|
return find_library_helper(libname, "-p")
|
||||||
|
end
|
||||||
|
|
||||||
|
function find_library.bsd(libname)
|
||||||
|
return find_library_helper(libname, "-r")
|
||||||
|
end
|
||||||
|
|
||||||
|
function find_library.darwin(libname)
|
||||||
|
local ok, lib = pcall(core.load, libname .. ".dylib")
|
||||||
|
if ok then return lib end
|
||||||
|
ok, lib = pcall(core.load, libname .. ".framework/" .. libname)
|
||||||
|
if ok then return lib end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load_library_helper(libname, libext)
|
||||||
|
if libname:match("/") or libname:match("%" .. libext) then
|
||||||
|
return core.load(libname)
|
||||||
|
else
|
||||||
|
local ok, lib = pcall(core.load, "lib" .. libname .. libext)
|
||||||
|
if not ok then
|
||||||
|
ok, lib = pcall(core.load, "./lib" .. libname .. libext)
|
||||||
|
if not ok then
|
||||||
|
local name = find_library[core.platform](libname)
|
||||||
|
if name then
|
||||||
|
lib = core.load(name)
|
||||||
|
else
|
||||||
|
error("library " .. libname .. " not found")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return lib
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function load_library.linux(libname)
|
||||||
|
return load_library_helper(libname, ".so")
|
||||||
|
end
|
||||||
|
|
||||||
|
load_library.bsd = load_library.linux
|
||||||
|
|
||||||
|
function load_library.darwin(libname)
|
||||||
|
return load_library_helper(libname, ".dylib")
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(load_library, { __index = function (t, plat)
|
||||||
|
return core.load
|
||||||
|
end } )
|
||||||
|
|
||||||
|
function load_library.windows(libname)
|
||||||
|
return core.load(libname)
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(loaded, { __index = function (t, libname)
|
||||||
|
local lib =
|
||||||
|
load_library[core.platform](libname)
|
||||||
|
t[libname] = lib
|
||||||
|
return lib
|
||||||
|
end })
|
||||||
|
|
||||||
|
setmetatable(_M, { __index = loaded })
|
||||||
|
|
||||||
|
for name, f in pairs(core) do
|
||||||
|
_M[name] = f
|
||||||
|
end
|
||||||
|
|
||||||
|
function load(libname)
|
||||||
|
return loaded[libname]
|
||||||
|
end
|
||||||
|
|
||||||
|
function callback(f, ...)
|
||||||
|
local cb = core.callback(f)
|
||||||
|
cb.types(cb, ...)
|
||||||
|
return cb
|
||||||
|
end
|
||||||
|
|
||||||
|
local array_methods = {}
|
||||||
|
|
||||||
|
local function array_next(arr, i)
|
||||||
|
if i < arr.length then
|
||||||
|
return i + 1, arr[i + 1]
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function array_methods:ipairs()
|
||||||
|
return array_next, self, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function array_get(arr, key)
|
||||||
|
if type(key) == "number" then
|
||||||
|
if key < 1 or key > arr.length then
|
||||||
|
error("array access out of bounds")
|
||||||
|
end
|
||||||
|
local offset = (key - 1) * arr.size + 1
|
||||||
|
return arr.buffer:get(offset, arr.type)
|
||||||
|
else
|
||||||
|
return array_methods[key]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function array_set(arr, key, val)
|
||||||
|
if type(key) == "number" then
|
||||||
|
if key < 1 or key > arr.length then
|
||||||
|
error("array access out of bounds")
|
||||||
|
end
|
||||||
|
local offset = (key - 1) * arr.size + 1
|
||||||
|
arr.buffer:set(offset, val, arr.type)
|
||||||
|
if type(val) == "string" or type(val) == "userdata" then
|
||||||
|
arr.pinned[key] = val
|
||||||
|
end
|
||||||
|
else
|
||||||
|
rawset(arr, key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function array(t, length, init)
|
||||||
|
local ok, size = pcall(core.sizeof, t)
|
||||||
|
if not ok then
|
||||||
|
error("type " .. t .. " does not exist")
|
||||||
|
end
|
||||||
|
if type(length) == "table" then
|
||||||
|
init = length
|
||||||
|
length = #length
|
||||||
|
end
|
||||||
|
local arr = { type = t, length = length, size = size, pinned = {} }
|
||||||
|
setmetatable(arr, { __index = array_get, __newindex = array_set })
|
||||||
|
if type(init) == "userdata" then
|
||||||
|
arr.buffer = init
|
||||||
|
else
|
||||||
|
arr.buffer = core.buffer(size * length)
|
||||||
|
if type(init) == "table" then
|
||||||
|
for i = 1, length do
|
||||||
|
arr[i] = init[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return arr
|
||||||
|
end
|
||||||
|
|
||||||
|
local function struct_new(s_proto, ptr)
|
||||||
|
local buf = core.buffer(ptr or s_proto.size)
|
||||||
|
local function struct_get(_, key)
|
||||||
|
if s_proto.offsets[key] then
|
||||||
|
return buf:get(s_proto.offsets[key] + 1, s_proto.types[key])
|
||||||
|
else
|
||||||
|
error("field " .. key .. " does not exist")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function struct_set(_, key, val)
|
||||||
|
if s_proto.offsets[key] then
|
||||||
|
buf:set(s_proto.offsets[key] + 1, val, s_proto.types[key])
|
||||||
|
else
|
||||||
|
error("field " .. key .. " does not exist")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return setmetatable({}, { __index = struct_get, __newindex = struct_set,
|
||||||
|
__call = function () return buf end })
|
||||||
|
end
|
||||||
|
|
||||||
|
local function struct_byval(s_proto)
|
||||||
|
local types = {}
|
||||||
|
local size = s_proto.size
|
||||||
|
for i = 0, size - 1, 4 do
|
||||||
|
if size - i == 1 then
|
||||||
|
types[#types + 1] = "char"
|
||||||
|
elseif size - i == 2 then
|
||||||
|
types[#types + 1] = "short"
|
||||||
|
else
|
||||||
|
types[#types + 1] = "int"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return unpack(types)
|
||||||
|
end
|
||||||
|
|
||||||
|
function defstruct(t)
|
||||||
|
local off = 0
|
||||||
|
local names, offsets, types = {}, {}, {}
|
||||||
|
for _, field in ipairs(t) do
|
||||||
|
local name, type = field[1], field[2]
|
||||||
|
names[#names + 1] = name
|
||||||
|
off = math.ceil(off / core.align(type)) * core.align(type)
|
||||||
|
offsets[name] = off
|
||||||
|
types[name] = type
|
||||||
|
off = off + core.sizeof(type)
|
||||||
|
end
|
||||||
|
return { names = names, offsets = offsets, types = types, size = off, new = struct_new,
|
||||||
|
byval = struct_byval }
|
||||||
|
end
|
||||||
|
|
||||||
|
function byval(buf)
|
||||||
|
if buf.size then
|
||||||
|
local size = buf.size
|
||||||
|
local types = { "char", "short"}
|
||||||
|
local vals = {}
|
||||||
|
for i = 1, size, 4 do
|
||||||
|
if size - i == 0 then
|
||||||
|
vals[#vals + 1] = buf:get(i, "char")
|
||||||
|
elseif size - i == 1 then
|
||||||
|
vals[#vals + 1] = buf:get(i, "short")
|
||||||
|
else
|
||||||
|
vals[#vals + 1] = buf:get(i, "int")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return unpack(vals)
|
||||||
|
else
|
||||||
|
error("this type of buffer can't be passed by value")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
<!DOCTYPE muclient>
|
<!DOCTYPE muclient>
|
||||||
<!-- Saved on Sunday, May 25, 2025, 1:55 PM -->
|
<!-- Saved on Friday, July 10, 2026, 11:31 PM -->
|
||||||
<!-- MuClient version 5.06 -->
|
<!-- MuClient version 5.06 -->
|
||||||
<!-- Written by Nick Gammon -->
|
<!-- Written by Nick Gammon -->
|
||||||
<!-- Home Page: http://www.mushclient.com/ -->
|
<!-- Home Page: http://www.mushclient.com/ -->
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
<world
|
<world
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
|
|
||||||
auto_say_override_prefix="-"
|
auto_say_override_prefix="-"
|
||||||
auto_say_string="say "
|
auto_say_string="say "
|
||||||
chat_name="Name-not-set"
|
chat_name="Name-not-set"
|
||||||
command_stack_character=";"
|
command_stack_character=";"
|
||||||
id="d2211ef7d54c4a7bd0cc9ab7"
|
id="7c5e6f4701b5e6f94dbeb6b2"
|
||||||
input_font_name="Fixedsys"
|
input_font_name="Fixedsys"
|
||||||
mapping_failure="Alas, you cannot go that way."
|
mapping_failure="Alas, you cannot go that way."
|
||||||
name="Cosmic Rage"
|
name="Cosmic Rage"
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
<triggers
|
<triggers
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
<trigger
|
<trigger
|
||||||
enabled="y"
|
enabled="y"
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
<aliases
|
<aliases
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
</aliases>
|
</aliases>
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
<timers
|
<timers
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
</timers>
|
</timers>
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
<macros
|
<macros
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
|
|
||||||
<macro name="up" type="send_now" >
|
<macro name="up" type="send_now" >
|
||||||
@@ -243,7 +243,7 @@
|
|||||||
<variables
|
<variables
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
</variables>
|
</variables>
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
<colours
|
<colours
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
|
|
||||||
<ansi>
|
<ansi>
|
||||||
@@ -309,7 +309,7 @@
|
|||||||
<keypad
|
<keypad
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
|
|
||||||
<key name="0" >
|
<key name="0" >
|
||||||
@@ -423,7 +423,7 @@
|
|||||||
<printing
|
<printing
|
||||||
muclient_version="5.06"
|
muclient_version="5.06"
|
||||||
world_file_version="15"
|
world_file_version="15"
|
||||||
date_saved="2025-05-25 13:55:57"
|
date_saved="2026-07-10 23:31:00"
|
||||||
>
|
>
|
||||||
|
|
||||||
<ansi>
|
<ansi>
|
||||||
@@ -448,7 +448,6 @@
|
|||||||
</printing>
|
</printing>
|
||||||
|
|
||||||
<!-- plugins -->
|
<!-- plugins -->
|
||||||
<include name="LuaAudio.xml" plugin="y" />
|
|
||||||
<include name="CaptureMudOutput.xml" plugin="y" />
|
<include name="CaptureMudOutput.xml" plugin="y" />
|
||||||
<include name="Copy_Output.xml" plugin="y" />
|
<include name="Copy_Output.xml" plugin="y" />
|
||||||
<include name="direction_plugin.xml" plugin="y" />
|
<include name="direction_plugin.xml" plugin="y" />
|
||||||
@@ -457,5 +456,7 @@
|
|||||||
<include name="Omit_Blank_Lines.xml" plugin="y" />
|
<include name="Omit_Blank_Lines.xml" plugin="y" />
|
||||||
<include name="Reconnecter.xml" plugin="y" />
|
<include name="Reconnecter.xml" plugin="y" />
|
||||||
<include name="Repeat_Command.xml" plugin="y" />
|
<include name="Repeat_Command.xml" plugin="y" />
|
||||||
|
<include name="GagManager.xml" plugin="y" />
|
||||||
|
<include name="LuaAudio.xml" plugin="y" />
|
||||||
<include name="CosmicRage.xml" plugin="y" />
|
<include name="CosmicRage.xml" plugin="y" />
|
||||||
</muclient>
|
</muclient>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ sequence="10000"
|
|||||||
for i = 1, 9 do
|
for i = 1, 9 do
|
||||||
Accelerator("alt + " .. i, "@buffer " .. i)
|
Accelerator("alt + " .. i, "@buffer " .. i)
|
||||||
end
|
end
|
||||||
Accelerator("alt + 0", "@buffer 10") -- 10 is a special case
|
Accelerator("alt + 0", "@buffer 10")
|
||||||
Accelerator("alt + right", "@buffer forward")
|
Accelerator("alt + right", "@buffer forward")
|
||||||
Accelerator("alt + left", "@buffer backward")
|
Accelerator("alt + left", "@buffer backward")
|
||||||
Accelerator("alt + down", "@buffer down")
|
Accelerator("alt + down", "@buffer down")
|
||||||
@@ -34,7 +34,8 @@ Accelerator("alt + pagedown", "@buffer scrolldown")
|
|||||||
Accelerator("alt + pageup", "@buffer scrollup")
|
Accelerator("alt + pageup", "@buffer scrollup")
|
||||||
Accelerator("alt + end", "@buffer bottom")
|
Accelerator("alt + end", "@buffer bottom")
|
||||||
Accelerator("alt + home", "@buffer top")
|
Accelerator("alt + home", "@buffer top")
|
||||||
Accelerator("alt+shift+delete", "@buffer clear")
|
Accelerator("alt+delete", "@buffer clear")
|
||||||
|
Accelerator("alt+shift+delete", "@buffer clearall")
|
||||||
Accelerator("alt+shift+right", "@buffer add")
|
Accelerator("alt+shift+right", "@buffer add")
|
||||||
Accelerator("alt+shift+left", "@buffer delete")
|
Accelerator("alt+shift+left", "@buffer delete")
|
||||||
Accelerator("f4", "@sp-settings master volume")
|
Accelerator("f4", "@sp-settings master volume")
|
||||||
@@ -62,13 +63,35 @@ Accelerator("f4", "@sp-settings master volume")
|
|||||||
Accelerator("ctrl+f9", "@sp-settings planetary music")
|
Accelerator("ctrl+f9", "@sp-settings planetary music")
|
||||||
Accelerator("ctrl+f10", "@sp-settings space combat music")
|
Accelerator("ctrl+f10", "@sp-settings space combat music")
|
||||||
Accelerator("ctrl+f11", "@sp-settings weather sounds")
|
Accelerator("ctrl+f11", "@sp-settings weather sounds")
|
||||||
Accelerator("alt+f12", "@sp-restart")
|
|
||||||
Accelerator("shift+alt+f9", "@sp-settings Interrupt OOC Social sounds when the next one plays")
|
Accelerator("shift+alt+f9", "@sp-settings Interrupt OOC Social sounds when the next one plays")
|
||||||
Accelerator("f2", "@sp-settings")
|
Accelerator("f2", "@sp-settings")
|
||||||
|
|
||||||
Accelerator("shift+f1", "@sp-toggle")
|
Accelerator("shift+f1", "@sp-toggle")
|
||||||
Accelerator("Ctrl+shift+enter", "@sp-settings buffer sounds")
|
Accelerator("Ctrl+shift+enter", "@sp-settings buffer sounds")
|
||||||
Accelerator("ctrl+shift+v", "@sp-version")
|
Accelerator("ctrl+shift+v", "@sp-version")
|
||||||
|
Accelerator("ctrl+s", "save_state_func")
|
||||||
|
Accelerator("alt+shift+h", "localsphelp")
|
||||||
|
Accelerator("ctrl+f2", "mainvoldown")
|
||||||
|
Accelerator("ctrl+f3", "mainvolup")
|
||||||
|
Accelerator("alt+f3", "intromusictoggle")
|
||||||
|
Accelerator("ctrl+f1", "smute")
|
||||||
|
Accelerator("f11", "f11_stop")
|
||||||
|
Accelerator("alt+f12", "alt_f12_stop")
|
||||||
|
|
||||||
|
function save_state_func()
|
||||||
|
SaveState()
|
||||||
|
Execute("tts_interrupt Client-side Soundpack Settings have been saved successfully!")
|
||||||
|
end
|
||||||
|
|
||||||
|
function f11_stop()
|
||||||
|
ppi.stop(0)
|
||||||
|
Execute("tts_interrupt All currently playing sounds have been stopped.")
|
||||||
|
Send("@sp-restart")
|
||||||
|
end
|
||||||
|
|
||||||
|
function alt_f12_stop()
|
||||||
|
ppi.stop(0)
|
||||||
|
Execute("tts_interrupt All currently playing sounds have been stopped.")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function RegisterMushClient(name, line, wildcards)
|
function RegisterMushClient(name, line, wildcards)
|
||||||
@@ -91,7 +114,15 @@ Note("You are currently running version " .. tostring(CURRENT_SP_VERSION).." of
|
|||||||
|
|
||||||
if version_diff > 0 then
|
if version_diff > 0 then
|
||||||
ColourNote("yellow", "", "Warning: Your soundpack is " .. version_diff .. " version(s) out of date. We are now on version " .. user_version .. " of the soundpack.")
|
ColourNote("yellow", "", "Warning: Your soundpack is " .. version_diff .. " version(s) out of date. We are now on version " .. user_version .. " of the soundpack.")
|
||||||
ColourNote("yellow", "", "Run Updater.bat to pull the latest version.")
|
ColourNote("yellow", "", "Launching the updater...")
|
||||||
|
ppi.play(dir.."sounds/ogg/general/comms/announcement.ogg", 0, 0, 100)
|
||||||
|
local bat_path = dir .. "..\\updator.bat"
|
||||||
|
-- Signature is (filename, params, defdir, operation, show) - this was
|
||||||
|
-- previously passing "open" as the filename, so it never actually
|
||||||
|
-- launched cmd.exe/updator.bat at all.
|
||||||
|
utils.shellexecute("cmd", '/c "' .. bat_path .. '"', "", "open", 1)
|
||||||
|
ColourNote("yellow", "", "The updater has been launched in a separate window. Once the update is complete, please restart MUSHclient to apply the updates!")
|
||||||
|
Execute("tts_interrupt Warning: Your soundpack is out of date. The updater has been launched. Once the update is complete, please restart MUSHclient to apply the updates!")
|
||||||
else
|
else
|
||||||
ColourNote("green", "", "Your soundpack is up to date!")
|
ColourNote("green", "", "Your soundpack is up to date!")
|
||||||
end
|
end
|
||||||
@@ -102,6 +133,109 @@ function url_encode(str)
|
|||||||
return string.format("%%%02X", string.byte(c))
|
return string.format("%%%02X", string.byte(c))
|
||||||
end))
|
end))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pending_downloads = {}
|
||||||
|
pending_play = {}
|
||||||
|
|
||||||
|
function download_sound(path, action, pan, volume, pitch, id)
|
||||||
|
if pending_downloads[path] then
|
||||||
|
table.insert(pending_play[path], {action=action, pan=pan, volume=volume, pitch=pitch, id=id})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
pending_downloads[path] = os.time()
|
||||||
|
pending_play[path] = { {action=action, pan=pan, volume=volume, pitch=pitch, id=id} }
|
||||||
|
|
||||||
|
local sound_path = dir .. "sounds/ogg/" .. path .. ".ogg"
|
||||||
|
local safe_path = sound_path:gsub("/", "\\")
|
||||||
|
local parent_dir = safe_path:match("^(.+)\\[^\\]+$")
|
||||||
|
local encoded_path = url_encode(path)
|
||||||
|
local url = "http://nathantech.net:3000/CosmicRage/CosmicRageSounds/raw/branch/main/ogg/" .. encoded_path .. ".ogg"
|
||||||
|
|
||||||
|
-- Download to a .part file and only rename it to the real filename after
|
||||||
|
-- curl succeeds, so check_downloads() can never mistake a half-written or
|
||||||
|
-- failed transfer for a completed one. Clears any leftover .err first, and
|
||||||
|
-- deletes it again on success, so a stale error/empty file can't linger.
|
||||||
|
local cmd_args = string.format(
|
||||||
|
'/C mkdir "%s" 2>nul & del /q "%s.err" 2>nul & ' ..
|
||||||
|
'curl.exe -s -S -L -f -o "%s.part" "%s" 2>"%s.err" ' ..
|
||||||
|
'&& (move /y "%s.part" "%s" >nul & del /q "%s.err" 2>nul) || del /q "%s.part" 2>nul',
|
||||||
|
parent_dir, safe_path, safe_path, url, safe_path, safe_path, safe_path, safe_path, safe_path
|
||||||
|
)
|
||||||
|
-- NOTE: correct shellexecute signature is (filename, params, defdir, operation, show)
|
||||||
|
utils.shellexecute("cmd", cmd_args, "", "open", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If a download hasn't finished or failed within this long, give up on it
|
||||||
|
-- rather than let a stuck curl process silently block that sound forever.
|
||||||
|
DOWNLOAD_TIMEOUT_SECONDS = 30
|
||||||
|
|
||||||
|
function check_downloads(name)
|
||||||
|
for path, started_at in pairs(pending_downloads) do
|
||||||
|
local sound_path = dir .. "sounds/ogg/" .. path .. ".ogg"
|
||||||
|
local safe_path = sound_path:gsub("/", "\\")
|
||||||
|
local f = io.open(sound_path, "r")
|
||||||
|
if f then
|
||||||
|
f:close()
|
||||||
|
local queue = pending_play[path]
|
||||||
|
pending_downloads[path] = nil
|
||||||
|
pending_play[path] = nil
|
||||||
|
if queue then
|
||||||
|
for _, p in ipairs(queue) do
|
||||||
|
play_sound_now(path, p.action, p.pan, p.volume, p.pitch, p.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local ef = io.open(safe_path .. ".err", "r")
|
||||||
|
if ef then
|
||||||
|
local msg = ef:read("*all")
|
||||||
|
ef:close()
|
||||||
|
if msg and msg ~= "" then
|
||||||
|
os.remove(safe_path .. ".err")
|
||||||
|
pending_downloads[path] = nil
|
||||||
|
pending_play[path] = nil
|
||||||
|
Note("Soundpack: download failed for " .. path .. ": " .. msg:gsub("[\r\n]", ""))
|
||||||
|
end
|
||||||
|
elseif os.time() - started_at > DOWNLOAD_TIMEOUT_SECONDS then
|
||||||
|
pending_downloads[path] = nil
|
||||||
|
pending_play[path] = nil
|
||||||
|
Note("Soundpack: download timed out for " .. path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function play_sound_now(path, action, pan, volume, pitch, id)
|
||||||
|
local sound_file = dir.."sounds/ogg/"..path..".ogg"
|
||||||
|
local temp
|
||||||
|
if (action == "play") then
|
||||||
|
temp = ppi.play(sound_file, 0, pan, volume)
|
||||||
|
if (pitch > -10000) then
|
||||||
|
ppi.setPitch(pitch, temp)
|
||||||
|
end
|
||||||
|
if (pan > -10000) then
|
||||||
|
ppi.setPan(pan, temp)
|
||||||
|
end
|
||||||
|
elseif (action == "playrec") then
|
||||||
|
temp = ppi.play(sound_file, 0, pan, volume)
|
||||||
|
SetVariable(id, temp)
|
||||||
|
if (pitch > -10000) then
|
||||||
|
ppi.setPitch(pitch, temp)
|
||||||
|
end
|
||||||
|
if (pan > -10000) then
|
||||||
|
ppi.setPan(pan, temp)
|
||||||
|
end
|
||||||
|
elseif (action == "loop") then
|
||||||
|
temp = ppi.play(sound_file, 1, pan, volume)
|
||||||
|
SetVariable(id, temp)
|
||||||
|
if (pitch > -10000) then
|
||||||
|
ppi.setPitch(pitch, temp)
|
||||||
|
end
|
||||||
|
if (pan > -10000) then
|
||||||
|
ppi.setPan(pan, temp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
]]>
|
]]>
|
||||||
|
|
||||||
|
|
||||||
@@ -197,40 +331,6 @@ match="^\$sphook\s+(?P<action>[\w]+):(?P<path>[-\w./\\\s]+):(?P<volume>[\w]+):(?
|
|||||||
pan="%5"
|
pan="%5"
|
||||||
id="%6"
|
id="%6"
|
||||||
volume=tonumber(volume)
|
volume=tonumber(volume)
|
||||||
if (path == "na") then
|
|
||||||
else
|
|
||||||
sound_path = dir .. "sounds/ogg/" .. path .. ".ogg"
|
|
||||||
directory_path = sound_path:match("(.*/)")
|
|
||||||
if directory_path then
|
|
||||||
test_file = io.open(directory_path .. "tester.txt", "w")
|
|
||||||
if test_file then
|
|
||||||
test_file:close()
|
|
||||||
os.remove(directory_path .. "checker.txt") -- Clean up
|
|
||||||
else
|
|
||||||
os.execute('mkdir "' .. directory_path .. '" 2>nul')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
file = io.open(sound_path, "r")
|
|
||||||
if file then
|
|
||||||
file:close()
|
|
||||||
else
|
|
||||||
local encoded_path = url_encode(path)
|
|
||||||
local download_url = "http://nathantech.net:3000/CosmicRage/CosmicRageSounds/raw/branch/main/ogg/" .. encoded_path .. ".ogg"
|
|
||||||
body = {}
|
|
||||||
result, status_code, headers, status = socket.http.request{
|
|
||||||
url = download_url, sink = ltn12.sink.table(body)
|
|
||||||
}
|
|
||||||
if result and status_code == 200 then
|
|
||||||
save_file = io.open(sound_path, "wb")
|
|
||||||
if save_file then
|
|
||||||
save_file:write(table.concat(body))
|
|
||||||
save_file:close()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Note("Download of "..path.." failed with status code: " .. tostring(status_code))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if (pitch == "na") then
|
if (pitch == "na") then
|
||||||
pitch = -10000
|
pitch = -10000
|
||||||
@@ -245,38 +345,10 @@ else
|
|||||||
pan = tonumber(pan)
|
pan = tonumber(pan)
|
||||||
end
|
end
|
||||||
|
|
||||||
if (action == "play") then
|
if (path == "na") then
|
||||||
temp = ppi.play(dir.."sounds/ogg/"..path..".ogg", 0, pan, volume)
|
|
||||||
if (pitch > -10000) then
|
|
||||||
ppi.setPitch(pitch, temp)
|
|
||||||
end
|
|
||||||
if (pan > -10000) then
|
|
||||||
ppi.setPan(pan, temp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (action=="playrec") then
|
|
||||||
temp=ppi.play(dir.."sounds/ogg/"..path..".ogg", 0, pan, volume)
|
|
||||||
SetVariable(id, temp)
|
|
||||||
if(pitch>-10000) then
|
|
||||||
ppi.setPitch(pitch,temp)
|
|
||||||
end
|
|
||||||
if(pan>-10000) then
|
|
||||||
ppi.setPan(pan,temp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (action=="loop") then
|
|
||||||
temp=ppi.play(dir.."sounds/ogg/"..path..".ogg", 1, pan, volume)
|
|
||||||
SetVariable(id, temp)
|
|
||||||
if(pitch>-10000) then
|
|
||||||
ppi.setPitch(pitch,temp)
|
|
||||||
end
|
|
||||||
if(pan>-10000) then
|
|
||||||
ppi.setPan(pan,temp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (action == "adjustsound") then
|
if (action == "adjustsound") then
|
||||||
temp = GetVariable(id)
|
temp = GetVariable(id)
|
||||||
if(volume>-1) then
|
if volume and volume > -1 then
|
||||||
ppi.setVol(volume, temp)
|
ppi.setVol(volume, temp)
|
||||||
end
|
end
|
||||||
if (pitch > -10000) then
|
if (pitch > -10000) then
|
||||||
@@ -285,18 +357,61 @@ if(volume>-1) then
|
|||||||
if (pan > -10000) then
|
if (pan > -10000) then
|
||||||
ppi.setPan(pan, temp)
|
ppi.setPan(pan, temp)
|
||||||
end
|
end
|
||||||
end
|
elseif (action == "stop") then
|
||||||
if (action=="stop") then
|
|
||||||
temp = GetVariable(id)
|
temp = GetVariable(id)
|
||||||
|
if temp then
|
||||||
ppi.stop(temp)
|
ppi.stop(temp)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local sound_path = dir .. "sounds/ogg/" .. path .. ".ogg"
|
||||||
|
local file = io.open(sound_path, "r")
|
||||||
|
if file then
|
||||||
|
file:close()
|
||||||
|
play_sound_now(path, action, pan, volume, pitch, id)
|
||||||
|
else
|
||||||
|
download_sound(path, action, pan, volume, pitch, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
</send>
|
</send>
|
||||||
</trigger>
|
</trigger>
|
||||||
</triggers>
|
</triggers>
|
||||||
|
|
||||||
<!-- Aliases -->
|
<!-- Timers -->
|
||||||
|
<timers>
|
||||||
|
<timer
|
||||||
|
enabled="y"
|
||||||
|
second="0.5"
|
||||||
|
script="check_downloads"
|
||||||
|
>
|
||||||
|
</timer>
|
||||||
|
</timers>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<aliases>
|
<aliases>
|
||||||
|
<alias
|
||||||
|
match="f11_stop"
|
||||||
|
enabled="y"
|
||||||
|
script="f11_stop"
|
||||||
|
omit_from_output="y"
|
||||||
|
omit_from_log="y"
|
||||||
|
omit_from_command_history="y"
|
||||||
|
send_to="12"
|
||||||
|
sequence="100"
|
||||||
|
>
|
||||||
|
</alias>
|
||||||
|
<alias
|
||||||
|
match="alt_f12_stop"
|
||||||
|
enabled="y"
|
||||||
|
script="alt_f12_stop"
|
||||||
|
omit_from_output="y"
|
||||||
|
omit_from_log="y"
|
||||||
|
omit_from_command_history="y"
|
||||||
|
send_to="12"
|
||||||
|
sequence="100"
|
||||||
|
>
|
||||||
|
</alias>
|
||||||
</aliases>
|
</aliases>
|
||||||
|
|
||||||
</muclient>
|
</muclient>
|
||||||
@@ -298,6 +298,10 @@ function OnPluginScreendraw(t, log, line)
|
|||||||
-- checking for an empty string, or a string composed only of spaces.
|
-- checking for an empty string, or a string composed only of spaces.
|
||||||
-- If we don't, NVDA says blank.
|
-- If we don't, NVDA says blank.
|
||||||
if (t == 0 or t == 1) and not line:find("^%s*$") then
|
if (t == 0 or t == 1) and not line:find("^%s*$") then
|
||||||
|
if GetVariable("skip_speech") == "1" then
|
||||||
|
SetVariable("skip_speech", "0")
|
||||||
|
return
|
||||||
|
end
|
||||||
if subst.status==0 then
|
if subst.status==0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ save_state="y"
|
|||||||
language="Lua"
|
language="Lua"
|
||||||
purpose="provides functions to help tts users."
|
purpose="provides functions to help tts users."
|
||||||
date_written="2010-04-06 08:37:40"
|
date_written="2010-04-06 08:37:40"
|
||||||
requires="4.46"
|
requires="4.82"
|
||||||
version="1.0"
|
version="1.1"
|
||||||
>
|
>
|
||||||
|
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -172,6 +172,17 @@ sequence="100">
|
|||||||
</alias>
|
</alias>
|
||||||
</aliases>
|
</aliases>
|
||||||
|
|
||||||
|
<!-- Timers -->
|
||||||
|
|
||||||
|
<timers>
|
||||||
|
<timer
|
||||||
|
enabled="y"
|
||||||
|
second="0.2"
|
||||||
|
script="poll_output_focus"
|
||||||
|
>
|
||||||
|
</timer>
|
||||||
|
</timers>
|
||||||
|
|
||||||
|
|
||||||
<!-- Get our standard constants -->
|
<!-- Get our standard constants -->
|
||||||
|
|
||||||
@@ -200,6 +211,7 @@ Accelerator("ctrl+shift+n","endline")
|
|||||||
Accelerator("ctrl+shift+y","topline")
|
Accelerator("ctrl+shift+y","topline")
|
||||||
Accelerator("ctrl+shift+h","whichline")
|
Accelerator("ctrl+shift+h","whichline")
|
||||||
Accelerator("ctrl+shift+alt+s","snap_shot")
|
Accelerator("ctrl+shift+alt+s","snap_shot")
|
||||||
|
Accelerator("ctrl+alt+j","toggle_jump_to_end")
|
||||||
|
|
||||||
function selectscr(eol)
|
function selectscr(eol)
|
||||||
buffercheck()
|
buffercheck()
|
||||||
@@ -280,12 +292,188 @@ function OnPluginCommandEntered(s)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------------
|
||||||
|
-- Notepad smart-scroll (formerly the separate NotepadSmartScroll
|
||||||
|
-- plugin, merged in here). See SmartAppendToNotepad's comment below
|
||||||
|
-- for why this needs raw Win32 access via Alien.
|
||||||
|
------------------------------------------------------------------
|
||||||
|
|
||||||
|
require "alien"
|
||||||
|
|
||||||
|
local user32 = alien.load("user32.dll")
|
||||||
|
local kernel32 = alien.load("kernel32.dll")
|
||||||
|
|
||||||
|
local FindWindowEx = user32.FindWindowExA
|
||||||
|
FindWindowEx:types{ ret = "pointer", "pointer", "pointer", "string", "string", abi = "stdcall" }
|
||||||
|
|
||||||
|
local GetWindowThreadProcessId = user32.GetWindowThreadProcessId
|
||||||
|
GetWindowThreadProcessId:types{ ret = "uint", "pointer", "ref uint", abi = "stdcall" }
|
||||||
|
|
||||||
|
local GetCurrentProcessId = kernel32.GetCurrentProcessId
|
||||||
|
GetCurrentProcessId:types{ ret = "uint", abi = "stdcall" }
|
||||||
|
|
||||||
|
local SendMessage = user32.SendMessageA
|
||||||
|
SendMessage:types{ ret = "long", "pointer", "uint", "uint", "long", abi = "stdcall" }
|
||||||
|
|
||||||
|
local GetModuleHandle = kernel32.GetModuleHandleA
|
||||||
|
GetModuleHandle:types{ ret = "pointer", "string", abi = "stdcall" }
|
||||||
|
|
||||||
|
local GetProcAddress = kernel32.GetProcAddress
|
||||||
|
GetProcAddress:types{ ret = "pointer", "pointer", "string", abi = "stdcall" }
|
||||||
|
|
||||||
|
local GetFocus = user32.GetFocus
|
||||||
|
GetFocus:types{ ret = "pointer", abi = "stdcall" }
|
||||||
|
|
||||||
|
-- EM_GETSEL's wParam/lParam are *pointers* to DWORD (needed to get the
|
||||||
|
-- real 32-bit selection range - the classic packed-return form is capped
|
||||||
|
-- at 64K). SendMessage above is already typed with "uint"/"long" for its
|
||||||
|
-- generic wParam/lParam, so a second binding of the same underlying
|
||||||
|
-- function is needed with "pointer" typed args for this one message.
|
||||||
|
local user32_handle = GetModuleHandle("user32.dll")
|
||||||
|
local SendMessagePtr = alien.funcptr(GetProcAddress(user32_handle, "SendMessageA"))
|
||||||
|
SendMessagePtr:types{ ret = "long", "pointer", "uint", "pointer", "pointer", abi = "stdcall" }
|
||||||
|
|
||||||
|
local WM_GETTEXTLENGTH = 0x000E
|
||||||
|
local EM_GETFIRSTVISIBLELINE = 0x00CE
|
||||||
|
local EM_LINESCROLL = 0x00B6
|
||||||
|
local EM_GETSEL = 0x00B0
|
||||||
|
local EM_SETSEL = 0x00B1
|
||||||
|
local EM_SCROLLCARET = 0x00B7
|
||||||
|
|
||||||
|
local my_pid = GetCurrentProcessId()
|
||||||
|
|
||||||
|
-- Finds the "Edit" control belonging to a MUSHclient notepad MDI child
|
||||||
|
-- window whose title exactly matches. Scoped to top-level windows owned
|
||||||
|
-- by this process, so it never touches unrelated apps on the desktop.
|
||||||
|
local function find_notepad_edit(title)
|
||||||
|
local top = nil
|
||||||
|
while true do
|
||||||
|
top = FindWindowEx(nil, top, nil, nil)
|
||||||
|
if not top then return nil end
|
||||||
|
|
||||||
|
local _, pid = GetWindowThreadProcessId(top, 0)
|
||||||
|
if pid == my_pid then
|
||||||
|
local mdiClient = FindWindowEx(top, nil, "MDIClient", nil)
|
||||||
|
if mdiClient then
|
||||||
|
local frame = FindWindowEx(mdiClient, nil, nil, title)
|
||||||
|
if frame then
|
||||||
|
local edit = FindWindowEx(frame, nil, "Edit", nil)
|
||||||
|
if edit then
|
||||||
|
return edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_selection(edit)
|
||||||
|
local start_buf = alien.buffer(4)
|
||||||
|
local end_buf = alien.buffer(4)
|
||||||
|
SendMessagePtr(edit, EM_GETSEL, start_buf, end_buf)
|
||||||
|
return start_buf:get(1, "uint"), end_buf:get(1, "uint")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scroll_to_line(edit, target)
|
||||||
|
local current = SendMessage(edit, EM_GETFIRSTVISIBLELINE, 0, 0)
|
||||||
|
local delta = target - current
|
||||||
|
if delta ~= 0 then
|
||||||
|
SendMessage(edit, EM_LINESCROLL, 0, delta)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Wraps AppendToNotepad(title, text): if the caret wasn't already sitting
|
||||||
|
-- at the very end of the text (i.e. the user has moved it to read
|
||||||
|
-- somewhere else), both the caret/selection and the scroll position are
|
||||||
|
-- restored after the append instead of being left jumped to the end.
|
||||||
|
-- AppendToNotepad hardcodes SetSel(len,len)+ReplaceSel on every call (see
|
||||||
|
-- scripting/methods/methods_notepad.cpp in the MUSHclient source) with no
|
||||||
|
-- scriptable override, so this reads the notepad Edit control's caret and
|
||||||
|
-- scroll position via raw Win32 calls (through the Alien FFI library,
|
||||||
|
-- running in-process on MUSHclient's own thread) before appending, and
|
||||||
|
-- restores them afterward when appropriate.
|
||||||
|
--
|
||||||
|
-- Note: text should use "\r\n" line endings, not bare "\n" - the plain
|
||||||
|
-- Win32 Edit control that backs the notepad doesn't recognise bare "\n"
|
||||||
|
-- as a line break for scrolling/line-count purposes.
|
||||||
|
function SmartAppendToNotepad(title, text)
|
||||||
|
local edit = find_notepad_edit(title)
|
||||||
|
local was_at_end = true
|
||||||
|
local sel_start, sel_end, first_visible
|
||||||
|
|
||||||
|
if edit then
|
||||||
|
local old_length = SendMessage(edit, WM_GETTEXTLENGTH, 0, 0)
|
||||||
|
sel_start, sel_end = get_selection(edit)
|
||||||
|
was_at_end = (sel_start == old_length) and (sel_end == old_length)
|
||||||
|
if not was_at_end then
|
||||||
|
first_visible = SendMessage(edit, EM_GETFIRSTVISIBLELINE, 0, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
AppendToNotepad(title, text)
|
||||||
|
|
||||||
|
if edit and not was_at_end then
|
||||||
|
-- Re-find in case the append above created/recreated the window.
|
||||||
|
local edit2 = find_notepad_edit(title) or edit
|
||||||
|
SendMessage(edit2, EM_SETSEL, sel_start, sel_end)
|
||||||
|
scroll_to_line(edit2, first_visible)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Moves the caret to the very end of a notepad and scrolls it into view.
|
||||||
|
local function jump_notepad_to_end(edit)
|
||||||
|
local len = SendMessage(edit, WM_GETTEXTLENGTH, 0, 0)
|
||||||
|
SendMessage(edit, EM_SETSEL, len, len)
|
||||||
|
SendMessage(edit, EM_SCROLLCARET, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function toggle_jump_to_end()
|
||||||
|
if GetVariable("jump_to_end_on_focus") == "0" then
|
||||||
|
SetVariable("jump_to_end_on_focus", "1")
|
||||||
|
Execute("tts_interrupt Jump to end when switching to output: on")
|
||||||
|
else
|
||||||
|
SetVariable("jump_to_end_on_focus", "0")
|
||||||
|
Execute("tts_interrupt Jump to end when switching to output: off")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Polls for the "output" notepad gaining focus (mouse click, Ctrl+Tab
|
||||||
|
-- between MDI windows, ActivateNotepad from a script, etc.), so the
|
||||||
|
-- jump-to-end behaviour applies regardless of how the user switches to
|
||||||
|
-- it. MUSHclient's own Accelerator/hotkey system can't see keystrokes
|
||||||
|
-- while a separate notepad window has focus (confirmed by Nick Gammon on
|
||||||
|
-- the MUSHclient forum), so this can't be event-driven - polling every
|
||||||
|
-- 0.2s is the safe, non-hooking way to catch it.
|
||||||
|
was_output_focused = false
|
||||||
|
|
||||||
|
function poll_output_focus()
|
||||||
|
if GetVariable("jump_to_end_on_focus") == "0" then
|
||||||
|
was_output_focused = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local edit = find_notepad_edit("output")
|
||||||
|
if not edit then
|
||||||
|
was_output_focused = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local focused = GetFocus()
|
||||||
|
local is_focused = (focused == edit)
|
||||||
|
if is_focused and not was_output_focused then
|
||||||
|
jump_notepad_to_end(edit)
|
||||||
|
end
|
||||||
|
was_output_focused = is_focused
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------------
|
||||||
|
|
||||||
function OnPluginInstall()
|
function OnPluginInstall()
|
||||||
if GetVariable("output") == nil then
|
if GetVariable("output") == nil then
|
||||||
SetVariable("output","1")
|
SetVariable("output","1")
|
||||||
end
|
end
|
||||||
|
if GetVariable("jump_to_end_on_focus") == nil then
|
||||||
|
SetVariable("jump_to_end_on_focus","1")
|
||||||
|
end
|
||||||
modes = {}
|
modes = {}
|
||||||
msgbuffer = {}
|
|
||||||
cline = 1
|
cline = 1
|
||||||
lastcount=0
|
lastcount=0
|
||||||
line = 0
|
line = 0
|
||||||
@@ -297,23 +485,7 @@ function OnPluginScreendraw(t,l,line)
|
|||||||
if(GetVariable("output")=="0") then
|
if(GetVariable("output")=="0") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if GetInfo(113) then
|
SmartAppendToNotepad("output", line.."\r\n")
|
||||||
AppendToNotepad("output", line.."\r\n")
|
|
||||||
else
|
|
||||||
table.insert(msgbuffer, line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function OnPluginGetFocus()
|
|
||||||
if GetVariable("output") == "0" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if #msgbuffer > 0 then
|
|
||||||
for i, buffered in ipairs(msgbuffer) do
|
|
||||||
AppendToNotepad("output", buffered.."\r\n")
|
|
||||||
end
|
|
||||||
msgbuffer={}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function prev_line()
|
function prev_line()
|
||||||
|
|||||||
31
readme.md
31
readme.md
@@ -40,7 +40,8 @@ MushClient is a portable client designed specifically for Cosmic Rage. The game
|
|||||||
- **Alt + PageUp**: Scroll buffer up
|
- **Alt + PageUp**: Scroll buffer up
|
||||||
- **Alt + End**: Jump to bottom of buffer
|
- **Alt + End**: Jump to bottom of buffer
|
||||||
- **Alt + Home**: Jump to top of buffer
|
- **Alt + Home**: Jump to top of buffer
|
||||||
- **Alt + Shift + Delete**: Clear buffer
|
- **Alt + Delete**: Clear current buffer
|
||||||
|
- **Alt + Shift + Delete**: Clear all buffers
|
||||||
- **Alt + Shift + Right**: Add buffer
|
- **Alt + Shift + Right**: Add buffer
|
||||||
- **Alt + Shift + Left**: Delete buffer
|
- **Alt + Shift + Left**: Delete buffer
|
||||||
|
|
||||||
@@ -71,13 +72,39 @@ MushClient is a portable client designed specifically for Cosmic Rage. The game
|
|||||||
- **Ctrl + F9**: Toggle planetary music
|
- **Ctrl + F9**: Toggle planetary music
|
||||||
- **Ctrl + F10**: Toggle space combat music
|
- **Ctrl + F10**: Toggle space combat music
|
||||||
- **Ctrl + F11**: Toggle weather sounds
|
- **Ctrl + F11**: Toggle weather sounds
|
||||||
- **Alt + F12**: Restart
|
|
||||||
- **Shift + Alt + F9**: Interrupt OOC social sounds on next play
|
- **Shift + Alt + F9**: Interrupt OOC social sounds on next play
|
||||||
|
- **Ctrl + F1**: Mute/unmute master sound
|
||||||
|
- **Ctrl + F2**: Master volume down
|
||||||
|
- **Ctrl + F3**: Master volume up
|
||||||
|
- **Alt + F3**: Toggle intro music
|
||||||
|
- **F11**: Stop all currently playing sounds and restart the soundpack
|
||||||
|
- **Alt + F12**: Stop all currently playing sounds
|
||||||
|
|
||||||
### Other Functions
|
### Other Functions
|
||||||
- **Shift + F1**: Toggle SP on/off
|
- **Shift + F1**: Toggle SP on/off
|
||||||
- **Ctrl + Shift + Enter**: Toggle buffer sounds
|
- **Ctrl + Shift + Enter**: Toggle buffer sounds
|
||||||
- **Ctrl + Shift + V**: Show version
|
- **Ctrl + Shift + V**: Show version
|
||||||
|
- **Ctrl + S**: Save client-side soundpack settings
|
||||||
|
- **Alt + Shift + H**: Show local help
|
||||||
|
|
||||||
|
### Accessible Output Window
|
||||||
|
The soundpack mirrors game text into a separate notepad-style window titled "output", useful for reviewing text with a screen reader without losing your place when new lines arrive.
|
||||||
|
|
||||||
|
- **Ctrl + Alt + O**: Toggle whether output is mirrored to the output window
|
||||||
|
- **Ctrl + Alt + J**: Toggle whether switching to the output window jumps you to the newest line
|
||||||
|
- **Ctrl + Shift + C**: Clear the output buffer
|
||||||
|
- **Ctrl + Alt + Shift + C**: Clear the output window
|
||||||
|
- **Ctrl + Shift + I**: Speak the current line
|
||||||
|
- **Ctrl + Shift + U**: Speak the previous line
|
||||||
|
- **Ctrl + Shift + O**: Speak the next line
|
||||||
|
- **Ctrl + Shift + Y**: Jump to and speak the top line
|
||||||
|
- **Ctrl + Shift + N**: Jump to and speak the bottom line
|
||||||
|
- **Ctrl + Shift + H**: Announce current line number and total line count
|
||||||
|
- **Ctrl + Shift + Space**: Start/stop selecting lines (copies with line breaks)
|
||||||
|
- **Ctrl + Shift + Alt + Space**: Start/stop selecting lines (copies joined by spaces, no line breaks)
|
||||||
|
- **Ctrl + Shift + Alt + S**: Take a snapshot of the whole buffer into a "snapshot" notepad
|
||||||
|
- **Ctrl + Alt + Enter**: Toggle stopping speech whenever you press Enter
|
||||||
|
- **Ctrl + 1** to **Ctrl + 0**: Recall a recent output line by position from the end (tap once to speak it, twice to copy it, three times to paste it into the command line)
|
||||||
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|||||||
Reference in New Issue
Block a user