├── .eslintrc.json
├── .github
└── workflows
│ ├── build-node-binaries.yml
│ └── tests.yml
├── .gitignore
├── .prettierrc
├── .smalltalk.ston
├── .squot
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── getElements.js
├── images
├── google.png
├── lively.png
├── squeakjs.png
└── youtube.png
├── logo
├── hat.svg
├── magicmouse.png
├── magicmouse.svg
└── squeak.svg
├── package.json
├── packages
├── BaselineOfMagicMouse.package
│ ├── .filetree
│ ├── .squot-contents
│ ├── BaselineOfMagicMouse.class
│ │ ├── README.md
│ │ ├── instance
│ │ │ └── baseline..st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── monticello.meta
│ │ ├── categories.st
│ │ └── initializers.st
│ └── properties.json
├── MagicMouse-Core.package
│ ├── .filetree
│ ├── .squot-contents
│ ├── AttachableFileStream.extension
│ │ ├── instance
│ │ │ └── asMMBufferedStream.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMBrowser.class
│ │ ├── README.md
│ │ ├── class
│ │ │ ├── open.st
│ │ │ ├── openOn..st
│ │ │ ├── rickRoll.st
│ │ │ └── searchFor..st
│ │ ├── instance
│ │ │ ├── addOrRemoveBookmark.st
│ │ │ ├── bookmarkButtonState.st
│ │ │ ├── bookmarks.st
│ │ │ ├── buildButtonBarWith..st
│ │ │ ├── buildTopBarWith..st
│ │ │ ├── buildWith..st
│ │ │ ├── changeLocation..st
│ │ │ ├── dragExtractedObject..st
│ │ │ ├── exploreExtractedObject.st
│ │ │ ├── extractedObjects..st
│ │ │ ├── extractedObjects.st
│ │ │ ├── fullscreen..st
│ │ │ ├── getBrowserMorph.st
│ │ │ ├── initialize.st
│ │ │ ├── locationText..st
│ │ │ ├── locationText.st
│ │ │ ├── searchFor..st
│ │ │ ├── searchText..st
│ │ │ ├── searchText.st
│ │ │ ├── selectedExtractedObject..st
│ │ │ ├── selectedExtractedObject.st
│ │ │ ├── showBookmarks.st
│ │ │ └── structuredDataChanged..st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMBrowserMorph.class
│ │ ├── README.md
│ │ ├── class
│ │ │ ├── chromeProfilePath..st
│ │ │ ├── chromeProfilePath.st
│ │ │ ├── debugEnabled..st
│ │ │ ├── debugEnabled.st
│ │ │ ├── debugNodejs..st
│ │ │ ├── debugNodejs.st
│ │ │ ├── doNotUsePrebuiltBinary..st
│ │ │ ├── doNotUsePrebuiltBinary.st
│ │ │ ├── gitRepositoryPath..st
│ │ │ ├── gitRepositoryPath.st
│ │ │ ├── initialize.st
│ │ │ ├── logo.st
│ │ │ ├── open.st
│ │ │ ├── openOn..st
│ │ │ ├── portalsOnRightClick..st
│ │ │ ├── portalsOnRightClick.st
│ │ │ ├── runChromeHeadless..st
│ │ │ ├── runChromeHeadless.st
│ │ │ ├── shutDown..st
│ │ │ ├── startUp..st
│ │ │ └── title.st
│ │ ├── instance
│ │ │ ├── acceptDroppingMorph.event..st
│ │ │ ├── assertNodeScriptExists.st
│ │ │ ├── changeLocation..st
│ │ │ ├── connect..st
│ │ │ ├── contentOfField.changedTo..st
│ │ │ ├── delete.st
│ │ │ ├── dispose.st
│ │ │ ├── doRefresh.st
│ │ │ ├── domainObjectFieldFor.object..st
│ │ │ ├── drawOn..st
│ │ │ ├── droppedDomainObject.at..st
│ │ │ ├── droppedString.at..st
│ │ │ ├── eventTypeCode..st
│ │ │ ├── extent..st
│ │ │ ├── fullscreen..st
│ │ │ ├── getIdAndRectFrom.startAt..st
│ │ │ ├── gitClone.named..st
│ │ │ ├── goBack.st
│ │ │ ├── goForward.st
│ │ │ ├── handleCodePortal..st
│ │ │ ├── handleFormFields..st
│ │ │ ├── handleGitClone..st
│ │ │ ├── handlePortal..st
│ │ │ ├── handlePortalImage..st
│ │ │ ├── handlePortalMorph..st
│ │ │ ├── handlePortalRefreshData..st
│ │ │ ├── handleStructuredData..st
│ │ │ ├── handlesKeyboard..st
│ │ │ ├── handlesMouseDown..st
│ │ │ ├── handlesMouseMove..st
│ │ │ ├── handlesMouseWheel..st
│ │ │ ├── imageUpdaterProcess..st
│ │ │ ├── imageUpdaterProcess.st
│ │ │ ├── initialize.st
│ │ │ ├── intoWorld..st
│ │ │ ├── isConnected.st
│ │ │ ├── isFullscreen.st
│ │ │ ├── keyStroke..st
│ │ │ ├── keyboardFocusColor.st
│ │ │ ├── location.st
│ │ │ ├── locationUpdated..st
│ │ │ ├── mouseDown..st
│ │ │ ├── mouseMove..st
│ │ │ ├── mouseUp..st
│ │ │ ├── mouseWheel..st
│ │ │ ├── movePortal.rect..st
│ │ │ ├── nameFromDomainObject..st
│ │ │ ├── outOfWorld..st
│ │ │ ├── portalMorphs..st
│ │ │ ├── portalMorphs.st
│ │ │ ├── possibleDomainObjectFieldsFor..st
│ │ │ ├── preferredExtent.st
│ │ │ ├── process..st
│ │ │ ├── process.st
│ │ │ ├── refreshPortals..st
│ │ │ ├── searchFor..st
│ │ │ ├── sendCommand.withBuffer..st
│ │ │ ├── sendEvent..st
│ │ │ ├── sendEvent.withBuffer..st
│ │ │ ├── sendEvent.withNumber..st
│ │ │ ├── sendEvent.withNumber.andNumber..st
│ │ │ ├── sendEvent.withPoint..st
│ │ │ ├── sendEvent.withString..st
│ │ │ ├── updateImage..st
│ │ │ ├── urlFromDomainObject..st
│ │ │ ├── viewportSize..st
│ │ │ ├── viewportSize.st
│ │ │ └── wantsDroppedMorph.event..st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMBufferedStream.class
│ │ ├── README.md
│ │ ├── instance
│ │ │ ├── binary.st
│ │ │ ├── maxReadBufferSize.st
│ │ │ └── waitForDataReady.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMFastJPEGReader.class
│ │ ├── README.md
│ │ ├── instance
│ │ │ ├── nextImage..st
│ │ │ └── nextImage.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMPluginDownloader.class
│ │ ├── README.md
│ │ ├── class
│ │ │ ├── download..st
│ │ │ ├── download.st
│ │ │ ├── downloadForAllPlatforms.st
│ │ │ ├── downloadIfConfirmed.st
│ │ │ ├── downloadIfNeededAndConfirmed.st
│ │ │ ├── filenameDict.st
│ │ │ ├── filenameForCurrentPlatform.st
│ │ │ └── removeDownloads.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMProcessWrapper.class
│ │ ├── README.md
│ │ ├── class
│ │ │ └── newForOS.st
│ │ ├── instance
│ │ │ ├── disableDebug.st
│ │ │ ├── enableDebug.st
│ │ │ ├── initialize.st
│ │ │ ├── process..st
│ │ │ ├── process.st
│ │ │ ├── startCommand.withArguments..st
│ │ │ ├── startNodeScript.arguments.nodeArguments..st
│ │ │ ├── stdinPutBuffer..st
│ │ │ ├── stdinPutChar..st
│ │ │ ├── stdinPutUint32..st
│ │ │ ├── stdinPutUint8..st
│ │ │ ├── stdoutNextBuffer..st
│ │ │ ├── stdoutNextChar.st
│ │ │ ├── stdoutNextUint32.st
│ │ │ ├── stdoutNextUint8.st
│ │ │ └── terminate.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMTextMorphWithModel.class
│ │ ├── README.md
│ │ ├── instance
│ │ │ ├── aboutToStyle..st
│ │ │ └── initialize.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMUnixProcessWrapper.class
│ │ ├── README.md
│ │ ├── instance
│ │ │ ├── startCommand.withArguments..st
│ │ │ ├── stdinPutBuffer..st
│ │ │ ├── stdinPutUint32..st
│ │ │ ├── stdinPutUint8..st
│ │ │ ├── stdoutNextBuffer..st
│ │ │ ├── stdoutNextUint32.st
│ │ │ ├── stdoutNextUint8.st
│ │ │ └── terminate.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMWindowsProcessWrapperFFI.class
│ │ ├── README.md
│ │ ├── instance
│ │ │ ├── peek..st
│ │ │ ├── readBuffer.length..st
│ │ │ ├── startCommand.withArguments..st
│ │ │ ├── stderrNextBuffer..st
│ │ │ ├── stdinPutBuffer..st
│ │ │ ├── stdinPutUint32..st
│ │ │ ├── stdinPutUint8..st
│ │ │ ├── stdoutNextBuffer..st
│ │ │ ├── stdoutNextUint32.st
│ │ │ ├── stdoutNextUint8.st
│ │ │ ├── terminate.st
│ │ │ └── waitFor.at..st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── MMWindowsProcessWrapperPW.class
│ │ ├── README.md
│ │ ├── instance
│ │ │ ├── startCommand.withArguments..st
│ │ │ ├── stdinPutBuffer..st
│ │ │ ├── stdinPutUint32..st
│ │ │ ├── stdinPutUint8..st
│ │ │ ├── stdoutNextBuffer..st
│ │ │ ├── stdoutNextUint32.st
│ │ │ ├── stdoutNextUint8.st
│ │ │ └── terminate.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── Object.extension
│ │ ├── class
│ │ │ └── rickRoll.st
│ │ ├── instance
│ │ │ └── rickRoll.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── SearchBar.extension
│ │ ├── instance
│ │ │ └── magicSearch.in..st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── SequenceableCollection.extension
│ │ ├── instance
│ │ │ └── findFirst.startingAt..st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── TextEditor.extension
│ │ ├── instance
│ │ │ └── browseClassFromIt.st
│ │ ├── methodProperties.json
│ │ └── properties.json
│ ├── monticello.meta
│ │ ├── categories.st
│ │ ├── initializers.st
│ │ ├── postscript.st
│ │ └── preambleOfRemoval.st
│ └── properties.json
└── MagicMouse-Tests.package
│ ├── .filetree
│ ├── .squot-contents
│ ├── MMBrowserMorphTest.class
│ ├── README.md
│ ├── instance
│ │ ├── testHandleStructuredData.st
│ │ └── testHandleStructuredDataEmpty.st
│ ├── methodProperties.json
│ └── properties.json
│ ├── monticello.meta
│ ├── categories.st
│ └── initializers.st
│ └── properties.json
├── patches
└── puppeteer-core+1.19.0.patch
├── run.js
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "commonjs": true,
6 | "es2021": true
7 | },
8 | "extends": ["eslint:recommended", "prettier"],
9 | "parserOptions": {
10 | "ecmaVersion": 13
11 | },
12 | "rules": {
13 | "no-constant-condition": ["error", { "checkLoops": false }]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/build-node-binaries.yml:
--------------------------------------------------------------------------------
1 | name: Build Node.js Binaries
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Use Node.js 14.x
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: 14.x
18 |
19 | - name: Get yarn cache directory path
20 | id: yarn-cache-dir-path
21 | run: echo "::set-output name=dir::$(yarn cache dir)"
22 | - uses: actions/cache@v2
23 | with:
24 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
25 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
26 | restore-keys: |
27 | ${{ runner.os }}-yarn-
28 |
29 |
30 | - run: yarn install --frozen-lockfile
31 | - run: yarn lint
32 | - run: yarn build
33 |
34 | - name: Upload Binaries
35 | run: |
36 | go get -u github.com/tcnksm/ghr
37 | $HOME/go/bin/ghr -replace -b "Version https://github.com/cmfcmf/MagicMouse/commit/$GITHUB_SHA" latest ./build/
38 | env:
39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | smalltalk: [Squeak64-trunk, Squeak64-5.3]
15 | name: ${{ matrix.smalltalk }}
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: hpi-swa/setup-smalltalkCI@v1
19 | with:
20 | smalltalk-image: ${{ matrix.smalltalk }}
21 | - run: smalltalkci -s ${{ matrix.smalltalk }}
22 | shell: bash
23 | timeout-minutes: 15
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
3 | # Created by https://www.gitignore.io/api/node
4 | # Edit at https://www.gitignore.io/?templates=node
5 |
6 | ### Node ###
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | lerna-debug.log*
14 |
15 | # Diagnostic reports (https://nodejs.org/api/report.html)
16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
17 |
18 | # Runtime data
19 | pids
20 | *.pid
21 | *.seed
22 | *.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 | lib-cov
26 |
27 | # Coverage directory used by tools like istanbul
28 | coverage
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # TypeScript v1 declaration files
50 | typings/
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Optional REPL history
59 | .node_repl_history
60 |
61 | # Output of 'npm pack'
62 | *.tgz
63 |
64 | # Yarn Integrity file
65 | .yarn-integrity
66 |
67 | # dotenv environment variables file
68 | .env
69 | .env.test
70 |
71 | # parcel-bundler cache (https://parceljs.org/)
72 | .cache
73 |
74 | # next.js build output
75 | .next
76 |
77 | # nuxt.js build output
78 | .nuxt
79 |
80 | # vuepress build output
81 | .vuepress/dist
82 |
83 | # Serverless directories
84 | .serverless/
85 |
86 | # FuseBox cache
87 | .fusebox/
88 |
89 | # DynamoDB Local files
90 | .dynamodb/
91 |
92 | # End of https://www.gitignore.io/api/node
93 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "printWidth": 100
4 | }
5 |
--------------------------------------------------------------------------------
/.smalltalk.ston:
--------------------------------------------------------------------------------
1 | SmalltalkCISpec {
2 | #loading : [
3 | SCIMetacelloLoadSpec {
4 | #baseline : 'MagicMouse',
5 | #directory : 'packages',
6 | #platforms : [ #squeak ],
7 | #load : 'tests'
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.squot:
--------------------------------------------------------------------------------
1 | OrderedDictionary {
2 | 'packages/BaselineOfMagicMouse.package' : #SquotCypressCodeSerializer,
3 | 'packages/MagicMouse-Core.package' : #SquotCypressCodeSerializer,
4 | 'packages/MagicMouse-Tests.package' : #SquotCypressCodeSerializer
5 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "attach",
10 | "name": "Attach",
11 | "port": 9229
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Christian Flach
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
MagicMouse Webbrowser
2 |
3 | [](https://github.com/cmfcmf/MagicMouse/actions/workflows/tests.yml)
4 | [](https://github.com/cmfcmf/MagicMouse/actions/workflows/build-node-binaries.yml)
5 |
6 | MagicMouse is a webbrowser for [Squeak](https://squeak.org). It uses Chrome/Chromium under the hood to render websites and displays them as ImageMorphs right inside Squeak.
7 |
8 | MagicMouse runs on all platforms that run Squeak and Chrome, but works best and is most tested on Windows.
9 |
10 | | | |
11 | | ---------------------------------------------- | ------------------------------------------------- |
12 | |  |  |
13 | |  |  |
14 |
15 | ## Features
16 |
17 | - Browse the web with a real browser right from Squeak.
18 | - Watch YouTube videos in Squeak.
19 | - Right-click on images and code to transform them into `Morph`s. I call those "Portals". Because portals are cool.
20 | - Supercharged search bar:
21 | - If you type something with at least one space, your search terms are googled.
22 | - If you start your search with `!s `, your search terms are used to search https://squeak.org.
23 | - `CTRL+L` toggles fullscreen.
24 | - Dropped texts are typed into form fields.
25 | - _browseIt_ any URL to open it in a browser.
26 | - Rick roll yourself! Send `rickRoll` to your favourite class or object to get started.
27 | - Open browsers reconnect after resuming Squeak.
28 | - Create and browse bookmarks.
29 | - [Home Desktop System](https://github.com/hpi-swa-lab/home-desktop-system) integration:
30 | - Extracts structured data into `DomainObject`s
31 | - Searches for dropped `DomainObject`s
32 | - Fills form fields with dropped `DomainObject`s
33 | - [Squot](https://github.com/hpi-swa/Squot) integration:
34 | - When browsing GitHub repositories, displays a direct download button that opens the repository in Squot.
35 | - [Google Slides](https://slides.google.com) integration:
36 | - Display morphs right inside your presentation! Any rectangular boxes added to the slides whose text starts with `!` will be evaluated.
37 | - [PolyCode](https://github.com/hpi-swa-lab/pp19-6-code-editor) integration:
38 | - Display mp3s, mp4s, svgs and more inside the code editor using MagicMouse.
39 |
40 | ## Installation
41 |
42 | ⚠ **See below for installation instructions if you want to develop MagicMouse!** ⚠
43 |
44 | 1. Install a recent version of Chrome or Chromium. MagicMouse uses the Chrome DevTools protocol to communicate with the browser and relies on the newish and experimental [`startScreencast`](https://chromedevtools.github.io/devtools-protocol/tot/Page#method-startScreencast) functionality. Chrome 76 works for me.
45 | 2. Install Squeak 5.2+ (you probably have that already :D).
46 | 3. Install MagicMouse via Metacello.
47 |
48 | ```smalltalk
49 | Metacello new
50 | baseline: 'MagicMouse';
51 | repository: 'github://cmfcmf/MagicMouse:master/packages';
52 | load.
53 | ```
54 |
55 | You will be prompted to download a binary that acts as a bridge between Squeak and Chrome. See the `MMPluginDownloader` for details. Binaries are stored at [Bintray](https://bintray.com/cmfcmf/MagicMouse/node-bridge/latest?tab=files#files/).
56 |
57 | ## Usage
58 |
59 | MagicMouse provides two core classes for you to use: `MMBrowserMorph`, a simple image morph, and `MMBrowser`, a webbrowser with location and search bar.
60 |
61 | In general, be advised that there is little to no error handling in place right now. If your webbrowser is no longer responding, there is a good chance it crashed. It is helpful to enable the `Enable debug` preference and watch the Transcript output to troubleshoot problems.
62 |
63 | ### `MMBrowserMorph`
64 |
65 | This ImageMorph displays a website. It intercepts keyboard and mouse events and sends them to Chrome.
66 | Open it by using the `open` or `openOn:` messages:
67 |
68 | ```smalltalk
69 | MMBrowserMorph open "open with default url"
70 |
71 | MMBrowserMorph openOn: 'https://github.com/cmfcmf/MagicMouse'
72 | ```
73 |
74 | ### `MMBrowser`
75 |
76 | This tool features a location bar, a Google search bar and the browser content itself. Open it using:
77 |
78 | ```smalltalk
79 | MMBrowser open "open with default url"
80 |
81 | MMBrowser openOn: 'https://github.com/cmfcmf/MagicMouse'
82 | ```
83 |
84 | ## How it works
85 |
86 | There are three components to make it work: Squeak, an intermediate Node.js script and Chrome.
87 |
88 | ### `run.js` <-[DevTools Protocol via WebSocket]-> Chrome
89 |
90 | If you are using the prebuilt binary (default and recommended for people who don't want to develop MagicMouse), the `run.js` script is packaged together with Node.js into a single binary.
91 | We use Node.js and the [puppeteer-core](https://www.npmjs.com/package/puppeteer-core) library to spawn and communicate with Chrome/Chromium. Puppeteer uses the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) to connect to Chrome. The DevTools protocol uses WebSockets for communication. The required Node.js script is located inside this repostiory and called `run.js`. It spawns the browser and then calls `startScreencast()` which instructs Chrome to regularly take a screenshot of the current window and send it to Node.js. Chrome is smart enough to only take screenshots when the page visually changes.
92 |
93 | ### Squeak <-[Pipe]-> `run.js`
94 |
95 | Squeak communicates with the `run.js` script via `stdout`, `stdin` and `stderr`. Depending on the operating system, we either use OSProcess (Unix) or FFI (Windows) to pipe these streams into Squeak. We used ProcessWrapper on Windows previously, but found it rather unreliable.
96 |
97 | ### `stdout` and `stdin`
98 |
99 | `stdout` and `stdin` are used for the communication between Squeak and `run.js`. We use a simple, custom protocol for communication. Each message starts with a `uint32_t` that specifies the length of the message in bytes. The following `char` identifies the type of the message. The rest of the message is specific to the message type. Some example messages include:
100 |
101 | #### `stdout`: `run.js` -> Squeak
102 |
103 | - browser screenshots
104 | - location changes
105 | - portal data (i.e., the data of an image that was requested by a "portal creation request")
106 | - portal positions (everytime the page is re-rendered, an updated list of all portal positions is sent to Squeak)
107 |
108 | #### `stdin`: Squeak -> `run.js`
109 |
110 | - mouse movement, mouse events
111 | - keyboard events
112 | - viewport size changes
113 | - portal creation requests
114 | - location change requests
115 |
116 | ### `stderr`
117 |
118 | `stderr` is used for debugging only. The `run.js` script prints debug information to `stderr`, which is read by Squeak and written to the Transcript if the `Enable debug` option is set.
119 |
120 | ## Installation for Developers
121 |
122 | The installation is a bit cumbersome at the moment:
123 |
124 | 1. Install MagicMouse via Metacello like described above. This will also download necessary dependencies.
125 | 2. Install MagicMouse via the Git Browser.
126 | 3. Clone this repository using your command-line Git client (**not** Squit). Choose a different folder than when you cloned using Squit.
127 | 4. Run `yarn install` within the cloned repository. You may need to install [Yarn](https://yarnpkg.com/lang/en/).
128 | 5. Open the Squeak Perference Browser and
129 | - Change the `Git Repository Path` in the MagicMouse category to match the location of the cloned repository.
130 | - Check the `Do not use prebuilt binary` option.
131 | - You probably also want to enable the `Enable debug` property to log debug output to the Transcript.
132 |
133 | ## Contributing
134 |
135 | Your contribution to this project is highly appreciated. If you are contributing to this project please note that the Smalltalk / Squeak part of the project uses the [poppy-print](https://github.com/hpi-swa-teaching/poppy-print) code formatter by @tom95.
136 |
137 | To install this formatter execute
138 |
139 | ```smalltalk
140 | Metacello new baseline: 'PoppyPrint'; repository: 'github://tom95/poppy-print/packages'; load.
141 | ```
142 |
143 | in a workspace. Then the following can be used to format the smalltalk code:
144 |
145 | ```smalltalk
146 | PPFormatter formatPackage: 'MagicMouse'.
147 | ```
148 |
149 | The nodejs part of the project also has a formatter called prettier. Additionally, it is linted via eslint.
150 |
151 | To format and lint the code with automated fixing use `yarn format`.
152 |
153 | Please make sure that all your submitted code is formatted with the given formatters and that the nodejs part is linted, before merging your contribution.
154 |
--------------------------------------------------------------------------------
/getElements.js:
--------------------------------------------------------------------------------
1 | const getElements = (fn, ...args) => {
2 | const ID_ATTRIBUTE = "data-magic-mouse-id";
3 |
4 | const getIdOf = async (element) => {
5 | let id = element.getAttribute(ID_ATTRIBUTE);
6 | if (!id) {
7 | id = await window.uuid();
8 | element.setAttribute(ID_ATTRIBUTE, id);
9 | }
10 | return id;
11 | };
12 |
13 | // Based on this Gist by @ciaranj
14 | // https://gist.github.com/ciaranj/7177fb342102e571db2784dc831f868b
15 | // which is based on this StackOverflow answer by Édouard Mercier:
16 | // https://stackoverflow.com/a/50916681/2560557
17 | const calculateContainsWindow = (element) => {
18 | const imageComputedStyle = window.getComputedStyle(element);
19 | const imageObjectFit = imageComputedStyle.getPropertyValue("object-fit");
20 | const coords = {};
21 | const imagePositions = imageComputedStyle.getPropertyValue("object-position").split(" ");
22 | let naturalWidth = element.naturalWidth;
23 | let naturalHeight = element.naturalHeight;
24 | if (element.tagName === "VIDEO") {
25 | naturalWidth = element.videoWidth;
26 | naturalHeight = element.videoHeight;
27 | } else if (element.tagName === "CANVAS") {
28 | naturalWidth = element.width;
29 | naturalHeight = element.height;
30 | }
31 | const horizontalPercentage = parseInt(imagePositions[0]) / 100;
32 | const verticalPercentage = parseInt(imagePositions[1]) / 100;
33 | const naturalRatio = naturalWidth / naturalHeight;
34 | const visibleRatio = element.clientWidth / element.clientHeight;
35 |
36 | if (imageObjectFit === "none") {
37 | coords.sourceWidth = element.clientWidth;
38 | coords.sourceHeight = element.clientHeight;
39 | coords.sourceX = (naturalWidth - element.clientWidth) * horizontalPercentage;
40 | coords.sourceY = (naturalHeight - element.clientHeight) * verticalPercentage;
41 | coords.destinationWidthPercentage = 1;
42 | coords.destinationHeightPercentage = 1;
43 | coords.destinationXPercentage = 0;
44 | coords.destinationYPercentage = 0;
45 | } else if (imageObjectFit === "contain" || imageObjectFit === "scale-down") {
46 | // TODO: handle the "scale-down" appropriately, once its meaning will be clear
47 | coords.sourceWidth = naturalWidth;
48 | coords.sourceHeight = naturalHeight;
49 | coords.sourceX = 0;
50 | coords.sourceY = 0;
51 | if (naturalRatio > visibleRatio) {
52 | coords.destinationWidthPercentage = 1;
53 | coords.destinationHeightPercentage =
54 | naturalHeight / element.clientHeight / (naturalWidth / element.clientWidth);
55 | coords.destinationXPercentage = 0;
56 | coords.destinationYPercentage =
57 | (1 - coords.destinationHeightPercentage) * verticalPercentage;
58 | } else {
59 | coords.destinationWidthPercentage =
60 | naturalWidth / element.clientWidth / (naturalHeight / element.clientHeight);
61 | coords.destinationHeightPercentage = 1;
62 | coords.destinationXPercentage =
63 | (1 - coords.destinationWidthPercentage) * horizontalPercentage;
64 | coords.destinationYPercentage = 0;
65 | }
66 | } else if (imageObjectFit === "cover") {
67 | if (naturalRatio > visibleRatio) {
68 | coords.sourceWidth = naturalHeight * visibleRatio;
69 | coords.sourceHeight = naturalHeight;
70 | coords.sourceX = (naturalWidth - coords.sourceWidth) * horizontalPercentage;
71 | coords.sourceY = 0;
72 | } else {
73 | coords.sourceWidth = naturalWidth;
74 | coords.sourceHeight = naturalWidth / visibleRatio;
75 | coords.sourceX = 0;
76 | coords.sourceY = (naturalHeight - coords.sourceHeight) * verticalPercentage;
77 | }
78 | coords.destinationWidthPercentage = 1;
79 | coords.destinationHeightPercentage = 1;
80 | coords.destinationXPercentage = 0;
81 | coords.destinationYPercentage = 0;
82 | } else if (imageObjectFit === "fill") {
83 | coords.sourceWidth = naturalWidth;
84 | coords.sourceHeight = naturalHeight;
85 | coords.sourceX = 0;
86 | coords.sourceY = 0;
87 | coords.destinationWidthPercentage = 1;
88 | coords.destinationHeightPercentage = 1;
89 | coords.destinationXPercentage = 0;
90 | coords.destinationYPercentage = 0;
91 | } else {
92 | console.error(
93 | "unexpected 'object-fit' attribute with value '" + imageObjectFit + "' relative to",
94 | );
95 | }
96 | return coords;
97 | };
98 |
99 | // Convert an
element to a base64-encoded string.
100 | const img2Base64 = (img) => {
101 | const canvas = document.createElement("canvas");
102 | canvas.width = img.naturalWidth;
103 | canvas.height = img.naturalHeight;
104 | const ctx = canvas.getContext("2d");
105 | ctx.drawImage(img, 0, 0);
106 | return canvas.toDataURL("image/png").split(",")[1];
107 | };
108 |
109 | const extractImg = async (img, includeData = true) => {
110 | const rect = img.getBoundingClientRect();
111 |
112 | const style = window.getComputedStyle(img);
113 | const offsetX = parseInt(style.paddingLeft, 10);
114 | const offsetY = parseInt(style.paddingTop, 10);
115 |
116 | const w = img.width;
117 | const h = img.height;
118 |
119 | return {
120 | id: await getIdOf(img),
121 | type: "img",
122 | x: rect.x + offsetX,
123 | y: rect.y + offsetY,
124 | w,
125 | h,
126 | data: includeData ? img2Base64(img) : null,
127 | };
128 | };
129 |
130 | const extractCanvas = async (canvas, includeData = true) => {
131 | const rect = canvas.getBoundingClientRect();
132 |
133 | const style = window.getComputedStyle(canvas);
134 | const offsetX = parseInt(style.paddingLeft, 10);
135 | const offsetY = parseInt(style.paddingTop, 10);
136 |
137 | let x = rect.x + offsetX;
138 | let y = rect.y + offsetY;
139 | let w = rect.width;
140 | let h = rect.height;
141 |
142 | if (style.objectFit === "contain" && style.objectPosition === "50% 50%") {
143 | const c = calculateContainsWindow(canvas);
144 | x += c.destinationXPercentage * rect.width;
145 | y += c.destinationYPercentage * rect.height;
146 | w = c.destinationWidthPercentage * w;
147 | h = c.destinationHeightPercentage * h;
148 | }
149 |
150 | return {
151 | id: await getIdOf(canvas),
152 | type: "canvas",
153 | x,
154 | y,
155 | w,
156 | h,
157 | data: includeData ? canvas.toDataURL("image/png").split(",")[1] : null,
158 | };
159 | };
160 |
161 | const extractPre = async (element, includeData = true) => {
162 | const viewport = {
163 | w: document.documentElement.clientWidth,
164 | h: document.documentElement.clientHeight,
165 | };
166 |
167 | const rect = element.getBoundingClientRect();
168 |
169 | let dw = 0;
170 | if (rect.right > viewport.w) {
171 | dw = rect.right - viewport.w;
172 | }
173 | let dh = 0;
174 | if (rect.bottom > viewport.h) {
175 | dh = rect.bottom - viewport.h;
176 | }
177 |
178 | const id = await getIdOf(element);
179 |
180 | return {
181 | id,
182 | type: "pre",
183 | x: rect.x,
184 | y: rect.y,
185 | w: rect.width - dw,
186 | h: rect.height - dh,
187 | data: includeData ? element.textContent : null,
188 | };
189 | };
190 |
191 | const extract = async (element, includeData = true) => {
192 | if (element.tagName === "IMG") {
193 | return extractImg(element, includeData);
194 | } else if (element.tagName === "CANVAS") {
195 | return extractCanvas(element, includeData);
196 | } else if (element.tagName === "PRE") {
197 | return extractPre(element, includeData);
198 | } else {
199 | const rect = element.getBoundingClientRect();
200 | return {
201 | id: await getIdOf(element),
202 | type: "other",
203 | x: rect.x,
204 | y: rect.y,
205 | w: rect.width,
206 | h: rect.height,
207 | data: null,
208 | };
209 | }
210 | };
211 |
212 | const extractElements = (x, y) => {
213 | return Promise.all(
214 | document
215 | .elementsFromPoint(x, y)
216 | .filter(
217 | (element) =>
218 | element.tagName === "IMG" || element.tagName === "CANVAS" || element.tagName === "PRE",
219 | )
220 | .map(async (element) => extract(element)),
221 | );
222 | };
223 |
224 | const refreshInfo = () => {
225 | const infos = [];
226 |
227 | document
228 | .querySelectorAll(`[${ID_ATTRIBUTE}]`)
229 | .forEach((element) => infos.push(extract(element, false)));
230 |
231 | return Promise.all(infos);
232 | };
233 |
234 | switch (fn) {
235 | case "extractElements":
236 | return extractElements(...args);
237 | case "refreshInfo":
238 | return refreshInfo(...args);
239 | }
240 | };
241 |
242 | module.exports = {
243 | getElements,
244 | };
245 |
--------------------------------------------------------------------------------
/images/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/images/google.png
--------------------------------------------------------------------------------
/images/lively.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/images/lively.png
--------------------------------------------------------------------------------
/images/squeakjs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/images/squeakjs.png
--------------------------------------------------------------------------------
/images/youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/images/youtube.png
--------------------------------------------------------------------------------
/logo/magicmouse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/logo/magicmouse.png
--------------------------------------------------------------------------------
/logo/magicmouse.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
611 |
--------------------------------------------------------------------------------
/logo/squeak.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "license": "MIT",
3 | "author": {
4 | "name": "Christian Flach",
5 | "email": "cmfcmf.flach@gmail.com"
6 | },
7 | "scripts": {
8 | "build": "pkg --targets node16-win,node16-macos,node16-linux --public --output ./build/magicmouse ./run.js",
9 | "postinstall": "patch-package",
10 | "lint": "eslint --cache --quiet ./ && prettier --list-different --config .prettierrc \"*.js\"",
11 | "format": "eslint --cache --fix --quiet . && prettier --write --config .prettierrc \"*.js\""
12 | },
13 | "dependencies": {
14 | "awaitify-stream": "^1.0.2",
15 | "chrome-finder": "^1.0.5",
16 | "get-pixels": "^3.3.2",
17 | "patch-package": "^6.1.2",
18 | "postinstall-postinstall": "^2.0.0",
19 | "puppeteer-core": "1.19.0",
20 | "smart-buffer": "^4.1.0",
21 | "uuid": "^8.3.2"
22 | },
23 | "devDependencies": {
24 | "eslint": "^8.5.0",
25 | "eslint-config-prettier": "^8.3.0",
26 | "pkg": "^5.5.1",
27 | "prettier": "^2.5.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/.filetree:
--------------------------------------------------------------------------------
1 | {
2 | "noMethodMetaData" : true,
3 | "separateMethodMetaAndSource" : false,
4 | "useCypressPropertiesFile" : true }
5 |
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/.squot-contents:
--------------------------------------------------------------------------------
1 | SquotTrackedObjectMetadata {
2 | #objectClassName : #PackageInfo,
3 | #objectsReplacedByNames : true,
4 | #serializer : #SquotCypressCodeSerializer
5 | }
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/BaselineOfMagicMouse.class/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/packages/BaselineOfMagicMouse.package/BaselineOfMagicMouse.class/README.md
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/BaselineOfMagicMouse.class/instance/baseline..st:
--------------------------------------------------------------------------------
1 | baseline
2 | baseline: spec
3 |
4 | spec for: #'common' do: [
5 | spec
6 | baseline: 'Cmfcmf' with: [
7 | spec
8 | repository: 'github://cmfcmf/SqueakUtils:master';
9 | loads: #('winffi')];
10 | project: 'OSProcess' with: [
11 | spec
12 | className: 'ConfigurationOfOSProcess';
13 | repository: 'http://www.squeaksource.com/MetacelloRepository';
14 | versionString: #'stable'];
15 | package: 'JSON' with: [spec repository: 'http://www.squeaksource.com/JSON'];
16 | package: 'MagicMouse-Core' with: [spec requires: #('Cmfcmf' 'OSProcess' 'JSON')];
17 | package: 'MagicMouse-Tests' with: [spec requires: #('MagicMouse-Core')].
18 | spec
19 | group: 'default' with: #('MagicMouse-Core');
20 | group: 'tests' with: #('MagicMouse-Tests');
21 | yourself];
22 | yourself
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/BaselineOfMagicMouse.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "baseline:" : "cmfcmf 8/3/2019 13:37" } }
6 |
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/BaselineOfMagicMouse.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "BaselineOfMagicMouse",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "",
8 | "instvars" : [
9 | ],
10 | "name" : "BaselineOfMagicMouse",
11 | "pools" : [
12 | ],
13 | "super" : "BaselineOf",
14 | "type" : "normal" }
15 |
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/monticello.meta/categories.st:
--------------------------------------------------------------------------------
1 | SystemOrganization addCategory: #BaselineOfMagicMouse!
2 |
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/monticello.meta/initializers.st:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/packages/BaselineOfMagicMouse.package/monticello.meta/initializers.st
--------------------------------------------------------------------------------
/packages/BaselineOfMagicMouse.package/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/.filetree:
--------------------------------------------------------------------------------
1 | {
2 | "noMethodMetaData" : true,
3 | "separateMethodMetaAndSource" : false,
4 | "useCypressPropertiesFile" : true }
5 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/.squot-contents:
--------------------------------------------------------------------------------
1 | SquotTrackedObjectMetadata {
2 | #objectClassName : #PackageInfo,
3 | #objectsReplacedByNames : true,
4 | #serializer : #SquotCypressCodeSerializer
5 | }
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/AttachableFileStream.extension/instance/asMMBufferedStream.st:
--------------------------------------------------------------------------------
1 | *MagicMouse-Core
2 | asMMBufferedStream
3 | "Answer a replacement for this object, with asynchronous event handling
4 | and buffered output. Do not close the ioHandle when this object is finalized."
5 |
6 | self keepOpen.
7 | ^ MMBufferedStream
8 | name: self name
9 | attachTo: self ioHandle
10 | writable: self isReadOnly not
11 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/AttachableFileStream.extension/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "asMMBufferedStream" : "cf 4/18/2019 19:14" } }
6 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/AttachableFileStream.extension/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "AttachableFileStream" }
3 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/README.md:
--------------------------------------------------------------------------------
1 | I am a browser with address and location bar as well as bookmarks.
2 | Open myself by doit'ing one of
3 |
4 | MMBrowser open
5 | MMBrowser openOn: 'https://squeak.org'
6 | MMBrowser searchFor: 'squeak'
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/class/open.st:
--------------------------------------------------------------------------------
1 | instance creation
2 | open
3 |
4 | ^ ToolBuilder open: self new
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/class/openOn..st:
--------------------------------------------------------------------------------
1 | instance creation
2 | openOn: aUrl
3 |
4 | | model |
5 | model := self new.
6 | model locationText: aUrl.
7 | ^ ToolBuilder open: model
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/class/rickRoll.st:
--------------------------------------------------------------------------------
1 | instance creation
2 | rickRoll
3 |
4 | | model instance |
5 | model := self new.
6 | model locationText: 'https://www.youtube.com/embed/DLzxrzFCyOs?autoplay=1&loop=1&controls=0&modestbranding=1&showinfo=0'.
7 | instance := ToolBuilder open: model.
8 | model fullscreen: true.
9 | ^ instance
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/class/searchFor..st:
--------------------------------------------------------------------------------
1 | instance creation
2 | searchFor: aString
3 |
4 | | model |
5 | model := self new.
6 | ToolBuilder open: model.
7 | model searchFor: aString
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/addOrRemoveBookmark.st:
--------------------------------------------------------------------------------
1 | bookmarks
2 | addOrRemoveBookmark
3 |
4 | | url |
5 | url := self locationText asString.
6 | Bookmarks remove: url ifAbsent: [Bookmarks add: url]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/bookmarkButtonState.st:
--------------------------------------------------------------------------------
1 | bookmarks
2 | bookmarkButtonState
3 |
4 | ^ self bookmarks includes: self locationText asString
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/bookmarks.st:
--------------------------------------------------------------------------------
1 | bookmarks
2 | bookmarks
3 |
4 | ^ Bookmarks ifNil: [Bookmarks := OrderedCollection new]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/buildButtonBarWith..st:
--------------------------------------------------------------------------------
1 | building
2 | buildButtonBarWith: aBuilder
3 |
4 | ^ {
5 | aBuilder pluggableButtonSpec new
6 | model: self;
7 | action: #showBookmarks;
8 | label: 'Bookmarks';
9 | yourself.
10 | aBuilder pluggableButtonSpec new
11 | model: self;
12 | action: [PreferenceBrowser open selectedCategory: #MagicMouse];
13 | label: 'Preferences';
14 | yourself}
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/buildTopBarWith..st:
--------------------------------------------------------------------------------
1 | building
2 | buildTopBarWith: aBuilder
3 |
4 | ^ {
5 | aBuilder pluggableButtonSpec new
6 | frame: ((LayoutFrame fractions: (0 @ 0 extent: 0 @ 1))
7 | rightOffset: 25;
8 | yourself);
9 | model: self;
10 | help: 'back';
11 | action: [browser goBack];
12 | label: '<';
13 | yourself.
14 | aBuilder pluggableButtonSpec new
15 | frame: ((LayoutFrame fractions: (0 @ 0 extent: 0 @ 1))
16 | leftOffset: 25;
17 | rightOffset: 50;
18 | yourself);
19 | model: self;
20 | help: 'forward';
21 | action: [browser goForward];
22 | label: '>';
23 | yourself.
24 | aBuilder pluggableInputFieldSpec new
25 | model: self;
26 | hardLineWrap: false;
27 | softLineWrap: false;
28 | getText: #locationText;
29 | setText: #changeLocation:;
30 | askBeforeDiscardingEdits: false;
31 | help: 'Location';
32 | frame: ((LayoutFrame fractions: (0 @ 0 extent: 0.8 @ 1))
33 | leftOffset: 50;
34 | rightOffset: -50;
35 | yourself);
36 | yourself.
37 | aBuilder pluggableButtonSpec new
38 | frame: ((LayoutFrame fractions: (0.8 @ 0 corner: 0.8 @ 1))
39 | leftOffset: -50;
40 | rightOffset: -25;
41 | yourself);
42 | model: self;
43 | label: 'B';
44 | action: #addOrRemoveBookmark;
45 | state: #bookmarkButtonState;
46 | help: 'bookmark';
47 | yourself.
48 | aBuilder pluggableButtonSpec new
49 | frame: ((LayoutFrame fractions: (0.8 @ 0 corner: 0.8 @ 1))
50 | leftOffset: -25;
51 | yourself);
52 | model: self;
53 | label: 'R';
54 | action: [browser doRefresh];
55 | help: 'refresh';
56 | yourself.
57 | aBuilder pluggableInputFieldSpec new
58 | model: self;
59 | hardLineWrap: false;
60 | softLineWrap: false;
61 | getText: #searchText;
62 | setText: #searchFor:;
63 | askBeforeDiscardingEdits: false;
64 | help: 'Search with Google';
65 | frame: (LayoutFrame fractions: (0.8 @ 0 extent: 0.2 @ 1));
66 | yourself}
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/buildWith..st:
--------------------------------------------------------------------------------
1 | building
2 | buildWith: aBuilder
3 |
4 | ^ aBuilder build: (aBuilder pluggableWindowSpec new
5 | label: MMBrowserMorph title;
6 | extent: 600 @ 400;
7 | model: self;
8 | children: {
9 | aBuilder pluggablePanelSpec new
10 | wantsResizeHandles: true;
11 | frame: (LayoutFrame fractions: (0 @ 0 extent: 1 @ 0) offsets: (0 @ 0 extent: 0 @ 25));
12 | children: (self buildTopBarWith: aBuilder);
13 | yourself.
14 | aBuilder pluggablePanelSpec new
15 | frame: (LayoutFrame fractions: (0 @ 0 extent: 1 @ 0) offsets: (0 @ 25 extent: 0 @ 25));
16 | layout: #horizontal;
17 | children: (self buildButtonBarWith: aBuilder);
18 | yourself.
19 | aBuilder pluggablePanelSpec new
20 | wantsResizeHandles: true;
21 | frame: (LayoutFrame fractions: (0 @ 0 extent: 1 @ 1) offsets: (0 @ 50 extent: 0 @ -50));
22 | spacing: 5;
23 | children: {
24 | aBuilder pluggablePanelSpec new
25 | model: self;
26 | frame: (0 @ 0 extent: 0.8 @ 1);
27 | children: #getBrowserMorph;
28 | yourself.
29 | aBuilder pluggableListSpec new
30 | model: self;
31 | frame: (0.8 @ 0 corner: 1 @ 1);
32 | list: #extractedObjects;
33 | doubleClick: #exploreExtractedObject;
34 | getSelected: #selectedExtractedObject;
35 | setSelected: #selectedExtractedObject:;
36 | dragItem: #dragExtractedObject:;
37 | yourself};
38 | yourself};
39 | yourself)
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/changeLocation..st:
--------------------------------------------------------------------------------
1 | public
2 | changeLocation: aText
3 |
4 | self locationText: aText.
5 | browser changeLocation: self locationText
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/dragExtractedObject..st:
--------------------------------------------------------------------------------
1 | extracted objects
2 | dragExtractedObject: aNumber
3 |
4 | | domainObject |
5 | domainObject := extractedObjects at: aNumber.
6 | domainObject undelete.
7 |
8 | ^ domainObject
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/exploreExtractedObject.st:
--------------------------------------------------------------------------------
1 | extracted objects
2 | exploreExtractedObject
3 |
4 | self selectedExtractedObject ifNotNil: [:object | object explore]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/extractedObjects..st:
--------------------------------------------------------------------------------
1 | accessing
2 | extractedObjects: aCollection
3 |
4 | extractedObjects := aCollection.
5 | self changed: #extractedObjects
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/extractedObjects.st:
--------------------------------------------------------------------------------
1 | accessing
2 | extractedObjects
3 |
4 | ^ extractedObjects
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/fullscreen..st:
--------------------------------------------------------------------------------
1 | public
2 | fullscreen: aBoolean
3 |
4 | browser fullscreen: aBoolean
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/getBrowserMorph.st:
--------------------------------------------------------------------------------
1 | building
2 | getBrowserMorph
3 |
4 | browser isConnected ifFalse: [
5 | browser connect: self locationText.
6 | browser layoutFrame: (LayoutFrame fractions: (0 @ 0 extent: 1 @ 1)).
7 | browser when: #locationChanged send: #locationText: to: self.
8 | browser when: #structuredDataChanged send: #structuredDataChanged: to: self].
9 | ^ {browser}
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/initialize.st:
--------------------------------------------------------------------------------
1 | initialize-release
2 | initialize
3 |
4 | super initialize.
5 | locationText := 'https://google.com'.
6 | searchText := ''.
7 | browser := MMBrowserMorph new.
8 | extractedObjects := OrderedCollection new
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/locationText..st:
--------------------------------------------------------------------------------
1 | accessing
2 | locationText: aText
3 |
4 | | url |
5 | url := aText asString.
6 | ((url findString: 'https://') = 0 and: [(url findString: 'http://') = 0]) ifTrue: [url := 'https://', url].
7 | locationText := url.
8 | self changed: #locationText.
9 | self changed: #bookmarkButtonState
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/locationText.st:
--------------------------------------------------------------------------------
1 | accessing
2 | locationText
3 |
4 | ^ locationText
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/searchFor..st:
--------------------------------------------------------------------------------
1 | public
2 | searchFor: aText
3 |
4 | | search |
5 | search := aText asString.
6 | self searchText: search.
7 | browser searchFor: search
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/searchText..st:
--------------------------------------------------------------------------------
1 | accessing
2 | searchText: aString
3 |
4 | searchText := aString.
5 | self changed: #searchText
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/searchText.st:
--------------------------------------------------------------------------------
1 | accessing
2 | searchText
3 |
4 | ^ searchText
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/selectedExtractedObject..st:
--------------------------------------------------------------------------------
1 | extracted objects
2 | selectedExtractedObject: anObject
3 |
4 | selectedExtractedObject := anObject
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/selectedExtractedObject.st:
--------------------------------------------------------------------------------
1 | extracted objects
2 | selectedExtractedObject
3 |
4 | ^ selectedExtractedObject
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/showBookmarks.st:
--------------------------------------------------------------------------------
1 | bookmarks
2 | showBookmarks
3 |
4 | | url |
5 | self bookmarks ifEmpty: [^ UIManager default inform: 'You have not created any bookmarks yet.'].
6 | url := UIManager default chooseFrom: self bookmarks values: self bookmarks.
7 | url ifNil: [^ self].
8 | self changeLocation: url
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/instance/structuredDataChanged..st:
--------------------------------------------------------------------------------
1 | extracted objects
2 | structuredDataChanged: data
3 |
4 | | objects domainObject |
5 | domainObject := Smalltalk classNamed: #DomainObject.
6 | domainObject ifNil: [^ self].
7 |
8 | objects := data collect: [:each | | object |
9 | object := domainObject new delete.
10 | each associationsDo: [:assoc | | field |
11 | field := assoc key asLegalSelector asSymbol.
12 | field = #name ifTrue: [field := #title].
13 | object perform: field asMutator with: assoc value].
14 | object].
15 | objects ifEmpty: [
16 | objects := {
17 | domainObject new delete
18 | title: self locationText asString;
19 | url: self locationText asString;
20 | yourself}].
21 | self extractedObjects: objects
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | "open" : "cmfcmf 8/3/2019 19:12",
4 | "openOn:" : "cmfcmf 8/3/2019 19:12",
5 | "rickRoll" : "cmfcmf 6/30/2019 17:41",
6 | "searchFor:" : "cmfcmf 8/3/2019 19:12" },
7 | "instance" : {
8 | "addOrRemoveBookmark" : "cmfcmf 7/7/2019 11:40",
9 | "bookmarkButtonState" : "cmfcmf 7/7/2019 11:38",
10 | "bookmarks" : "cmfcmf 7/7/2019 11:35",
11 | "buildButtonBarWith:" : "MB 1/30/2022 10:33",
12 | "buildTopBarWith:" : "MB 1/30/2022 10:33",
13 | "buildWith:" : "MB 1/30/2022 10:33",
14 | "changeLocation:" : "cf 5/17/2019 09:19",
15 | "dragExtractedObject:" : "MB 1/30/2022 10:33",
16 | "exploreExtractedObject" : "cmfcmf 6/11/2019 09:24",
17 | "extractedObjects" : "cmfcmf 6/11/2019 08:54",
18 | "extractedObjects:" : "cmfcmf 6/11/2019 08:55",
19 | "fullscreen:" : "cmfcmf 6/30/2019 17:35",
20 | "getBrowserMorph" : "MB 1/30/2022 10:33",
21 | "initialize" : "MB 1/30/2022 10:33",
22 | "locationText" : "cf 5/1/2019 15:47",
23 | "locationText:" : "MB 1/30/2022 10:33",
24 | "searchFor:" : "cf 5/27/2019 21:35",
25 | "searchText" : "cf 5/1/2019 15:47",
26 | "searchText:" : "cf 5/1/2019 15:58",
27 | "selectedExtractedObject" : "cmfcmf 6/11/2019 09:06",
28 | "selectedExtractedObject:" : "cmfcmf 6/11/2019 09:06",
29 | "showBookmarks" : "cmfcmf 7/7/2019 11:37",
30 | "structuredDataChanged:" : "MB 1/30/2022 10:33" } }
31 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowser.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | "Bookmarks" ],
7 | "commentStamp" : "cmfcmf 8/3/2019 19:12",
8 | "instvars" : [
9 | "locationText",
10 | "searchText",
11 | "browser",
12 | "extractedObjects",
13 | "selectedExtractedObject" ],
14 | "name" : "MMBrowser",
15 | "pools" : [
16 | ],
17 | "super" : "Model",
18 | "type" : "normal" }
19 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/README.md:
--------------------------------------------------------------------------------
1 | I am an ImageMorph that connects to Chrome/Chromium to display a website.
2 | Open myself by doit'ing one of
3 |
4 | MMBrowser open
5 | MMBrowser openOn: 'https://squeak.org'
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/chromeProfilePath..st:
--------------------------------------------------------------------------------
1 | preferences
2 | chromeProfilePath: aString
3 |
4 | ChromeProfilePath := aString
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/chromeProfilePath.st:
--------------------------------------------------------------------------------
1 | preferences
2 | chromeProfilePath
3 |
4 |
5 | ^ ChromeProfilePath ifNil: [(FileDirectory default / 'chromeUserDir') name]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/debugEnabled..st:
--------------------------------------------------------------------------------
1 | preferences
2 | debugEnabled: aBoolean
3 |
4 | DebugEnabled := aBoolean
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/debugEnabled.st:
--------------------------------------------------------------------------------
1 | preferences
2 | debugEnabled
3 |
4 |
5 | ^ DebugEnabled ifNil: [false]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/debugNodejs..st:
--------------------------------------------------------------------------------
1 | preferences
2 | debugNodejs: aBoolean
3 |
4 | DebugNodejs := aBoolean
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/debugNodejs.st:
--------------------------------------------------------------------------------
1 | preferences
2 | debugNodejs
3 |
4 |
5 | ^ DebugNodejs ifNil: [false]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/doNotUsePrebuiltBinary..st:
--------------------------------------------------------------------------------
1 | preferences
2 | doNotUsePrebuiltBinary: aBoolean
3 |
4 | DoNotUsePrebuiltBinary := aBoolean
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/doNotUsePrebuiltBinary.st:
--------------------------------------------------------------------------------
1 | preferences
2 | doNotUsePrebuiltBinary
3 |
4 |
5 | ^ DoNotUsePrebuiltBinary ifNil: [false]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/gitRepositoryPath..st:
--------------------------------------------------------------------------------
1 | preferences
2 | gitRepositoryPath: aString
3 |
4 | GitRepositoryPath := aString
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/gitRepositoryPath.st:
--------------------------------------------------------------------------------
1 | preferences
2 | gitRepositoryPath
3 |
4 |
5 | ^ GitRepositoryPath ifNil: ['.']
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/initialize.st:
--------------------------------------------------------------------------------
1 | class initialization
2 | initialize
3 | "MMBrowserMorph initialize"
4 |
5 | Smalltalk addToStartUpList: self.
6 | Smalltalk addToShutDownList: self
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/open.st:
--------------------------------------------------------------------------------
1 | instance creation
2 | open
3 |
4 | ^ self openOn: 'https://google.com'
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/openOn..st:
--------------------------------------------------------------------------------
1 | instance creation
2 | openOn: aUrl
3 |
4 | ^ self new
5 | connect: aUrl;
6 | openInWindowLabeled: self title;
7 | yourself
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/portalsOnRightClick..st:
--------------------------------------------------------------------------------
1 | preferences
2 | portalsOnRightClick: aBoolean
3 |
4 | PortalsOnRightClick := aBoolean
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/portalsOnRightClick.st:
--------------------------------------------------------------------------------
1 | preferences
2 | portalsOnRightClick
3 |
4 |
5 | ^ PortalsOnRightClick ifNil: [true]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/runChromeHeadless..st:
--------------------------------------------------------------------------------
1 | preferences
2 | runChromeHeadless: aBoolean
3 |
4 | RunChromeHeadless := aBoolean
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/runChromeHeadless.st:
--------------------------------------------------------------------------------
1 | preferences
2 | runChromeHeadless
3 |
4 |
5 | ^ RunChromeHeadless ifNil: [true]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/shutDown..st:
--------------------------------------------------------------------------------
1 | system startup
2 | shutDown: quitting
3 |
4 | quitting ifTrue: [self allInstancesDo: #dispose]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/startUp..st:
--------------------------------------------------------------------------------
1 | system startup
2 | startUp: resuming
3 |
4 | resuming ifTrue: [
5 | DockingBarMorph allInstancesDo: [:each | each searchBarMorph instVarNamed: 'setTextSelector' put: #magicSearch:in:].
6 | self allInstancesDo: [:each | each connect: each location]]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/class/title.st:
--------------------------------------------------------------------------------
1 | accessing
2 | title
3 |
4 | ^ 'MagicMouse Webbrowser'
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/acceptDroppingMorph.event..st:
--------------------------------------------------------------------------------
1 | dropping/grabbing
2 | acceptDroppingMorph: aMorph event: evt
3 |
4 | | position target |
5 | position := evt position - self position.
6 | target := (aMorph isKindOf: TransferMorph)
7 | ifTrue: [
8 | aMorph passenger isText
9 | ifTrue: [aMorph passenger asString]
10 | ifFalse: [aMorph passenger]]
11 | ifFalse: [
12 | (aMorph isSystemWindow and: [aMorph model isKindOf: StringHolder])
13 | ifTrue: [aMorph model contents asString]
14 | ifFalse: [aMorph]].
15 |
16 | (Smalltalk classNamed: #DomainObject) ifNotNil: [:domainObject | (target isKindOf: domainObject) ifTrue: [self droppedDomainObject: target at: position]].
17 |
18 | target isString ifTrue: [self droppedString: target at: position]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/assertNodeScriptExists.st:
--------------------------------------------------------------------------------
1 | connecting
2 | assertNodeScriptExists
3 |
4 | | path directory |
5 | path := self class gitRepositoryPath.
6 | directory := FileDirectory on: path.
7 | self
8 | assert: [directory exists]
9 | description: 'You need to specify a valid directory in the Preference Browser'.
10 | self
11 | assert: [directory fileExists: 'run.js']
12 | description: 'The specified directory exists but is missing the run.js file. Make sure to NOT specify the path to the directory cloned via the Git Browser, but rather a manually cloned directory!'.
13 | self
14 | assert: [directory directoryExists: 'node_modules']
15 | description: 'You need to run yarn install inside the MagicMouse git directory.'
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/changeLocation..st:
--------------------------------------------------------------------------------
1 | public
2 | changeLocation: aUrl
3 |
4 | location := aUrl.
5 | self sendCommand: $l withBuffer: aUrl asByteArray
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/connect..st:
--------------------------------------------------------------------------------
1 | connecting
2 | connect: aUrl
3 |
4 | | path arguments |
5 | process := MMProcessWrapper newForOS.
6 | self class debugEnabled ifTrue: [process enableDebug].
7 | arguments := {
8 | aUrl.
9 | viewportSize x.
10 | viewportSize y.
11 | self class runChromeHeadless ifTrue: ['headless'] ifFalse: ['windowed'].
12 | self class chromeProfilePath}.
13 | self class doNotUsePrebuiltBinary
14 | ifTrue: [
15 | self assertNodeScriptExists.
16 | path := self class gitRepositoryPath, FileDirectory slash, 'run.js'.
17 | process
18 | startNodeScript: path
19 | arguments: arguments
20 | nodeArguments: (self class debugNodejs ifTrue: [{'--inspect'}] ifFalse: [{}])]
21 | ifFalse: [
22 | path := (FileDirectory default / MMPluginDownloader filenameForCurrentPlatform) fullName.
23 | process startCommand: path withArguments: arguments].
24 |
25 | location := aUrl.
26 |
27 | imageUpdaterProcess := [
28 | [ | length command payload |
29 | length := process stdoutNextUint32.
30 | payload := process stdoutNextBuffer: length.
31 | command := payload unsignedCharAt: 1.
32 | command caseOf: {
33 | [$i] -> [self updateImage: payload].
34 | [$h] -> [self handlePortal: payload].
35 | [$l] -> [self locationUpdated: payload].
36 | [$s] -> [self handleStructuredData: payload].
37 | [$g] -> [self handleGitClone: payload].
38 | [$f] -> [self handleFormFields: payload]}] repeat] fork
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/contentOfField.changedTo..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | contentOfField: anId changedTo: text
3 |
4 | | bytes content id |
5 | id := anId squeakToUtf8.
6 | content := text asString squeakToUtf8.
7 | bytes := ByteArray new: 8 + id byteSize + content byteSize.
8 | bytes signedLongAt: 1 put: id byteSize.
9 | bytes replaceFrom: 5 to: 5 + id byteSize - 1 with: id asByteArray.
10 |
11 | bytes signedLongAt: 5 + id byteSize put: content byteSize.
12 | bytes
13 | replaceFrom: 5 + id byteSize + 4
14 | to: 5 + id byteSize + 4 + content byteSize - 1
15 | with: content asByteArray.
16 | self sendCommand: $t withBuffer: bytes
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/delete.st:
--------------------------------------------------------------------------------
1 | disconnecting
2 | delete
3 |
4 | super delete.
5 | self dispose
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/dispose.st:
--------------------------------------------------------------------------------
1 | disconnecting
2 | dispose
3 |
4 | process ifNotNil: [:p |
5 | p terminate.
6 | process := nil].
7 | imageUpdaterProcess ifNotNil: [:p |
8 | p terminate.
9 | imageUpdaterProcess := nil]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/doRefresh.st:
--------------------------------------------------------------------------------
1 | public
2 | doRefresh
3 |
4 | self changeLocation: location
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/domainObjectFieldFor.object..st:
--------------------------------------------------------------------------------
1 | domain objects
2 | domainObjectFieldFor: aString object: aDomainObject
3 |
4 | | fields |
5 | fields := self possibleDomainObjectFieldsFor: aString.
6 | fields do: [:each | (aDomainObject respondsTo: each) ifTrue: [^ each]].
7 | ^ #unknown
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/drawOn..st:
--------------------------------------------------------------------------------
1 | drawing
2 | drawOn: aCanvas
3 | "Draw my background color to cover the areas when my viewport doesn't match my extent."
4 |
5 | aCanvas fillRectangle: self bounds color: self color.
6 | super drawOn: aCanvas
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/droppedDomainObject.at..st:
--------------------------------------------------------------------------------
1 | dropping/grabbing
2 | droppedDomainObject: aDomainObject at: aPoint
3 |
4 | | bytes |
5 | tmpDomainObject := aDomainObject.
6 | bytes := ByteArray new: 8.
7 | bytes
8 | signedLongAt: 1 put: aPoint x;
9 | signedLongAt: 5 put: aPoint y.
10 | self sendCommand: $f withBuffer: bytes
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/droppedString.at..st:
--------------------------------------------------------------------------------
1 | dropping/grabbing
2 | droppedString: aString at: aPoint
3 |
4 | | bytes strBuffer |
5 | strBuffer := aString withUnixLineEndings squeakToUtf8 asByteArray.
6 | bytes := ByteArray new: 8.
7 | bytes
8 | signedLongAt: 1 put: aPoint x;
9 | signedLongAt: 5 put: aPoint y.
10 | self sendCommand: $s withBuffer: bytes, strBuffer
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/eventTypeCode..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | eventTypeCode: aType
3 |
4 | ^ (Dictionary newFrom: {
5 | #mouseLeftUp -> 0.
6 | #mouseLeftDown -> 1.
7 | #mouseRightUp -> 2.
8 | #mouseRightDown -> 3.
9 | #mouseMove -> 4.
10 | #keyPress -> 5.
11 | #resize -> 6.
12 | #mouseWheel -> 7.
13 | #portal -> 8.
14 | #back -> 9.
15 | #forward -> 10})
16 | at: aType
17 | ifAbsent: [self error: 'unknown event type']
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/extent..st:
--------------------------------------------------------------------------------
1 | geometry
2 | extent: aPoint
3 |
4 | (viewportSize ~= aPoint and: [self isConnected]) ifTrue: [self sendEvent: #resize withPoint: aPoint].
5 |
6 | viewportSize := aPoint.
7 | super extent: aPoint
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/fullscreen..st:
--------------------------------------------------------------------------------
1 | public
2 | fullscreen: aBoolean
3 |
4 | aBoolean
5 | ifTrue: [
6 | previousOwner := self owner.
7 | self
8 | beSticky;
9 | openInWorld: self world;
10 | extent: self world extent;
11 | position: 0 @ 0]
12 | ifFalse: [
13 | self beUnsticky.
14 | previousOwner addMorph: self.
15 | previousOwner := nil]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/getIdAndRectFrom.startAt..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | getIdAndRectFrom: payload startAt: start
3 |
4 | | id x y w h idEnd |
5 | idEnd := payload findFirst: [:each | each = 0] startingAt: start.
6 | id := (payload copyFrom: start to: idEnd - 1) asString utf8ToSqueak.
7 | x := payload signedLongAt: idEnd + 1.
8 | y := payload signedLongAt: idEnd + 5.
9 | w := payload signedLongAt: idEnd + 9.
10 | h := payload signedLongAt: idEnd + 13.
11 |
12 | ^ Array with: id with: (x @ y extent: w @ h) with: idEnd + 17
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/gitClone.named..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | gitClone: url named: name
3 | "This code is based on SquitBrowser>>actionProjectClone"
4 |
5 | | browser workingCopy directory repositoryPath |
6 | browser := SquitBrowser new.
7 | self assert: (browser isRemoteSupported: url).
8 |
9 | workingCopy := SquotWorkingCopy newOnObjectMemory.
10 | workingCopy name: name.
11 | directory := UIManager default chooseDirectory: 'Repository directory'.
12 | directory ifNil: [^ self].
13 | repositoryPath := directory pathName.
14 | workingCopy repository: (SquitRepository new initializeInDirectory: (FileSystem disk root resolve: repositoryPath)).
15 | workingCopy loadedHistorian: (workingCopy repository historianNamed: 'master').
16 | workingCopy register.
17 |
18 | browser withRemoteErrorHandlingDo: [
19 | browser
20 | handlingCredentialsOf: workingCopy loadedHistorian
21 | do: [workingCopy repository cloneFrom: url]
22 | ifCanceled: []].
23 | browser refresh.
24 | ToolBuilder open: browser
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/goBack.st:
--------------------------------------------------------------------------------
1 | public
2 | goBack
3 |
4 | self sendEvent: #back
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/goForward.st:
--------------------------------------------------------------------------------
1 | public
2 | goForward
3 |
4 | self sendEvent: #forward
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handleCodePortal..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handleCodePortal: payload
3 |
4 | | info data morph id next rect |
5 | self assert: [$c = (payload unsignedCharAt: 2)].
6 | info := self getIdAndRectFrom: payload startAt: 3.
7 | id := info first.
8 | rect := info second.
9 | next := info third.
10 |
11 | data := (payload copyFrom: next to: payload size) asString utf8ToSqueak.
12 |
13 | morph := MMTextMorphWithModel new.
14 | morph
15 | position: self position + rect origin;
16 | extent: rect extent;
17 | useDefaultStyler;
18 | setText: data;
19 | yourself.
20 | self addMorph: morph.
21 | self portalMorphs at: id put: morph.
22 | World activeHand newKeyboardFocus: morph
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handleFormFields..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handleFormFields: payload
3 |
4 | | start fields descriptions ids |
5 | start := 6.
6 | fields := OrderedCollection new.
7 | descriptions := OrderedCollection new.
8 | ids := OrderedCollection new.
9 | (payload signedLongAt: 2) timesRepeat: [ | h w x y descriptionSize description id idSize |
10 | x := payload signedLongAt: start.
11 | y := payload signedLongAt: start + 4.
12 | w := payload signedLongAt: start + 8.
13 | h := payload signedLongAt: start + 12.
14 | descriptionSize := payload unsignedLongAt: start + 16.
15 | description := payload copyFrom: start + 20 to: start + 20 + descriptionSize - 1.
16 | start := start + 20 + descriptionSize.
17 | idSize := payload unsignedLongAt: start.
18 | id := payload copyFrom: start + 4 to: start + 4 + idSize - 1.
19 |
20 | fields add: (x @ y extent: w @ h).
21 | descriptions add: description asString utf8ToSqueak.
22 | ids add: id asString utf8ToSqueak.
23 | start := start + 4 + idSize].
24 |
25 | "Looks like there is no form where the DomainObject was dropped."
26 | fields ifEmpty: [
27 | (self urlFromDomainObject: tmpDomainObject) ifNotNil: [:url |
28 | self changeLocation: url.
29 | ^ tmpDomainObject := nil].
30 | (self nameFromDomainObject: tmpDomainObject) ifNotNil: [:name |
31 | self searchFor: name.
32 | ^ tmpDomainObject := nil]].
33 |
34 | self submorphs
35 | select: [:each | each isKindOf: HObjectFieldMorph]
36 | thenDo: [:each | self removeMorph: each].
37 | fields withIndexDo: [:boundingBox :index | | description id fieldMorph |
38 | description := descriptions at: index.
39 | id := ids at: index.
40 | fieldMorph := (HObjectFieldMorph
41 | for: tmpDomainObject
42 | wanting: (self domainObjectFieldFor: description object: tmpDomainObject))
43 | position: self position + boundingBox topLeft;
44 | extent: boundingBox extent;
45 | when: #textChanged
46 | evaluate: [:text | self contentOfField: id changedTo: text];
47 | yourself.
48 | self addMorph: fieldMorph.
49 | self portalMorphs at: id put: fieldMorph.
50 | self contentOfField: id changedTo: fieldMorph getTextFromInnerMorphs].
51 |
52 | tmpDomainObject := nil
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handleGitClone..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handleGitClone: payload
3 |
4 | | url name nameLength |
5 | nameLength := payload unsignedLongAt: 2.
6 | name := (payload copyFrom: 6 to: 6 + nameLength - 1) asString utf8ToSqueak.
7 | url := (payload copyFrom: 6 + nameLength to: payload size) asString utf8ToSqueak.
8 |
9 | self gitClone: url named: name
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlePortal..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handlePortal: payload
3 |
4 | (payload unsignedCharAt: 2) caseOf: {
5 | [$i] -> [self handlePortalImage: payload].
6 | [$m] -> [self handlePortalMorph: payload].
7 | [$c] -> [self handleCodePortal: payload].
8 | [$r] -> [self handlePortalRefreshData: payload]}
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlePortalImage..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handlePortalImage: payload
3 |
4 | | data form morph id info next rect |
5 | self assert: [$i = (payload unsignedCharAt: 2)].
6 | info := self getIdAndRectFrom: payload startAt: 3.
7 | id := info first.
8 | rect := info second.
9 | next := info third.
10 |
11 | data := payload copyFrom: next to: payload size.
12 |
13 | form := Form fromBinaryStream: data readStream.
14 | form := form scaledToSize: rect extent.
15 |
16 | morph := ImageMorph new.
17 | morph
18 | position: self position + rect origin;
19 | image: form.
20 | self portalMorphs at: id put: morph.
21 | self addMorph: morph.
22 | morph addHalo
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlePortalMorph..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handlePortalMorph: payload
3 |
4 | | info data id next rect morph |
5 | self assert: [$m = (payload unsignedCharAt: 2)].
6 | info := self getIdAndRectFrom: payload startAt: 3.
7 | id := info first.
8 | rect := info second.
9 | next := info third.
10 |
11 | data := (payload copyFrom: next to: payload size) asString utf8ToSqueak.
12 | morph := Compiler evaluate: data allButFirst.
13 | morph
14 | position: self position + rect origin;
15 | extent: rect extent;
16 | yourself.
17 | self addMorph: morph.
18 | self portalMorphs at: id put: morph
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlePortalRefreshData..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handlePortalRefreshData: payload
3 |
4 | | start portalInfos |
5 | self assert: [$r = (payload unsignedCharAt: 2)].
6 | start := 7.
7 | portalInfos := OrderedCollection new.
8 | (payload signedLongAt: 3) timesRepeat: [ | portalInfo rect |
9 | portalInfo := self getIdAndRectFrom: payload startAt: start.
10 | portalInfos add: portalInfo.
11 | start := portalInfo third].
12 |
13 | self refreshPortals: portalInfos.
14 |
15 | portalInfos do: [:each | | id rect |
16 | id := each first.
17 | rect := each second.
18 | self movePortal: id rect: rect]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handleStructuredData..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | handleStructuredData: payload
3 |
4 | | start structuredData |
5 | start := 6.
6 | structuredData := OrderedCollection new.
7 | (payload unsignedLongAt: 2) timesRepeat: [ | json length |
8 | length := payload unsignedLongAt: start.
9 | json := payload copyFrom: start + 4 to: start + 4 + length - 1.
10 | structuredData add: (Json readFrom: json asString utf8ToSqueak readStream).
11 | start := start + 4 + length].
12 | self triggerEvent: #structuredDataChanged with: structuredData.
13 | ^ structuredData
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlesKeyboard..st:
--------------------------------------------------------------------------------
1 | events
2 | handlesKeyboard: evt
3 |
4 | ^ true
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlesMouseDown..st:
--------------------------------------------------------------------------------
1 | events
2 | handlesMouseDown: anEvent
3 |
4 | ^ true
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlesMouseMove..st:
--------------------------------------------------------------------------------
1 | events
2 | handlesMouseMove: anEvent
3 |
4 | ^ true
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/handlesMouseWheel..st:
--------------------------------------------------------------------------------
1 | events
2 | handlesMouseWheel: anEvent
3 |
4 | ^ true
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/imageUpdaterProcess..st:
--------------------------------------------------------------------------------
1 | accessing
2 | imageUpdaterProcess: anObject
3 |
4 | imageUpdaterProcess := anObject
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/imageUpdaterProcess.st:
--------------------------------------------------------------------------------
1 | accessing
2 | imageUpdaterProcess
3 |
4 | ^ imageUpdaterProcess
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/initialize.st:
--------------------------------------------------------------------------------
1 | initialization
2 | initialize
3 |
4 | viewportSize := 300 @ 300.
5 | super initialize.
6 | self clipSubmorphs: true.
7 | self color: Color white.
8 | "Mark the image as opaque. Although we render PNGs, the browser should never send us an image of the webpage that includes transparency."
9 | self isOpaque: true.
10 | self image: self class logo.
11 | self portalMorphs: Dictionary new
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/intoWorld..st:
--------------------------------------------------------------------------------
1 | initialization
2 | intoWorld: aWorld
3 |
4 | super intoWorld: aWorld.
5 | self isConnected ifFalse: [self connect: self location]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/isConnected.st:
--------------------------------------------------------------------------------
1 | accessing
2 | isConnected
3 |
4 | ^ process notNil
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/isFullscreen.st:
--------------------------------------------------------------------------------
1 | accessing
2 | isFullscreen
3 |
4 | ^ previousOwner notNil
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/keyStroke..st:
--------------------------------------------------------------------------------
1 | events
2 | keyStroke: anEvent
3 |
4 | anEvent keyString = ''
5 | ifTrue: [self fullscreen: previousOwner isNil]
6 | ifFalse: [self sendEvent: #keyPress withString: anEvent keyString]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/keyboardFocusColor.st:
--------------------------------------------------------------------------------
1 | events
2 | keyboardFocusColor
3 |
4 | ^ nil
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/location.st:
--------------------------------------------------------------------------------
1 | accessing
2 | location
3 |
4 | ^ location
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/locationUpdated..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | locationUpdated: payload
3 |
4 | location := (payload copyFrom: 2 to: payload size) asString utf8ToSqueak.
5 | self refreshPortals: {}.
6 | self triggerEvent: #locationChanged with: location
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/mouseDown..st:
--------------------------------------------------------------------------------
1 | events
2 | mouseDown: anEvent
3 |
4 | | position |
5 | position := anEvent position - self position max: 0 @ 0.
6 | (self class portalsOnRightClick and: [anEvent yellowButtonChanged])
7 | ifTrue: [self sendEvent: #portal withPoint: position]
8 | ifFalse: [
9 | self
10 | sendEvent: #mouseMove withPoint: position;
11 | sendEvent: (anEvent yellowButtonChanged
12 | ifTrue: [#mouseRightDown]
13 | ifFalse: [#mouseLeftDown]).
14 | self activeHand keyboardFocus == self ifTrue: [^ self].
15 | self activeHand newKeyboardFocus: self]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/mouseMove..st:
--------------------------------------------------------------------------------
1 | events
2 | mouseMove: anEvent
3 |
4 | | position |
5 | position := anEvent position - self position max: 0 @ 0.
6 | self sendEvent: #mouseMove withPoint: position
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/mouseUp..st:
--------------------------------------------------------------------------------
1 | events
2 | mouseUp: anEvent
3 |
4 | | position |
5 | position := anEvent position - self position max: 0 @ 0.
6 | self
7 | sendEvent: #mouseMove withPoint: position;
8 | sendEvent: (anEvent yellowButtonChanged ifTrue: [#mouseRightUp] ifFalse: [#mouseLeftUp])
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/mouseWheel..st:
--------------------------------------------------------------------------------
1 | events
2 | mouseWheel: anEvent
3 |
4 | self sendEvent: #mouseWheel withNumber: (anEvent isWheelUp ifTrue: [0] ifFalse: [1])
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/movePortal.rect..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | movePortal: id rect: rect
3 |
4 | self portalMorphs at: id ifPresent: [:morph |
5 | morph
6 | position: self position + rect origin;
7 | extent: rect extent]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/nameFromDomainObject..st:
--------------------------------------------------------------------------------
1 | domain objects
2 | nameFromDomainObject: aDomainObject
3 |
4 | | name |
5 | name := aDomainObject ? #fullName.
6 | name ifNil: [ | firstName lastName |
7 | firstName := aDomainObject ? #firstName.
8 | lastName := aDomainObject ? #lastName.
9 | (firstName notNil and: [lastName notNil]) ifTrue: [name := '{1} {2}' format: {firstName. lastName}]].
10 | "name ifNil: [name := aDomainObject ? #name]."
11 | name ifNil: [name := aDomainObject ? #title].
12 | name ifNil: [name := aDomainObject ? #headline].
13 | name ifNil: [name := aDomainObject ? #content].
14 | name ifNil: [name := aDomainObject ? #description].
15 |
16 | ^ name
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/outOfWorld..st:
--------------------------------------------------------------------------------
1 | initialization
2 | outOfWorld: aWorld
3 |
4 | super outOfWorld: aWorld.
5 | self dispose
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/portalMorphs..st:
--------------------------------------------------------------------------------
1 | accessing
2 | portalMorphs: aDictionary
3 |
4 | portalMorphs := aDictionary
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/portalMorphs.st:
--------------------------------------------------------------------------------
1 | accessing
2 | portalMorphs
3 |
4 | ^ portalMorphs
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/possibleDomainObjectFieldsFor..st:
--------------------------------------------------------------------------------
1 | domain objects
2 | possibleDomainObjectFieldsFor: aString
3 |
4 | | names separators |
5 | names := OrderedCollection new.
6 | names add: aString asCamelCase withFirstCharacterDownshifted asSymbol.
7 |
8 | separators := #(#/ #or #, #';').
9 | separators do: [:separator |
10 | aString
11 | splitBy: separator
12 | do: [:each | each ~= aString ifTrue: [names add: each asCamelCase withFirstCharacterDownshifted asSymbol]]].
13 |
14 | names addAll: (aString
15 | caseOf: {
16 | ['address line 1'] -> [#(#street)].
17 | ['address1'] -> [#(#street)].
18 | ['postcode'] -> [#(#postalCode)].
19 | ['firstname'] -> [#(#firstName)].
20 | ['lastname'] -> [#(#lastName)]}
21 | otherwise: [#()]).
22 |
23 | ^ names
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/preferredExtent.st:
--------------------------------------------------------------------------------
1 | accessing
2 | preferredExtent
3 |
4 | ^ viewportSize
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/process..st:
--------------------------------------------------------------------------------
1 | accessing
2 | process: anObject
3 |
4 | process := anObject
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/process.st:
--------------------------------------------------------------------------------
1 | accessing
2 | process
3 |
4 | ^ process
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/refreshPortals..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | refreshPortals: portalInfos
3 |
4 | self portalMorphs keysAndValuesDo: [:id :morph | | isMyChild isOnPage |
5 | isMyChild := morph owner = self.
6 | isOnPage := portalInfos anySatisfy: [:each | id = each first].
7 | (isMyChild and: [isOnPage not]) ifTrue: [morph delete].
8 | (isMyChild not or: [isOnPage not]) ifTrue: [self portalMorphs removeKey: id]]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/searchFor..st:
--------------------------------------------------------------------------------
1 | public
2 | searchFor: aString
3 |
4 | self changeLocation: 'https://www.google.com/search?q=', aString encodeForHTTP
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/sendCommand.withBuffer..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | sendCommand: aCharacter withBuffer: aBuffer
3 |
4 | self process
5 | stdinPutUint32: aBuffer size + 1;
6 | stdinPutChar: aCharacter;
7 | stdinPutBuffer: aBuffer asByteArray
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/sendEvent..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | sendEvent: anEventType
3 |
4 | self sendEvent: anEventType withBuffer: #()
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/sendEvent.withBuffer..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | sendEvent: anEventType withBuffer: aBuffer
3 |
4 | self process
5 | stdinPutUint32: aBuffer size + 2;
6 | stdinPutChar: $e;
7 | stdinPutUint8: (self eventTypeCode: anEventType);
8 | stdinPutBuffer: aBuffer asByteArray
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/sendEvent.withNumber..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | sendEvent: anEventType withNumber: aNumber
3 |
4 | self sendEvent: anEventType withBuffer: ((ByteArray new: 4)
5 | unsignedLongAt: 1 put: aNumber asInteger;
6 | yourself)
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/sendEvent.withNumber.andNumber..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | sendEvent: anEventType withNumber: firstNumber andNumber: secondNumber
3 |
4 | self sendEvent: anEventType withBuffer: ((ByteArray new: 8)
5 | unsignedLongAt: 1 put: firstNumber asInteger;
6 | unsignedLongAt: 5 put: secondNumber asInteger;
7 | yourself)
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/sendEvent.withPoint..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | sendEvent: anEventType withPoint: aPoint
3 |
4 | self sendEvent: anEventType withNumber: aPoint x andNumber: aPoint y
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/sendEvent.withString..st:
--------------------------------------------------------------------------------
1 | -> to chrome
2 | sendEvent: anEventType withString: aString
3 |
4 | self sendEvent: anEventType withBuffer: aString squeakToUtf8 asByteArray
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/updateImage..st:
--------------------------------------------------------------------------------
1 | <- from chrome
2 | updateImage: payload
3 |
4 | | format png form |
5 | format := payload unsignedCharAt: 2.
6 | format = $r
7 | ifTrue: [ | width height |
8 | width := payload unsignedLongAt: 3.
9 | height := payload unsignedLongAt: 7.
10 | png := payload copyFrom: 11 to: payload size.
11 | form := Form extent: width @ height depth: 32.
12 | 1 to: width * height - 1 do: [:index |
13 | form bits
14 | at: index
15 | put: (png at: index * 4 + 3) asInteger << 0 + ((png at: index * 4 + 2) asInteger << 8) + ((png at: index * 4 + 1) asInteger << 16) + ((png at: index * 4 + 0) asInteger << 24)]]
16 | ifFalse: [ | reader |
17 | format caseOf: {
18 | [$p] -> [
19 | reader := PNGReadWriter on: (payload copyFrom: 3 to: payload size) readStream.
20 | form := reader nextImage].
21 | [$j] -> [
22 | reader := MMFastJPEGReader on: (payload readStream
23 | skip: 2;
24 | yourself).
25 | form := reader nextImage: self image]}].
26 | form extent > viewportSize ifTrue: ["We need to crop the incoming image if we don't run Chrome in headless mode
27 | or if we just resized the window and Chrome still sends us a bigger image than now requested."
28 | form := form contentsOfArea: (0 @ 0 extent: viewportSize)].
29 |
30 | self image: form
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/urlFromDomainObject..st:
--------------------------------------------------------------------------------
1 | domain objects
2 | urlFromDomainObject: aDomainObject
3 |
4 | | url |
5 | ^ #(#url #website #homepage #uri #domain)
6 | detect: [:message |
7 | url := aDomainObject ? message.
8 | url notNil and: [url notEmpty]]
9 | ifFound: [:message | url]
10 | ifNone: [nil]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/viewportSize..st:
--------------------------------------------------------------------------------
1 | accessing
2 | viewportSize: anObject
3 |
4 | viewportSize := anObject
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/viewportSize.st:
--------------------------------------------------------------------------------
1 | accessing
2 | viewportSize
3 |
4 | ^ viewportSize
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/instance/wantsDroppedMorph.event..st:
--------------------------------------------------------------------------------
1 | dropping/grabbing
2 | wantsDroppedMorph: aMorph event: evt
3 |
4 | (aMorph isKindOf: TransferMorph) ifTrue: [ | passenger |
5 | passenger := aMorph passenger.
6 | (Smalltalk classNamed: #DomainObject) ifNotNil: [:domainObject | (passenger isKindOf: domainObject) ifTrue: [^ true]].
7 | passenger isText ifTrue: [^ true]].
8 |
9 | (aMorph isSystemWindow and: [aMorph model isKindOf: Workspace]) ifTrue: [^ true].
10 |
11 | ^ false
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | "chromeProfilePath" : "MB 1/30/2022 10:33",
4 | "chromeProfilePath:" : "cmfcmf 11/11/2020 20:04",
5 | "debugEnabled" : "MB 1/30/2022 10:33",
6 | "debugEnabled:" : "cmfcmf 11/11/2020 19:53",
7 | "debugNodejs" : "MB 1/30/2022 10:33",
8 | "debugNodejs:" : "cmfcmf 11/11/2020 20:05",
9 | "doNotUsePrebuiltBinary" : "MB 1/30/2022 10:33",
10 | "doNotUsePrebuiltBinary:" : "cmfcmf 11/11/2020 20:05",
11 | "gitRepositoryPath" : "MB 1/30/2022 10:33",
12 | "gitRepositoryPath:" : "cmfcmf 11/11/2020 20:05",
13 | "initialize" : "MB 1/30/2022 10:33",
14 | "logo" : "MB 1/30/2022 10:33",
15 | "open" : "cmfcmf 6/30/2019 13:11",
16 | "openOn:" : "cmfcmf 6/30/2019 13:11",
17 | "portalsOnRightClick" : "MB 1/30/2022 10:33",
18 | "portalsOnRightClick:" : "cmfcmf 11/11/2020 20:05",
19 | "runChromeHeadless" : "MB 1/30/2022 10:33",
20 | "runChromeHeadless:" : "cmfcmf 11/11/2020 20:06",
21 | "shutDown:" : "cmfcmf 6/27/2019 10:20",
22 | "startUp:" : "MB 1/30/2022 10:33",
23 | "title" : "cmfcmf 6/30/2019 13:10" },
24 | "instance" : {
25 | "acceptDroppingMorph:event:" : "MB 1/30/2022 10:33",
26 | "assertNodeScriptExists" : "MB 1/30/2022 10:33",
27 | "changeLocation:" : "cmfcmf 6/19/2019 20:17",
28 | "connect:" : "MB 1/30/2022 10:33",
29 | "contentOfField:changedTo:" : "MB 1/30/2022 10:33",
30 | "delete" : "cmfcmf 8/3/2019 19:16",
31 | "dispose" : "MB 1/30/2022 10:33",
32 | "doRefresh" : "cmfcmf 7/2/2019 08:20",
33 | "domainObjectFieldFor:object:" : "MB 1/30/2022 10:33",
34 | "drawOn:" : "MB 1/30/2022 10:33",
35 | "droppedDomainObject:at:" : "MB 1/30/2022 10:33",
36 | "droppedString:at:" : "MB 1/30/2022 10:33",
37 | "eventTypeCode:" : "MB 1/30/2022 10:33",
38 | "extent:" : "MB 1/30/2022 10:33",
39 | "fullscreen:" : "MB 1/30/2022 10:33",
40 | "getIdAndRectFrom:startAt:" : "MB 1/30/2022 10:33",
41 | "gitClone:named:" : "MB 1/30/2022 10:33",
42 | "goBack" : "cmfcmf 7/2/2019 08:21",
43 | "goForward" : "cmfcmf 7/2/2019 08:21",
44 | "handleCodePortal:" : "MB 1/30/2022 10:33",
45 | "handleFormFields:" : "MB 1/30/2022 10:33",
46 | "handleGitClone:" : "MB 1/30/2022 10:33",
47 | "handlePortal:" : "MB 1/30/2022 10:33",
48 | "handlePortalImage:" : "MB 1/30/2022 10:33",
49 | "handlePortalMorph:" : "MB 1/30/2022 10:33",
50 | "handlePortalRefreshData:" : "MB 1/30/2022 10:33",
51 | "handleStructuredData:" : "cmfcmf 8/3/2019 19:35",
52 | "handlesKeyboard:" : "cmfcmf 7/3/2019 09:15",
53 | "handlesMouseDown:" : "T 4/18/2019 20:02",
54 | "handlesMouseMove:" : "cf 4/22/2019 14:02",
55 | "handlesMouseWheel:" : "TB 4/19/2019 09:54",
56 | "imageUpdaterProcess" : "cf 5/13/2019 15:43",
57 | "imageUpdaterProcess:" : "MB 1/30/2022 10:33",
58 | "initialize" : "MB 1/30/2022 10:33",
59 | "intoWorld:" : "cmfcmf 7/7/2019 11:03",
60 | "isConnected" : "cmfcmf 7/3/2019 09:15",
61 | "isFullscreen" : "cmfcmf 7/7/2019 19:03",
62 | "keyStroke:" : "cmfcmf 6/30/2019 14:24",
63 | "keyboardFocusColor" : "TB 4/19/2019 10:02",
64 | "location" : "cmfcmf 6/19/2019 20:18",
65 | "locationUpdated:" : "cmfcmf 7/8/2019 17:41",
66 | "mouseDown:" : "MB 1/30/2022 10:33",
67 | "mouseMove:" : "MB 1/30/2022 10:33",
68 | "mouseUp:" : "MB 1/30/2022 10:33",
69 | "mouseWheel:" : "cmfcmf 7/3/2019 09:16",
70 | "movePortal:rect:" : "cf 5/13/2019 16:36",
71 | "nameFromDomainObject:" : "MB 1/30/2022 10:33",
72 | "outOfWorld:" : "cmfcmf 6/30/2019 11:37",
73 | "portalMorphs" : "cf 5/13/2019 15:43",
74 | "portalMorphs:" : "cf 5/13/2019 15:43",
75 | "possibleDomainObjectFieldsFor:" : "MB 1/30/2022 10:33",
76 | "preferredExtent" : "T 4/18/2019 21:14",
77 | "process" : "cf 5/13/2019 15:43",
78 | "process:" : "MB 1/30/2022 10:33",
79 | "refreshPortals:" : "MB 1/30/2022 10:33",
80 | "searchFor:" : "cmfcmf 8/3/2019 19:29",
81 | "sendCommand:withBuffer:" : "MB 1/30/2022 10:33",
82 | "sendEvent:" : "cf 4/22/2019 22:32",
83 | "sendEvent:withBuffer:" : "MB 1/30/2022 10:33",
84 | "sendEvent:withNumber:" : "cf 4/22/2019 22:34",
85 | "sendEvent:withNumber:andNumber:" : "cf 4/22/2019 22:35",
86 | "sendEvent:withPoint:" : "cf 4/22/2019 22:35",
87 | "sendEvent:withString:" : "cmfcmf 7/3/2019 09:17",
88 | "updateImage:" : "MB 1/30/2022 10:33",
89 | "urlFromDomainObject:" : "MB 1/30/2022 10:33",
90 | "viewportSize" : "cf 5/13/2019 15:43",
91 | "viewportSize:" : "MB 1/30/2022 10:33",
92 | "wantsDroppedMorph:event:" : "MB 1/30/2022 10:33" } }
93 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBrowserMorph.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | "ChromeProfilePath",
7 | "DebugEnabled",
8 | "DebugNodejs",
9 | "DoNotUsePrebuiltBinary",
10 | "GitRepositoryPath",
11 | "Logo",
12 | "PortalsOnRightClick",
13 | "RunChromeHeadless" ],
14 | "commentStamp" : "cmfcmf 8/3/2019 19:11",
15 | "instvars" : [
16 | "process",
17 | "viewportSize",
18 | "imageUpdaterProcess",
19 | "portalMorphs",
20 | "location",
21 | "tmpDomainObject",
22 | "previousOwner" ],
23 | "name" : "MMBrowserMorph",
24 | "pools" : [
25 | ],
26 | "super" : "ImageMorph",
27 | "type" : "normal" }
28 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBufferedStream.class/README.md:
--------------------------------------------------------------------------------
1 | I am a custom stream that is used by MMUnixProcessWrapper to communicate with the process.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBufferedStream.class/instance/binary.st:
--------------------------------------------------------------------------------
1 | properties-setting
2 | binary
3 | "We need to overwrite this method, because it doesn't work when using BufferedAsyncFileReadStreams. If we don't change the readBuffer, the call to 'self uint32' fails, because the underlying collection returns characters."
4 |
5 | super binary.
6 | self readBuffer: (ReadWriteStream with: ByteArray new)
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBufferedStream.class/instance/maxReadBufferSize.st:
--------------------------------------------------------------------------------
1 | defaults
2 | maxReadBufferSize
3 |
4 | self flag: #todo.
5 | "This buffer will still overflow."
6 | ^ 400000000
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBufferedStream.class/instance/waitForDataReady.st:
--------------------------------------------------------------------------------
1 | read ahead buffer
2 | waitForDataReady
3 | "Block until at least one character is available in the readBuffer. This is not
4 | thread safe, and only one Process is permitted to send this message."
5 |
6 | self flag: #todo.
7 | " for some reason the browser stops working after some time when simply using 'self dataAvailableSemaphore wait'"
8 | (self dataAvailableSemaphore waitTimeoutMSecs: 30000) ifTrue: [self error: 'Did not receive a frame in 30 seconds']
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBufferedStream.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "binary" : "MB 1/30/2022 10:33",
6 | "maxReadBufferSize" : "MB 1/30/2022 10:33",
7 | "waitForDataReady" : "MB 1/30/2022 10:33" } }
8 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMBufferedStream.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 19:05",
8 | "instvars" : [
9 | ],
10 | "name" : "MMBufferedStream",
11 | "pools" : [
12 | ],
13 | "super" : "BufferedAsyncFileReadStream",
14 | "type" : "normal" }
15 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMFastJPEGReader.class/README.md:
--------------------------------------------------------------------------------
1 | I am a faster alternative to the JPEGReadWriter2 by allowing my users to provide me with a form to reuse for the next image.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMFastJPEGReader.class/instance/nextImage..st:
--------------------------------------------------------------------------------
1 | public access
2 | nextImage: aForm
3 | "Process the next image, but try to reuse aForm. Reuse is not guranteed."
4 |
5 | | bytes width height components jpegDecompressStruct jpegErrorMgr2Struct form |
6 | bytes := stream upToEnd.
7 | stream close.
8 | jpegDecompressStruct := ByteArray new: self primJPEGDecompressStructSize.
9 | jpegErrorMgr2Struct := ByteArray new: self primJPEGErrorMgr2StructSize.
10 | self primJPEGReadHeader: jpegDecompressStruct fromByteArray: bytes errorMgr: jpegErrorMgr2Struct.
11 | width := self primImageWidth: jpegDecompressStruct.
12 | height := self primImageHeight: jpegDecompressStruct.
13 | components := self primImageNumComponents: jpegDecompressStruct.
14 |
15 | "If the provided form has the same size and depth, reuse it instead of creating a new form."
16 | (aForm extent = (width @ height) and: [aForm depth = 32])
17 | ifTrue: [form := aForm]
18 | ifFalse: [form := Form extent: width @ height depth: 32].
19 |
20 | self
21 | primJPEGReadImage: jpegDecompressStruct
22 | fromByteArray: bytes
23 | onForm: form
24 | doDithering: true
25 | errorMgr: jpegErrorMgr2Struct.
26 | ^ form
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMFastJPEGReader.class/instance/nextImage.st:
--------------------------------------------------------------------------------
1 | public access
2 | nextImage
3 | "Create a dummy form the nextImage: call can reuse."
4 |
5 | ^ self nextImage: (Form extent: 1 @ 1)
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMFastJPEGReader.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "nextImage" : "MB 1/30/2022 10:33",
6 | "nextImage:" : "MB 1/30/2022 10:33" } }
7 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMFastJPEGReader.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 19:03",
8 | "instvars" : [
9 | ],
10 | "name" : "MMFastJPEGReader",
11 | "pools" : [
12 | ],
13 | "super" : "JPEGReadWriter2",
14 | "type" : "normal" }
15 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/README.md:
--------------------------------------------------------------------------------
1 | I am a helper class to download prebuilt binaries. I am called postLoad of the MagicMouse-Core package.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/download..st:
--------------------------------------------------------------------------------
1 | downloading
2 | download: aString
3 |
4 | UIManager default informUserDuring: [:bar | | document fileStream |
5 | bar value: 'Downloading plugin'.
6 | document := ('https://github.com/cmfcmf/MagicMouse/releases/download/latest/', aString) asUrl retrieveContents.
7 | bar value: 'Saving plugin'.
8 | fileStream := FileDirectory default fileNamed: aString.
9 | fileStream
10 | binary;
11 | nextPutAll: document content;
12 | close.
13 | OSProcess isUnix ifTrue: [OSProcess command: 'chmod +x ', fileStream fullName]]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/download.st:
--------------------------------------------------------------------------------
1 | downloading
2 | download
3 |
4 | self download: self filenameForCurrentPlatform
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/downloadForAllPlatforms.st:
--------------------------------------------------------------------------------
1 | downloading
2 | downloadForAllPlatforms
3 |
4 | self filenameDict valuesDo: [:each | self download: each]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/downloadIfConfirmed.st:
--------------------------------------------------------------------------------
1 | downloading
2 | downloadIfConfirmed
3 |
4 | | confirmed |
5 | confirmed := true.
6 | Smalltalk at: #SmalltalkCI ifAbsent: ["Don't ask for confirmation when running SmalltalkCI"
7 | confirmed := self confirm: 'To use MagicMouse, you need to download/update an additional binary to interface with Chrome/Chromium. Do you want to continue and download now?'].
8 | confirmed ifTrue: [self download]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/downloadIfNeededAndConfirmed.st:
--------------------------------------------------------------------------------
1 | downloading
2 | downloadIfNeededAndConfirmed
3 |
4 | (FileDirectory default fileExists: self filenameForCurrentPlatform) ifFalse: [self downloadIfConfirmed]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/filenameDict.st:
--------------------------------------------------------------------------------
1 | filenames
2 | filenameDict
3 |
4 | ^ Dictionary newFrom: {'Win32' -> 'magicmouse-win.exe'. 'Mac OS' -> 'magicmouse-macos'. 'unix' -> 'magicmouse-linux'}
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/filenameForCurrentPlatform.st:
--------------------------------------------------------------------------------
1 | filenames
2 | filenameForCurrentPlatform
3 |
4 | ^ self filenameDict at: Smalltalk platformName
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/class/removeDownloads.st:
--------------------------------------------------------------------------------
1 | deleting
2 | removeDownloads
3 |
4 | self filenameDict valuesDo: [:each | FileDirectory default deleteFileNamed: each ifAbsent: []]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | "download" : "cmfcmf 8/3/2019 16:21",
4 | "download:" : "MB 1/30/2022 10:33",
5 | "downloadForAllPlatforms" : "MB 1/30/2022 10:33",
6 | "downloadIfConfirmed" : "MB 1/30/2022 10:33",
7 | "downloadIfNeededAndConfirmed" : "MB 1/30/2022 10:33",
8 | "filenameDict" : "MB 1/30/2022 10:33",
9 | "filenameForCurrentPlatform" : "cmfcmf 8/3/2019 16:23",
10 | "removeDownloads" : "MB 1/30/2022 10:33" },
11 | "instance" : {
12 | } }
13 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMPluginDownloader.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 19:01",
8 | "instvars" : [
9 | ],
10 | "name" : "MMPluginDownloader",
11 | "pools" : [
12 | ],
13 | "super" : "Object",
14 | "type" : "normal" }
15 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/README.md:
--------------------------------------------------------------------------------
1 | I am an abstract superclass for operating-specific implementations of spawning, wrapping and terminating a process.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/class/newForOS.st:
--------------------------------------------------------------------------------
1 | instance creation
2 | newForOS
3 |
4 | ^ OSProcess isWindows
5 | ifTrue: [
6 | "Smalltalk platformSubtype = 'X64'
7 | ifTrue: ["
8 | MMWindowsProcessWrapperFFI new
9 | "]
10 | ifFalse: [MMWindowsProcessWrapperPW new]"]
11 | ifFalse: [MMUnixProcessWrapper new]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/disableDebug.st:
--------------------------------------------------------------------------------
1 | accessing
2 | disableDebug
3 |
4 | debug := false
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/enableDebug.st:
--------------------------------------------------------------------------------
1 | accessing
2 | enableDebug
3 | "Enable debugging to the Transcript. Must be called before a process is started."
4 |
5 | debug := true.
6 | Transcript class forceUpdate ifTrue: [Transcript show: 'WARNING: The "Force transcript updates to screen" preference is enabled. Consider to disable it to improve performance.']
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/initialize.st:
--------------------------------------------------------------------------------
1 | initialize-release
2 | initialize
3 |
4 | super initialize.
5 | debug := false
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/process..st:
--------------------------------------------------------------------------------
1 | accessing
2 | process: anObject
3 |
4 | process := anObject
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/process.st:
--------------------------------------------------------------------------------
1 | accessing
2 | process
3 |
4 | ^ process
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/startCommand.withArguments..st:
--------------------------------------------------------------------------------
1 | starting
2 | startCommand: command withArguments: arguments
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/startNodeScript.arguments.nodeArguments..st:
--------------------------------------------------------------------------------
1 | starting
2 | startNodeScript: aString arguments: arguments nodeArguments: nodeArguments
3 |
4 | | node |
5 | node := OSProcess isWindows ifTrue: ['node'] ifFalse: ['/usr/bin/node'].
6 | self startCommand: node withArguments: nodeArguments, {aString. '--'}, arguments
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdinPutBuffer..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutBuffer: aByteArray
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdinPutChar..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutChar: aCharacter
3 |
4 | self stdinPutUint8: aCharacter asciiValue
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdinPutUint32..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint32: anInteger
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdinPutUint8..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint8: anInteger
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdoutNextBuffer..st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextBuffer: anInteger
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdoutNextChar.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextChar
3 |
4 | ^ self stdoutNextUint8 asCharacter
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdoutNextUint32.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint32
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/stdoutNextUint8.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint8
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/instance/terminate.st:
--------------------------------------------------------------------------------
1 | stopping
2 | terminate
3 |
4 | self subclassResponsibility
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | "newForOS" : "MB 1/30/2022 10:33" },
4 | "instance" : {
5 | "disableDebug" : "cmfcmf 8/3/2019 19:00",
6 | "enableDebug" : "MB 1/30/2022 10:33",
7 | "initialize" : "cmfcmf 8/3/2019 19:00",
8 | "process" : "cmfcmf 8/3/2019 19:00",
9 | "process:" : "cmfcmf 8/3/2019 19:00",
10 | "startCommand:withArguments:" : "cmfcmf 8/3/2019 19:00",
11 | "startNodeScript:arguments:nodeArguments:" : "MB 1/30/2022 10:33",
12 | "stdinPutBuffer:" : "cmfcmf 8/3/2019 19:00",
13 | "stdinPutChar:" : "cmfcmf 8/3/2019 19:00",
14 | "stdinPutUint32:" : "cmfcmf 8/3/2019 19:00",
15 | "stdinPutUint8:" : "cmfcmf 8/3/2019 19:00",
16 | "stdoutNextBuffer:" : "cmfcmf 8/3/2019 19:00",
17 | "stdoutNextChar" : "cmfcmf 8/3/2019 19:00",
18 | "stdoutNextUint32" : "cmfcmf 8/3/2019 19:00",
19 | "stdoutNextUint8" : "cmfcmf 8/3/2019 19:00",
20 | "terminate" : "cmfcmf 8/3/2019 19:00" } }
21 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMProcessWrapper.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 18:59",
8 | "instvars" : [
9 | "process",
10 | "debug" ],
11 | "name" : "MMProcessWrapper",
12 | "pools" : [
13 | ],
14 | "super" : "Object",
15 | "type" : "normal" }
16 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMTextMorphWithModel.class/README.md:
--------------------------------------------------------------------------------
1 | I am a PluggableTextMorphPlus that doesn't require a model. I an used to display code portals.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMTextMorphWithModel.class/instance/aboutToStyle..st:
--------------------------------------------------------------------------------
1 | morphic ui
2 | aboutToStyle: aStyler
3 |
4 | ^ true
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMTextMorphWithModel.class/instance/initialize.st:
--------------------------------------------------------------------------------
1 | initialization
2 | initialize
3 |
4 | super initialize.
5 | self model: self
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMTextMorphWithModel.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "aboutToStyle:" : "cf 5/13/2019 08:41",
6 | "initialize" : "cf 5/13/2019 08:46" } }
7 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMTextMorphWithModel.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 18:52",
8 | "instvars" : [
9 | ],
10 | "name" : "MMTextMorphWithModel",
11 | "pools" : [
12 | ],
13 | "super" : "PluggableTextMorphPlus",
14 | "type" : "normal" }
15 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/README.md:
--------------------------------------------------------------------------------
1 | I am a wrapper around a Unix process managed using OSProcess.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/startCommand.withArguments..st:
--------------------------------------------------------------------------------
1 | starting
2 | startCommand: command withArguments: arguments
3 |
4 | | stdout outPipe stderr errPipe stdin inPipe |
5 | outPipe := OSPipe nonBlockingPipe.
6 | stdout := outPipe writer.
7 |
8 | errPipe := OSPipe nonBlockingPipe.
9 | stderr := errPipe writer.
10 |
11 | inPipe := OSPipe blockingPipe.
12 | stdin := inPipe reader.
13 | stdinWriter := inPipe writer binary.
14 |
15 | process := UnixProcess
16 | forkJob: command
17 | arguments: (arguments collect: #asString)
18 | environment: nil
19 | descriptors: {stdin. stdout. stderr}.
20 |
21 | stdoutReader := outPipe reader asMMBufferedStream.
22 | stdoutReader
23 | setBlocking;
24 | binary.
25 |
26 | debug ifTrue: [ | stderrReader |
27 | Transcript clear.
28 | stderrReader := errPipe reader.
29 | stderrReader ascii.
30 | [
31 | [stderrReader closed] whileFalse: [
32 | stderrReader next
33 | ifNil: [(Delay forSeconds: 1) wait]
34 | ifNotNil: [:next | Transcript show: next]]] fork]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/stdinPutBuffer..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutBuffer: aBuffer
3 |
4 | aBuffer ifNotEmpty: [
5 | stdinWriter
6 | nextPutAll: aBuffer;
7 | flush]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/stdinPutUint32..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint32: anInteger
3 |
4 | stdinWriter
5 | uint32: anInteger;
6 | flush
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/stdinPutUint8..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint8: anInteger
3 |
4 | stdinWriter
5 | nextPut: anInteger;
6 | flush
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/stdoutNextBuffer..st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextBuffer: anInteger
3 |
4 | ^ stdoutReader next: anInteger
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/stdoutNextUint32.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint32
3 |
4 | ^ stdoutReader uint32
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/stdoutNextUint8.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint8
3 |
4 | ^ stdoutReader next
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/instance/terminate.st:
--------------------------------------------------------------------------------
1 | stopping
2 | terminate
3 |
4 | self process
5 | closeStreams;
6 | sigterm
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "startCommand:withArguments:" : "MB 1/30/2022 10:33",
6 | "stdinPutBuffer:" : "MB 1/30/2022 10:33",
7 | "stdinPutUint32:" : "cmfcmf 8/3/2019 18:58",
8 | "stdinPutUint8:" : "cmfcmf 8/3/2019 18:58",
9 | "stdoutNextBuffer:" : "cf 4/24/2019 09:42",
10 | "stdoutNextUint32" : "cmfcmf 8/3/2019 18:58",
11 | "stdoutNextUint8" : "cmfcmf 8/3/2019 18:58",
12 | "terminate" : "cmfcmf 8/3/2019 18:58" } }
13 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMUnixProcessWrapper.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 18:57",
8 | "instvars" : [
9 | "stdinWriter",
10 | "stdoutReader" ],
11 | "name" : "MMUnixProcessWrapper",
12 | "pools" : [
13 | ],
14 | "super" : "MMProcessWrapper",
15 | "type" : "normal" }
16 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/README.md:
--------------------------------------------------------------------------------
1 | I am a wrapper around a Windows process managed using FFI.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/peek..st:
--------------------------------------------------------------------------------
1 | private
2 | peek: aHandle
3 |
4 | ^ CMFWinFFI peek: (aHandle getHandle unsignedLongLongAt: 1)
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/readBuffer.length..st:
--------------------------------------------------------------------------------
1 | private
2 | readBuffer: aHandle length: anInteger
3 |
4 | | bytesRead buffer |
5 | buffer := ByteArray new: anInteger.
6 |
7 | bytesRead := CMFWinFFI makePointerOf: ExternalType ulong.
8 | CMFWinFFI noError: [
9 | CMFWinFFI
10 | readFile: (aHandle getHandle unsignedLongLongAt: 1)
11 | buffer: buffer
12 | bytesToRead: anInteger
13 | bytesRead: bytesRead
14 | overlapped: nil].
15 | bytesRead := bytesRead getHandle unsignedLongAt: 1.
16 | self assert: bytesRead = anInteger.
17 |
18 | ^ buffer
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/startCommand.withArguments..st:
--------------------------------------------------------------------------------
1 | starting
2 | startCommand: command withArguments: arguments
3 |
4 | | startupInfo pipe commandLine |
5 | self flag: #todo.
6 | "Properly escape arguments?"
7 | commandLine := command, ' ', ((arguments collect: [:arg | '"', arg, '"']) joinSeparatedBy: ' ').
8 |
9 | pipe := CMFWinFFI makePipe: false.
10 | stdinRead := pipe first.
11 | stdinWrite := pipe second.
12 |
13 | pipe := CMFWinFFI makePipe: true size: 1000000000.
14 | stdoutRead := pipe first.
15 | stdoutWrite := pipe second.
16 |
17 | debug ifTrue: [
18 | pipe := CMFWinFFI makePipe: true.
19 | stderrRead := pipe first.
20 | stderrWrite := pipe second].
21 |
22 | startupInfo := CMFWinStartupInfo new.
23 | startupInfo
24 | cb: CMFWinStartupInfo byteSize;
25 | dwFlags: (1 bitOr: 256);
26 | wShowWindow: 0;
27 | hStdInput: (stdinRead getHandle unsignedLongLongAt: 1);
28 | hStdOutput: (stdoutWrite getHandle unsignedLongLongAt: 1);
29 | yourself.
30 | "STARTF_USESHOWWINDOW"
31 | "STARTF_USESTDHANDLES"
32 | "SW_HIDE"
33 | debug ifTrue: [startupInfo hStdError: (stderrWrite getHandle unsignedLongLongAt: 1)].
34 |
35 | processInformation := CMFWinProcessInformation new.
36 | CMFWinFFI noError: [
37 | CMFWinFFI
38 | createProcessApplicationName: nil
39 | commandLine: commandLine
40 | processAttributes: nil
41 | threadAttributes: nil
42 | inheritHandles: true
43 | creationFlags: 0
44 | environment: nil
45 | currentDirectory: nil
46 | startupInfo: startupInfo
47 | processInformation: processInformation].
48 |
49 | debug ifTrue: [
50 | Transcript clear.
51 | [
52 | [
53 | [ | bytesAvailable |
54 | bytesAvailable := self peek: stderrRead.
55 | bytesAvailable > 0 ifTrue: [Transcript show: (self stderrNextBuffer: bytesAvailable) asString].
56 | (Delay forMilliseconds: 200) wait.
57 | true] ifError: [false]] whileTrue] fork]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/stderrNextBuffer..st:
--------------------------------------------------------------------------------
1 | stderr
2 | stderrNextBuffer: anInteger
3 |
4 | self waitFor: anInteger at: stderrRead.
5 | ^ self readBuffer: stderrRead length: anInteger
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/stdinPutBuffer..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutBuffer: aBuffer
3 |
4 | | bytesWritten |
5 | bytesWritten := CMFWinFFI makePointerOf: ExternalType ulong.
6 | CMFWinFFI noError: [
7 | CMFWinFFI
8 | writeFile: (stdinWrite getHandle unsignedLongLongAt: 1)
9 | buffer: aBuffer asByteArray
10 | bytesToWrite: aBuffer size
11 | bytesWritten: bytesWritten
12 | overlapped: nil].
13 | bytesWritten := bytesWritten getHandle unsignedLongAt: 1.
14 | self assert: [bytesWritten = aBuffer size]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/stdinPutUint32..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint32: anInteger
3 |
4 | | buffer |
5 | buffer := ByteArray new: 4.
6 | buffer byteAt: 1 put: (anInteger digitAt: 4).
7 | buffer byteAt: 2 put: (anInteger digitAt: 3).
8 | buffer byteAt: 3 put: (anInteger digitAt: 2).
9 | buffer byteAt: 4 put: (anInteger digitAt: 1).
10 | self stdinPutBuffer: buffer
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/stdinPutUint8..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint8: anInteger
3 |
4 | | buffer |
5 | buffer := ByteArray new: 1.
6 | buffer byteAt: 1 put: anInteger.
7 | self stdinPutBuffer: buffer
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/stdoutNextBuffer..st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextBuffer: anInteger
3 |
4 | self waitFor: anInteger at: stdoutRead.
5 | ^ self readBuffer: stdoutRead length: anInteger
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/stdoutNextUint32.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint32
3 |
4 | | n buffer |
5 | buffer := self stdoutNextBuffer: 4.
6 | n := buffer byteAt: 1.
7 | n := (n bitShift: 8) + (buffer byteAt: 2).
8 | n := (n bitShift: 8) + (buffer byteAt: 3).
9 | n := (n bitShift: 8) + (buffer byteAt: 4).
10 | ^ n
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/stdoutNextUint8.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint8
3 |
4 | | buffer |
5 | buffer := self stdoutNextBuffer: 1.
6 | ^ buffer byteAt: 1
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/terminate.st:
--------------------------------------------------------------------------------
1 | stopping
2 | terminate
3 |
4 | processInformation ifNotNil: [
5 | CMFWinFFI terminateProcess: processInformation hProcess exitCode: 0.
6 |
7 | CMFWinFFI closeHandle: processInformation hThread.
8 | CMFWinFFI closeHandle: processInformation hProcess].
9 |
10 | [CMFWinFFI closeHandle: (stdinRead getHandle unsignedLongLongAt: 1)] ifError: [].
11 | [CMFWinFFI closeHandle: (stdinWrite getHandle unsignedLongLongAt: 1)] ifError: [].
12 |
13 | [CMFWinFFI closeHandle: (stdoutRead getHandle unsignedLongLongAt: 1)] ifError: [].
14 | [CMFWinFFI closeHandle: (stdoutWrite getHandle unsignedLongLongAt: 1)] ifError: [].
15 |
16 | [stderrRead ifNotNil: [CMFWinFFI closeHandle: (stderrRead getHandle unsignedLongLongAt: 1)]] ifError: [].
17 | [stderrWrite ifNotNil: [CMFWinFFI closeHandle: (stderrWrite getHandle unsignedLongLongAt: 1)]] ifError: []
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/instance/waitFor.at..st:
--------------------------------------------------------------------------------
1 | private
2 | waitFor: anInteger at: aHandle
3 |
4 | [(self peek: aHandle) < anInteger] whileTrue: [
5 | (Delay forMilliseconds: 5) wait
6 | "Processor yield"]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "peek:" : "cf 5/12/2019 15:16",
6 | "readBuffer:length:" : "MB 1/30/2022 10:33",
7 | "startCommand:withArguments:" : "MB 1/30/2022 10:33",
8 | "stderrNextBuffer:" : "cf 5/12/2019 14:36",
9 | "stdinPutBuffer:" : "MB 1/30/2022 10:33",
10 | "stdinPutUint32:" : "cf 5/12/2019 14:44",
11 | "stdinPutUint8:" : "cf 5/12/2019 14:02",
12 | "stdoutNextBuffer:" : "cf 5/12/2019 14:36",
13 | "stdoutNextUint32" : "cmfcmf 8/3/2019 18:58",
14 | "stdoutNextUint8" : "cmfcmf 8/3/2019 18:58",
15 | "terminate" : "MB 1/30/2022 10:33",
16 | "waitFor:at:" : "MB 1/30/2022 10:33" } }
17 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperFFI.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 18:56",
8 | "instvars" : [
9 | "stdinRead",
10 | "stdinWrite",
11 | "stdoutRead",
12 | "stdoutWrite",
13 | "stderrRead",
14 | "stderrWrite",
15 | "processInformation" ],
16 | "name" : "MMWindowsProcessWrapperFFI",
17 | "pools" : [
18 | ],
19 | "super" : "MMProcessWrapper",
20 | "type" : "normal" }
21 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/README.md:
--------------------------------------------------------------------------------
1 | I am a wrapper around ProcessWrapper (funny, eh?). As ProcessWrapper is rather unstable, I am not currently in use. Instead, use MMWindowsProcessWrapperFFI, a wrapper around FFI.
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/startCommand.withArguments..st:
--------------------------------------------------------------------------------
1 | starting
2 | startCommand: command withArguments: arguments
3 |
4 | self process: ProcessWrapper new.
5 | self process
6 | useStdout;
7 | useStderr.
8 | self flag: #todo.
9 | "Properly escape arguments"
10 | self process startWithShellCommand: command, ' ', ((arguments collect: [:arg | '"', arg, '"']) joinSeparatedBy: ' ').
11 | self process stdoutStream binary.
12 | self process stderrStream ascii.
13 |
14 | debug ifTrue: [
15 | Transcript clear.
16 | [[self process stderrStream atEnd] whileFalse: [Transcript show: self process stderrStream next]] fork]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/stdinPutBuffer..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutBuffer: aBuffer
3 |
4 | aBuffer ifNotEmpty: [self process writeToStdin: aBuffer asByteArray]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/stdinPutUint32..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint32: anInteger
3 |
4 | self stdinPutBuffer: {anInteger digitAt: 4. anInteger digitAt: 3. anInteger digitAt: 2. anInteger digitAt: 1}
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/stdinPutUint8..st:
--------------------------------------------------------------------------------
1 | stdin
2 | stdinPutUint8: anInteger
3 |
4 | self stdinPutBuffer: {anInteger}
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/stdoutNextBuffer..st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextBuffer: anInteger
3 |
4 | ^ self process stdoutStream next: anInteger
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/stdoutNextUint32.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint32
3 |
4 | | n |
5 | n := self process stdoutStream next.
6 | n := (n bitShift: 8) + self process stdoutStream next.
7 | n := (n bitShift: 8) + self process stdoutStream next.
8 | n := (n bitShift: 8) + self process stdoutStream next.
9 | ^ n
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/stdoutNextUint8.st:
--------------------------------------------------------------------------------
1 | stdout
2 | stdoutNextUint8
3 |
4 | ^ self process stdoutStream next
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/instance/terminate.st:
--------------------------------------------------------------------------------
1 | stopping
2 | terminate
3 |
4 | self flag: #todo.
5 | "ProcessWrapper>>terminate never returns, therefore we just ask the process to end :("
6 | [
7 | self
8 | stdinPutUint32: 1;
9 | stdinPutChar: $k] ifError: ["Silently ignore errors"
10 | ]
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "startCommand:withArguments:" : "MB 1/30/2022 10:33",
6 | "stdinPutBuffer:" : "cmfcmf 8/3/2019 18:53",
7 | "stdinPutUint32:" : "MB 1/30/2022 10:33",
8 | "stdinPutUint8:" : "cmfcmf 8/3/2019 18:53",
9 | "stdoutNextBuffer:" : "cmfcmf 8/3/2019 18:53",
10 | "stdoutNextUint32" : "cmfcmf 8/3/2019 18:53",
11 | "stdoutNextUint8" : "cmfcmf 8/3/2019 18:53",
12 | "terminate" : "MB 1/30/2022 10:33" } }
13 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/MMWindowsProcessWrapperPW.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Core",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "cmfcmf 8/3/2019 18:54",
8 | "instvars" : [
9 | ],
10 | "name" : "MMWindowsProcessWrapperPW",
11 | "pools" : [
12 | ],
13 | "super" : "MMProcessWrapper",
14 | "type" : "normal" }
15 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/Object.extension/class/rickRoll.st:
--------------------------------------------------------------------------------
1 | *MagicMouse-Core
2 | rickRoll
3 |
4 | MMBrowser rickRoll
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/Object.extension/instance/rickRoll.st:
--------------------------------------------------------------------------------
1 | *MagicMouse-Core
2 | rickRoll
3 |
4 | self class rickRoll
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/Object.extension/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | "rickRoll" : "cmfcmf 6/30/2019 17:42" },
4 | "instance" : {
5 | "rickRoll" : "cmfcmf 6/30/2019 17:42" } }
6 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/Object.extension/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "Object" }
3 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/SearchBar.extension/instance/magicSearch.in..st:
--------------------------------------------------------------------------------
1 | *MagicMouse-Core
2 | magicSearch: text in: morph
3 |
4 | (text beginsWith: '!s')
5 | ifTrue: [^ MMBrowser searchFor: 'site:squeak.org ', (text copyAfter: $ )].
6 | (text includes: $ ) ifTrue: [^ MMBrowser searchFor: text].
7 | (text beginsWith: '!') ifTrue: [^ MMBrowser searchFor: (text copyAfter: $!)].
8 | ^ self smartSearch: text in: morph
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/SearchBar.extension/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "magicSearch:in:" : "cmfcmf 6/30/2019 17:49" } }
6 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/SearchBar.extension/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "SearchBar" }
3 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/SequenceableCollection.extension/instance/findFirst.startingAt..st:
--------------------------------------------------------------------------------
1 | *MagicMouse-Core
2 | findFirst: aBlock startingAt: anInteger
3 | "Return the index of my first element for which aBlock evaluates as true."
4 |
5 | | index |
6 | index := anInteger - 1.
7 | [(index := index + 1) <= self size] whileTrue:
8 | [(aBlock value: (self at: index)) ifTrue: [^index]].
9 | ^ 0
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/SequenceableCollection.extension/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "findFirst:startingAt:" : "cf 5/13/2019 15:03" } }
6 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/SequenceableCollection.extension/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "SequenceableCollection" }
3 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/TextEditor.extension/instance/browseClassFromIt.st:
--------------------------------------------------------------------------------
1 | *MagicMouse-Core
2 | browseClassFromIt
3 | "Launch a hierarchy browser for the class indicated by the current selection. If multiple classes matching the selection exist, let the user choose among them."
4 | | aClass text |
5 | text := self selection string withBlanksTrimmed.
6 | self lineSelectAndEmptyCheck: [ ^ self ].
7 | aClass := UIManager default
8 | classFromPattern: text
9 | withCaption: 'choose a class to browse...'
10 | in: model environment.
11 | aClass ifNil: [
12 | ((text beginsWith: 'http://') or: [text beginsWith: 'https://']) ifTrue: [
13 | ^ MMBrowser openOn: text].
14 | ^ morph flash ].
15 | self systemNavigation
16 | spawnHierarchyForClass: aClass
17 | selector: nil
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/TextEditor.extension/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "browseClassFromIt" : "cmfcmf 7/3/2019 11:24" } }
6 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/TextEditor.extension/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "TextEditor" }
3 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/monticello.meta/categories.st:
--------------------------------------------------------------------------------
1 | SystemOrganization addCategory: #'MagicMouse-Core'!
2 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/monticello.meta/initializers.st:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/packages/MagicMouse-Core.package/monticello.meta/initializers.st
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/monticello.meta/postscript.st:
--------------------------------------------------------------------------------
1 | (PackageInfo named: 'MagicMouse-Core') postscript: 'MMPluginDownloader downloadIfConfirmed'!
2 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/monticello.meta/preambleOfRemoval.st:
--------------------------------------------------------------------------------
1 | (PackageInfo named: 'MagicMouse-Core') preambleOfRemoval: 'MMPluginDownloader removeDownloads'!
2 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Core.package/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/.filetree:
--------------------------------------------------------------------------------
1 | {
2 | "noMethodMetaData" : true,
3 | "separateMethodMetaAndSource" : false,
4 | "useCypressPropertiesFile" : true }
5 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/.squot-contents:
--------------------------------------------------------------------------------
1 | SquotTrackedObjectMetadata {
2 | #objectClassName : #PackageInfo,
3 | #objectsReplacedByNames : true,
4 | #serializer : #SquotCypressCodeSerializer
5 | }
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/MMBrowserMorphTest.class/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/packages/MagicMouse-Tests.package/MMBrowserMorphTest.class/README.md
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/MMBrowserMorphTest.class/instance/testHandleStructuredData.st:
--------------------------------------------------------------------------------
1 | as yet unclassified
2 | testHandleStructuredData
3 |
4 | | payload result |
5 | payload := ByteArray new: 11.
6 | payload unsignedCharAt: 1 put: $s.
7 | payload unsignedLongAt: 2 put: 1.
8 | payload unsignedLongAt: 6 put: 2.
9 | payload unsignedCharAt: 10 put: ${.
10 | payload unsignedCharAt: 11 put: $}.
11 | result := MMBrowserMorph new handleStructuredData: payload.
12 | self assert: result size equals: 1.
13 | self assert: result first size equals: 0
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/MMBrowserMorphTest.class/instance/testHandleStructuredDataEmpty.st:
--------------------------------------------------------------------------------
1 | as yet unclassified
2 | testHandleStructuredDataEmpty
3 |
4 | | payload result |
5 | payload := ByteArray new: 5.
6 | payload unsignedCharAt: 1 put: $s.
7 | payload unsignedLongAt: 2 put: 0.
8 | result := MMBrowserMorph new handleStructuredData: payload.
9 | self assert: result size equals: 0
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/MMBrowserMorphTest.class/methodProperties.json:
--------------------------------------------------------------------------------
1 | {
2 | "class" : {
3 | },
4 | "instance" : {
5 | "testHandleStructuredData" : "MB 1/30/2022 10:33",
6 | "testHandleStructuredDataEmpty" : "MB 1/30/2022 10:33" } }
7 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/MMBrowserMorphTest.class/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "category" : "MagicMouse-Tests",
3 | "classinstvars" : [
4 | ],
5 | "classvars" : [
6 | ],
7 | "commentStamp" : "",
8 | "instvars" : [
9 | ],
10 | "name" : "MMBrowserMorphTest",
11 | "pools" : [
12 | ],
13 | "super" : "TestCase",
14 | "type" : "normal" }
15 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/monticello.meta/categories.st:
--------------------------------------------------------------------------------
1 | SystemOrganization addCategory: #'MagicMouse-Tests'!
2 |
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/monticello.meta/initializers.st:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmfcmf/MagicMouse/14f228841339e8a82ba7044389764a9ca53c0372/packages/MagicMouse-Tests.package/monticello.meta/initializers.st
--------------------------------------------------------------------------------
/packages/MagicMouse-Tests.package/properties.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/patches/puppeteer-core+1.19.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/puppeteer-core/lib/Events.js b/node_modules/puppeteer-core/lib/Events.js
2 | index c994de5..fbc9d65 100644
3 | --- a/node_modules/puppeteer-core/lib/Events.js
4 | +++ b/node_modules/puppeteer-core/lib/Events.js
5 | @@ -36,6 +36,7 @@ const Events = {
6 | Popup: 'popup',
7 | WorkerCreated: 'workercreated',
8 | WorkerDestroyed: 'workerdestroyed',
9 | + ScreencastFrame: 'screencastframe'
10 | },
11 |
12 | Browser: {
13 | diff --git a/node_modules/puppeteer-core/lib/Page.js b/node_modules/puppeteer-core/lib/Page.js
14 | index 0a8acb5..64d5558 100644
15 | --- a/node_modules/puppeteer-core/lib/Page.js
16 | +++ b/node_modules/puppeteer-core/lib/Page.js
17 | @@ -124,6 +124,7 @@ class Page extends EventEmitter {
18 | client.on('Performance.metrics', event => this._emitMetrics(event));
19 | client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
20 | client.on('Page.fileChooserOpened', event => this._onFileChooser(event));
21 | + client.on('Page.screencastFrame', event => this.emit(Events.Page.ScreencastFrame, event));
22 | this._target._isClosedPromise.then(() => {
23 | this.emit(Events.Page.Close);
24 | this._closed = true;
25 | @@ -176,6 +177,54 @@ class Page extends EventEmitter {
26 | });
27 | }
28 |
29 | + /**
30 | + * @param {!Object=} options
31 | + * @return {!Promise}
32 | + */
33 | + async startScreencast(options = {}) {
34 | + if (options.format)
35 | + console.assert(options.format === 'png' || options.format === 'jpeg', 'Unknown options.format value: ' + options.format);
36 | +
37 | + if (options.quality) {
38 | + console.assert(screenshotType === 'jpeg', 'options.quality is unsupported for the ' + screenshotType + ' screenshots');
39 | + console.assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality));
40 | + console.assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
41 | + console.assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality);
42 | + }
43 | + if (options.maxWidth) {
44 | + console.assert(typeof options.maxWidth === 'number', 'Expected options.maxWidth to be a number but found ' + (typeof options.maxWidth));
45 | + console.assert(Number.isInteger(options.maxWidth), 'Expected options.maxWidth to be an integer');
46 | + console.assert(options.maxHeight >= 0, 'Expected options.maxWidth to be greater than 0, got ' + options.maxWidth);
47 | + }
48 | + if (options.maxHeight) {
49 | + console.assert(typeof options.maxHeight === 'number', 'Expected options.maxHeight to be a number but found ' + (typeof options.maxHeight));
50 | + console.assert(Number.isInteger(options.maxHeight), 'Expected options.maxHeight to be an integer');
51 | + console.assert(options.maxHeight >= 0, 'Expected options.maxHeight to be greater than 0, got ' + options.maxHeight);
52 | + }
53 | + if (options.everyNthFrame) {
54 | + console.assert(typeof options.everyNthFrame === 'number', 'Expected options.everyNthFrame to be a number but found ' + (typeof options.everyNthFrame));
55 | + console.assert(Number.isInteger(options.everyNthFrame), 'Expected options.everyNthFrame to be an integer');
56 | + console.assert(options.everyNthFrame >= 0, 'Expected options.everyNthFrame to be greater than 0, got ' + options.everyNthFrame);
57 | + }
58 | + await this._client.send('Page.startScreencast', options);
59 | + }
60 | +
61 | + /**
62 | + * @param {!number} sessionId
63 | + * @return {!Promise}
64 | + */
65 | + async screencastFrameAck(sessionId) {
66 | + console.assert(sessionId, 'Expected sessionId');
67 | + await this._client.send('Page.screencastFrameAck', { sessionId });
68 | + }
69 | +
70 | + /**
71 | + * @return {!Promise}
72 | + */
73 | + async stopScreencast() {
74 | + await this._client.send('Page.stopScreencast');
75 | + }
76 | +
77 | /**
78 | * @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options
79 | */
80 |
--------------------------------------------------------------------------------
/run.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require("puppeteer-core");
2 | const SmartBuffer = require("smart-buffer").SmartBuffer;
3 | const getPixels = require("get-pixels");
4 | const findChrome = require("chrome-finder");
5 | const awaitifyStream = require("awaitify-stream");
6 | const { getElements } = require("./getElements");
7 | const uuid = require("uuid").v1;
8 |
9 | Array.prototype.flat = function () {
10 | return this.reduce((acc, x) => acc.concat(x), []);
11 | };
12 | Array.prototype.flatMap = function (mapper) {
13 | return this.map(mapper).flat();
14 | };
15 |
16 | SmartBuffer.prototype.writeStringPrependSize = function (string) {
17 | const buffer = Buffer.from(string, "utf8");
18 | this.writeUInt32LE(buffer.length);
19 | this.writeBuffer(buffer);
20 | };
21 |
22 | const ID_ATTRIBUTE = "data-magic-mouse-id";
23 | const IMAGE_FORMAT = "jpeg"; // "raw", "jpeg", "png"
24 |
25 | console.error("Node.js arguments", process.argv);
26 | console.error("Image format", IMAGE_FORMAT);
27 |
28 | const run = async () => {
29 | const url = process.argv[process.argv.length - 5];
30 | const screenSize = {
31 | x: parseInt(process.argv[process.argv.length - 4], 10),
32 | y: parseInt(process.argv[process.argv.length - 3], 10),
33 | };
34 | const headless = process.argv[process.argv.length - 2] === "headless";
35 | const chromeProfilePath = process.argv[process.argv.length - 1];
36 |
37 | const terminate = async () => {
38 | console.error("Terminating...");
39 | // await page.stopScreencast();
40 | await browser.close();
41 | process.exit();
42 | };
43 |
44 | const sendCommand = (buffer) => {
45 | const lenBuffer = new Buffer(4);
46 | lenBuffer.writeUInt32BE(buffer.length);
47 | process.stdout.write(lenBuffer);
48 | process.stdout.write(buffer);
49 | };
50 |
51 | const browser = await puppeteer.launch({
52 | ignoreDefaultArgs: true,
53 | args: [
54 | "--enable-automation",
55 | ...(headless ? ["--headless"] : []),
56 | // '--start-fullscreen',
57 | "--force-device-scale-factor=1",
58 | `--user-data-dir=${chromeProfilePath}`,
59 | ],
60 | executablePath: findChrome(),
61 | });
62 | const page = await browser.newPage();
63 | await page.setBypassCSP(true);
64 | await page.setUserAgent(
65 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
66 | );
67 | await page.setViewport({ width: screenSize.x, height: screenSize.y });
68 |
69 | process.on("SIGTERM", terminate);
70 |
71 | const instrumentGoogleSlides = async () => {
72 | if (!page.url().startsWith("https://docs.google.com/presentation")) {
73 | return;
74 | }
75 | console.error("Instrumenting Google Slides");
76 | const morphPositions = (
77 | await Promise.all(
78 | page
79 | .frames()
80 | .filter((frame) => !frame.isDetached())
81 | .map(async (frame) =>
82 | frame.evaluate(
83 | (ID_ATTRIBUTE) =>
84 | Promise.all(
85 | Array.from(
86 | new Set(
87 | Array.from(
88 | document.querySelectorAll('.punch-viewer-svgpage g[id*="paragraph"]'),
89 | ).map((element) => element.parentNode),
90 | ),
91 | )
92 | .map((element) => ({
93 | element,
94 | text: Array.from(element.querySelectorAll('g[id*="paragraph"]'))
95 | .map((paragraph) =>
96 | Array.from(paragraph.querySelectorAll(".sketchy-text-content-text text"))
97 | .map((text) => text.textContent)
98 | .join(" "),
99 | )
100 | .join("\r"),
101 | }))
102 | .filter((tmp) => tmp.text.startsWith("!"))
103 | .map((tmp) => {
104 | let element = tmp.element;
105 | let path = null;
106 | do {
107 | element = element.parentNode;
108 | path = element.querySelector("path");
109 | } while (path === null);
110 | return { ...tmp, path };
111 | })
112 | .map(async (tmp) => {
113 | const rect = tmp.path.getBoundingClientRect();
114 |
115 | let id = tmp.path.getAttribute(ID_ATTRIBUTE);
116 | if (!id) {
117 | id = await window.uuid();
118 | tmp.path.setAttribute(ID_ATTRIBUTE, id);
119 | }
120 |
121 | return {
122 | id,
123 | type: "morph",
124 | x: rect.x,
125 | y: rect.y,
126 | w: rect.width,
127 | h: rect.height,
128 | data: tmp.text,
129 | };
130 | }),
131 | ),
132 | ID_ATTRIBUTE,
133 | ),
134 | ),
135 | )
136 | ).flat();
137 |
138 | console.error("Google Slides Morph Positions", morphPositions);
139 | morphPositions.forEach(sendPortalDataCommand);
140 | };
141 |
142 | const instrumentGitHub = async () => {
143 | if (!page.url().startsWith("https://github.com/")) {
144 | return;
145 | }
146 | console.error("Instrumenting clone button");
147 | page.evaluate(() => {
148 | const bar = document.getElementsByClassName("file-navigation");
149 | if (bar.length === 0) {
150 | return;
151 | }
152 |
153 | const urlField = document.querySelector(".https-clone-options input");
154 | if (!urlField) {
155 | return;
156 | }
157 | const cloneUrl = urlField.value;
158 | let name = cloneUrl.split("/")[4];
159 | name = name.substr(0, name.length - 4);
160 | bar[0].insertAdjacentHTML(
161 | "beforeEnd",
162 | `Clone to Squeak`,
163 | );
164 |
165 | const normalCloneButton = document.getElementsByClassName("get-repo-select-menu");
166 | if (normalCloneButton.length === 0 || normalCloneButton[0].children.length === 0) {
167 | return;
168 | }
169 | normalCloneButton[0].children[0].classList.remove("btn-primary");
170 | });
171 | };
172 |
173 | const parseLDJsons = async () => {
174 | const ldJsons = await page.$$eval('script[type="application/ld+json"]', (nodes) =>
175 | nodes.map((node) => JSON.parse(node.innerText)),
176 | );
177 | console.error("LD JSON", ldJsons);
178 | const buf = new SmartBuffer();
179 | buf.writeString("s");
180 | buf.writeUInt32LE(ldJsons.length);
181 | ldJsons.forEach((ldJson) => {
182 | const json = JSON.stringify(ldJson);
183 | buf.writeStringPrependSize(json);
184 | });
185 | await sendCommand(buf.toBuffer());
186 | };
187 |
188 | const ignoreExecutionContextDestroyed = (error) => {
189 | // Sometimes the frame is destroyed right after navigation, thus throwing an error
190 | // when trying to evaluate a function in the frame's context. That's why we catch
191 | // those errors here.
192 | if (
193 | !error.message.endsWith(
194 | "Execution context was destroyed, most likely because of a navigation.",
195 | ) &&
196 | !error.message.endsWith("Cannot find context with specified id")
197 | ) {
198 | throw error;
199 | }
200 | };
201 |
202 | page.on("dialog", async (dialog) => {
203 | // TODO: We could send the dialog contents to Squeak and ask the user what to do.
204 | // For now, accept all dialogs, so that the page remains responsive.
205 | console.error("Dialog!", dialog.defaultValue(), dialog.message(), dialog.type());
206 | await dialog.accept();
207 | });
208 |
209 | page.on("framenavigated", async (frame) => {
210 | await new Promise((resolve) => setTimeout(() => resolve(), 50));
211 | try {
212 | await parseLDJsons();
213 | await instrumentGoogleSlides();
214 | await instrumentGitHub();
215 |
216 | if (!frame.parentFrame()) {
217 | const url = page.url();
218 | console.error(`Navigating to ${url}`);
219 | const buf = new SmartBuffer();
220 | buf.writeString("l");
221 | buf.writeString(url);
222 | sendCommand(buf.toBuffer());
223 | }
224 | } catch (error) {
225 | ignoreExecutionContextDestroyed(error);
226 | }
227 | });
228 |
229 | page.on("domcontentloaded", async () => {
230 | await parseLDJsons();
231 | // TODO: Is this needed when we have framenavigated?
232 | // await instrumentGitHub();
233 | // await instrumentGoogleSlides();
234 | });
235 |
236 | const refreshTrackedElements = async () => {
237 | const trackedElements = (
238 | await Promise.all(
239 | page
240 | .frames()
241 | .filter((frame) => !frame.isDetached())
242 | .map((frame) =>
243 | frame.evaluate(getElements, "refreshInfo").catch((error) => {
244 | ignoreExecutionContextDestroyed(error);
245 | return [];
246 | }),
247 | ),
248 | )
249 | ).flat();
250 | // console.error(trackedElements);
251 | const buf = new SmartBuffer();
252 | buf.writeString("hr"); // portal refresh
253 | buf.writeUInt32LE(trackedElements.length);
254 | trackedElements.forEach((element) =>
255 | buf
256 | .writeStringNT(element.id)
257 | .writeInt32LE(element.x)
258 | .writeInt32LE(element.y)
259 | .writeInt32LE(element.w)
260 | .writeInt32LE(element.h),
261 | );
262 | sendCommand(buf.toBuffer());
263 | };
264 |
265 | page.on("screencastframe", async (frame) => {
266 | const screenshot = Buffer.from(frame.data, "base64");
267 |
268 | const buf = new SmartBuffer();
269 | if (IMAGE_FORMAT === "png") {
270 | buf.writeString("ip");
271 | buf.writeBuffer(screenshot);
272 | } else if (IMAGE_FORMAT === "jpeg") {
273 | buf.writeString("ij");
274 | buf.writeBuffer(screenshot);
275 | } else if (IMAGE_FORMAT === "raw") {
276 | const pixels = await new Promise((resolve, reject) =>
277 | getPixels(screenshot, "image/png", (err, pixels) => {
278 | if (err) {
279 | reject(err);
280 | } else {
281 | resolve(pixels);
282 | }
283 | }),
284 | );
285 | buf.writeString("ir");
286 | buf.writeUInt32LE(pixels.shape[0]);
287 | buf.writeUInt32LE(pixels.shape[1]);
288 | buf.writeBuffer(Buffer.from(pixels.data));
289 | } else {
290 | throw new Error(`Unsupported image format ${IMAGE_FORMAT}.`);
291 | }
292 | sendCommand(buf.toBuffer());
293 |
294 | await refreshTrackedElements();
295 |
296 | await page.screencastFrameAck(frame.sessionId);
297 | console.error(`Sent frame at ${Date.now() / 1000}`);
298 | });
299 |
300 | await page.exposeFunction("uuid", () => uuid());
301 | await page.exposeFunction("gitClone", async (name, url) => {
302 | console.error("GIT CLONE", name, url);
303 | const buf = new SmartBuffer();
304 | buf.writeString("g");
305 | buf.writeStringPrependSize(name);
306 | buf.writeString(url);
307 | await sendCommand(buf.toBuffer());
308 | });
309 |
310 | const sendPortalDataCommand = (data) => {
311 | const buf = new SmartBuffer();
312 | switch (data.type) {
313 | case "img":
314 | case "canvas":
315 | buf.writeString("hi");
316 | break;
317 | case "pre":
318 | buf.writeString("hc");
319 | break;
320 | case "morph":
321 | buf.writeString("hm");
322 | break;
323 | }
324 | buf.writeStringNT(data.id);
325 | buf.writeInt32LE(data.x);
326 | buf.writeInt32LE(data.y);
327 | buf.writeInt32LE(data.w);
328 | buf.writeInt32LE(data.h);
329 | switch (data.type) {
330 | case "img":
331 | case "canvas":
332 | buf.writeBuffer(Buffer.from(data.data, "base64"));
333 | break;
334 | case "pre":
335 | case "morph":
336 | buf.writeString(data.data);
337 | break;
338 | }
339 | sendCommand(buf.toBuffer());
340 | };
341 |
342 | // TODO: This throws an error in case the page can't be reached (e.g., when you have no network connection)
343 | console.error(`Navigating to ${url}`);
344 | await page.goto(url);
345 |
346 | console.error("Starting screencast...");
347 | await page.startScreencast({
348 | format: IMAGE_FORMAT === "jpeg" ? "jpeg" : "png",
349 | everyNthFrame: 1,
350 | });
351 | console.error("Recording screencast...");
352 |
353 | const reader = awaitifyStream.createReader(process.stdin);
354 | while (true) {
355 | const size = (await reader.readAsync(4)).readUInt32BE();
356 | console.error(`Waiting for payload of size ${size}`);
357 | const command = String.fromCharCode((await reader.readAsync(1)).readUInt8());
358 | const payload =
359 | size > 1 ? SmartBuffer.fromBuffer(await reader.readAsync(size - 1)) : new SmartBuffer();
360 | console.error(`Received command ${command} with payload of size ${size}.`);
361 |
362 | switch (command) {
363 | case "s": {
364 | const x = payload.readInt32LE();
365 | const y = payload.readInt32LE();
366 | const str = payload.readString();
367 | console.error(`Dropped String at ${x}@${y}: "${str}"`);
368 |
369 | await page.evaluateHandle(
370 | (x, y, str) => {
371 | const field = document
372 | .elementsFromPoint(x, y)
373 | .find(
374 | (element) =>
375 | ["INPUT", "TEXTAREA"].includes(element.tagName) ||
376 | element.contentEditable === "true",
377 | );
378 | if (!field) {
379 | return;
380 | }
381 | if (field.tagName === "INPUT") {
382 | field.value = str;
383 | } else {
384 | field.innerText = str;
385 | }
386 | },
387 | x,
388 | y,
389 | str,
390 | );
391 | break;
392 | }
393 | case "f": {
394 | const x = payload.readInt32LE();
395 | const y = payload.readInt32LE();
396 | console.error(`DROPPED MORPH AT ${x}@${y}`);
397 |
398 | const form = await page.evaluateHandle(
399 | (x, y) => document.elementsFromPoint(x, y).find((element) => element.tagName === "FORM"),
400 | x,
401 | y,
402 | );
403 |
404 | const fields =
405 | (await form.jsonValue()) !== undefined
406 | ? await form.$$("input:not([type=checkbox])") /* , select */
407 | : [];
408 |
409 | const buf = new SmartBuffer();
410 | buf.writeString("f");
411 | const inputs = (
412 | await Promise.all(
413 | fields.map(async (field) => {
414 | if ((await (await field.getProperty("offsetParent")).jsonValue()) === null) {
415 | return undefined;
416 | }
417 | let description = await (await field.getProperty("placeholder")).jsonValue();
418 | if (description === undefined || description.length === 0) {
419 | description = await (await field.getProperty("name")).jsonValue();
420 | }
421 | if (description === undefined || description.length === 0) {
422 | return undefined;
423 | }
424 | const box = (await field.boxModel()).content;
425 | console.error(box);
426 | const boundingBox = {
427 | x: Math.round(box[0].x),
428 | y: Math.round(box[0].y),
429 | width: Math.round(box[2].x - box[0].x),
430 | height: Math.round(box[2].y - box[0].y),
431 | };
432 | console.error(boundingBox);
433 | return {
434 | boundingBox,
435 | description: description,
436 | id: await (
437 | await page.evaluateHandle(
438 | async (element, ID_ATTRIBUTE) => {
439 | let id = element.getAttribute(ID_ATTRIBUTE);
440 | if (!id) {
441 | id = await window.uuid();
442 | element.setAttribute(ID_ATTRIBUTE, id);
443 | }
444 | return id;
445 | },
446 | field,
447 | ID_ATTRIBUTE,
448 | )
449 | ).jsonValue(),
450 | };
451 | }),
452 | )
453 | ).filter((input) => input !== undefined);
454 | buf.writeInt32LE(inputs.length);
455 | inputs.forEach(({ boundingBox, description, id }) => {
456 | buf.writeInt32LE(boundingBox.x);
457 | buf.writeInt32LE(boundingBox.y);
458 | buf.writeInt32LE(boundingBox.width);
459 | buf.writeInt32LE(boundingBox.height);
460 | buf.writeStringPrependSize(description);
461 | buf.writeStringPrependSize(id);
462 | });
463 | sendCommand(buf.toBuffer());
464 | break;
465 | }
466 | case "t": {
467 | const id = payload.readString(payload.readInt32LE());
468 | const text = payload.readString(payload.readInt32LE());
469 | console.error(`Update Text: ${id} ${text}`);
470 | await page.evaluate(
471 | (id, text, ID_ATTRIBUTE) => {
472 | const element = document.querySelector(`[${ID_ATTRIBUTE}="${id}"]`);
473 | if (!element) {
474 | return;
475 | }
476 | if (element.tagName === "INPUT") {
477 | element.value = text;
478 | } else if (element.tagName === "SELECT") {
479 | const option = Array.from(element.options).find(
480 | (option) => option.innerText.toLocaleLowerCase() === text.toLocaleLowerCase(),
481 | );
482 | if (option) {
483 | element.value = option.value;
484 | }
485 | }
486 | },
487 | id,
488 | text.trim(),
489 | ID_ATTRIBUTE,
490 | );
491 | break;
492 | }
493 | case "k": {
494 | // This command is necessary because the Squeak ProcessWrapper is unable to terminate processes.
495 | await terminate();
496 | break;
497 | }
498 | case "l": {
499 | const url = payload.readString();
500 | console.error(`Navigating to ${url}`);
501 | try {
502 | await page.goto(url);
503 | } catch (error) {
504 | console.error(error);
505 | }
506 | break;
507 | }
508 | case "e": {
509 | // TODO check read boundaries for each event type
510 | const eventType = payload.readUInt8();
511 | console.error("Received event type", eventType);
512 | switch (eventType) {
513 | case 0:
514 | page.mouse.up({ button: "left" });
515 | break;
516 | case 1:
517 | page.mouse.down({ button: "left" });
518 | break;
519 | case 2:
520 | page.mouse.up({ button: "right" });
521 | break;
522 | case 3:
523 | page.mouse.down({ button: "right" });
524 | break;
525 | case 4: {
526 | const x = payload.readUInt32LE();
527 | const y = payload.readUInt32LE();
528 | console.error("Moving to", x, y);
529 | page.mouse.move(x, y);
530 | break;
531 | }
532 | case 5: {
533 | let squeakKeyString = payload.readString(size, "ascii");
534 |
535 | let keyName;
536 | const modifiers = [];
537 | if (squeakKeyString.startsWith(" when pressing CTRL+A on Windows
539 | squeakKeyString = squeakKeyString.replace("Cmd-", "");
540 | }
541 | if (squeakKeyString.startsWith(""
554 | ) {
555 | squeakKeyString = squeakKeyString[1];
556 | }
557 |
558 | if (squeakKeyString.length > 1) {
559 | const conversion = {
560 | "": "Space",
561 | "": "Tab",
562 | "": "Enter",
563 | "": "Enter",
564 | "": "Enter",
565 | "": "Backspace",
566 | "": "Delete",
567 | "": "Escape",
568 | "": "ArrowDown",
569 | "": "ArrowUp",
570 | "": "ArrowLeft",
571 | "": "ArrowRight",
572 | "": "End",
573 | "": "Home",
574 | "": "PageDown",
575 | "": "PageUp",
576 | // '': '', // TODO
577 | "": "Insert",
578 | };
579 | keyName = conversion[squeakKeyString];
580 | if (keyName === undefined) {
581 | console.error("Unknown key", squeakKeyString);
582 | break;
583 | }
584 | } else {
585 | keyName = squeakKeyString;
586 | }
587 | console.error("Typing", squeakKeyString, modifiers);
588 | try {
589 | await Promise.all(modifiers.map((key) => page.keyboard.down(key)));
590 | await page.keyboard.press(keyName);
591 | await Promise.all(modifiers.map((key) => page.keyboard.up(key)));
592 | } catch (error) {
593 | console.error(error);
594 | }
595 | break;
596 | }
597 | case 6: {
598 | const x = payload.readUInt32LE();
599 | const y = payload.readUInt32LE();
600 | page.setViewport({ width: x, height: y });
601 | break;
602 | }
603 | case 7: {
604 | const y = payload.readUInt32LE();
605 | console.error("scroll", y);
606 | page.evaluate((y) => window.scrollBy(0, y == 0 ? -20 : 20), y);
607 | break;
608 | }
609 | case 8: {
610 | // These are relative to the visible part of the page, not the top left corner
611 | const x = payload.readUInt32LE();
612 | const y = payload.readUInt32LE();
613 | console.error(`Portal event at ${x},${y}`);
614 |
615 | let elements;
616 | try {
617 | elements = await page.evaluate(getElements, "extractElements", x, y);
618 | } catch (error) {
619 | elements = [];
620 | console.error(error);
621 | }
622 | if (elements.length === 0) {
623 | console.error("No elements found to create a portal");
624 | break;
625 | }
626 | const element = elements[0];
627 | console.error({
628 | id: element.id,
629 | type: element.type,
630 | x: element.x,
631 | y: element.y,
632 | w: element.w,
633 | h: element.h,
634 | });
635 |
636 | sendPortalDataCommand(element);
637 | break;
638 | }
639 | case 9: {
640 | console.error("go back");
641 | await page.goBack();
642 | break;
643 | }
644 | case 10: {
645 | console.error("go forward");
646 | await page.goForward();
647 | break;
648 | }
649 | }
650 | break;
651 | }
652 | }
653 | }
654 | };
655 |
656 | // We catch all errors, log them to the console and exit.
657 | run().catch((error) => console.error(error));
658 |
--------------------------------------------------------------------------------