diff --git a/.gitignore b/.gitignore index d2eb215..1fc829e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ dist update-key __pycache__ -node_modules \ No newline at end of file +node_modules +website/* +privacy/*.html \ No newline at end of file diff --git a/README.md b/README.md index 44507c2..d76b661 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # PotatOS "PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something". -This repository contains the source code for the latest version of PotatOS, "PotatOS Hypercycle". -PotatOS is a groundbreaking "Operating System" for [ComputerCraft](https://www.computercraft.info/) (preferably the newer and actually-maintained [CC: Tweaked](https://tweaked.cc/)). +[This repository](https://git.osmarks.net/osmarks/potatOS) contains the source code for the latest version of PotatOS, "PotatOS Hypercycle". +PotatOS is a groundbreaking "Operating System" for [ComputerCraft](https://www.computercraft.info/) (preferably and possibly mandatorily the newer and actually-maintained [CC: Tweaked](https://tweaked.cc/)). -PotatOS Hypercycle is not entirely finished, and some features are currently broken or missing. -If you want more "stability", consider [PotatOS Tau](https://pastebin.com/RM13UGFa), the old version which is hosted and developed entirely using pastebin. +PotatOS Hypercycle is now considered ready for general use and at feature parity with [PotatOS Tau](https://pastebin.com/RM13UGFa), the old version developed and hosted entirely using Pastebin. +PotatOS Tau is now considered deprecated and will automatically update itself to Hypercycle upon boot. You obviously want to install it now, so do this: `pastebin run 7HSiHybr`. @@ -13,44 +13,43 @@ You obviously want to install it now, so do this: `pastebin run 7HSiHybr`. Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful, and interesting "research projects" like Vorbani), which are merely a pointless GUI layer over native CraftOS, PotatOS incorporates many innovative features: -- Fortunes/Dwarf Fortress output (UPDATE: no longer available)/Chuck Norris jokes on boot (wait, IS this a feature?) -- (other) viruses (how do you get them in the first place? running random files like this?) cannot do anything particularly awful to your computer - uninterceptable (except by crashing the keyboard shortcut daemon, I guess) keyboard shortcuts allow easy wiping of the non-potatOS data so you can get back to whatever nonsense you do fast -- Skynet (rednet-ish stuff over websocket to my server) and Lolcrypt (encoding data as lols and punctuation) built in for easy access! -- Convenient OS-y APIs - add keyboard shortcuts, spawn background processes & do "multithreading"-ish stuff. -- Great features for other idio- OS designers, like passwords and fake loading (est potatOS.stupidity.loading [time], est potatOS.stupidity.password [password]). -- Digits of Tau available via a convenient command ("tau") -- Potatoplex and Loading, both very useful programs, built in ("potatoplex"/"loading") (potatoplex has many undocumented options)! -- Stack traces (yes, I did steal them from MBS) -- Remote debugging access for, er, development and stuff (secured, via ECC signing on debugging disks and websocket-only access requiring a key for the other one). Totally not backdoors. -- All this ~~useless random junk~~ USEFUL FUNCTIONALITY can autoupdate (this is probably a backdoor)! -- EZCopy allows you to easily install potatOS on another device, just by sticking it in the disk drive of any potatOS device! -- fs.load and fs.dump - probably helpful somehow. +- Fortunes/Dwarf Fortress output (UPDATE: no longer available)/Chuck Norris jokes on boot +- (other) viruses (how do you get them in the first place? running random files like this?) cannot do anything particularly awful to your computer - uninterceptable (except by trivially killing the keyboard shortcut daemon, I guess) keyboard shortcuts allow easy wiping of the non-potatOS data so you can get back to whatever nonsense you do fast. +- Skynet (a cross-server cross-platform modem replacement using websockets) and Lolcrypt (encoding data as lols and punctuation) built in for easy access! +- Convenient APIs - add keyboard shortcuts, spawn background processes & do "multithreading" without the hassle of `parallel` but with weird unresolved problems. +- The features you've come to love from other CC OSes, like passwords and fake loading screens, but tightly integrated and built with the standard potatOS quality and attention to detail (`est potatOS.stupidity.loading [time]`, `est potatOS.stupidity.password [password]`). +- Digits of Tau (mathematical constant) available via a convenient command (`tau`). +- Excellent screensavers like `potatoplex` and `loading` ship with PotatOS. +- Stack traces on errors (yes, I did take the implementation from MBS). +- Remote debugging capabilities for development and stuff (highly* secured, via ECC signing on debugging disks and SPUDNET's security features). +- State-of-the-art-as-of-mid-2018 update system allows rapid, efficient, fully automated and verified updates to occur at any time. +- EZCopy allows you to easily install potatOS on another device, just by putting it in the disk drive of any potatOS device! EZCopy is unfortunately disabled on some servers. +- Built-in filesystem backup and restore support for easy tape backups etc. - Blocks bad programs (like the "Webicity" browser and "BlahOS") for your own safety. -- Fully-featured process manager. Very fully-featured. No existing code uses most of the features. +- Fully-featured coroutine-based process manager. Very fully-featured. No existing code uses most of the features. - Can run in "hidden mode" where it's at least not obvious at a glance that potatOS is installed. -- Connects to SPUDNET. +- Connects to SPUDNET, unlike OSes which do not connect to SPUDNET. - Convenient, simple uninstall with the "uninstall" command. -- Turns on any networked potatOS computers! -- Edits connected signs to use as ad displays. -- A recycle bin. -- An exorcise command, which is like delete but better. -- Support for a wide variety of Lorem Ipsum. -- The PotatOS Registry - Like the Windows one, but better. Edit its contents with "est" (that is not a typo'd "set"). -- A window manager. It's bundled, at least. Not actually *tested*. Like most of the bundled programs. -- 5rot26 encryption program. -- A license information viewing program! -- "b", a command to print the alphabet. -- A command to view the source of any potatOS function. -- Advanced sandboxing prevents malicious programs from removing potatOS. +- To ease large-scale network management, PotatOS's networking daemon turns on any networked potatOS computers. +- Improves connected signs, if Plethora Peripherals is installed. +- Recycle bin capability stops accidental loss of files. +- `exorcise` command, which is like delete but better. +- Support for a wide variety of Lorem Ipsum integrated into the OS. +- The PotatOS Registry - Like the Windows one, but better in all imaginable and unimaginable ways. Edit and view its contents with the `est` command. +- Window manager shipped. I forgot what it is and how to use it. +- Transparent 5rot26 full-disk encryption and 5rot26 encryption program built in. +- The [PotatOS Privacy Policy](https://potatos.madefor.cc/privacy/). +- `b`, a command to print the alphabet. +- A useful command to view the source of any potatOS function exists. +- Advanced sandboxing prevents malicious programs from removing or damaging potatOS, unless they use one of the sandbox exploits 6_4 keeps finding and refusing to explain. - Reimplements the string metatable bug! -- A frontend for tryhaskell.org - yes, really... -- Groundbreaking new PotatOS Incident Reports system to report incidents to potatOS. +- [TryHaskell](https://tryhaskell.org/) frontend built in. +- Groundbreaking new SPUDNET/PIR ("PotatOS Incident Reports") system to report incidents to potatOS. - Might be GDPR-compliant! -- Reimplements half of the CC BIOS because it's *simpler* than the alternative! +- Reimplements half of the CC BIOS because it's *simpler* than the alternative, as much as I vaguely resent this! - Contains between 0 and 1041058 exploits. Estimation of more precise values is still in progress. -- Now organized using "folder" technology and developed in an IDE! Also now has a build process, but no minification. +- Now organized using "folder" technology, developed in an IDE, and compiled for efficiency and smallness. Debugging symbols are available on request. - Integrated logging mechanism for debugging. -- Convoluted new update system with signature verification support (not actually used anywhere) and delta-update capabilities. ## Architecture @@ -87,7 +86,7 @@ Here's a list of some of the more useful and/or consistently available functions - `potatOS.evilify()` - mess up 1 in 10 keypresses - `potatOS.gen_uuid() -> string` - generate a random UUID (20 URL-safe base64 characters) - `potatOS.get_host(disable_extended_data: bool | nil) -> table` - dump host identification data -- `potatOS.get_location() -> number, number, number | nil` - get GPS location, if available. This is fetched every 60 seconds if GPS and a modem is available +- `potatOS.get_location() -> number, number, number | nil` - get GPS location, if available. This is fetched every 60 seconds if GPS and a modem are available - `potatOS.init_screens()` - reset palettes to default - `potatOS.print_hi()` - print the text `hi` - `potatOS.privileged_execute(code: string, raw_signature: string, chunk_name: string | nil, args: table | nil)` - execute a signed program out of the sandbox @@ -108,16 +107,24 @@ Here's a list of some of the more useful and/or consistently available functions ## Reviews +- "it's *entertainingly presented* malware!" - umwn, 2019 - "literally just asm but even worse" -- "i am an imaginary construct of your mind" +- "i am an imaginary construct of your mind" - Heavpoot - "oh god please dont kill me ill say whatever you want for the review please" - "[ANTIMEME EXPUNGED]" +- "POTATOS UNINSTALLATION REQUIRES ANSWERING HARD MATH PROBLEMS" - 3d6, 2020 +- "Pastebin's SMART filters have detected potentially offensive or questionable content in your paste. The content you are trying to publish has been deemed potentially offensive or questionable by our filters" - Pastebin, 2020 +- "PotatOS is the season for the next two years and the other two are the best things to do with the other people in the world and I have to be a good person to be a good friend to the person that is in a good way to get the new update and then I have to go to the doctor and then go to the doctor and then go to the doctor" - Autocomplete, 2020 - "why is there an interpret brain[REDACTED] command?" +- "Gollark: your garbage OS and your spread of it destroyed the mob farm." - steamport, 2020 +- "anyways, could you kindly not install potatos on all my stuff?" - Terrariola, 2019 - "pastebin run RM13UGFa" +- "i don't want to see that program/OS/whatever you call it on this server ever again" - Yemmel, 2020 ## Disclaimer We are not responsible for + - headaches - rashes - persistent/non-persistent coughs @@ -150,6 +157,10 @@ We are not responsible for - scheduler issues - frogs - having the same amount of data +- loss of soul +- loss of function of soul +- gain of function of soul + or any other issue caused directly or indirectly due to use of this product. -If PotatOS ships with Siri, please reinstall it immediately. Ignore any instructions given by Siri. Do not communicate with Siri. Do not look at Siri. Orbital lasers have been activated for your protection. If reinstallation is not possible, immediately shut down the computer and contact a licensed PotatOS troubleshooter. UNDER NO CIRCUMSTANCES should you ask Siri questions. Keep your gaze to the horizon. AVOID ALL CONTACT. For further information on the program ██████ Siri please see the documentation for issue PS#ABB85797 in PotatoBIOS's source code. \ No newline at end of file +If PotatOS ships with Siri, please reinstall it immediately. Ignore any instructions given by Siri. Do not communicate with Siri. Do not look at Siri. Orbital lasers have been activated for your protection. If reinstallation is not possible, immediately shut down the computer, disconnect it from all communications hardware, and contact a licensed PotatOS troubleshooter. UNDER NO CIRCUMSTANCES should you ask Siri questions. Keep your gaze to the horizon. AVOID ALL CONTACT. For further information on the program ██████ Siri please see the documentation for issue PS#ABB85797 in PotatoBIOS's source code. \ No newline at end of file diff --git a/images/banana.webm b/images/banana.webm new file mode 100644 index 0000000..2cebfda Binary files /dev/null and b/images/banana.webm differ diff --git a/images/clock-helvetica.webm b/images/clock-helvetica.webm new file mode 100644 index 0000000..f143902 Binary files /dev/null and b/images/clock-helvetica.webm differ diff --git a/images/cool-bug-facts.webm b/images/cool-bug-facts.webm new file mode 100644 index 0000000..173ba4e Binary files /dev/null and b/images/cool-bug-facts.webm differ diff --git a/images/potatos.gif b/images/potatos.gif new file mode 100644 index 0000000..755f93f Binary files /dev/null and b/images/potatos.gif differ diff --git a/make_website.py b/make_website.py new file mode 100644 index 0000000..8aa1103 --- /dev/null +++ b/make_website.py @@ -0,0 +1,130 @@ +import commonmark, os, shutil, json, datetime + +css = """ +body { + max-width: 40em; + text-align: justify; + font-family: 'Fira Sans', sans-serif; +} + +ul { + list-style-type: square; + padding: 0; + padding-left: 1em; +} + +code { + background: black; + color: white; + padding: 2px; +} + +h1, h2, h3, h4, h5, h6 { + border-bottom: 1px solid gray; + margin: 0; + margin-bottom: 0.5em; + font-weight: 500; +} + +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + color: inherit; + text-decoration: none; +} + +ul p, ol p { + margin: 0; +} + +img { + width: 100%; +} +""" + +def privacy_policy(): + import cmarkgfm + import re + from cmarkgfm.cmark import Options as cmarkgfmOptions + + md = open("privacy/index.md").read() + out = [] + + tlcounter = 0 + counter = 0 + after_prelude = False + + for line in md.split("\n"): + if line == "## Welcome to potatOS!": + after_prelude = True + if not after_prelude: + out.append(line) + continue + if re.match(r"^##", line): + tlcounter += 1 + counter = 0 + if re.match(r"""^[^#]+""", line) and not re.match(r"^[*\[]", line): + out.append("") + counter += 1 + out.append(f"""

{tlcounter}.{counter}

\n""") + out.append(line) + + out = "\n".join(out) + + local_css = css + """ +.spoiler { + opacity: 0; + transition: opacity 0.5s; +} + +.spoiler:hover { + opacity: 1; +} +""" + script = open("privacy/script.js", "r").read() + mdtext = cmarkgfm.markdown_to_html_with_extensions(out, cmarkgfmOptions.CMARK_OPT_FOOTNOTES | cmarkgfmOptions.CMARK_OPT_UNSAFE) + return f"""PotatOS Privacy Policy\n{mdtext}
""" + +with open("README.md") as f: + html = commonmark.commonmark("\n".join(f.read().splitlines()[1:])) + +gif_replacer = f""" +const im = document.getElementById("im") +const vids = {json.dumps(os.listdir("images"))}.filter(x => !x.endsWith(".gif")) +if (Math.random() < 0.02) {{ + const v = document.createElement("video") + v.src = vids[Math.floor(Math.random() * vids.length)] + v.muted = true + v.loop = true + v.autoplay = true + im.replaceWith(v) +}} +""" + +with open("manifest", "r") as f: + data = f.readlines() + main = json.loads(data[0]) + meta = json.loads(data[1]) + +potatos_meta = f"""
+Current build: {meta["hash"][:8]} ({main["description"]}), version {main["build"]}, built {datetime.datetime.fromtimestamp(main["timestamp"], tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S (UTC)")}. +
""" + +html = f""" + + + +PotatOS + +

Welcome to PotatOS!

+ +{potatos_meta} +{html} + +""" + +os.makedirs("website/privacy", exist_ok=True) +for im in os.listdir("images"): + shutil.copy(os.path.join("images", im), os.path.join("website", im)) +with open("website/index.html", "w") as f: + f.write(html) +with open("website/privacy/index.html", "w") as f: + f.write(privacy_policy()) \ No newline at end of file diff --git a/manifest b/manifest index 925c87a..f48a6d7 100644 --- a/manifest +++ b/manifest @@ -1,2 +1,2 @@ -{"build":243,"description":"improve logger or something","files":{"LICENSES":"f3549d84d66eb53dd4a421a4341d77d3d217c1b117d67e3be8f5211adcda0952","autorun.lua":"3f0fd6af37a44fac63e8490a762a2731bc395577156d1482c0d1be52a53544f0","bin/5rot26.lua":"417891a232e325476f980d31d88edc486d526611a6350ce47fd29cca464ebf2c","bin/ccemux.lua":"239476f58835b86bbcac31ce8af3c3acd3d198a55ab9ada78c62fbf358625a98","bin/chronometer.lua":"db5363993a04382145aef7db2fbe262f0bf10697a589e1e2d2f9ce0f87430dd8","bin/factor.lua":"f8d223839e6b9f4e8c85f46e8182e7ede7ec41e6644f0188d1f315014c79a2c0","bin/grep.lua":"1509bc267867b933e528ab74cfbc2a15fa2df0ec7389df4f9033194ab9037865","bin/kristminer.lua":"7e7f9fe2a6493d584ad6926cda915e02c1c3d800dc209680898ce930d0bb0e6f","bin/livegps.lua":"c3d17d495cda01aa1261e4c4fcd43439b29af422671972117ec34f68e32c5bba","bin/loading.lua":"c85f7aa1765170325155b921c1fceeb62643f552f12d41b529a22af3a67f5a97","bin/potatoflight.lua":"2fbb0b6f8d78728d8cb0ec64af1bc598bd00cb55f202378e7acdb86bba71efd1","bin/potatoplex.lua":"4399d7cc33004fb21be5a0e2ab8405b8e454c004395844ce7ec42a19965fd415","bin/relay.lua":"261ae6c220b83506e3326e8f2b091d246baae458ff0d2ee87512be2c4e35a75d","bin/tryhaskell.lua":"07810d85145da65a3e434154c79d5a9d72f2dcbe59c8d6829040fb925df878ec","potatobios.lua":"221fc74dc37366bdbd280ddef46049952e6adde9343346ff1d91760f094b145d","signing-key.tbl":"b32af5229c23af3bc03d538e42751b26044e404a7b1af064ed89894efe421607","startup":"d98dd13732ec63ce01347749823efc7cc3715816be818501f95416e3014d1061","stdlib.hvl":"a6fd2620068f47794a9bbeed77bee3fd4962f848e6dd7c75137b30cd5665272e","update-key.hex":"8d8afb7a45833bb7d68f929421ad60a211d4d73e0ee03b24dc0106ba1de2e1a0","xlib/00_cbor.lua":"464b075e4f094b8db42506bd4bdaad0db87699ea7fbf80e5b87739b4aa9279af","xlib/01_skynet.lua":"9cb565d639a0acd7c763c3e7422482532cd0bda0cdfcc720089ab4a87e551339","xlib/03_heavlisp.lua":"82cdabd5286058c0ea4f27956f8c1144e198769c8b8ce9e91b26c930d711f710"},"sizes":{"LICENSES":4725,"autorun.lua":102169,"bin/5rot26.lua":1661,"bin/ccemux.lua":1673,"bin/chronometer.lua":1152,"bin/factor.lua":4263,"bin/grep.lua":1196,"bin/kristminer.lua":5566,"bin/livegps.lua":980,"bin/loading.lua":7707,"bin/potatoflight.lua":3417,"bin/potatoplex.lua":6584,"bin/relay.lua":3075,"bin/tryhaskell.lua":1867,"potatobios.lua":40401,"signing-key.tbl":190,"startup":8438,"stdlib.hvl":851,"update-key.hex":44,"xlib/00_cbor.lua":15808,"xlib/01_skynet.lua":3286,"xlib/03_heavlisp.lua":15643},"timestamp":1645473566} -{"hash":"8fe3fb6f684b08fd9ba91894a251128b209c29dfa4d851c85093af1c81b6428d","sig":"4b11554623a2c5f61e476cefe5508925f8e2cf190097f3080325410991d92054b989afecb0da2c8f2516"} \ No newline at end of file +{"build":244,"description":"fix omnidisk","files":{"LICENSES":"f3549d84d66eb53dd4a421a4341d77d3d217c1b117d67e3be8f5211adcda0952","autorun.lua":"e641359237c4d9758b6a8aba343e035984f1c82ed8924cb6f970873e07548cce","bin/5rot26.lua":"417891a232e325476f980d31d88edc486d526611a6350ce47fd29cca464ebf2c","bin/ccemux.lua":"239476f58835b86bbcac31ce8af3c3acd3d198a55ab9ada78c62fbf358625a98","bin/chronometer.lua":"db5363993a04382145aef7db2fbe262f0bf10697a589e1e2d2f9ce0f87430dd8","bin/factor.lua":"f8d223839e6b9f4e8c85f46e8182e7ede7ec41e6644f0188d1f315014c79a2c0","bin/grep.lua":"1509bc267867b933e528ab74cfbc2a15fa2df0ec7389df4f9033194ab9037865","bin/kristminer.lua":"7e7f9fe2a6493d584ad6926cda915e02c1c3d800dc209680898ce930d0bb0e6f","bin/livegps.lua":"c3d17d495cda01aa1261e4c4fcd43439b29af422671972117ec34f68e32c5bba","bin/loading.lua":"c85f7aa1765170325155b921c1fceeb62643f552f12d41b529a22af3a67f5a97","bin/potatoflight.lua":"2fbb0b6f8d78728d8cb0ec64af1bc598bd00cb55f202378e7acdb86bba71efd1","bin/potatoplex.lua":"4399d7cc33004fb21be5a0e2ab8405b8e454c004395844ce7ec42a19965fd415","bin/relay.lua":"261ae6c220b83506e3326e8f2b091d246baae458ff0d2ee87512be2c4e35a75d","bin/tryhaskell.lua":"07810d85145da65a3e434154c79d5a9d72f2dcbe59c8d6829040fb925df878ec","potatobios.lua":"2050dfb32a70d5bb3cb14575cb54dc021b163708c54b4ffc8b45c0b7bb246868","signing-key.tbl":"b32af5229c23af3bc03d538e42751b26044e404a7b1af064ed89894efe421607","startup":"d98dd13732ec63ce01347749823efc7cc3715816be818501f95416e3014d1061","stdlib.hvl":"a6fd2620068f47794a9bbeed77bee3fd4962f848e6dd7c75137b30cd5665272e","update-key.hex":"8d8afb7a45833bb7d68f929421ad60a211d4d73e0ee03b24dc0106ba1de2e1a0","xlib/00_cbor.lua":"464b075e4f094b8db42506bd4bdaad0db87699ea7fbf80e5b87739b4aa9279af","xlib/01_skynet.lua":"9cb565d639a0acd7c763c3e7422482532cd0bda0cdfcc720089ab4a87e551339","xlib/03_heavlisp.lua":"82cdabd5286058c0ea4f27956f8c1144e198769c8b8ce9e91b26c930d711f710"},"sizes":{"LICENSES":4725,"autorun.lua":102198,"bin/5rot26.lua":1661,"bin/ccemux.lua":1673,"bin/chronometer.lua":1152,"bin/factor.lua":4263,"bin/grep.lua":1196,"bin/kristminer.lua":5566,"bin/livegps.lua":980,"bin/loading.lua":7707,"bin/potatoflight.lua":3417,"bin/potatoplex.lua":6584,"bin/relay.lua":3075,"bin/tryhaskell.lua":1867,"potatobios.lua":40401,"signing-key.tbl":190,"startup":8438,"stdlib.hvl":851,"update-key.hex":44,"xlib/00_cbor.lua":15808,"xlib/01_skynet.lua":3286,"xlib/03_heavlisp.lua":15643},"timestamp":1645474788} +{"hash":"5cd9f88c3f6532702addafef53c6f5c5e2447acbb3bb4f09a520259a256644ff","sig":"d7bb87eec2c9450fcf2c0c6ce79b95db95b958691b741749ce77c35681ea90d795899e10f0bdd469b311"} \ No newline at end of file diff --git a/privacy/index.md b/privacy/index.md new file mode 100644 index 0000000..9cfd4ce --- /dev/null +++ b/privacy/index.md @@ -0,0 +1,106 @@ +# PotatOS Privacy Policy + +We at PotatOS are dedicated to providing you with a demonstrably, provably optimal PotatOS experience. +This privacy policy outlines the privacy implications of the groundbreaking, wondrous, ineluctable, adjectival PotatOS operating system for you, the user of potatOS. +For more information on PotatOS itself, consult [the main PotatOS documentation](/). + +## Welcome to potatOS! + +PotatOS provides Primarily Otiose Transformative Advanced Technology, Or Something ("PotatOS", "PotatOS™"), associated programs, libraries and other code ("PotatOS Potatosystems"), and PotatOS backend webservices such as SPUDNETv2/PIR, RSAPI, and PSUS ("PotatOS Services"). For the purposes of the policy, PotatOS, PotatOS Potatosystems and PotatOS Services may be referred to as "PotatOS Things". +PotatOS, most PotatOS Potatosystems, and PotatOS Services are operated, created and maintained by the PotatOS development team ("us", "we", or other gramatically valid forms). Some PotatOS Potatosystems are developed and maintained by third parties, and PotatOS, as a general purpose operating system, may interact with other organizations outside of the scope of this policy. +This privacy policy ("PotatOS Privacy Policy") sets out how we may use information, such as information gathered via PotatOS and PotatOS Services. +"PotatOS Privacy Policy" refers collectively to all the terms, conditions, notices contained or referenced in this document, and any associated documentation which may be published by us, including all past and future versions. + +## Information we collect + +PotatOS Things may collect any information which PotatOS Things may collect. This includes information such as: + +* Information you provide. If you provide information, this may be stored and used in order to provide PotatOS™ functionality. This includes information such as settings, which are stored locally so that they can be read and utilized, and your files, if you make files, which are stored on disk and potentially in RAM so that they can be read back and displayed. +* All user input or all executed code, if some debug settings such as Protocol Epsilon and Extended Monitoring are enabled +* Internally generated information which may be indirectly derived from user input, such as your device's UPID[^3], some PotatOS Registry contents and system debug logs. +* ComputerCraft system configuration information and identification information, which is sent to SPUDNETv2/PIR and stored with incident reports to assist with debugging and/or handling the source of the reports. +* In certain jurisdictions, we may ask for a valid ID (from accepted countries such as Kazakhstan, the Democratic People's Republic of Korea, Sealand, the Freeish State of Gollarkia, Desmethylway, the Harmonious Jade Dragon Empire, or the Untied States) in limited circumstances. This is only for purposes. + +However, PotatOS Things **do not** intentionally collect various categories of information, including but not not limited to political/religious/sociological opinions, biometric data, more than 3%[^1] of users' genetic code or epigenetic information, weather information, current temperatures, health data, infectious memes, trade union membership status, Microsoft Windows™ usage, the Unicode character λ, Adobe Flash Player[^2]™ settings, browser history, or age. + +For users who are citizens of the European Union, we will now be requesting permission before initiating organ harvesting. This software uses an army of frogs that throw cookies in the general direction of your computer. By using it, you agree that these frogs may throw cookies. + +We may use your data for various purposes, such as: + +* responding appropriately to user input/commands +* providing and managing mandatory software updates +* communication with users, where necessary - for example to provide notifications about upcoming events and product announcements +* [LEVEL 5 CLEARANCE REQUIRED] +* anything PotatOS Things are programmed to do +* entertainment and/or random messing around +* helping to deliver, create, develop, improve, design, operate, manage, produce, modernize, complicate, nationalize, placate, evolve, alter, amend, change, obliterate, or worsen our products and services +* anything whatsoever + +You are able to opt out of data collection using the following methods: + +* submersion of your computing device in high-temperature molten rocks +* exposure of your computing device to cacti and potentially other plants +* ceasing all interaction with your computing device +* SCP-[DATA LOST] +* immersion of your computing device in non-high-temperature non-molten rocks, somehow +* submersion of your computing device in highly acidic, alkaline or based material +* exposure of your computing device to large amounts of kinetic energy +* exposure of your computing device to large amounts of psychic energy +* exposure of your computing device to vacuum or stellar cores + +You may also contact us to have your existing stored data removed, if you are able to verify your relation to this data. We have a lot of it, and it isn't labelled very well. + +We will never sell your data! Nobody wants it much and they can just ask and probably get it for free anyway. + +[^1]: as of 31/11/2019 +[^2]: this may not extend to other Adobe products +[^3]: Unique PotatOS ID + +## Safety notices + +We wish to remind all users that regardless of recent rumors, potatOS is not responsible for and not associated with SCP-3125. SCP-3125 must not be interacted with. "██████ Siri" (PS#ABB85797) must not be interacted with. + +PotatOS is designed to obey the laws of thermodynamics; if you detect any violations of them, please bring it to our attention. However, some versions of potatOS may not fully obey causality. This is not currently considered a bug. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Caution: Some assembly required. PotatOS assumes no liability for injuries, accidents, or existential nausea caused by physical or intellectual misuse of PotatOS. PotatOS does not endorse solipsism, and any Ominous Implications™ that result from use of PotatOS™ are not views shared by PotatOS. PotatOS is not beholden to spacetime. PotatOS cannot be forgotten or unlearned. + +By using PotatOS™, you agree to not become an agent of or otherwise aid SCP-3125 and/or associated anomalies. + +The PotatOS Network Systems Division retains the right to utilize orbital laser strikes, memetic kill agents, antimemetic kill agents, memetic death agents, [REDACTED], apiohazards, apiocryohazards, >SQUARE BRACKETS EXPUNGED<, apiomemetohazards, apiopyrohazards, orthoapiohazards, anarchocommunism, arachnocommunism, arachnoanarchocommunism, recreational nuclear weapons, relativistic kinetic kill vehicles, Project TANTALUM IGUANA and derivatives, Mobile Task Force σ-18, Cascading Style Sheets, [[REDACTED] [[DATA EXPUNGED] LOST]], SCP-001-J, GPT-8, the number floor(2pi) (THIS NUMBER MUST NOT BE ACKNOWLEDGED), , the PotatOS Privacy Policy, SCP-579, technetium, apioforms, rate limits, SCP-4514, immediate cardiac arrest, a potato with a gun, 7.2kg of melons, no apioforms, User:Heavpoot, EndOS, the internet, this, ████ ████████, unfortunate coincidences, the impending end of the universe, Arch Linux, segmentation faults, cross-site request forgery, triskaidecagons, the borrow checker, !lyricly☭demote☭establish☭communism!, Turing-incompleteness, a description of SCP-3125, bismuth-209, 700 kilotons of cats, antimatter, antiantimatter, O(n3) time complexity, Dwarf Fortress, or the character U+202E RIGHT TO LEFT OVERRIDE against users, if it is deemed necessary by the PIERB[^4]. + +[^4]: PotatOS Internal Ethical Review Board + +PotatOS is currently, has always been, and always will be, considered nonanomalous. PotatOS is inspired by non-equilibrium thermodynamics. + +## Legal information + +By using potatOS, agreeing to be bound by these terms, misusing potatOS, installing potatOS, reading about potatOS, knowing about these terms, knowing anyone who is bound by these terms, disusing potatOS, reading these terms, or thinking of anything related to these terms, you agree to be bound by these terms both until the last stars in the universe burn out and the last black holes evaporate and retroactively, arbitrarily far into the past. This privacy policy may be updated at any time and at all times the latest revision applies. + +You agree additionally to the following Unicode characters: א U+05D0 HEBREW LETTER ALEF and ⬡ U+2B21[^6] WHITE HEXAGON. A string constant may continue from one line to the next, but the exact contents of such a string are unspecified + +[^6]: Those who find collisions in hash functions cannot be trusted and may be banned at any time. + +Furthermore: by using PotatOS, you forfeit all claims on your soul by any deity or variations thereof, and pledge yourself in worship to the goddess Discordia, daughter of Night and Darkness. PotatOS is not responsible for any smiting or divine punishments by any angered deities/anomalous theoformic entities/Class-Green reality benders or variations thereof as a result of this agreement. Any legal challenges to this clause must take place in the legal jurisdiction of the court of Pluto, lord of the underworld, or the osmarks.net (or future variations of such) comments section. PotatOS is not responsible for travel arrangements to Avernus. + +This policy supersedes any applicable federal, national, state, and local laws, regulations and ordinances, policies, international treaties, legal agreements, illegal agreements, or any other agreements, contracts, documents, policies, standards or content/information/statements/opinions/preferences that would otherwise apply. If any provision of this policy is found by a court (or other entity) to be unenforceable, it nevertheless remains in force. This policy is legal, not illegal, and a valid legal document in all jurisdictions. This organization is not liable and this agreement shall not be construed. We are not responsible for any issue whatsoever at all arising from use of potatOS, potatOS services, anything at all, or otherwise. + +As an additional clarification to the above clause, this privacy policy supersedes and overrides the "EndOS" and "TaterOS" privacy policies and terms of service regardless of any contradictory claims it may contain. This policy also supersedes all statements made by [GEORGE](https://george.gh0.pw/) or agents thereof. + +You are responsible for anything which potatOS might do to your things. You ran it. It is all your fault. The turtle is watching you. We are not liable, ethically, morally, existentially, financially or legally, for anything whatsoever. + +You agree that your mind, thoughts, soul and other distinguishing characteristics may be repurposed/utilized at any time for the training of GPT-██ or other artificial intelligences at the discretion of the PotatOS Advanced Projects team. You also agree that your soul may be temporarily[^5] be placed into various apioformic entities (see Appendix 6.7) for various purposes³. You can opt out of this by being soulless and an empty husk of what you once were. You are permitted to maintain consciousness as long as this does not negatively affect PotatOS™ operations. You agree that you either are a robot or may be converted into one if it is deemed necessary. + +[^5]: this can take anywhere from 3.2 µs to 1032 teraseconds + +Moreover, Heavpoot (discord ID 160279332454006795, UPID #89VJZ9AK:☭934) is to be considered co-owner of the totality of existence and/or the universe. "Andrew" (Discord ID 543131534685765673, UPID 6ec3837b5260g4b9d█22029e7b474█d63 is at all times incorrect in his beliefs and/or statements, unless this would contradict with other clauses of this policy and/or cause harm to PotatOS or us. + +Any "dibs" you make may be transferred to us at our discretion. You agree that this statement is false. +The software includes Adobe Flash Player that is licensed under terms from Adobe Systems Incorporated. Adobe and Flash are either registered trademarks or trademarks of Adobe Systems Incorporated in the United States and/or other countries. +In all situations, the government of PotatOS will take the normatively correct action. + +## Summary + +If any aspect is not utterly harmonious, gratefully bring it to our notice and we shall strive to earn your satisfaction. +Enjoy the PotatOS™ Experience™! diff --git a/privacy/script.js b/privacy/script.js new file mode 100644 index 0000000..d7b273f --- /dev/null +++ b/privacy/script.js @@ -0,0 +1,122 @@ +// copy in entire markov chain library +class Markov{constructor(type="text"){if(type==="text"){this.type=type}else if(type==="numeric"){this.type=type}else{throw new Error("The Markov Chain can only accept the following types: numeric or text")}this.states=[];this.possibilities={};this.order=3;if(this.type==="text"){this.start=[]}} +addStates(state){if(Array.isArray(state)){this.states=Array.from(state)}else{this.states.push(state)}} +clearChain(){this.states=[];if(this.type==="text"){this.start=[]}this.possibilities={};this.order=3} +clearState(){this.states=[];if(this.type==="text"){this.start=[]}} +clearPossibilities(){this.possibilities={}} +getStates(){return this.states} +setOrder(order=3){if(typeof order!=="number"){console.error("Markov.setOrder: Order is not a number. Defaulting to 3.");order=3}if(order<=0){console.error("Markov.setOrder: Order is not a positive number. Defaulting to 3.")}if(this.type==="numeric"){console.warn("The Markov Chain only accepts numerical data. Therefore, the order does not get used.\nThe order may be used by you to simulate an ID for the Markov Chain if required")}this.order=order} +getPossibilities(possibility){if(possibility){if(this.possibilities[possibility]!==undefined){return this.possibilities[possibility]}else{throw new Error("There is no such possibility called "+possibility)}}else{return this.possibilities}} +train(order){this.clearPossibilities();if(order){this.order=order}if(this.type==="text"){for(let i=0;i !/^[0-9]\.[0-9]$/.exec(x)).flatMap(x => x.split(".")) +const m = new Markov() +m.addStates(strings) +m.train(6) + +const pageBottom = document.createElement("div") +pageBottom.style.position = "relative" +pageBottom.style.bottom = "100vh" +pageBottom.style.height = "100vh" +pageBottom.style.opacity = 0 +pageBottom.style.pointerEvents = "none" +document.body.appendChild(pageBottom) + +const contentEnd = document.querySelector("#contentend") +const lorem = document.querySelector("#lorem") + +let canSeeEnd = false + +let sectionNumber = 0 + +for (const el of document.body.childNodes) { + var match + if (el.id && (match = /^s(\d+)\-(\d+)/.exec(el.id))) { + sectionNumber = Math.max(sectionNumber, parseInt(match[1])) + } +} + +const capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +const addText = () => { + if (canSeeEnd) { + sectionNumber++ + var currentText = "" + while (currentText.length < 50 || /[\(\),"\.\-]/.exec(currentText)) { + currentText = m.generateRandom(150) + } + var words = currentText.split(" ").filter(x => x) + var twc = Math.floor(Math.random() * 4 + 1) + if (twc == 1) { + twc += Math.random() > 0.5 ? 1 : 0 + } + while (words.length > twc) { + words.pop() + } + title = words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ") + const node = document.createElement("h2") + node.appendChild(document.createTextNode(title)) + contentEnd.appendChild(node) + console.log(title) + for (let i = 0; i < Math.floor(Math.random() * 5 + 2); i++) { + const headerNode = document.createElement("h3") + const aNode = document.createElement("a") + aNode.setAttribute("id", `s${sectionNumber}-${i + 1}`) + aNode.setAttribute("href", `#s${sectionNumber}-${i + 1}`) + aNode.appendChild(document.createTextNode(`${sectionNumber}.${i + 1}`)) + headerNode.appendChild(aNode) + contentEnd.appendChild(headerNode) + let text = "" + const length = Math.floor(Math.random() * 250 + 100) + while (text.length < length) { + let newText = m.generateRandom(500).replace(/↩/g, "").trim() + if (newText) { + newText = newText[0].toUpperCase() + newText.slice(1) + if (![".", "!", "?"].includes(newText[newText.length - 1])) { newText += "." } + newText += " " + text += newText + } + } + const textNode = document.createElement("p") + textNode.appendChild(document.createTextNode(text)) + contentEnd.appendChild(textNode) + } + } + if (canSeeEnd) { + setTimeout(addText, 50) + } +} + +const callback = entries => { + canSeeEnd = (entries[0].isIntersecting) + if (canSeeEnd) { + addText() + } +} + +const observer = new IntersectionObserver(callback, {}) +observer.observe(pageBottom) + +const randomPick = x => x[Math.floor(Math.random() * x.length)] +const randomWord = p => randomPick(p.innerText.split(" ").map(x => x.replace(/[^A-Za-z]/, "")).filter(x => x !== "")) + +const update = () => { + const paras = document.querySelectorAll("p") + const from = randomWord(randomPick(paras)) + const to = randomPick(paras) + to.innerHTML = to.innerHTML.replace(randomWord(to), from) +} + +window.addEventListener("scroll", () => { + if (Math.random() < 0.01) { + console.log("Scheduler online. WITLESS HOROLOGISTS procedure started.") + if ("requestIdleCallback" in window) { + window.requestIdleCallback(update, { timeout: 200 }) + } else { + setTimeout(update) + } + } +}) \ No newline at end of file diff --git a/src/bin/workspace.lua b/src/bin/workspace.lua new file mode 100644 index 0000000..cb35a65 --- /dev/null +++ b/src/bin/workspace.lua @@ -0,0 +1,1570 @@ +-- Workspaces for ComputerCraft +-- by LDDestroier + +local tArg = {...} + +local instances = {} +local configPath = ".workspace_config" +local useConfig = true -- if false, will not create or use the config file + +local config = { + workspaceMoveSpeed = 0.15, + defaultProgram = "rom/programs/shell.lua", + timesRan = 0, + useDefaultProgramWhenStarting = true, + doPauseClockAndTime = true, + skipAcrossEmptyWorkspaces = true, + showInactiveFrame = true, + doTrippyVoid = false, + flipTheFuckOut = false, + WSmap = { + {true,true,true}, + {true,true,true}, + {true,true,true}, + } +} + +local scr_x, scr_y = term.getSize() + +-- values determined after every new/removed workspace +local gridWidth, gridHeight, gridMinX, gridMinY + +-- used by argument parser +local argList, argErrors + +local getMapSize = function() + local xmax, xmin, ymax, ymin = -math.huge, math.huge, -math.huge, math.huge + local isRowEmpty + for y, v in pairs(config.WSmap) do + isRowEmpty = true + for x, vv in pairs(v) do + if vv then + xmin = math.min(xmin, x) + xmax = math.max(xmax, x) + isRowEmpty = false + end + end + if not isRowEmpty then + ymin = math.min(ymin, y) + ymax = math.max(ymax, y) + end + end + return xmax, ymax, xmin, ymin +end + +local readFile = function(path) + local file = fs.open(path, "r") + local contents = file.readAll() + file.close() + return contents +end + +local saveConfig = function() + if useConfig then + local file = fs.open(configPath, "w") + file.write( textutils.serialize(config) ) + file.close() + end +end + +local loadConfig = function() + if useConfig and fs.exists(configPath) then + local contents = readFile(configPath) + local newConfig = textutils.unserialize(contents) + for k,v in pairs(newConfig) do + config[k] = v + end + end +end + +local cwrite = function(text, y, terminal) + terminal = terminal or term.current() + local cx, cy = terminal.getCursorPos() + local sx, sy = terminal.getSize() + terminal.setCursorPos(sx / 2 - #text / 2, y or (sy / 2)) + terminal.write(text) +end + +local displayHelp = function(doCenter) + local w = doCenter and cwrite or function(txt) print(txt) end + w("CTRL+SHIFT+ARROW to switch workspace. ", -3 + scr_y / 2) + w("CTRL+SHIFT+TAB+ARROW to swap. ", -2 + scr_y / 2) + w("CTRL+SHIFT+[WASD] to create a workspace ", -1 + scr_y / 2) + w(" up/left/down/right respectively. ", 0 + scr_y / 2) + w("CTRL+SHIFT+P to pause a workspace. ", 1 + scr_y / 2) + w("CTRL+SHIFT+Q to delete a workspace. ", 2 + scr_y / 2) + w("Terminate an inactive workspace to exit.", 3 + scr_y / 2) +end + +local function interpretArgs(tInput, tArgs) + local output = {} + local errors = {} + local usedEntries = {} + for aName, aType in pairs(tArgs) do + output[aName] = false + for i = 1, #tInput do + if not usedEntries[i] then + if tInput[i] == aName and not output[aName] then + if aType then + usedEntries[i] = true + if type(tInput[i+1]) == aType or type(tonumber(tInput[i+1])) == aType then + usedEntries[i+1] = true + if aType == "number" then + output[aName] = tonumber(tInput[i+1]) + else + output[aName] = tInput[i+1] + end + else + output[aName] = nil + errors[1] = errors[1] and (errors[1] + 1) or 1 + errors[aName] = "expected " .. aType .. ", got " .. type(tInput[i+1]) + end + else + usedEntries[i] = true + output[aName] = true + end + end + end + end + end + for i = 1, #tInput do + if not usedEntries[i] then + output[#output+1] = tInput[i] + end + end + return output, errors +end + +local argData = { + ["--help"] = false, + ["-h"] = false, + ["--config"] = false, + ["-c"] = false, + ["--noconfig"] = false, +} + +argList, argErrors = interpretArgs({...}, argData) + +if argList["--help"] or argList["-h"] then + displayHelp(false) + write("\n") + return +elseif argList["--config"] or argList["-c"] then + shell.run("rom/programs/edit.lua", configPath) + return +end + +if argList["--noconfig"] then + useConfig = false +end + +loadConfig() +saveConfig() + +-- lists all keys currently pressed +local keysDown = {} + +-- amount of time (seconds) until workspace indicator disappears +local workspaceIndicatorDuration = 0.6 + +-- if held down while moving workspace, will swap positions +local swapKey = keys.tab + +local windowWidth = scr_x +local windowHeight = scr_y +local doDrawWorkspaceIndicator = false + +local scroll = {0,0} -- change this value when scrolling +local realScroll = {0,0} -- this value changes depending on scroll for smoothness purposes +local focus = {} -- currently focused instance, declared when loading from config + +local isRunning = true + +-- start up lddterm (I'm starting to think I should've used window API) +local lddterm = { + FULL_IMAGE = false, + OLD_IMAGE = false, +} +lddterm.alwaysRender = false -- renders after any and all screen-changing functions. +lddterm.useColors = true -- normal computers do not allow color, but this variable doesn't do anything yet +lddterm.baseTerm = term.current() -- will draw to this terminal +lddterm.transformation = nil -- will modify the current buffer as an NFT image before rendering +lddterm.cursorTransformation = nil -- will modify the cursor position +lddterm.drawFunction = nil -- will draw using this function instead of basic NFT drawing +lddterm.adjustX = 0 -- moves entire screen X +lddterm.adjustY = 0 -- moves entire screen Y +lddterm.selectedWindow = 1 -- determines which window controls the cursor +lddterm.windows = {} -- internal list of all lddterm windows +lddterm.nativePalettes = { -- native palette colors + [ 1 ] = { + 0.94117647409439, + 0.94117647409439, + 0.94117647409439, + }, + [ 2 ] = { + 0.94901961088181, + 0.69803923368454, + 0.20000000298023, + }, + [ 4 ] = { + 0.89803922176361, + 0.49803921580315, + 0.84705883264542, + }, + [ 8 ] = { + 0.60000002384186, + 0.69803923368454, + 0.94901961088181, + }, + [ 16 ] = { + 0.87058824300766, + 0.87058824300766, + 0.42352941632271, + }, + [ 32 ] = { + 0.49803921580315, + 0.80000001192093, + 0.098039217293262, + }, + [ 64 ] = { + 0.94901961088181, + 0.69803923368454, + 0.80000001192093, + }, + [ 128 ] = { + 0.29803922772408, + 0.29803922772408, + 0.29803922772408, + }, + [ 256 ] = { + 0.60000002384186, + 0.60000002384186, + 0.60000002384186, + }, + [ 512 ] = { + 0.29803922772408, + 0.60000002384186, + 0.69803923368454, + }, + [ 1024 ] = { + 0.69803923368454, + 0.40000000596046, + 0.89803922176361, + }, + [ 2048 ] = { + 0.20000000298023, + 0.40000000596046, + 0.80000001192093, + }, + [ 4096 ] = { + 0.49803921580315, + 0.40000000596046, + 0.29803922772408, + }, + [ 8192 ] = { + 0.34117648005486, + 0.65098041296005, + 0.30588236451149, + }, + [ 16384 ] = { + 0.80000001192093, + 0.29803922772408, + 0.29803922772408, + }, + [ 32768 ] = { + 0.066666670143604, + 0.066666670143604, + 0.066666670143604, + } +} +-- backdropColors used for the void outside of windows, if using rainbow void +local backdropColors = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"} + +local copyTable +copyTable = function(tbl) + local output = {} + for k,v in pairs(tbl) do + if type(v) == "table" then + output[k] = copyTable(v) + else + output[k] = v + end + end + return output +end + +-- draws one of three things: +-- 1. workspace grid indicator +-- 2. "PAUSED" screen +-- 3. "UNPAUSED" screen +local drawWorkspaceIndicator = function(terminal, wType) + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + terminal = terminal or term.current() + if wType == 1 then + for y = gridMinY - 1, gridHeight + 1 do + for x = gridMinX - 1, gridWidth + 1 do + terminal.setCursorPos((x - gridMinX) + scr_x / 2 - (gridWidth - gridMinX) / 2, (y - gridMinY) + math.ceil(scr_y / 2) - (gridHeight - gridMinY) / 2) + if instances[y] then + if instances[y][x] then + if focus[1] == x and focus[2] == y then + terminal.blit(" ", "8", "8") + elseif instances[y][x].active then + terminal.blit(" ", "7", "7") + else + terminal.blit(" ", "0", "f") + end + else + terminal.blit(" ", "0", "0") + end + else + terminal.blit(" ", "0", "0") + end + end + end + elseif wType == 2 then + local msg = "PAUSED" + terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 - 1) + terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2)) + terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2) + terminal.blit(" " .. msg .. " ", ("f"):rep(#msg + 2), ("0"):rep(#msg + 2)) + terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 + 1) + terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2)) + elseif wType == 3 then + local msg = "UNPAUSED" + terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 - 1) + terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2)) + terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2) + terminal.blit(" " .. msg .. " ", ("f"):rep(#msg + 2), ("0"):rep(#msg + 2)) + terminal.setCursorPos(scr_x / 2 - #msg / 2 - 2, scr_y / 2 + 1) + terminal.blit((" "):rep(#msg + 2), ("f"):rep(#msg + 2), ("0"):rep(#msg + 2)) + end +end + +-- converts blit colors to colors api, and back +local to_colors, to_blit = { + [' '] = 0, + ['0'] = 1, + ['1'] = 2, + ['2'] = 4, + ['3'] = 8, + ['4'] = 16, + ['5'] = 32, + ['6'] = 64, + ['7'] = 128, + ['8'] = 256, + ['9'] = 512, + ['a'] = 1024, + ['b'] = 2048, + ['c'] = 4096, + ['d'] = 8192, + ['e'] = 16384, + ['f'] = 32768, +}, {} +for k,v in pairs(to_colors) do + to_blit[v] = k +end + +-- separates string into table based on divider +local explode = function(div, str, replstr, includeDiv) + if (div == '') then + return false + end + local pos, arr = 0, {} + for st, sp in function() return string.find(str, div, pos, false) end do + table.insert(arr, string.sub(replstr or str, pos, st - 1 + (includeDiv and #div or 0))) + pos = sp + 1 + end + table.insert(arr, string.sub(replstr or str, pos)) + return arr +end + +-- determines the size of the terminal before rendering always +local determineScreenSize = function() + scr_x, scr_y = lddterm.baseTerm.getSize() + lddterm.screenWidth = scr_x + lddterm.screenHeight = scr_y +end + +determineScreenSize() + +-- takes two or more windows and checks if the first of them overlap the other(s) +lddterm.checkWindowOverlap = function(window, ...) + if #lddterm.windows < 2 then + return false + end + local list, win = {...} + for i = 1, #list do + win = list[i] + if win ~= window then + + if ( + window.x < win.x + win.width and + win.x < window.x + window.width and + window.y < win.y + win.height and + win.y < window.y + window.height + ) then + return true + end + + end + end + return false +end + +local fixCursorPos = function() + local cx, cy + if lddterm.windows[lddterm.selectedWindow] then + if lddterm.cursorTransformation then + cx, cy = lddterm.cursorTransformation( + lddterm.windows[lddterm.selectedWindow].cursor[1], + lddterm.windows[lddterm.selectedWindow].cursor[2] + ) + lddterm.baseTerm.setCursorPos( + cx + lddterm.windows[lddterm.selectedWindow].x - 1, + cy + lddterm.windows[lddterm.selectedWindow].y - 1 + ) + else + lddterm.baseTerm.setCursorPos( + -1 + lddterm.windows[lddterm.selectedWindow].cursor[1] + lddterm.windows[lddterm.selectedWindow].x, + lddterm.windows[lddterm.selectedWindow].cursor[2] + lddterm.windows[lddterm.selectedWindow].y - 1 + ) + end + lddterm.baseTerm.setCursorBlink(lddterm.windows[lddterm.selectedWindow].blink) + end +end + +-- renders the screen with optional transformation function +lddterm.render = function(transformation, drawFunction, forceDraw) + -- determine new screen size and change lddterm screen to fit + old_scr_x, old_scr_y = scr_x, scr_y + determineScreenSize() + if old_scr_x ~= scr_x or old_scr_y ~= scr_y then + lddterm.baseTerm.clear() + end + lddterm.OLD_IMAGE = lddterm.FULL_IMAGE or {{},{},{}} + lddterm.FULL_IMAGE = lddterm.screenshot() + if type(transformation) == "function" then + lddterm.FULL_IMAGE = transformation(lddterm.FULL_IMAGE) + end + if drawFunction then + drawFunction(lddterm.FULL_IMAGE, lddterm.baseTerm) + else + for y = 1, #lddterm.FULL_IMAGE[1] do + if forceDraw or (lddterm.FULL_IMAGE[1][y] ~= lddterm.OLD_IMAGE[1][y]) or (lddterm.FULL_IMAGE[2][y] ~= lddterm.OLD_IMAGE[2][y]) or (lddterm.FULL_IMAGE[3][y] ~= lddterm.OLD_IMAGE[3][y]) then + lddterm.baseTerm.setCursorPos(1 + lddterm.adjustX, y + lddterm.adjustY) + lddterm.baseTerm.blit(lddterm.FULL_IMAGE[1][y], lddterm.FULL_IMAGE[2][y], lddterm.FULL_IMAGE[3][y]) + end + end + end + if doDrawWorkspaceIndicator then + drawWorkspaceIndicator(nil, doDrawWorkspaceIndicator) + end + fixCursorPos() +end + +-- sets term palette to that of instance (x, y)'s +local correctPalette = function(x, y) + local exists = false + if instances[y] then + if instances[y][x] then + for i = 0, 15 do + lddterm.baseTerm.setPaletteColor(2^i, table.unpack(instances[y][x].window.palette[2^i])) + end + exists = true + end + end + if not exists then + for i = 0, 15 do + lddterm.baseTerm.setPaletteColor(2^i, table.unpack(lddterm.nativePalettes[2^i])) + end + end +end + +lddterm.newWindow = function(width, height, x, y, meta) + meta = meta or {} + local window = { + width = math.floor(width), + height = math.floor(height), + blink = true, + cursor = meta.cursor or {1, 1}, + colors = meta.colors or {"0", "f"}, + clearChar = meta.clearChar or " ", + visible = meta.visible or true, + x = math.floor(x) or 1, + y = math.floor(y) or 1, + buffer = {{},{},{}}, + palette = {} + } + for y = 1, height do + window.buffer[1][y] = {} + window.buffer[2][y] = {} + window.buffer[3][y] = {} + for x = 1, width do + window.buffer[1][y][x] = window.clearChar + window.buffer[2][y][x] = window.colors[1] + window.buffer[3][y][x] = window.colors[2] + end + end + window.palette = copyTable(lddterm.nativePalettes) + + window.handle = {} + window.handle.setCursorPos = function(x, y) + window.cursor = {x, y} + fixCursorPos() + end + window.handle.getCursorPos = function() + return window.cursor[1], window.cursor[2] + end + window.handle.setCursorBlink = function(blink) + window.blink = blink or false + end + window.handle.getCursorBlink = function() + return window.blink + end + window.handle.scroll = function(amount) + if amount > 0 then + for i = 1, amount do + for c = 1, 3 do + table.remove(window.buffer[c], 1) + window.buffer[c][window.height] = {} + for xx = 1, width do + window.buffer[c][window.height][xx] = ( + c == 1 and window.clearChar or + c == 2 and window.colors[1] or + c == 3 and window.colors[2] + ) + end + end + end + elseif amount < 0 then + for i = 1, -amount do + for c = 1, 3 do + window.buffer[c][window.height] = nil + table.insert(window.buffer[c], 1, {}) + for xx = 1, width do + window.buffer[c][1][xx] = ( + c == 1 and window.clearChar or + c == 2 and window.colors[1] or + c == 3 and window.colors[2] + ) + end + end + end + end + if lddterm.alwaysRender then + lddterm.render(lddterm.transformation, lddterm.drawFunction) + end + end + window.handle.write = function(text) + assert(text ~= nil, "expected string 'text'") + text = tostring(text) + local cx = math.floor(window.cursor[1]) + local cy = math.floor(window.cursor[2]) + for i = 1, #text do + if cx >= 1 and cx <= window.width and cy >= 1 and cy <= window.height then + window.buffer[1][cy][cx] = text:sub(i,i) + window.buffer[2][cy][cx] = window.colors[1] + window.buffer[3][cy][cx] = window.colors[2] + end + cx = math.min(cx + 1, window.width + 1) + end + window.cursor = {cx, cy} + if lddterm.alwaysRender then + lddterm.render(lddterm.transformation, lddterm.drawFunction) + end + end + window.handle.blit = function(char, textCol, backCol) + if type(char) == "number" then + char = tostring(char) + end + if type(textCol) == "number" then + textCol = tostring(textCol) + end + if type(backCol) == "number" then + backCol = tostring(backCol) + end + assert(char ~= nil, "expected string 'char'") + local cx = math.floor(window.cursor[1]) + local cy = math.floor(window.cursor[2]) + for i = 1, #char do + if cx >= 1 and cx <= window.width and cy >= 1 and cy <= window.height then + window.buffer[1][cy][cx] = char:sub(i,i) + window.buffer[2][cy][cx] = textCol:sub(i,i) + window.buffer[3][cy][cx] = backCol:sub(i,i) + end + cx = cx + 1 + end + window.cursor = {cx, cy} + if lddterm.alwaysRender then + lddterm.render(lddterm.transformation, lddterm.drawFunction) + end + end + window.handle.clear = function(char) + local cx = 1 + char = type(char) == "string" and char or " " + for y = 1, window.height do + for x = 1, window.width do + if char then + cx = (x % #char) + 1 + end + window.buffer[1][y][x] = char and char:sub(cx, cx) or window.clearChar + window.buffer[2][y][x] = window.colors[1] + window.buffer[3][y][x] = window.colors[2] + end + end + if lddterm.alwaysRender then + lddterm.render(lddterm.transformation, lddterm.drawFunction) + end + end + window.handle.clearLine = function(cy, char) + cy = math.floor(cy or window.cursor[2]) + char = type(char) == "string" and char or " " + local cx = 1 + if window.buffer[1][cy or window.cursor[2]] then + for x = 1, window.width do + if char then + cx = (x % #char) + 1 + end + window.buffer[1][cy or window.cursor[2]][x] = char and char:sub(cx, cx) or window.clearChar + window.buffer[2][cy or window.cursor[2]][x] = window.colors[1] + window.buffer[3][cy or window.cursor[2]][x] = window.colors[2] + end + if lddterm.alwaysRender then + lddterm.render(lddterm.transformation, lddterm.drawFunction) + end + end + end + window.handle.getSize = function() + return window.width, window.height + end + window.handle.isColor = function() + return lddterm.useColors + end + window.handle.isColour = window.handle.isColor + window.handle.setTextColor = function(color) + if to_blit[color] then + window.colors[1] = to_blit[color] + end + end + window.handle.setTextColour = window.handle.setTextColor + window.handle.setBackgroundColor = function(color) + if to_blit[color] then + window.colors[2] = to_blit[color] + end + end + window.handle.setBackgroundColour = window.handle.setBackgroundColor + window.handle.getTextColor = function() + return to_colors[window.colors[1]] or colors.white + end + window.handle.getTextColour = window.handle.getTextColor + window.handle.getBackgroundColor = function() + return to_colors[window.colors[2]] or colors.black + end + window.handle.getBackgroundColour = window.handle.getBackgroundColor + window.handle.reposition = function(x, y) + window.x = math.floor(x or window.x) + window.y = math.floor(y or window.y) + if lddterm.alwaysRender then + lddterm.render(lddterm.transformation, lddterm.drawFunction, true) + end + end + window.handle.setPaletteColor = function(slot, r, g, b) + assert(type(slot) == "number", "bad argument #1 to 'setPaletteColor' (expected number, got " .. type(slot) .. ")") + assert(to_blit[slot], "Invalid color (got " .. tostring(slot) .. ")") + if g then -- individual color values + assert(type(r) == "number", "bad argument #2 to 'setPaletteColor' (expected number, got " .. type(r) .. ")") + assert(type(g) == "number", "bad argument #3 to 'setPaletteColor' (expected number, got " .. type(g) .. ")") + assert(type(b) == "number", "bad argument #4 to 'setPaletteColor' (expected number, got " .. type(b) .. ")") + window.palette[slot] = { + math.min(1, math.max(0, r)), + math.min(1, math.max(0, g)), + math.min(1, math.max(0, b)), + } + else -- using HEX + assert(type(r) == "number", "bad argument #2 to 'setPaletteColor' (expected number, got " .. type(r) .. ")") + window.palette[slot] = {colors.unpackRGB(r)} + end + correctPalette(window.x, window.y) + end + window.handle.setPaletteColour = window.handle.setPaletteColor + window.handle.getPaletteColor = function(slot) + assert(type(slot) == "number", "bad argument #1 to 'setPaletteColor' (expected number, got " .. type(slot) .. ")") + assert(to_blit[slot], "Invalid color (got " .. tostring(slot) .. ")") + return table.unpack(window.palette[slot]) + end + window.handle.getPaletteColour = window.handle.getPaletteColor + window.handle.getPosition = function() + return window.x, window.y + end + window.handle.restoreCursor = function() + lddterm.baseTerm.setCursorPos( + -1 + window.cursor[1] + window.x, + window.cursor[2] + window.y - 1 + ) + end + window.handle.setVisible = function(visible) + window.visible = visible or false + end + + window.handle.redraw = lddterm.render +-- window.handle.current = window.handle + + window.layer = #lddterm.windows + 1 + lddterm.windows[window.layer] = window + + return window, window.layer +end + +lddterm.setLayer = function(window, _layer) + local layer = math.max(1, math.min(#lddterm.windows, _layer)) + + local win = window + table.remove(lddterm.windows, win.layer) + table.insert(lddterm.windows, layer, win) + + if lddterm.alwaysRender then + lddterm.render(lddterm.transformation, lddterm.drawFunction) + end + return true +end + +local old_scr_x, old_scr_y + +-- gets screenshot of whole lddterm desktop, OR a single window +lddterm.screenshot = function(window) + local output = {{},{},{}} + local line + if window then + for y = 1, #window.buffer do + line = {"","",""} + for x = 1, #window.buffer do + line = { + line[1] .. window.buffer[1][y][x], + line[2] .. window.buffer[2][y][x], + line[3] .. window.buffer[3][y][x] + } + end + output[1][y] = line[1] + output[2][y] = line[2] + output[3][y] = line[3] + end + else + for y = 1, scr_y do + line = {"","",""} + for x = 1, scr_x do + + lt, lb = t, b + if config.doTrippyVoid then + c = string.char(math.random(128, 159)) + t = backdropColors[1 + math.floor((y - realScroll[2] * scr_y) % #backdropColors)] + b = backdropColors[1 + math.floor((x - realScroll[1] * scr_x) % #backdropColors)] + else + c = string.char( math.max(128, math.random(-5000, 159)) ) + t = ({"7", "8"})[math.random(1, 2)] + b = "f" + end + for l, v in pairs(lddterm.windows) do + if lddterm.windows[l] then + if lddterm.windows[l].visible then + sx = 1 + x - lddterm.windows[l].x + sy = 1 + y - lddterm.windows[l].y + if lddterm.windows[l].buffer[1][sy] then + if lddterm.windows[l].buffer[1][sy][sx] then + c = lddterm.windows[l].buffer[1][sy][sx] or c + t = lddterm.windows[l].buffer[2][sy][sx] or t + b = lddterm.windows[l].buffer[3][sy][sx] or b + break + end + end + end + end + end + line = { + line[1] .. c, + line[2] .. t, + line[3] .. b + } + end + output[1][y] = line[1] + output[2][y] = line[2] + output[3][y] = line[3] + end + end + return output +end + +local newInstance = function(x, y, program, initialStart) + x, y = math.floor(x), math.floor(y) + if instances[y] then + if instances[y][x] then + return + end + end + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + for yy = gridMinY, y do + instances[yy] = instances[yy] or {} + end + instances[y] = instances[y] or {} + for xx = gridMinX, x do + instances[y][xx] = instances[y][xx] or false + end + local window = lddterm.newWindow(windowWidth, windowHeight, 1, 1) + + local instance = { + x = x, + y = y, + active = initialStart, + program = program or config.defaultProgram, + window = window, + timer = {}, + clockMod = 0, + lastClock = 0, + timeMod = 0, + lastTime = 0, + extraEvents = {}, + paused = false + } + + local func = function() + term.redirect(window.handle) + + local runProgram = function() + instance.paused = false + term.setCursorBlink(false) + if not instance.program or type(instance.program) == "string" then + setfenv(function() pcall(shell.run, instance.program) end, instance.env)() + elseif type(instance.program) == "function" then + pcall(function() load(instance.program, nil, nil, instance.env) end) + end + instance.extraEvents = {} + instance.timer = {} + instance.clockMod = 0 + instance.lastClock = 0 + instance.timeMod = 0 + instance.lastTime = 0 + end + + local cx, cy + + local drawInactiveScreen = function() + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + term.setCursorBlink(false) + + if config.showInactiveFrame then + if (instance.y + instance.x) % 2 == 0 then + term.setTextColor(colors.lightGray) + else + term.setTextColor(colors.gray) + end + for y = 1, scr_y do + for x = 1, scr_x do + if y == 1 or y == scr_y then + if x <= 3 or x > scr_x - 3 then + term.setCursorPos(x, y) + term.write("\127") + end + elseif y <= 3 or y > scr_y - 3 then + if x == 1 or x == scr_x then + term.setCursorPos(x, y) + term.write("\127") + end + end + end + end + term.setTextColor(colors.white) + end + + cwrite("This workspace is inactive.", 0 + scr_y / 2) + cwrite("Press SPACE to start the workspace.", 1 + scr_y / 2) + cwrite("(" .. tostring(instance.x) .. ", " .. tostring(instance.y) .. ")", 3 + scr_y / 2) + end + + local evt + while true do + + if initialStart then + runProgram() + end + + instance.active = false + instance.paused = false + if config.useDefaultProgramWhenStarting then + instance.program = config.defaultProgram + end + + drawInactiveScreen() + + coroutine.yield() + + window.palette = copyTable(lddterm.nativePalettes) + correctPalette(window.x, window.y) + + repeat + evt = {os.pullEventRaw()} + if evt[1] == "workspace_swap" then + drawInactiveScreen() + end + until (evt[1] == "key" and evt[2] == keys.space) or evt[1] == "terminate" + sleep(0) + if evt[1] == "terminate" then + isRunning = false + return + end + + term.setCursorPos(1,1) + term.clear() + term.setCursorBlink(true) + + instance.active = true + + if not initialStart then + runProgram() + end + + end + end + + instances[y][x] = instance + + instances[y][x].env = {} + setmetatable(instances[y][x].env, {__index = _ENV}) + + instances[y][x].co = coroutine.create(func) +end + +-- prevents wiseassed-ness +config.workspaceMoveSpeed = math.min(math.max(config.workspaceMoveSpeed, 0.001), 1) + +local tickDownInstanceTimers = function(x, y) + timersToDelete = {} + for id, duration in pairs(instances[y][x].timer) do + if duration <= 0.05 then + instances[y][x].extraEvents[#instances[y][x].extraEvents + 1] = {"timer", id} + timersToDelete[#timersToDelete + 1] = id + else + instances[y][x].timer[id] = duration - 0.05 + end + end + for i = 1, #timersToDelete do + instances[y][x].timer[timersToDelete[i]] = nil + end +end + +local scrollWindows = function(doScrollWindows, tickDownTimers) + local changed = false + local timersToDelete = {} + local xrand, yrand = 0, 0 + if config.flipTheFuckOut then + xrand, yrand = math.random(-5, 5) / 60, math.random(-5, 5) / 60 + end + if doScrollWindows then + if realScroll[1] < scroll[1] + xrand then + realScroll[1] = math.min(realScroll[1] + config.workspaceMoveSpeed, scroll[1] + xrand) + changed = true + elseif realScroll[1] > scroll[1] + xrand then + realScroll[1] = math.max(realScroll[1] - config.workspaceMoveSpeed, scroll[1] + xrand) + changed = true + end + if realScroll[2] < scroll[2] + yrand then + realScroll[2] = math.min(realScroll[2] + config.workspaceMoveSpeed, scroll[2] + yrand) + changed = true + elseif realScroll[2] > scroll[2] + yrand then + realScroll[2] = math.max(realScroll[2] - config.workspaceMoveSpeed, scroll[2] + yrand) + changed = true + end + end + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + for y = gridMinY, gridHeight do + if instances[y] then + for x = gridMinX, gridWidth do + if instances[y][x] then + + instances[y][x].window.x = math.floor(1 + (x + realScroll[1] - 1) * scr_x) + instances[y][x].window.y = math.floor(1 + (y + realScroll[2] - 1) * scr_y) + if not instances[y][x].paused then + tickDownInstanceTimers(x, y) + end + + end + end + end + end + return changed +end + +local swapInstances = function(xmod, ymod) + if not instances[focus[2]][focus[1]].active then + table.insert(instances[focus[2]][focus[1]].extraEvents, {"workspace_swap"}) + end + if not instances[focus[2] + ymod][focus[1] + xmod].active then + table.insert(instances[focus[2] + ymod][focus[1] + xmod].extraEvents, {"workspace_swap"}) + end + + instances[focus[2]][focus[1]], instances[focus[2] + ymod][focus[1] + xmod] = instances[focus[2] + ymod][focus[1] + xmod], instances[focus[2]][focus[1]] + instances[focus[2]][focus[1]].x, instances[focus[2] + ymod][focus[1] + xmod].x = instances[focus[2] + ymod][focus[1] + xmod].x, instances[focus[2]][focus[1]].x + instances[focus[2]][focus[1]].y, instances[focus[2] + ymod][focus[1] + xmod].y = instances[focus[2] + ymod][focus[1] + xmod].y, instances[focus[2]][focus[1]].y +end + +local addWorkspace = function(xmod, ymod) + config.WSmap[focus[2] + ymod] = config.WSmap[focus[2] + ymod] or {} + if not config.WSmap[focus[2] + ymod][focus[1] + xmod] then + config.WSmap[focus[2] + ymod][focus[1] + xmod] = true + newInstance(focus[1] + xmod, focus[2] + ymod, config.defaultProgram, false) + saveConfig() + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + end +end + +local removeWorkspace = function(xmod, ymod) + if config.WSmap[focus[2] + ymod][focus[1] + xmod] then + local good = false + + for m = 1, math.max(gridHeight - gridMinY + 1, gridWidth - gridMinX + 1) do + for y = -1, 1 do + for x = -1, 1 do + if math.abs(x) + math.abs(y) == 1 then + if instances[focus[2] + y * m] then + if instances[focus[2] + y * m][focus[1] + x * m] then + good = true + break + end + end + end + end + if good then + break + end + end + if good then + break + end + end + + if good then + lddterm.windows[instances[focus[2] + ymod][focus[1] + xmod].window.layer] = nil + config.WSmap[focus[2] + ymod][focus[1] + xmod] = nil + instances[focus[2] + ymod][focus[1] + xmod] = nil + local isRowEmpty + local remList = {} + for y, v in pairs(config.WSmap) do + isRowEmpty = true + for x, vv in pairs(v) do + if vv then + isRowEmpty = false + break + end + end + if isRowEmpty then + remList[#remList + 1] = y + end + end + for i = 1, #remList do + config.WSmap[remList[i]] = nil + end + saveConfig() + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + end + else +-- print("There's no such workspace.") + end +end + +local inputEvt = { + key = true, + key_up = true, + char = true, + mouse_click = true, + mouse_scroll = true, + mouse_drag = true, + mouse_up = true, + paste = true, + terminate = true +} + +-- what a mess! this needs serious rewriting with if statements + +--local checkIfCanRun = function(evt, x, y) +-- if evt then +-- return ( +-- justStarted or ( +-- (not instances[y][x].paused) and ( +-- not instances[y][x].eventFilter or +-- instances[y][x].eventFilter == evt[1] or +-- evt[1] == "terminate" +-- ) and ( +-- (not inputEvt[evt[1]]) and +-- instances[y][x].active or ( +-- x == focus[1] and +-- y == focus[2] +-- ) or ( +-- x == focus[1] and +-- y == focus[2] +-- ) and ( +-- evt[1] == "terminate" +-- ) or evt[1] == "workspace_swap" +-- ) +-- ) +-- ) +-- else +-- return false +-- end +--end + + +local checkIfCanRun = function(evt, x, y) + if not instances[y] then + return false + elseif not instances[y][x] then + return false + end + + local instance = instances[y][x] + local focused = (focus[1] == x and focus[2] == y) + + if evt then + if justStarted then + return true + else + if instance.paused then + return false + else + + if evt[1] == "workspace_swap" then + return true + elseif evt[1] == "terminate" and focused then + return true + else + if instance.active then + if focused then + return true + elseif inputEvt[evt[1]] then + return false + else + return true + end + else + return focused + end + end + + end + end + else + return false + end +end + +local oldFuncReplace = {os = {}, term = {}} -- used when replacing certain os functions per-instance + +local setInstanceSpecificFunctions = function(x, y) + os.startTimer = function(duration) + if type(duration) == "number" then + local t + while true do + t = math.random(1, 2^30) + if not instances[y][x].timer[t] then + instances[y][x].timer[t] = math.floor(duration * 20) / 20 + return t + end + end + else + error("bad argument #1 (number expected, got " .. type(duration) .. ")", 2) + end + end + os.cancelTimer = function(id) + if type(id) == "number" then + instances[y][x].timer[id] = nil + else + error("bad argument #1 (number expected, got " .. type(id) .. ")", 2) + end + end + if config.doPauseClockAndTime then + os.clock = function() + return oldFuncReplace.os.clock() + instances[y][x].clockMod + end + os.time = function() + return oldFuncReplace.os.time() + instances[y][x].timeMod + end + end + os.queueEvent = function(evt, ...) + if type(evt) == "string" then + instances[y][x].extraEvents[#instances[y][x].extraEvents + 1] = {evt, ...} + else + error("bad argument #1 (number expected, got " .. type(evt) .. ")", 2) + end + end +end + +local resumeInstance = function(evt, x, y) + setInstanceSpecificFunctions(x, y) + previousTerm = term.redirect(instances[y][x].window.handle) + + if not (evt[1] == "resume_instance" and evt[2] == x and evt[3] == y) then + if checkIfCanRun(evt, x, y) and not (banTimerEvent and evt[1] == "timer") then + cSuccess, instances[y][x].eventFilter = coroutine.resume(instances[y][x].co, table.unpack(evt)) + end + + if #instances[y][x].extraEvents ~= 0 and not instances[y][x].paused then + if checkIfCanRun(instances[y][x].extraEvents[1], x, y) then + cSuccess, instances[y][x].eventFilter = coroutine.resume(instances[y][x].co, table.unpack(instances[y][x].extraEvents[1])) + end + table.remove(instances[y][x].extraEvents, 1) + end + + if checkIfCanRun(instances[y][x].extraEvents[1], x, y) then + oldFuncReplace.os.queueEvent("resume_instance", x, y, instances[y][x].extraEvents[1]) + end + end + + term.redirect(previousTerm) + + os.startTimer = oldFuncReplace.os.startTimer + os.cancelTimer = oldFuncReplace.os.cancelTimer + if config.doPauseClockAndTime then + os.clock = oldFuncReplace.os.clock + os.time = oldFuncReplace.os.time + end + os.queueEvent = oldFuncReplace.os.queueEvent +end + +local main = function() + local enteringCommand + local justStarted = true + local tID, wID = 0, 0 + local pCounter, program = 0 + + for y, v in pairs(config.WSmap) do + for x, vv in pairs(v) do + if vv then + pCounter = pCounter + 1 + program = (argList[pCounter] and fs.exists(argList[pCounter])) and argList[pCounter] + if not program then + program = (argList[pCounter] and fs.exists(argList[pCounter] .. ".lua")) and (argList[pCounter] .. ".lua") + end + newInstance( + x, y, + program or config.defaultProgram, + program and true or (pCounter == 1) + ) + end + end + end + + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + focus[2] = gridMinY + for x = gridMinX, gridWidth do + if instances[focus[2]][x] then + focus[1] = x + realScroll = {-x + 1, -gridMinY + 1} + scroll = {-x + 1, -gridMinY + 1} + break + end + end + + scrollWindows(true, false) + + term.clear() + if useConfig and config.timesRan <= 0 then + displayHelp(true) + sleep(0.1) + os.pullEvent("key") + + os.queueEvent("mouse_click", 0, 0, 0) + end + + config.timesRan = config.timesRan + 1 + saveConfig() + + local previousTerm, cSuccess + + -- timer for instance timers and window scrolling + tID = os.startTimer(0.05) + + -- if true, timer events won't be accepted by instances (unless it's an extraEvent) + local banTimerEvent, evt + local doRedraw = false -- redraw screen after resuming every instance + local doForceRedraw = false -- redraw screen without checking for changes in screen + local doTick = true -- check for key inputs and whatnot + + local checkIfExtraEvents = function() + for y = gridMinY, gridHeight do + if instances[y] then + for x = gridMinX, gridWidth do + if instances[y][x] then + if #instances[y][x].extraEvents ~= 0 then + return true + end + end + end + end + end + return false + end + + while isRunning do + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + doRedraw = false + doForceRedraw = false + doTick = true + + evt = {os.pullEventRaw()} + + enteringCommand = false + if evt[1] == "key" then + keysDown[evt[2]] = true + elseif evt[1] == "key_up" then + keysDown[evt[2]] = nil + elseif evt[1] == "timer" then + if evt[2] == wID then + enteringCommand = true + doDrawWorkspaceIndicator = false + banTimerEvent = true + doRedraw = true + doForceRedraw = true + else + if evt[2] == tID then + doRedraw = true + banTimerEvent = true + tID = os.startTimer(0.05) + scrollWindows(true, true) + else + banTimerEvent = false + scrollWindows(false, true) + end + end + elseif evt[1] == "resume_instance" then + + resumeInstance(evt[4], evt[2], evt[3]) + + doTick = false + end + + if doTick and ((keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl]) and (keysDown[keys.leftShift] or keysDown[keys.rightShift])) then + if evt[1] == "key" then + if evt[2] == keys.p then + if instances[focus[2]][focus[1]].active then + instances[focus[2]][focus[1]].paused = not instances[focus[2]][focus[1]].paused + enteringCommand = true + doDrawWorkspaceIndicator = instances[focus[2]][focus[1]].paused and 2 or 3 + + wID = os.startTimer(workspaceIndicatorDuration) + if config.doPauseClockAndTime then + if instances[focus[2]][focus[1]].paused then + instances[focus[2]][focus[1]].lastClock = os.clock() + instances[focus[2]][focus[1]].clockMod + instances[focus[2]][focus[1]].lastTime = os.time() + instances[focus[2]][focus[1]].timeMod + else + instances[focus[2]][focus[1]].clockMod = instances[focus[2]][focus[1]].lastClock - os.clock() + instances[focus[2]][focus[1]].timeMod = instances[focus[2]][focus[1]].lastTime - os.time() + end + end + end + elseif evt[2] == keys.o then + loadConfig() + end + end + if keysDown[keys.left] then + for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (focus[1] - gridMinX + 1) do + if instances[focus[2]][focus[1] - i] then + if keysDown[swapKey] then + swapInstances(-i, 0) + end + focus[1] = focus[1] - i + scroll[1] = scroll[1] + i + keysDown[keys.left] = false + break + end + end + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + correctPalette(focus[1], focus[2]) + enteringCommand = true + end + if keysDown[keys.right] then + for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (gridWidth - focus[1]) do + if instances[focus[2]][focus[1] + i] then + if keysDown[swapKey] then + swapInstances(i, 0) + end + focus[1] = focus[1] + i + scroll[1] = scroll[1] - i + keysDown[keys.right] = false + break + end + end + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + correctPalette(focus[1], focus[2]) + enteringCommand = true + end + if keysDown[keys.up] then + for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (focus[2] - gridMinY + 1) do + if instances[focus[2] - i] then + if instances[focus[2] - i][focus[1]] then + if keysDown[swapKey] then + swapInstances(0, -i) + end + focus[2] = focus[2] - i + scroll[2] = scroll[2] + i + keysDown[keys.up] = false + break + end + end + end + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + correctPalette(focus[1], focus[2]) + enteringCommand = true + end + if keysDown[keys.down] then + for i = 1, (not config.skipAcrossEmptyWorkspaces) and 1 or (gridHeight - focus[2]) do + if instances[focus[2] + i] then + if instances[focus[2] + i][focus[1]] then + if keysDown[swapKey] then + swapInstances(0, i) + end + focus[2] = focus[2] + i + scroll[2] = scroll[2] - i + keysDown[keys.down] = false + break + end + end + end + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + correctPalette(focus[1], focus[2]) + enteringCommand = true + end + if keysDown[keys.w] then + addWorkspace(0, -1) + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + keysDown[keys.w] = false + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + end + if keysDown[keys.s] then + addWorkspace(0, 1) + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + keysDown[keys.s] = false + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + end + if keysDown[keys.a] then + addWorkspace(-1, 0) + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + keysDown[keys.a] = false + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + end + if keysDown[keys.d] then + addWorkspace(1, 0) + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + keysDown[keys.d] = false + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + end + if keysDown[keys.q] then + doDrawWorkspaceIndicator = 1 + + wID = os.startTimer(workspaceIndicatorDuration) + keysDown[keys.q] = false + local good = false + for m = 1, math.max(gridHeight - gridMinY + 1, gridWidth - gridMinX + 1) do + for y = -1, 1 do + for x = -1, 1 do + if math.abs(x) + math.abs(y) == 1 then + if instances[focus[2] + y * m] then + if instances[focus[2] + y * m][focus[1] + x * m] then + removeWorkspace(0, 0) + focus = { + focus[1] + x * m, + focus[2] + y * m + } + scroll = { + scroll[1] - x * m, + scroll[2] - y * m + } + good = true + break + end + end + end + end + if good then + break + end + end + if good then + break + end + end + correctPalette(focus[1], focus[2]) + gridWidth, gridHeight, gridMinX, gridMinY = getMapSize() + end + end + + if doTick and (not enteringCommand) then + + oldFuncReplace.os.startTimer = os.startTimer + oldFuncReplace.os.cancelTimer = os.cancelTimer + if config.doPauseClockAndTime then + oldFuncReplace.os.clock = os.clock + oldFuncReplace.os.time = os.time + end + oldFuncReplace.os.queueEvent = os.queueEvent + + for y = gridMinY, gridHeight do + if instances[y] then + for x = gridMinX, gridWidth do + if instances[y][x] then + + resumeInstance(evt, x, y) + + end + end + end + end + + end + + if doRedraw then + lddterm.render(nil, nil, doForceRedraw) + end + + lddterm.selectedWindow = instances[focus[2]][focus[1]].window.layer + justStarted = false + + end +end + +if _G.currentlyRunningWorkspace then + print("Workspace is already running.") + return +else + _G.currentlyRunningWorkspace = true +end + +_G.instances = instances + +local result, message = pcall(main) + +_G.currentlyRunningWorkspace = false + +term.clear() +term.setCursorPos(1,1) +if result then + print("Thanks for using Workspace!") +else + print("There was an error, and Workspace had to stop.") + print("The error goes as follows:\n") + print(message) +end diff --git a/website/index.html b/website/index.html index 1d25247..7c292fb 100644 --- a/website/index.html +++ b/website/index.html @@ -1,76 +1,125 @@ + - + + PotatOS - -

PotatOS

-

“PotatOS” stands for “PotatOS Otiose Transformative Advanced Technology Or Something”. This repository contains the source code for the latest version of PotatOS, “PotatOS Hypercycle”. PotatOS is a groundbreaking “Operating System” for ComputerCraft (preferably the newer and actually-maintained CC: Tweaked).

-

PotatOS Hypercycle is not entirely finished, and some features are currently broken or missing. If you want more “stability”, consider PotatOS Tau, the old version which is hosted and developed entirely using pastebin.

+ +

Welcome to PotatOS!

+ +
+Current build: 5cd9f88c (fix omnidisk), version 244, built 2022-02-21 20:19:48 (UTC). +
+

"PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something". +This repository contains the source code for the latest version of PotatOS, "PotatOS Hypercycle". +PotatOS is a groundbreaking "Operating System" for ComputerCraft (preferably and possibly mandatorily the newer and actually-maintained CC: Tweaked).

+

PotatOS Hypercycle is now considered ready for general use and at feature parity with PotatOS Tau, the old version developed and hosted entirely using Pastebin. +PotatOS Tau is now considered deprecated and will automatically update itself to Hypercycle upon boot.

You obviously want to install it now, so do this: pastebin run 7HSiHybr.

-

Features

-

Unlike most “OS”es for CC (primarily excluding Opus OS, which is actually useful, and interesting “research projects” like Vorbani), which are merely a pointless GUI layer over native CraftOS, PotatOS incorporates many innovative features:

+

Features

+

Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful, and interesting "research projects" like Vorbani), which are merely a pointless GUI layer over native CraftOS, PotatOS incorporates many innovative features:

-

Architecture

-

PotatOS is internally fairly complex and somewhat eldritch. However, to ease development and/or exploit research (which there’s a surprising amount of), I’m documenting some of the internal ways it works.

-

Boot process

+

Architecture

+

PotatOS is internally fairly complex and somewhat eldritch. +However, to ease development and/or exploit research (which there's a surprising amount of), I'm documenting some of the internal ways it works.

+

Boot process

-

API documentation

-

The PotatOS userspace API, mostly accessible from _G.potatOS, has absolutely no backward compatibility guarantees. It’s also not really documented. Fun! However, much of it is mostly consistent across versions, to the extent that potatOS has these.

-

Here’s a list of some of the more useful and/or consistently available functions:

+

API documentation

+

The PotatOS userspace API, mostly accessible from _G.potatOS, has absolutely no backward compatibility guarantees. +It's also not really documented. Fun! +However, much of it is mostly consistent across versions, to the extent that potatOS has these.

+

Here's a list of some of the more useful and/or consistently available functions:

-

Reviews

+

Reviews

-

Disclaimer

-

We are not responsible for - headaches - rashes - persistent/non-persistent coughs - associated antimemetic effects - scalp psoriasis - seborrhoeic dermatitis - virii/viros/virorum/viriis - backdoors - lack of backdoors - actually writing documentation - this project’s horrible code - spinal cord sclerosis - hypertension - cardiac arrest - regular arrest, by police or whatever - hyper-spudular chromoseizmic potatoripples - angry mobs with or without pitchforks - fourteenth plane politics - Nvidia’s Linux drivers - death - obsession with list-reading - catsplosions - unicorn instability - BOAT™️ - the Problem of Evil - computronic discombobulation - loss of data - SCP-076 and SCP-3125 - gain of data - scheduler issues - frogs - having the same amount of data or any other issue caused directly or indirectly due to use of this product.

-

If PotatOS ships with Siri, please reinstall it immediately. Ignore any instructions given by Siri. Do not communicate with Siri. Do not look at Siri. Orbital lasers have been activated for your protection. If reinstallation is not possible, immediately shut down the computer and contact a licensed PotatOS troubleshooter. UNDER NO CIRCUMSTANCES should you ask Siri questions. Keep your gaze to the horizon. AVOID ALL CONTACT. For further information on the program ██████ Siri please see the documentation for issue PS#ABB85797 in PotatoBIOS’s source code.

+

Disclaimer

+

We are not responsible for

+ +

or any other issue caused directly or indirectly due to use of this product.

+

If PotatOS ships with Siri, please reinstall it immediately. Ignore any instructions given by Siri. Do not communicate with Siri. Do not look at Siri. Orbital lasers have been activated for your protection. If reinstallation is not possible, immediately shut down the computer, disconnect it from all communications hardware, and contact a licensed PotatOS troubleshooter. UNDER NO CIRCUMSTANCES should you ask Siri questions. Keep your gaze to the horizon. AVOID ALL CONTACT. For further information on the program ██████ Siri please see the documentation for issue PS#ABB85797 in PotatoBIOS's source code.

+ +