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) ;
  }
} ;
1 Like

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) ;
  }
} ;
4 Likes

@sttk3
I tried this script of yours on mac m1 but it doesn’t work
I have manifest 5 and api 2

am I doing something wrong?

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() ;



document.getElementById("btnPopulate").addEventListener("click", test1);


async function test1() {

alert("ok")
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) ;
  }
} ;

}


async function test1run() {
  await require('photoshop').core.executeAsModal(test1, {
    commandName: 'Action Commands',
  })
}



@haller My code is a sample showing that it can be executed by opening the file alert.psjs on the desktop in Photoshop, and then the next time it will change to not even require permission.

What is your code trying to do? I could not see the intent just by reading the code.

Thanks for the reply. I didn’t do anything exceptional, I read now that it’s an example, so it doesn’t work, However I put a psjs file on the desktop with the name alert. psjs, I copied your code and I glued it as you see above, but it does not work, No warnings appear in debug. do I need to add some entries in the manifest file?

Could you answer the question I asked you? In other words, please tell me how you would judge it a success or not.

Already found some candidates for the necessary permissions or strange parts of the code, but we’ll discuss them in more detail after your answer.

I answered you above
the code doesn’t work for me
I put the alert.psjs file on the desktop
and the script you entered doesn’t make it work for me
I don’t get any errors in the debug, so I don’t know what’s wrong.

I don’t know what else I should tell you honestly

I already understood that your code does not work. Yet you have not answered my question.

Do you want to see the alert.psjs execution succeed? Or do you want to see the alert("ok") succeed? Or something else? Please indicate your purpose.

alert(“ok” ) and an error, sorry I didn’t understand

If there is no valid answer, this issue will be closed.