Linux display 命令在终端中有效,但在 systemd 服务中无效
Linux display command works in terminal, but not in systemd service
我制作了一个网络应用程序来关闭我的电脑屏幕,有一些不同的技术,但相当简单:
我有一个 html/js 前端检测按钮点击(屏幕打开/屏幕关闭),它通过 ajax
将选项发送到 PHP 后端
然后 php 通过 tcp 端口连接,将选项发送到用 golang 编写的程序
然后我的 golang 程序执行命令来打开 off/on 屏幕。
命令它 运行s 是 ("xset -display :0 dpms force off")
我遇到的问题是,只有在终端中 运行 运行 golang 程序时该命令才有效,但是当我将其设置为服务时,该命令将无法正常工作。
这是golang代码:
package main
import (
"os/exec"
"net"
"fmt"
"bufio"
)
func main() {
fmt.Println("Launching server")
ln, _ := net.Listen("tcp", ":7777")
fmt.Println("Listening...\n")
for {
// accept connection on port
conn, _ := ln.Accept()
fmt.Println("New connection")
// listen for message ending in \n
message, _ := bufio.NewReader(conn).ReadString('\n')
rec := string(message)
// remove trailing \n
rec = rec[:len(rec)-1]
fmt.Println("Message Received: ", "\""+rec+"\"")
returnMessage := "fail"
if (rec == "screensOff") {
fmt.Println("Turning off screens...")
//execute screens off command
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "off")
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(string(stdout))
returnMessage = "done"
}
} else if (rec == "screensOn") {
fmt.Println("Turning on screens...");
//execute screens on command
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "on")
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(string(stdout))
returnMessage = "done"
}
returnMessage = "done"
}
conn.Write([]byte(returnMessage + "\n"))
conn.Close()
fmt.Println("Connection closed\n")
}
}
相关PHP代码:
<?php
function sendServiceMessage($message) {
$host = "localhost";
$port = 7777;
$timeout = 30;
// connect to service
$socket = fsockopen($host, $port, $errnum, $errstr, $timeout);
if (!is_resource($socket)) {
exit("connection fail: ".$errnum." ".$errstr);
}
else {
// send message
fputs($socket, $message."\n");
// receive return message
$recieved = "";
while (!feof($socket)) {
$recieved .= fgets ($socket, 1024);
}
}
// close connection
fclose($socket);
if ($recieved == "done") {
return true;
}
return false;
}
sendServiceMessage("screensOff");
我使用 systemd 来设置服务,所以在构建程序并将其放置在 /usr/bin/
...$ go build screenControl.go
...$ sudo cp screenControl /usr/bin/screenControl
我可以 运行 终端中的 screenControl 程序,以及网络应用程序中的 select "screens off",一切都按预期工作:
...$ screenControl
Launching server
Listening...
New Connection
Message Received: "screensOff"
Turning off screens...
Connection closed
然后我创建了一个系统单元文件(/etc/systemd/system/screenControl.service):
[Unit]
Description=Screen control service
[Service]
ExecStart=/usr/bin/screenControl
Restart=on-abort
[Install]
WantedBy=multi-user.target
我启动了服务并检查了它:
...$ systemctl start screenControl
...$ systemctl status screenControl
● screenControl.service - Screen control service
Loaded: loaded (/etc/systemd/system/screenControl.service; disabled; vendor preset: enabled)
Active: active (running) since Sun 2015-12-13 22:31:54 GMT; 6s ago
Main PID: 19871 (screenControl)
CGroup: /system.slice/screenControl.service
└─19871 /usr/bin/screenControl
Dec 13 22:31:54 User systemd[1]: Started Screen control service.
Dec 13 22:31:54 User screenControl[19871]: Launching server
Dec 13 22:31:54 User screenControl[19871]: Listening...
所以它是 运行ning,但是当我 select 现在在网络应用程序中关闭屏幕时,没有任何反应......我再次检查了服务状态,它正在接收消息以打开屏幕关闭但命令正在退出并出现错误:
...
Dec 13 22:31:54 User screenControlTest[19871]: Launching server
Dec 13 22:31:54 User screenControlTest[19871]: Listening...
Dec 13 22:32:25 User screenControlTest[19871]: New connection
Dec 13 22:32:25 User screenControlTest[19871]: Message Received: "screensOff"
Dec 13 22:32:25 User screenControlTest[19871]: Turning off screens...
Dec 13 22:32:25 User screenControlTest[19871]: exit status 1
Dec 13 22:32:25 User screenControlTest[19871]: Connection closed
这里有什么问题,我怎样才能让该命令作为服务工作?一旦它开始工作,我想让服务在机器打开时自动启动,尽管使用 systemd 我认为这很简单:
...$ systemctl enable screenControl
任何帮助都会很棒,谢谢 :)
编辑
让 golang 程序显示 xset 命令的标准错误后,我现在也有错误消息:
xset: unable to open display ""
xset
命令只是X服务器的一个客户端。它通过检查 DISPLAY
环境变量来确定要与哪个 X 服务器通信,当您 运行 您的命令作为系统服务时不会设置该变量。
并且即使您确保 DISPLAY
在 运行 设置您的守护程序时设置,它也可能 运行 作为不同的用户帐户并且默认情况下拒绝访问显示。
更好的选择是 运行 将您的守护进程作为用户会话的一部分。这将解决身份验证问题(它将 运行ning 和你一样),以及定位显示的能力(环境变量应该是可见的)。当您未登录时,守护进程不会 运行ning,但这对于这个特定用例可能无关紧要。
您已使用 "Ubuntu" 标记您的问题,其中会话仍由 Upstart. You can create new user session jobs by creating a file in ~/.config/upstart
. The details of the file format can be found in the init(5)
man page 管理。
根据 David Budworth 的评论,修复非常简单;由于该服务在 root 下 运行ning,因此没有设置 DISPLAY 环境变量。
在 go 中,您可以像这样使用 exec 时设置环境变量:
//execute screens off command
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "off")
cmd.Env = []string{"DISPLAY=:0"} // set the display before executing
stdout, stderr := cmd.CombinedOutput() //execute and return all output
从 James Henstridge 的回答中我发现我还需要 运行 xhost +SI:localuser:root
以允许 root 用户访问 X 服务器。
您可以在用户登录后为他们执行此操作,方法是将此行添加到 /etc/profile
文件的顶部
xhost +SI:localuser:root > /dev/null 2>&1
或
即使没有用户登录(显示登录屏幕时),您也可以使用它
首先我创建了目录/opt/scripts
然后创建了文件 /opt/scripts/xhost.sh
并使用 chmod +x /opt/scripts/xhost.sh
赋予它可执行权限
在这个文件中只有一行:
xhost +SI:localuser:root > /dev/null 2>&1
然后编辑文件 /etc/lightdm/lightdm.conf
(我必须创建它,但如果它存在就编辑它)并添加行
display-setup-script=/opt/scripts/xhost.sh
所以我的 lightdm.conf
文件如下所示:
[SeatDefaults]
greeter-session=unity-greeter
user-session=ubuntu
display-setup-script=/opt/scripts/xhost.sh
这告诉 LightDM(Ubuntu 中的显示管理器 运行ning)运行 脚本 /opt/scripts/xhost.sh
在 X 服务器启动之后但在其他任何事情之前,因此root 立即获得 xhost 授权!
注:
显示设置脚本在 X 服务器启动后 运行 但在用户会话/欢迎程序 运行 之前。如果您需要在 X 服务器中配置任何特殊内容,请设置此项。它是 运行 作为 root。如果此命令 returns 错误代码 X 服务器已停止。
来源:https://wiki.ubuntu.com/LightDM
我制作了一个网络应用程序来关闭我的电脑屏幕,有一些不同的技术,但相当简单:
我有一个 html/js 前端检测按钮点击(屏幕打开/屏幕关闭),它通过 ajax
将选项发送到 PHP 后端然后 php 通过 tcp 端口连接,将选项发送到用 golang 编写的程序
然后我的 golang 程序执行命令来打开 off/on 屏幕。 命令它 运行s 是 ("xset -display :0 dpms force off")
我遇到的问题是,只有在终端中 运行 运行 golang 程序时该命令才有效,但是当我将其设置为服务时,该命令将无法正常工作。
这是golang代码:
package main
import (
"os/exec"
"net"
"fmt"
"bufio"
)
func main() {
fmt.Println("Launching server")
ln, _ := net.Listen("tcp", ":7777")
fmt.Println("Listening...\n")
for {
// accept connection on port
conn, _ := ln.Accept()
fmt.Println("New connection")
// listen for message ending in \n
message, _ := bufio.NewReader(conn).ReadString('\n')
rec := string(message)
// remove trailing \n
rec = rec[:len(rec)-1]
fmt.Println("Message Received: ", "\""+rec+"\"")
returnMessage := "fail"
if (rec == "screensOff") {
fmt.Println("Turning off screens...")
//execute screens off command
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "off")
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(string(stdout))
returnMessage = "done"
}
} else if (rec == "screensOn") {
fmt.Println("Turning on screens...");
//execute screens on command
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "on")
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(string(stdout))
returnMessage = "done"
}
returnMessage = "done"
}
conn.Write([]byte(returnMessage + "\n"))
conn.Close()
fmt.Println("Connection closed\n")
}
}
相关PHP代码:
<?php
function sendServiceMessage($message) {
$host = "localhost";
$port = 7777;
$timeout = 30;
// connect to service
$socket = fsockopen($host, $port, $errnum, $errstr, $timeout);
if (!is_resource($socket)) {
exit("connection fail: ".$errnum." ".$errstr);
}
else {
// send message
fputs($socket, $message."\n");
// receive return message
$recieved = "";
while (!feof($socket)) {
$recieved .= fgets ($socket, 1024);
}
}
// close connection
fclose($socket);
if ($recieved == "done") {
return true;
}
return false;
}
sendServiceMessage("screensOff");
我使用 systemd 来设置服务,所以在构建程序并将其放置在 /usr/bin/
...$ go build screenControl.go
...$ sudo cp screenControl /usr/bin/screenControl
我可以 运行 终端中的 screenControl 程序,以及网络应用程序中的 select "screens off",一切都按预期工作:
...$ screenControl
Launching server
Listening...
New Connection
Message Received: "screensOff"
Turning off screens...
Connection closed
然后我创建了一个系统单元文件(/etc/systemd/system/screenControl.service):
[Unit]
Description=Screen control service
[Service]
ExecStart=/usr/bin/screenControl
Restart=on-abort
[Install]
WantedBy=multi-user.target
我启动了服务并检查了它:
...$ systemctl start screenControl
...$ systemctl status screenControl
● screenControl.service - Screen control service
Loaded: loaded (/etc/systemd/system/screenControl.service; disabled; vendor preset: enabled)
Active: active (running) since Sun 2015-12-13 22:31:54 GMT; 6s ago
Main PID: 19871 (screenControl)
CGroup: /system.slice/screenControl.service
└─19871 /usr/bin/screenControl
Dec 13 22:31:54 User systemd[1]: Started Screen control service.
Dec 13 22:31:54 User screenControl[19871]: Launching server
Dec 13 22:31:54 User screenControl[19871]: Listening...
所以它是 运行ning,但是当我 select 现在在网络应用程序中关闭屏幕时,没有任何反应......我再次检查了服务状态,它正在接收消息以打开屏幕关闭但命令正在退出并出现错误:
...
Dec 13 22:31:54 User screenControlTest[19871]: Launching server
Dec 13 22:31:54 User screenControlTest[19871]: Listening...
Dec 13 22:32:25 User screenControlTest[19871]: New connection
Dec 13 22:32:25 User screenControlTest[19871]: Message Received: "screensOff"
Dec 13 22:32:25 User screenControlTest[19871]: Turning off screens...
Dec 13 22:32:25 User screenControlTest[19871]: exit status 1
Dec 13 22:32:25 User screenControlTest[19871]: Connection closed
这里有什么问题,我怎样才能让该命令作为服务工作?一旦它开始工作,我想让服务在机器打开时自动启动,尽管使用 systemd 我认为这很简单:
...$ systemctl enable screenControl
任何帮助都会很棒,谢谢 :)
编辑
让 golang 程序显示 xset 命令的标准错误后,我现在也有错误消息:
xset: unable to open display ""
xset
命令只是X服务器的一个客户端。它通过检查 DISPLAY
环境变量来确定要与哪个 X 服务器通信,当您 运行 您的命令作为系统服务时不会设置该变量。
并且即使您确保 DISPLAY
在 运行 设置您的守护程序时设置,它也可能 运行 作为不同的用户帐户并且默认情况下拒绝访问显示。
更好的选择是 运行 将您的守护进程作为用户会话的一部分。这将解决身份验证问题(它将 运行ning 和你一样),以及定位显示的能力(环境变量应该是可见的)。当您未登录时,守护进程不会 运行ning,但这对于这个特定用例可能无关紧要。
您已使用 "Ubuntu" 标记您的问题,其中会话仍由 Upstart. You can create new user session jobs by creating a file in ~/.config/upstart
. The details of the file format can be found in the init(5)
man page 管理。
根据 David Budworth 的评论,修复非常简单;由于该服务在 root 下 运行ning,因此没有设置 DISPLAY 环境变量。
在 go 中,您可以像这样使用 exec 时设置环境变量:
//execute screens off command
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "off")
cmd.Env = []string{"DISPLAY=:0"} // set the display before executing
stdout, stderr := cmd.CombinedOutput() //execute and return all output
从 James Henstridge 的回答中我发现我还需要 运行 xhost +SI:localuser:root
以允许 root 用户访问 X 服务器。
您可以在用户登录后为他们执行此操作,方法是将此行添加到 /etc/profile
文件的顶部
xhost +SI:localuser:root > /dev/null 2>&1
或
即使没有用户登录(显示登录屏幕时),您也可以使用它
首先我创建了目录/opt/scripts
然后创建了文件 /opt/scripts/xhost.sh
并使用 chmod +x /opt/scripts/xhost.sh
在这个文件中只有一行:
xhost +SI:localuser:root > /dev/null 2>&1
然后编辑文件 /etc/lightdm/lightdm.conf
(我必须创建它,但如果它存在就编辑它)并添加行
display-setup-script=/opt/scripts/xhost.sh
所以我的 lightdm.conf
文件如下所示:
[SeatDefaults]
greeter-session=unity-greeter
user-session=ubuntu
display-setup-script=/opt/scripts/xhost.sh
这告诉 LightDM(Ubuntu 中的显示管理器 运行ning)运行 脚本 /opt/scripts/xhost.sh
在 X 服务器启动之后但在其他任何事情之前,因此root 立即获得 xhost 授权!
注:
显示设置脚本在 X 服务器启动后 运行 但在用户会话/欢迎程序 运行 之前。如果您需要在 X 服务器中配置任何特殊内容,请设置此项。它是 运行 作为 root。如果此命令 returns 错误代码 X 服务器已停止。 来源:https://wiki.ubuntu.com/LightDM