cmd.Run () навсегда зависает в golang на Macos, когда экран заблокирован / выключен

#macos #go #osascript

#macos #Вперед #osascript

Вопрос:

Я запускаю приложение golang на Macos. В нем есть несколько кодов, подобных следующему:

 for {
    time.Sleep(time.Second * 5)
    cmd := exec.Command("/usr/bin/osascript", "-e", `display dialog "hello" with title "hello"`)
    err := cmd.Run()
}
  

Это работает нормально, если я не блокирую экран (когда экран всегда включен). Но код err := cmd.Run() будет зависать вечно, если экран заблокирован и выключен при выполнении этой строки. Когда я разблокирую экран (включаю его), for цикл просто зависает там навсегда и никогда не продолжит свое выполнение.

Я не уверен, относится ли эта проблема к golang или как macOS обрабатывает osascript. Кто-нибудь, пожалуйста, может сказать мне, как это обойти? Большое спасибо.

PS: Я использую тот же код в Linux и заменяю /usr/bin/osascript на /usr/bin/xmessage , и это всегда работает нормально без каких-либо проблем, даже если экран заблокирован / выключен в Linux.

Отредактировано:

Мое решение — использовать chrome вместо:

 cmd := exec.Command(`/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`, "-new-window", "/path/hello.html")
  

Ответ №1:

Похоже, это связано с тем, как macOS переводит процессы в режим ожидания при блокировке экрана. Из-за этого osasscript дочерний процесс никогда не завершает выполнение и блокирует цикл for.

Единственное, что вы можете сделать, это запустить команду с контекстом тайм-аута. Я пробовал, и это работает. Выполнение возобновится, когда экран разблокирован и истечет время ожидания.

Пример:

 package main

import (
    "context"
    "fmt"
    "os/exec"
    "time"
)

func main() {
    for {
        time.Sleep(time.Second * 5)

        // run your command with a timeout
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        cmd := exec.CommandContext(
            ctx,
            "/usr/bin/osascript",
            "-e",
            `display dialog "hello" with title "hello"`,
        )

        err := cmd.Run()
        if err != nil {
            fmt.Println(err)
        }
        // don't forget to cancel your context to avoid context leak
        cancel()
    }
}
  

В качестве альтернативы, если вам не нужен тайм-аут, вы можете проверить, заблокирован ли экран, прежде чем пытаться вызвать диалоговое окно отображения.

 package main

import (
    "context"
    "fmt"
    "os"
    "os/exec"
    "strings"
    "time"
)

func main() {
    for {
        time.Sleep(time.Second * 5)

        ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
        cmd := exec.CommandContext(
            ctx,
            "python",
            "-c",
            "import sys,Quartz; d=Quartz.CGSessionCopyCurrentDictionary(); print d",
        )

        var err error
        var b []byte
        if b, err = cmd.CombinedOutput(); err != nil {
            cancel()
            continue
        }
        cancel()

        // if screen is not locked
        if !strings.Contains(string(b), "CGSSessionScreenIsLocked = 1") {
            cmd = exec.Command(
                "/usr/bin/osascript",
                "-e",
                "display dialog "Hello"",
            )
            cmd.Stdout = os.Stdout
            cmd.Stderr = os.Stderr

            err = cmd.Run()
            if err != nil {
                fmt.Println("err: ", err)
            }
        }
    }
}
  

Комментарии:

1. Большое спасибо за ответ. Это работает, но есть проблема. Мне нужно, чтобы диалоговое окно присутствовало вечно, пока я не нажму кнопку «OK» в диалоговом окне, когда экран НЕ выключен. С решением диалоговое окно исчезнет по истечении времени ожидания. Есть ли какое-либо лучшее решение? В противном случае мне приходится указывать очень большое значение тайм-аута, что не очень идеально. Спасибо.

2. О, конечно, есть решение. Дайте мне несколько минут.

3. Большое вам спасибо. Не торопитесь. ^_^

4. Похоже, проблема конкретно в display dialog я обновил код вторым примером, который проверяет, заблокирован ли экран, прежде чем пытаться вызвать диалоговое окно отображения.

5. Большое вам спасибо за ваш код и время. Раньше я не понимал, что эта проблема возникает только с osascript. Теперь я использую cmd := exec.Command(`/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`, "-new-window", "/path/hello.html") и пока все идет хорошо. Я думаю, что пока я буду использовать браузер. 🙂

Ответ №2:

Вы можете просто использовать amp; в конце вашей команды, которая заставит программу работать в фоновом режиме.

  for {
    time.Sleep(time.Second * 5)
    cmd := exec.Command("/usr/bin/osascript", "-e", `display dialog "hello" with title "hello"`, "amp;")
    err := cmd.Run()
}