{"id":24443705,"url":"https://github.com/endlessm/hack-toolbox-app","last_synced_at":"2026-04-21T20:08:05.541Z","repository":{"id":33351910,"uuid":"147884880","full_name":"endlessm/hack-toolbox-app","owner":"endlessm","description":"App containing all toolboxes for Endless Hack","archived":false,"fork":false,"pushed_at":"2022-03-25T13:35:21.000Z","size":66109,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-01-20T22:17:06.383Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/endlessm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-07T23:48:22.000Z","updated_at":"2024-10-22T19:45:17.000Z","dependencies_parsed_at":"2022-08-07T20:31:12.043Z","dependency_job_id":null,"html_url":"https://github.com/endlessm/hack-toolbox-app","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endlessm%2Fhack-toolbox-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endlessm%2Fhack-toolbox-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endlessm%2Fhack-toolbox-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endlessm%2Fhack-toolbox-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/endlessm","download_url":"https://codeload.github.com/endlessm/hack-toolbox-app/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243515565,"owners_count":20303258,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-20T22:17:11.830Z","updated_at":"2025-12-28T20:53:13.018Z","avatar_url":"https://github.com/endlessm.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Hack Toolbox\n============\n\n## Description of architecture ##\n\n### Hack toolbox ###\n\nThe hack toolbox is a single application, that opens and closes windows\n(\"toolbox windows\") at an appropriate time when Hack apps are flipped\nover.\nIts responsibility is to make sure, when the user clicks the\nflip-to-hack button, that there is a toolbox window open and ready.\nGNOME Shell does the actual job of matching the toolbox window to the\napp window and executing the flipping animation.\n\nThe toolbox application exports an action on DBus, called `flip`.\nWhen the user clicks the flip-to-hack button, this action is activated,\nwith two parameters: the application ID (e.g. `com.hack_computer.Fizzics`)\nand the DBus object path of the window that was flipped (e.g.\n`/com/hack_computer/Fizzics/window/1`).\nThe latter parameter is theoretically to be able to handle flipping\napplications with more than one window, but that wasn't currently used.\n\nThe hack toolbox application picks out the proper toolbox widget with a\n`switch` statement, creates an instance of it, and puts it in a toolbox\nwindow.\nMost toolbox widgets inherit from `Toolbox` in `toolbox.js`, which\nprovides all the toolbox chrome such as the \"masthead\" in the upper left\ncorner, an interface to add \"topics\", and the collapsing feature.\n\nThe toolbox code should communicate with the application over DBus,\neither through a DBus interface provided especially for this purpose, or\nthrough a general-purpose interface such as Clippy.\n\n#### Delayed apply ####\n\nA toolbox window _may_ export an action on DBus called `flip-back`.\nIf it does, GNOME Shell will _not_ flip back to the application window\nimmediately, but wait for the application window to be closed and for a\nnew one to appear.\nThis is in order to implement toolboxes that cannot adjust the app\ninstantly, but instead have to restart it for the changes to be applied,\nsuch as the Framework toolbox.\nIn this case, it's the toolbox's responsibility to close the app and\nrestart it with any necessary arguments.\n(Note! If the toolbox exports the `flip-back` action but no new window\nappears, then GNOME Shell will never flip the toolbox window.)\n\n## Hacking an application ##\n\nIn most toolboxes, there are two ways to hack an application; either\nthrough changing GUI widgets in a \"control panel\", or assigning values\nto variables, using JavaScript syntax, in a \"code view\".\nThe toolbox generally follows a model-view architecture, where the\ncontrol panel and code view are both views of the same model.\nIt's the model's responsibility to communicate with the application\nthat's being hacked.\nFor applications using the hack-toy-apps mechanism, this communication\nhappens via Clippy; see `src/clippyWrapper.js`.\n\nCode views do some validation of the ranges and types of the values\nassigned to the variables.\nIf the code in a code view doesn't pass validation, then the model isn't\nupdated and therefore neither is the control panel.\n\nIt's often possible to assign values in a code view that are outside the\nranges allowed by the control panel widgets.\nThis is intentional.\nIt's also possible to do some rudimentary programming in the code view\nsuch as `obstacleSize = shipSize / 3;`.\nA relationship such as this can't be reflected in the model or the\ncontrol panel widgets, but it will be preserved in the code view,\nunless it's edited some other way.\n(That is, given the above example, changing the 'obstacle size' control\npanel widget will clobber the code view's relationship between\n`obstacleSize` and `shipSize`.)\n\n## Topics ##\n\nTopics are exported as objects on DBus which can be manipulated by the\nquest scripts.\nYou can hide or reveal a topic, make it insensitive (ignore mouse and\nkeyboard input), and get a notification when it is clicked on.\nTopics can also have lockscreens (see below.)\n\n## User Functions ##\n\nUser functions are code views that contain a function definition,\ninstead of a list of variable assignments.\nThey define behaviour, so they don't have a control panel counterpart.\n\nThe user functions and variables belonging to an app, together basically\nform an API for what the player can hack in that app.\nNote that these functions and variables aren't literally \"injected\" into\nthe app's code.\nThe variables are turned into DBus properties, and the user functions\nare passed in as strings and executed by the app (via a mechanism\nsimilar to \"Validation\" below.)\nIt's necessary for these to be passed via such a well-defined API so\nthat internal app code can be as complicated as it needs to be without\nthe additional requirement of being didactic, and so that refactors of\napp code don't break the user's work.\n\n## Validation ##\n\nThe code in a code view (variables or user functions) is validated,\nbasically by executing it with a glorified `eval()`.\nErrors are indicated in the margin, just like a lot of real code\neditors.\n\nEach user function has a \"player API\" available to it.\nThis is best illustrated with an example.\nIn the Sidetrack toolbox's Instructions topic, the user function looks\nlike this:\n```js\nfunction instructions() {\n    riley.jump();\n    riley.forward();\n    // etc.\n}\n```\nThe player API here consists of a `riley` object with some methods.\nInternally, the player API is placed onto a \"scope\" object, and the user\ncode is executed inside the scope of a `with` block:\n\n```js\nwith (scope) {\n    function instructions() {\n        riley.jump();\n        riley.forward();\n        // ...\n    }\n}\n```\n\nThe `with` statement ensures that when trying to resolve the name\n`riley`, the value of `scope.riley` will be used.\n(This is probably one of a very few justified uses of the `with`\nstatement in modern JavaScript.)\n\nWith the above, we can create a function that executes the user code,\npass in a scope object with the player API, then observe any changes\nthat happen to the scope object after executing the user function:\n\n```js\n// Note: simplified for clarity, compared to the actual code\nconst userCode = new Function('scope', `\n    with (scope) {\n        function instructions() {\n            riley.jump();\n            riley.forward();\n            // ...\n        }\n    }`);\nconst scope = {\n    riley: {\n        queue: [],\n        jump() {\n            this.queue.push('jump');\n        },\n        forward() {\n            this.queue.push('forward');\n        },\n    },\n};\nuserCode(scope);\nif (scope.riley.queue.length !== 8)\n    throw new Error('Not enough instructions');\n```\n\n## Lockscreens ##\n\nFor some areas, the player has to get past a \"lockscreen\" by opening it\nwith an inventory item, usually a \"key\".\nThe presence of the inventory item is detected via the game state\nservice.\n\nA lockscreen consists of two static images and an animation.\nThe first static image, `no-key`, is shown when the lockscreen is locked\nand the player doesn't have the inventory item to open it.\nThe second, `has-key`, is usually a glowy version of the first, and is\nshown when the needed inventory item is present.\nThe animation is shown when the player clicks on the `has-key` image,\nand is a green-screen transition showing the lock sliding away,\nrevealing the toolbox or other thing underneath.\n\n## Glossary ##\n\n**Code view** — A code editor widget that allows controlling parameters\nof an application by setting the values of pre-filled variables.\nIt also allows changing the behaviour of an application by injecting a\n**user function**.\n\n**Control panel** — A graphical UI that allows to control some\nparameters of an application through sliders, dropdowns, checkboxes,\ntext entries, and other GUI widgets.\n\n**Lockscreen** — A \"shield\" that blocks access to a **toolbox**, a\n**control panel** widget, or a **topic**.\nEach lockscreen can be opened with a certain inventory item.\n\n**Masthead** — The upper left corner of a toolbox that displays relevant\ninformation such as the application being hacked.\n\n**Toolbox** — The contents of a **toolbox window**, often including a\n**masthead**, **topics**, **control panel**, and **code view**.\nSee `src/toolbox.js` and its app-specific subclasses.\n\n**Toolbox app** — The single application that opens and closes toolbox\nwindows.\nSee `src/app.js`.\n\n**Toolbox window** — The window that is paired with an application\nwindow by GNOME Shell, when the user clicks flip-to-hack.\nSee `src/window.js`.\n\n**Topic** — A grouping of related **control panel** widgets.\nTopics are listed along the left-hand edge of the toolbox window.\nWhen a topic is selected, its control panel widgets appear in the\ntoolbox window.\n\n**User function** — A **code view** with a function definition that the\nplayer can use to change the behaviour of an app.\n\n## Development\n\n### Building a Flatpak bundle\n\nUse the `./build-flatpak.sh` script to build a Flatpak from the latest\ngit commit.\n\n### Building a local Flatpak\n\nUse the `build-local-flatpak.sh` script for developing. The script\nalso takes any extra arguments for `flatpak-builder`, thus, if you\nwant to quickly build a Flatpak with any changes you may have done,\nand install it in the user installation base, you can do:\n\n`./tools/build-local-flatpak.sh --install`\n\n\n### Testing local changes\nCommit your local changes.\nUse the `./build-flatpak.sh` script to build a Flatpak from the latest\ngit commit.\nUpdate the local application using `flatpak update --assumeyes --no-deps com.hack_computer.HackToolbox`\nRun the local version using `flatpak run --env=HACK_TOOLBOX_PERSIST=1 com.hack_computer.HackToolbox`\n\n### Coding Style\n\nThe continuous integration tool runs checks to validate the PRs. To\nrun the checks locally before sending your PR, you will need to\ninstall:\n\n- [eslint](https://github.com/eslint/eslint)\n- [yamllint](https://github.com/adrienverge/yamllint)\n\nThen call:\n\n``` shell\neslint .\nyamllint .\n```\n\n### Glade Catalog\n\nIf you add a new widget, you will have to add it to the Glade catalog.\n\nInstall the Glade app (it's available in Endless OS through\nflathub). Go to **Preferences**. In **Extra Catalog Paths**, click on\nthe plus icon, then find your checkout directory, and select the\ndirectory `data/glade/` where the `toolbox.glade.xml` is.\n\nRestart Glade. If the catalog was imported, you will see entries for\n\"Hack Toolbox\" widgets in the Widgets menu of the central panel. Your\nnew widget should be among them.\n\n## Future Work ##\n\n### Reorganizing topics ###\n\nThe topics were added later in this codebase's lifetime, after a number\nof toolboxes had already been designed.\nSome toolboxes have not yet been adapted to the topics system, and are\novercrowded because everything is stuck into one topic.\nThese toolboxes would need a reorganization.\nFor some toolboxes, it is probably necessary to figure out how to group\nrelated topics together and expand / collapse them.\n\n### Consistent lockscreens ###\n\nWe currently have several ways of restricting access to toolboxes,\nindividual topics within toolboxes, and individual control panel widgets\nwithin topics.\nA lockscreen can be placed at any of these levels.\nAs well, a topic can be initially hidden and revealed later via a DBus\ninterface, or a topic can be made temporarily insensitive (not reacting\nto mouse or keyboard input.)\n\nFuture work should consolidate these methods of restricting and granting\naccess as players progress through the curriculum.\n\n### Refactoring user functions ###\n\nFor historical reasons, user functions are passed back to the\napplication without the surrounding function declaration.\nSee https://phabricator.endlessm.com/T26818 for a proposal to change\nthis, and why it should be done.\n\n### Better error detection ###\n\nThe method of validation described above isn't good enough for a good\nlearning environment.\nFor one thing, since we use the built-in JavaScript parser by way of\n`new Function` (which is `eval()` in disguise), the parser bails out at\nthe first error.\nSo if there's more than one syntax error in user code, we can only\ndetect the first one.\nAnd if there's any syntax error at all in user code, we can't execute\nthe code on the scope object to perform any further validation.\nFuture work could investigate more sophisticated JavaScript parsers from\nthe Node.js world, such as Esprima.\n\nSecond, while it will never be possible to detect all runtime errors\nwhile running the code, we should be able to detect more than we do now\nwith static type analysis.\nFor example,\n\n```js\nfunction spawnEnemy() {\n    if (ticksSinceSpawn \u003e 9.46e8)\n        return 'finalboss';\n}\n```\n\nSuch an error wouldn't be detected unless we evaluated the user code\nwith all possible values of `ticksSinceSpawn`.\nBut with type analysis, we could detect that `'finalboss'` isn't a valid\nreturn value according to the player API of that function.\nFuture work could investigate using TypeScript or Flow for user code,\nwithout exposing those complications to the player.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendlessm%2Fhack-toolbox-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fendlessm%2Fhack-toolbox-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendlessm%2Fhack-toolbox-app/lists"}