Panel with buttons to run psjs files

My goal is to create a panel using UXP with buttons that launch psjs file scripts, but from reading current documentation, inter-scripting is currently not possible?

@samsonsreaper This is not possible currently. You cannot have a plugin to launch scripts.
In a script you can use require to load another script module and call its methods.

Photoshop enables you to drag and drop script, so any reason why you need this panel?

I’m also interested in this feature. Didn’t try myself so far, and I see there’s no point to do that yet.

@Sujai, as a use case - my plugin allows users assign different actions to custom buttons on the panel (tool selection, menu item, actionset, layer create, etc.), so I’d really like to implement a feature, where users would be allowed to assign a script to a button and clicking it would run that script.

As a workaround, I guess running a script and recording it o an actionset could work as a workaround, but, as mentioned, I didn’t test it

if you don’t mind the default warning message you can just use good old app.open :

executeAsModal(async () => {
    let myScriptFile = await fs.getEntryWithUrl("file:\\D:\\myScript.psjs");
    await app.open(myScriptFile);
})
5 Likes

That is an interesting idea. It actually works.

This is a script that executes if the script warning is disabled, and if enabled, disables it and requests the user to restart.

It works on macOS, and it might work on Windows if I can locate the preferences folder.

const photoshop = require('photoshop') ;
const { app, core } = photoshop ;
const os = require('os') ;
const fs = require('fs') ;
const { localFileSystem } = require('uxp').storage ;

/**
  * Returns path to PSUserConfig.txt
  * @return {string} 
*/
const getPSUserConfigPath = () => {
  const psVersion = require('uxp').host.version ;
  const mainVersion = psVersion.split('.')[0] ;

  const versionTable = {
    '23': '2022', 
    '24': '2023', 
    '25': '2024'
  } ;
  const yyyy = versionTable[mainVersion] ;

  let preferencesPath ;
  if( /darwin/i.test(os.platform()) ) {
    // macOS
    preferencesPath = `${os.homedir()}/Library/Preferences/Adobe Photoshop ${yyyy} Settings` ;
  } else {
    // Windows
    // preferencesPath = `I don’t know detail` ;
  }

  const userConfigPath = path.join(preferencesPath, 'PSUserConfig.txt') ;
  return `file:${userConfigPath}` ;
} ;

/**
  * Returns srcPath exists or not
  * @return {boolean} 
*/
const exists = (srcPath) => {
  let res = false ;
  try {
    fs.lstatSync(srcPath) ;
    res = true ;
  } catch(e) {
    // skip
  }

  return res ;
} ;

/**
  * Returns whether or not to warn when script is opened in Photoshop
  * @return {boolean} 
*/
const getWarnRunningScripts = () => {
  let res = false ;

  const userConfigPath = getPSUserConfigPath() ;
  if(exists(userConfigPath)) {
    const configText = fs.readFileSync(userConfigPath, {encoding: 'utf-8'}) ;
    const matchObj = configText.match(/(WarnRunningScripts +)([01])/) ;
    if(matchObj[2]) {
      res = Boolean(Number(matchObj[2])) ;
    }
  }

  return res ;
} ;

/**
  * Set whether or not to warn when script is opened in Photoshop
  * @param {boolean | integer} bool new value
*/
const setWarnRunningScripts = (bool) => {
  const newValue = parseInt(Number(Boolean(bool))).toString() ;

  const userConfigPath = getPSUserConfigPath() ;
  let newText ;
  if(exists(userConfigPath)) {
    const oldText = fs.readFileSync(userConfigPath, {encoding: 'utf-8'}) ;
    const pattern = /(WarnRunningScripts +)([01])/ ;
    if(pattern.test(oldText)) {
      newText = oldText.replace(pattern, `$1${newValue}`) ;
    } else {
      newText = `${oldText}\nWarnRunningScripts ${newValue}` ;
    }
  } else {
    newText = `WarnRunningScripts ${newValue}` ;
  }

  try {
    fs.writeFileSync(userConfigPath, newText, {encoding: 'utf-8'}) ;
  } catch(e) {
    // skip
  }
} ;

/**
  * Execute script file via app.open
  * @param {string} targetPath script path e.g. 'file:/Users/username/Desktop/script.psjs'
*/
const execScriptViaOpen = async (targetPath) => {
  if(getWarnRunningScripts()) {
    // If script warnings are enabled, disable them and require user to restart
    setWarnRunningScripts(false) ;
    app.showAlert('Restart Photoshop once to execute scripts.') ;
  } else {
    const scriptEntry = await localFileSystem.getEntryWithUrl(targetPath) ;
    app.open(scriptEntry) ;
  }
} ;

const main = async () => {
  try {
    await core.executeAsModal(
      async (context) => {
        await execScriptViaOpen(`file:${os.homedir()}/Desktop/alert.psjs`) ;
      }, 

      {
        'commandName': 'execScriptViaOpen'
      }
    ) ;
  } catch(e) {
    console.log(e) ;
  }
} ;

I thought of this but it’s a bad practice to run quietly since we can’t guarantee users to not run malicious scripts from others in the future.

maybe communicating or giving the option with a fair warning is an acceptable behaviour but other than that it could harm someone potentially.

1 Like

