mirror of
https://github.com/DreamMaoMao/maomaowm.git
synced 2026-06-06 03:01:52 -04:00
mmsg: add man page and usage message
This commit is contained in:
parent
6849e5b4db
commit
a11cf12f28
3 changed files with 200 additions and 21 deletions
25
meson.build
25
meson.build
|
|
@ -1,4 +1,4 @@
|
||||||
project('mango', ['c', 'cpp'],
|
project('mango', ['c'],
|
||||||
version : '0.14.0',
|
version : '0.14.0',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,10 +24,6 @@ if sysconfdir.startswith(prefix) and not is_nixos
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# 打印调试信息,确认 sysconfdir 的值
|
|
||||||
# message('prefix: ' + prefix)
|
|
||||||
# message('sysconfdir: ' + sysconfdir)
|
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
libm = cc.find_library('m')
|
libm = cc.find_library('m')
|
||||||
xcb = dependency('xcb', required : get_option('xwayland'))
|
xcb = dependency('xcb', required : get_option('xwayland'))
|
||||||
|
|
@ -42,12 +38,11 @@ libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1')
|
||||||
pixman_dep = dependency('pixman-1')
|
pixman_dep = dependency('pixman-1')
|
||||||
cjson_dep = dependency('libcjson')
|
cjson_dep = dependency('libcjson')
|
||||||
|
|
||||||
|
# version info
|
||||||
# 获取版本信息
|
|
||||||
git = find_program('git', required : false)
|
git = find_program('git', required : false)
|
||||||
is_git_repo = false
|
is_git_repo = false
|
||||||
|
|
||||||
# 检查当前目录是否是 Git 仓库
|
# check if current directory is a git repo
|
||||||
if git.found()
|
if git.found()
|
||||||
git_status = run_command(git, 'rev-parse', '--is-inside-work-tree', check : false)
|
git_status = run_command(git, 'rev-parse', '--is-inside-work-tree', check : false)
|
||||||
if git_status.returncode() == 0 and git_status.stdout().strip() == 'true'
|
if git_status.returncode() == 0 and git_status.stdout().strip() == 'true'
|
||||||
|
|
@ -56,18 +51,18 @@ if git.found()
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if is_git_repo
|
if is_git_repo
|
||||||
# 如果是 Git 目录,获取 Commit Hash 和最新的 tag
|
# if current directory is a git repo, get commit hash and latest tag
|
||||||
commit_hash = run_command(git, 'rev-parse', '--short', 'HEAD', check : false).stdout().strip()
|
commit_hash = run_command(git, 'rev-parse', '--short', 'HEAD', check : false).stdout().strip()
|
||||||
latest_tag = meson.project_version()
|
latest_tag = meson.project_version()
|
||||||
version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash)
|
version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash)
|
||||||
else
|
else
|
||||||
# 如果不是 Git 目录,使用项目版本号和 "release" 字符串
|
# if not a git repo, use project version and "release" string
|
||||||
commit_hash = 'release'
|
commit_hash = 'release'
|
||||||
latest_tag = meson.project_version()
|
latest_tag = meson.project_version()
|
||||||
version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash)
|
version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# 定义编译参数
|
# define compilation args
|
||||||
c_args = [
|
c_args = [
|
||||||
'-g',
|
'-g',
|
||||||
'-Wno-unused-function',
|
'-Wno-unused-function',
|
||||||
|
|
@ -77,7 +72,7 @@ c_args = [
|
||||||
'-DSYSCONFDIR="@0@"'.format('/etc'),
|
'-DSYSCONFDIR="@0@"'.format('/etc'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# 仅在 debug 选项启用时添加调试参数
|
# add debug args only when debug option is enabled
|
||||||
if get_option('asan')
|
if get_option('asan')
|
||||||
c_args += [
|
c_args += [
|
||||||
'-fsanitize=address',
|
'-fsanitize=address',
|
||||||
|
|
@ -90,7 +85,7 @@ if xcb.found() and xlibs.found()
|
||||||
c_args += '-DXWAYLAND'
|
c_args += '-DXWAYLAND'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# 链接参数(根据 debug 状态添加 ASAN)
|
# define link args
|
||||||
link_args = []
|
link_args = []
|
||||||
if get_option('asan')
|
if get_option('asan')
|
||||||
link_args += '-fsanitize=address'
|
link_args += '-fsanitize=address'
|
||||||
|
|
@ -135,7 +130,7 @@ wayland_scanner_private_code = generator(
|
||||||
arguments: ['private-code', '@INPUT@', '@OUTPUT@']
|
arguments: ['private-code', '@INPUT@', '@OUTPUT@']
|
||||||
)
|
)
|
||||||
|
|
||||||
# 在 mmsg 目标中使用生成器
|
# use generator in mmsg target
|
||||||
executable('mmsg',
|
executable('mmsg',
|
||||||
'mmsg/mmsg.c',
|
'mmsg/mmsg.c',
|
||||||
wayland_scanner_private_code.process(dwl_ipc_protocol),
|
wayland_scanner_private_code.process(dwl_ipc_protocol),
|
||||||
|
|
@ -150,8 +145,10 @@ executable('mmsg',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mandir = get_option('mandir')
|
||||||
desktop_install_dir = join_paths(prefix, 'share/wayland-sessions')
|
desktop_install_dir = join_paths(prefix, 'share/wayland-sessions')
|
||||||
portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal')
|
portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal')
|
||||||
install_data('assets/mango.desktop', install_dir : desktop_install_dir)
|
install_data('assets/mango.desktop', install_dir : desktop_install_dir)
|
||||||
install_data('assets/mango-portals.conf', install_dir : portal_install_dir)
|
install_data('assets/mango-portals.conf', install_dir : portal_install_dir)
|
||||||
install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango'))
|
install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango'))
|
||||||
|
install_data('mmsg/mmsg.1', install_dir : join_paths(mandir, 'man1'))
|
||||||
122
mmsg/mmsg.1
Normal file
122
mmsg/mmsg.1
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
.TH "mmsg" "1" "2026-05-31" "mmsg 0.14.0" "mmsg manual"
|
||||||
|
.SH NAME
|
||||||
|
mmsg \- send commands to the mango Wayland compositor and receive JSON responses
|
||||||
|
.SH SYNOPSIS
|
||||||
|
\fBmmsg\fR <command> [\fIargs\fR...]
|
||||||
|
.br
|
||||||
|
\fBmmsg\fR \-\-help | \-h | help
|
||||||
|
.SH DESCRIPTION
|
||||||
|
\fBmmsg\fR connects to a running \fBmango\fR(1) compositor via the UNIX domain socket
|
||||||
|
specified by the \fIMANGO_INSTANCE_SIGNATURE\fR environment variable. It sends
|
||||||
|
the given command and prints the JSON reply to standard output. In \fBwatch\fR
|
||||||
|
mode it keeps the connection open and prints a stream of updates as they
|
||||||
|
occur.
|
||||||
|
.PP
|
||||||
|
If \fB\-\-help\fR, \fB\-h\fR or \fBhelp\fR is given, a complete usage summary is printed and
|
||||||
|
the program exits with success.
|
||||||
|
.SH COMMANDS
|
||||||
|
.SS "One\-shot queries (get)"
|
||||||
|
All \fBget\fR commands print a single JSON object and then close the connection.
|
||||||
|
.TP
|
||||||
|
\fBget version\fR
|
||||||
|
Return compositor version.
|
||||||
|
.TP
|
||||||
|
\fBget keymode\fR
|
||||||
|
Return current keymode.
|
||||||
|
.TP
|
||||||
|
\fBget keyboardlayout\fR
|
||||||
|
Return current keyboard layout.
|
||||||
|
.TP
|
||||||
|
\fBget last_open_surface\fR [\fImonitor\fR]
|
||||||
|
Return the last open surface (application) for the given monitor.
|
||||||
|
If \fImonitor\fR is omitted the focused monitor is used.
|
||||||
|
.TP
|
||||||
|
\fBget monitor\fR <name>
|
||||||
|
Return details of the named monitor.
|
||||||
|
.TP
|
||||||
|
\fBget focusing\-client\fR
|
||||||
|
Return details of the currently focused client.
|
||||||
|
.TP
|
||||||
|
\fBget client\fR <id>
|
||||||
|
Return details of the client with the given numeric ID.
|
||||||
|
.TP
|
||||||
|
\fBget tag\fR <monitor> <index>
|
||||||
|
Return details of a specific tag (1\-based index) on the named monitor.
|
||||||
|
.TP
|
||||||
|
\fBget all\-clients\fR
|
||||||
|
List all clients.
|
||||||
|
.TP
|
||||||
|
\fBget all\-monitors\fR
|
||||||
|
List all monitors.
|
||||||
|
.TP
|
||||||
|
\fBget all\-tags\fR
|
||||||
|
List tags for all monitors.
|
||||||
|
.TP
|
||||||
|
\fBget tags\fR <monitor>
|
||||||
|
List tags for a specific monitor.
|
||||||
|
.TP
|
||||||
|
\fBdispatch\fR <func>[,arg...] [client,<id>]
|
||||||
|
Invoke an internal compositor function.
|
||||||
|
The function name and its arguments are separated by commas.
|
||||||
|
Optionally add \fBclient,<id>\fR (before or after the function) to target a
|
||||||
|
specific client.
|
||||||
|
.br
|
||||||
|
Examples:
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBmmsg dispatch togglefloating\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBmmsg dispatch movewin,10,100\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBmmsg dispatch movewin,10,100 client,4\fR
|
||||||
|
.RE
|
||||||
|
.SS "Persistent streams (watch)"
|
||||||
|
\fBWatch\fR commands keep the connection open and continuously output JSON
|
||||||
|
updates. The initial state is sent immediately, followed by updates whenever
|
||||||
|
relevant changes occur.
|
||||||
|
.TP
|
||||||
|
\fBwatch monitor\fR <name>
|
||||||
|
Stream updates for the named monitor.
|
||||||
|
.TP
|
||||||
|
\fBwatch focusing\-client\fR
|
||||||
|
Stream updates for the focused client.
|
||||||
|
.TP
|
||||||
|
\fBwatch client\fR <id>
|
||||||
|
Stream updates for the client with the given ID.
|
||||||
|
.TP
|
||||||
|
\fBwatch tags\fR <monitor>
|
||||||
|
Stream tag updates for the named monitor.
|
||||||
|
.TP
|
||||||
|
\fBwatch all\-monitors\fR
|
||||||
|
Stream updates for all monitors.
|
||||||
|
.TP
|
||||||
|
\fBwatch all\-tags\fR
|
||||||
|
Stream updates for all tags.
|
||||||
|
.TP
|
||||||
|
\fBwatch all\-clients\fR
|
||||||
|
Stream updates for all clients.
|
||||||
|
.TP
|
||||||
|
\fBwatch keymode\fR
|
||||||
|
Stream keymode changes.
|
||||||
|
.TP
|
||||||
|
\fBwatch keyboardlayout\fR
|
||||||
|
Stream keyboard layout changes.
|
||||||
|
.TP
|
||||||
|
\fBwatch last_open_surface\fR [\fImonitor\fR]
|
||||||
|
Stream last open surface changes for a specific monitor (or the focused one).
|
||||||
|
.SH ENVIRONMENT
|
||||||
|
.TP
|
||||||
|
\fIMANGO_INSTANCE_SIGNATURE\fR
|
||||||
|
Path to the compositor's IPC socket. Set automatically by \fBmango\fR(1).
|
||||||
|
If unset, \fBmmsg\fR prints an error and exits.
|
||||||
|
.SH EXIT STATUS
|
||||||
|
.TP
|
||||||
|
\fB0\fR
|
||||||
|
Success (or help message printed).
|
||||||
|
.TP
|
||||||
|
\fBEXIT_FAILURE\fR
|
||||||
|
An error occurred (connection refused, send/receive error, etc.).
|
||||||
|
.SH SEE ALSO
|
||||||
|
\fBmango\fR(1)
|
||||||
|
.SH BUGS
|
||||||
|
Report issues at the project's issue tracker.
|
||||||
74
mmsg/mmsg.c
74
mmsg/mmsg.c
|
|
@ -7,7 +7,73 @@
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static void usage(void) {
|
||||||
|
printf("Usage: mmsg <command> [args...]\n\n");
|
||||||
|
printf("One-shot queries (get):\n");
|
||||||
|
printf(
|
||||||
|
" get version Show compositor version\n");
|
||||||
|
printf(" get keymode Show current keymode\n");
|
||||||
|
printf(" get keyboardlayout Show current keyboard "
|
||||||
|
"layout\n");
|
||||||
|
printf(" get last_open_surface [monitor] Show last open surface "
|
||||||
|
"(default focused monitor)\n");
|
||||||
|
printf(" get monitor <name> Show monitor details\n");
|
||||||
|
printf(" get focusing-client Show focused client "
|
||||||
|
"details\n");
|
||||||
|
printf(" get client <id> Show client details by "
|
||||||
|
"ID\n");
|
||||||
|
printf(" get tag <monitor> <index> Show tag details "
|
||||||
|
"(1‑based index)\n");
|
||||||
|
printf(" get all-clients List all clients\n");
|
||||||
|
printf(" get all-monitors List all monitors\n");
|
||||||
|
printf(" get all-tags List all tags (all "
|
||||||
|
"monitors)\n");
|
||||||
|
printf(
|
||||||
|
" get tags <monitor> List tags for a monitor\n");
|
||||||
|
printf(" dispatch <func>[,arg...] [client,<id>] Call a compositor "
|
||||||
|
"function\n");
|
||||||
|
printf(" <func> and arguments are separated by commas.\n");
|
||||||
|
printf(" Add 'client,<id>' at the beginning or end to target a "
|
||||||
|
"specific client.\n");
|
||||||
|
printf(" Examples:\n");
|
||||||
|
printf(" dispatch togglefloating\n");
|
||||||
|
printf(" dispatch movewin,10,100\n");
|
||||||
|
printf(" dispatch movewin,10,100 client,4\n");
|
||||||
|
printf("Persistent streams (watch):\n");
|
||||||
|
printf(
|
||||||
|
" watch monitor <name> Stream monitor changes\n");
|
||||||
|
printf(" watch focusing-client Stream focused client "
|
||||||
|
"changes\n");
|
||||||
|
printf(
|
||||||
|
" watch client <id> Stream client changes\n");
|
||||||
|
printf(" watch tags <monitor> Stream tag changes for "
|
||||||
|
"a monitor\n");
|
||||||
|
printf(" watch all-monitors Stream all monitors "
|
||||||
|
"changes\n");
|
||||||
|
printf(
|
||||||
|
" watch all-tags Stream all tags changes\n");
|
||||||
|
printf(" watch all-clients Stream all clients "
|
||||||
|
"changes\n");
|
||||||
|
printf(
|
||||||
|
" watch keymode Stream keymode changes\n");
|
||||||
|
printf(" watch keyboardlayout Stream keyboard layout "
|
||||||
|
"changes\n");
|
||||||
|
printf(" watch last_open_surface [monitor] Stream last open "
|
||||||
|
"surface changes\n\n");
|
||||||
|
printf("Environment:\n");
|
||||||
|
printf(" MANGO_INSTANCE_SIGNATURE IPC socket path (set by the "
|
||||||
|
"compositor)\n\n");
|
||||||
|
printf("Run 'mmsg --help', '-h' or 'help' to see this message.\n");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc >= 2 &&
|
||||||
|
(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0 ||
|
||||||
|
strcmp(argv[1], "help") == 0)) {
|
||||||
|
usage();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
fprintf(stderr, "Usage: mmsg <command> [args...]\n");
|
fprintf(stderr, "Usage: mmsg <command> [args...]\n");
|
||||||
fprintf(stderr, " get <type> ... one-shot request\n");
|
fprintf(stderr, " get <type> ... one-shot request\n");
|
||||||
|
|
@ -37,7 +103,6 @@ int main(int argc, char *argv[]) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拼接命令,缓冲区大小 4096 以容纳较长参数
|
|
||||||
char cmd[4096] = {0};
|
char cmd[4096] = {0};
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
|
|
@ -51,7 +116,6 @@ int main(int argc, char *argv[]) {
|
||||||
offset += n;
|
offset += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加换行符
|
|
||||||
int n = snprintf(cmd + offset, sizeof(cmd) - offset, "\n");
|
int n = snprintf(cmd + offset, sizeof(cmd) - offset, "\n");
|
||||||
if (n < 0 || n >= (int)(sizeof(cmd) - offset)) {
|
if (n < 0 || n >= (int)(sizeof(cmd) - offset)) {
|
||||||
fprintf(stderr, "Error: command too long to append newline.\n");
|
fprintf(stderr, "Error: command too long to append newline.\n");
|
||||||
|
|
@ -59,14 +123,12 @@ int main(int argc, char *argv[]) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送命令,使用 MSG_NOSIGNAL 避免 SIGPIPE
|
|
||||||
if (send(sock, cmd, strlen(cmd), MSG_NOSIGNAL) < 0) {
|
if (send(sock, cmd, strlen(cmd), MSG_NOSIGNAL) < 0) {
|
||||||
perror("send");
|
perror("send");
|
||||||
close(sock);
|
close(sock);
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将 socket 封装为行缓冲文件流,自动处理 TCP 拆包,按完整行读取
|
|
||||||
FILE *stream = fdopen(sock, "r");
|
FILE *stream = fdopen(sock, "r");
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
perror("fdopen");
|
perror("fdopen");
|
||||||
|
|
@ -74,7 +136,6 @@ int main(int argc, char *argv[]) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按行读取并输出,直到连接关闭(get 模式服务端主动 close)或出错
|
|
||||||
char *line = NULL;
|
char *line = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
while (getline(&line, &len, stream) != -1) {
|
while (getline(&line, &len, stream) != -1) {
|
||||||
|
|
@ -82,11 +143,10 @@ int main(int argc, char *argv[]) {
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否因读取错误退出(而非正常 EOF)
|
|
||||||
if (ferror(stream)) {
|
if (ferror(stream)) {
|
||||||
perror("recv");
|
perror("recv");
|
||||||
free(line);
|
free(line);
|
||||||
fclose(stream); // 关闭 stream 同时关闭 socket
|
fclose(stream);
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue