octalmage / robotjs

Node.js Desktop Automation.
http://robotjs.io
MIT License
12.37k stars 961 forks source link

Robotjs works perfectly in dev - does nothing in production (macOS) #535

Closed sweeetland closed 4 years ago

sweeetland commented 4 years ago

Hi Guys

Sorry I've been stuck with this one for quite a while now and I'm not really sure what else to try.

I am using robotjs to simulate a cmd + v keyboard press to paste from the clipboard into the previously focused application. I am using electron-webpack and in dev everything works as it should. However, when I build for production using electron-builder and run the dmg everything else works fine except robotjs does nothing.

I'm probably just missing a build step but I'm not sure. I've looked at electron-builder and electron-webpack experimented with different configurations but had no joy. Any and all help with this is very much appreciated, thanks!

Environment

Here's a simplified version of the code:

package.json

{
    "name": "app",
    "version": "0.0.4",
    "license": "MIT",
    "electronWebpack": {
        "main": {
            "sourceDirectory": "src/main"
        },
        "renderer": {
            "sourceDirectory": "src/renderer"
        },
        "whiteListedModules": [
            "robotjs"
        ]
    },
    "build": {
        "appId": "com.electron.app",
        "productName": "app",
        "compression": "store",
        "files": [
            "**/*"
        ],
        "mac": {
            "category": "public.app-category.utilities"
        },
        "publish": {
            "provider": "s3",
            "bucket": "app-releases-dev",
            "region": "eu-west-1"
        },
        "buildDependenciesFromSource": "true",
        "npmRebuild": "true"
    },
    "scripts": {
        "dev": "electron-webpack dev",
        "compile": "electron-webpack",
        "dist": "NODE_ENV=production yarn compile && electron-builder",
        "dist:mac": "NODE_ENV=production yarn compile && electron-builder --mac --x64",
        "publish:mac": "NODE_ENV=production yarn compile && electron-builder --mac --x64 --publish always",
        "dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null",
        "postinstall": "electron-builder install-app-deps"
    },
    "resolutions": {
        "js-yaml": "3.13.1"
    },
    "dependencies": {
        "antd": "^3.26.0",
        "auto-launch": "^5.0.5",
        "electron-better-ipc": "^0.7.0",
        "electron-clipboard-extended": "^1.1.1",
        "electron-log": "^4.0.0",
        "electron-store": "^5.1.0",
        "electron-updater": "^4.2.0",
        "mousetrap": "^1.6.3",
        "node-cron": "^2.0.3",
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
        "robotjs": "^0.6.0",
        "source-map-support": "^0.5.12"
    },
    "devDependencies": {
        "@types/auto-launch": "^5.0.1",
        "@types/electron-clipboard-extended": "^1.0.1",
        "@types/jest": "^24.0.23",
        "@types/mousetrap": "^1.6.3",
        "@types/node": "^12.12.14",
        "@types/node-cron": "^2.0.2",
        "@types/react": "^16.9.13",
        "@types/react-dom": "^16.9.4",
        "@typescript-eslint/eslint-plugin": "^2.9.0",
        "@typescript-eslint/parser": "^2.9.0",
        "electron": "7.1.5",
        "electron-builder": "^21.2.0",
        "electron-webpack": "^2.7.4",
        "electron-webpack-ts": "^3.2.0",
        "eslint": "^6.7.1",
        "eslint-config-prettier": "^6.7.0",
        "eslint-config-react-app": "^5.0.2",
        "eslint-loader": "^3.0.2",
        "eslint-plugin-flowtype": "^4.5.2",
        "eslint-plugin-import": "^2.18.2",
        "eslint-plugin-jsx-a11y": "^6.2.3",
        "eslint-plugin-prettier": "^3.1.1",
        "eslint-plugin-react": "^7.17.0",
        "eslint-plugin-react-hooks": "^2.3.0",
        "node-sass": "^4.13.0",
        "prettier": "^1.19.1",
        "sass-loader": "^8.0.0",
        "typescript": "^3.7.2",
        "webpack": "~4.35.3"
    }
}

main/index.ts

import { app, BrowserWindow, globalShortcut, Menu } from 'electron'
import { join } from 'path'
import { format as formatUrl } from 'url'

import { ipcMain } from 'electron-better-ipc'
import { keyTap } from 'robotjs'

const isDevelopment = process.env.NODE_ENV !== 'production'

// global reference to mainWindow (necessary to prevent window from being garbage collected)
export let mainWindow: BrowserWindow | null

function createMainWindow() {
    const window = new BrowserWindow({
        fullscreenable: false,
        resizable: false,
        width: 265,
        height: 395,
        webPreferences: { nodeIntegration: true },
        show: false,
        title: 'appname',
        frame: false
    })

    window.setAlwaysOnTop(true, 'normal')
    window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true })

    if (isDevelopment) {
        // window.webContents.openDevTools()
        window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`)
    } else {
        window.loadURL(
            formatUrl({
                pathname: join(__dirname, 'index.html'),
                protocol: 'file',
                slashes: true
            })
        )
    }

    // Emitted when the window is closed.
    window.on('closed', () => {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null
    })

    window.on('blur', () => {
        Menu.sendActionToFirstResponder('hide:')
    })

    window.webContents.on('devtools-opened', () => {
        window.focus()
        setImmediate(() => {
            window.focus()
        })
    })

    window.on('minimize', (event: any) => {
        event.preventDefault()
        window.hide()
    })

    return window
}

const toggle = () =>
    mainWindow!.isVisible() ? Menu.sendActionToFirstResponder('hide:') : mainWindow!.show()

// Quit when all windows are closed.
app.on('window-all-closed', () => {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', () => {
    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (!mainWindow) mainWindow = createMainWindow()
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
    app.dock.hide()
    mainWindow = createMainWindow()

    ipcMain.answerRenderer('paste-on-close', () => {
        toggle()
        keyTap('v', 'command')
    })

    globalShortcut.register('Command+Control+V', () => {
        toggle()
    })
    console.log('app ready... 🚀')
})
sweeetland commented 4 years ago

So the issue was due to macOS permissions.

Un-granting accessibility and then checking the box again has seemed to fix the issue.

aabuhijleh commented 4 years ago

This is an issue I'm facing as well. If you deploy a new version of the app (I'm using Electron), even if the accessibility permission is already granted to the older version, it needs to be ungranted then granted again for robotjs to work.

This is annoying and there's no way to prompt the user to grant accessibility permissions again because they are already granted!

@sweeetland Did you ever find a solution to this issue?

cesarvarela commented 3 years ago

Not working when packaged for Windows either, any news?

xiajingren commented 2 years ago

macOS Monterey 12.3.1

Robotjs works perfectly in dev - does nothing in production, any news ?

xiajingren commented 2 years ago

This is an issue I'm facing as well. If you deploy a new version of the app (I'm using Electron), even if the accessibility permission is already granted to the older version, it needs to be ungranted then granted again for robotjs to work.

This is annoying and there's no way to prompt the user to grant accessibility permissions again because they are already granted!

@sweeetland Did you ever find a solution to this issue?

hi. @aabuhijleh @sweeetland

I have the same situation, have you solved it?

thx.