thanks for the replies, it’s interesting to observe that one of the biggest thread in this forum is a about inter-scripting, so there is definitely an interest for it as i think many people have probably written small personal scripts that they never really planned to market and just use privately. But i can see the reasoning why it’s restricted.

It’s mainly the reason i wanted to do it, i have written some small scripts used by my artists(save and export tools) and i just wanted to put them on the new UXP platform

1 Like

I didn’t have to deal with this myself, but is there a chance it’s appRoamingLibrary for both OSes?

If Adobe determines that it can be abused, they will plug this hole also.

@Karmalakas The appRoamingLibrary is type Symbol, but I couldn’t figure out how to turn it into a string or an entry.

I might be wrong but doesn’t it have a nativePath property?

appRoamingLibrary.nativePath was undefined. Well, it can be found by looking up the Windows version. It is not so difficult.

const { domains } = require('uxp').storage ;

try {
  console.log(domains.appRoamingLibrary) ;
} catch(e) {
  console.log(e) ;
}
/*
  Symbol(appRoamingLibrary)
  TypeError: Cannot convert a Symbol value to a string
*/

console.log(domains.appRoamingLibrary.nativePath) ;
// --> undefined

Now it works on both macOS/Windows. Discard the previous code because the initial value of getWarnRunningScripts was incorrect.

const photoshop = require('photoshop') ;
const { app, core } = photoshop ;
const fs = require('fs') ;
const { localFileSystem } = require('uxp').storage ;
const { entrypoints } = require('uxp') ;
const os = require('os') ;
const homePath = os.homedir() ;

/**
  * Returns path to PSUserConfig.txt
  * @return {string} 
*/
const getPSUserConfigPath = () => {
  const psVersion = require('uxp').host.version ;
  const mainVersion = psVersion.split('.')[0] ;

  const versionTable = {
    '23': '2022', 
    '24': '2023', 
    '25': '2024'
  } ;
  const yyyy = versionTable[mainVersion] ;

  let preferencesPath ;
  if( /darwin/i.test(os.platform()) ) {
    // macOS
    preferencesPath = `${homePath}/Library/Preferences/Adobe Photoshop ${yyyy} Settings` ;
  } else {
    // Windows
    preferencesPath = `${homePath}\\AppData\\Roaming\\Adobe\\Adobe Photoshop ${yyyy}\\Adobe Photoshop ${yyyy} Settings` ;
  }

  const userConfigPath = path.join(preferencesPath, 'PSUserConfig.txt') ;
  return `file:${userConfigPath}` ;
} ;

/**
  * Returns srcPath exists or not
  * @return {boolean} 
*/
const exists = (srcPath) => {
  let res = false ;
  try {
    fs.lstatSync(srcPath) ;
    res = true ;
  } catch(e) {
    // skip
  }

  return res ;
} ;

/**
  * Returns whether or not to warn when script is opened in Photoshop
  * @return {boolean} 
*/
const getWarnRunningScripts = () => {
  let res = true ;

  const userConfigPath = getPSUserConfigPath() ;
  if(exists(userConfigPath)) {
    const configText = fs.readFileSync(userConfigPath, {encoding: 'utf-8'}) ;
    const matchObj = configText.match(/(WarnRunningScripts +)([01])/) ;
    if(matchObj[2]) {
      res = Boolean(Number(matchObj[2])) ;
    }
  }

  return res ;
} ;

/**
  * Set whether or not to warn when script is opened in Photoshop
  * @param {boolean | integer} bool new value
*/
const setWarnRunningScripts = (bool) => {
  const newValue = parseInt(Number(Boolean(bool))).toString() ;

  const userConfigPath = getPSUserConfigPath() ;
  let newText ;
  if(exists(userConfigPath)) {
    const oldText = fs.readFileSync(userConfigPath, {encoding: 'utf-8'}) ;
    const pattern = /(WarnRunningScripts +)([01])/ ;
    if(pattern.test(oldText)) {
      newText = oldText.replace(pattern, `$1${newValue}`) ;
    } else {
      newText = `${oldText}\nWarnRunningScripts ${newValue}` ;
    }
  } else {
    newText = `WarnRunningScripts ${newValue}` ;
  }

  try {
    fs.writeFileSync(userConfigPath, newText, {encoding: 'utf-8'}) ;
  } catch(e) {
    // skip
  }
} ;

/**
  * Execute script file via app.open
  * @param {string} targetPath script path e.g. 'file:/Users/username/Desktop/script.psjs'
*/
const execScriptViaOpen = async (targetPath) => {
  if(getWarnRunningScripts()) {
    // If script warnings are enabled, disable them and require user to restart
    setWarnRunningScripts(false) ;
    app.showAlert('Restart Photoshop once to execute scripts.') ;
  } else {
    const scriptEntry = await localFileSystem.getEntryWithUrl(targetPath) ;
    app.open(scriptEntry) ;
  }
} ;

const main = async () => {
  try {
    await core.executeAsModal(
      async (context) => {
        await execScriptViaOpen( path.join(`file:${homePath}`, 'Desktop', 'alert.psjs') ) ;
      }, 

      {
        'commandName': 'execScriptViaOpen'
      }
    ) ;
  } catch(e) {
    console.log(e) ;
  }
} ;
2 Likes