mirror of
https://github.com/labwc/labwc.git
synced 2026-06-13 14:33:18 -04:00
Add swaymsg-compatible IPC interface with labmsg client
This commit is contained in:
parent
bce14a5ad7
commit
8328c05041
20 changed files with 2291 additions and 11 deletions
389
clients/labmsg.c
Normal file
389
clients/labmsg.c
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* labmsg - IPC client for labwc (swaymsg-compatible interface)
|
||||
*/
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <json-c/json.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define IPC_MAGIC "labwc-ipc"
|
||||
#define IPC_MAGIC_LEN 9
|
||||
#define IPC_HEADER_SIZE (IPC_MAGIC_LEN + 4 + 4)
|
||||
|
||||
/* Message types */
|
||||
enum ipc_msg_type {
|
||||
IPC_RUN_COMMAND = 0,
|
||||
IPC_GET_WORKSPACES = 1,
|
||||
IPC_SUBSCRIBE = 2,
|
||||
IPC_GET_OUTPUTS = 3,
|
||||
IPC_GET_TREE = 4,
|
||||
IPC_GET_BAR_CONFIG = 6,
|
||||
IPC_GET_VERSION = 7,
|
||||
IPC_GET_CONFIG = 9,
|
||||
IPC_SEND_TICK = 10,
|
||||
IPC_SYNC = 11,
|
||||
IPC_GET_INPUTS = 100,
|
||||
IPC_GET_SEATS = 101,
|
||||
};
|
||||
|
||||
static const char *version_str = "labmsg 0.1";
|
||||
|
||||
static const struct option long_options[] = {{"help", no_argument, NULL, 'h'},
|
||||
{"monitor", no_argument, NULL, 'm'}, {"pretty", no_argument, NULL, 'p'},
|
||||
{"quiet", no_argument, NULL, 'q'}, {"raw", no_argument, NULL, 'r'},
|
||||
{"socket", required_argument, NULL, 's'},
|
||||
{"type", required_argument, NULL, 't'},
|
||||
{"version", no_argument, NULL, 'v'}, {0, 0, 0, 0}};
|
||||
|
||||
static const char usage_str[] =
|
||||
"Usage: labmsg [options] [message]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -h, --help Show help and exit\n"
|
||||
" -m, --monitor Monitor for events (subscribe mode)\n"
|
||||
" -p, --pretty Force pretty-printed JSON output\n"
|
||||
" -q, --quiet Suppress response output\n"
|
||||
" -r, --raw Force raw JSON output\n"
|
||||
" -s, --socket <path> Override socket path\n"
|
||||
" -t, --type <type> Message type (default: run_command)\n"
|
||||
" -v, --version Show version and exit\n"
|
||||
"\n"
|
||||
"Message types:\n"
|
||||
" run_command, get_workspaces, subscribe, get_outputs,\n"
|
||||
" get_tree, get_bar_config, get_version, get_config,\n"
|
||||
" send_tick, get_inputs, get_seats\n";
|
||||
|
||||
static int
|
||||
parse_msg_type(const char *name)
|
||||
{
|
||||
if (!name || !strcmp(name, "run_command")) {
|
||||
return IPC_RUN_COMMAND;
|
||||
} else if (!strcmp(name, "get_workspaces")) {
|
||||
return IPC_GET_WORKSPACES;
|
||||
} else if (!strcmp(name, "subscribe")) {
|
||||
return IPC_SUBSCRIBE;
|
||||
} else if (!strcmp(name, "get_outputs")) {
|
||||
return IPC_GET_OUTPUTS;
|
||||
} else if (!strcmp(name, "get_tree")) {
|
||||
return IPC_GET_TREE;
|
||||
} else if (!strcmp(name, "get_bar_config")) {
|
||||
return IPC_GET_BAR_CONFIG;
|
||||
} else if (!strcmp(name, "get_version")) {
|
||||
return IPC_GET_VERSION;
|
||||
} else if (!strcmp(name, "get_config")) {
|
||||
return IPC_GET_CONFIG;
|
||||
} else if (!strcmp(name, "send_tick")) {
|
||||
return IPC_SEND_TICK;
|
||||
} else if (!strcmp(name, "get_inputs")) {
|
||||
return IPC_GET_INPUTS;
|
||||
} else if (!strcmp(name, "get_seats")) {
|
||||
return IPC_GET_SEATS;
|
||||
}
|
||||
fprintf(stderr, "Unknown message type: %s\n", name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
ipc_connect(const char *socket_path)
|
||||
{
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr = {0};
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("connect");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool
|
||||
ipc_send(int fd, uint32_t type, const char *payload, uint32_t len)
|
||||
{
|
||||
char header[IPC_HEADER_SIZE];
|
||||
memcpy(header, IPC_MAGIC, IPC_MAGIC_LEN);
|
||||
memcpy(header + IPC_MAGIC_LEN, &len, sizeof(uint32_t));
|
||||
memcpy(header + IPC_MAGIC_LEN + 4, &type, sizeof(uint32_t));
|
||||
|
||||
if (write(fd, header, IPC_HEADER_SIZE) != IPC_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
if (len > 0 && write(fd, payload, len) != (ssize_t)len) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
read_exact(int fd, void *buf, size_t count)
|
||||
{
|
||||
size_t total = 0;
|
||||
while (total < count) {
|
||||
ssize_t n = read(fd, (char *)buf + total, count - total);
|
||||
if (n <= 0) {
|
||||
return false;
|
||||
}
|
||||
total += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ipc_recv(int fd, uint32_t *type, char **payload, uint32_t *len)
|
||||
{
|
||||
char header[IPC_HEADER_SIZE];
|
||||
if (!read_exact(fd, header, IPC_HEADER_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(header, IPC_MAGIC, IPC_MAGIC_LEN) != 0) {
|
||||
fprintf(stderr, "Invalid IPC response magic\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(len, header + IPC_MAGIC_LEN, sizeof(uint32_t));
|
||||
memcpy(type, header + IPC_MAGIC_LEN + 4, sizeof(uint32_t));
|
||||
|
||||
if (*len > 0) {
|
||||
*payload = malloc(*len + 1);
|
||||
if (!*payload) {
|
||||
return false;
|
||||
}
|
||||
if (!read_exact(fd, *payload, *len)) {
|
||||
free(*payload);
|
||||
*payload = NULL;
|
||||
return false;
|
||||
}
|
||||
(*payload)[*len] = '\0';
|
||||
} else {
|
||||
*payload = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
print_json(const char *data, bool pretty)
|
||||
{
|
||||
struct json_object *obj = json_tokener_parse(data);
|
||||
if (!obj) {
|
||||
/* Not valid JSON, print as-is */
|
||||
printf("%s\n", data);
|
||||
return;
|
||||
}
|
||||
|
||||
int flags = pretty ? (JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED)
|
||||
: JSON_C_TO_STRING_PLAIN;
|
||||
|
||||
printf("%s\n", json_object_to_json_string_ext(obj, flags));
|
||||
json_object_put(obj);
|
||||
}
|
||||
|
||||
/* Check if any command in the result array failed */
|
||||
static bool
|
||||
check_command_success(const char *data)
|
||||
{
|
||||
struct json_object *obj = json_tokener_parse(data);
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
if (json_object_get_type(obj) == json_type_array) {
|
||||
int len = json_object_array_length(obj);
|
||||
for (int i = 0; i < len; i++) {
|
||||
struct json_object *item =
|
||||
json_object_array_get_idx(obj, i);
|
||||
struct json_object *s = NULL;
|
||||
if (json_object_object_get_ex(item, "success", &s)) {
|
||||
if (!json_object_get_boolean(s)) {
|
||||
success = false;
|
||||
/* Print error to stderr */
|
||||
struct json_object *err = NULL;
|
||||
if (json_object_object_get_ex(item,
|
||||
"error", &err)) {
|
||||
fprintf(stderr, "Error: %s\n",
|
||||
json_object_get_string(
|
||||
err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (json_object_get_type(obj) == json_type_object) {
|
||||
struct json_object *s = NULL;
|
||||
if (json_object_object_get_ex(obj, "success", &s)) {
|
||||
success = json_object_get_boolean(s);
|
||||
}
|
||||
}
|
||||
|
||||
json_object_put(obj);
|
||||
return success;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
|
||||
bool monitor = false;
|
||||
bool pretty = false;
|
||||
bool raw = false;
|
||||
bool quiet = false;
|
||||
const char *socket_path = NULL;
|
||||
const char *type_str = NULL;
|
||||
bool force_pretty = false;
|
||||
|
||||
int c;
|
||||
while (1) {
|
||||
int index = 0;
|
||||
c = getopt_long(argc, argv, "hmpqrs:t:v", long_options, &index);
|
||||
if (c == -1) {
|
||||
break;
|
||||
}
|
||||
switch (c) {
|
||||
case 'h':
|
||||
printf("%s", usage_str);
|
||||
return 0;
|
||||
case 'm':
|
||||
monitor = true;
|
||||
break;
|
||||
case 'p':
|
||||
pretty = true;
|
||||
force_pretty = true;
|
||||
break;
|
||||
case 'q':
|
||||
quiet = true;
|
||||
break;
|
||||
case 'r':
|
||||
raw = true;
|
||||
break;
|
||||
case 's':
|
||||
socket_path = optarg;
|
||||
break;
|
||||
case 't':
|
||||
type_str = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
printf("%s\n", version_str);
|
||||
return 0;
|
||||
default:
|
||||
fprintf(stderr, "%s", usage_str);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Auto-detect pretty mode */
|
||||
if (!force_pretty && !raw) {
|
||||
pretty = isatty(STDOUT_FILENO);
|
||||
}
|
||||
|
||||
/* Determine message type */
|
||||
int msg_type = parse_msg_type(type_str);
|
||||
if (msg_type < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Build payload from remaining args */
|
||||
char *payload = NULL;
|
||||
if (optind < argc) {
|
||||
/* Concatenate remaining args with spaces */
|
||||
size_t total = 0;
|
||||
for (int i = optind; i < argc; i++) {
|
||||
total += strlen(argv[i]) + 1;
|
||||
}
|
||||
payload = malloc(total);
|
||||
payload[0] = '\0';
|
||||
for (int i = optind; i < argc; i++) {
|
||||
if (i > optind) {
|
||||
strcat(payload, " ");
|
||||
}
|
||||
strcat(payload, argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get socket path */
|
||||
if (!socket_path) {
|
||||
socket_path = getenv("LABWC_IPC_SOCK");
|
||||
}
|
||||
if (!socket_path) {
|
||||
fprintf(stderr, "LABWC_IPC_SOCK not set and no -s option\n");
|
||||
free(payload);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fd = ipc_connect(socket_path);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Failed to connect to %s\n", socket_path);
|
||||
free(payload);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Send message */
|
||||
uint32_t payload_len = payload ? strlen(payload) : 0;
|
||||
if (!ipc_send(fd, msg_type, payload, payload_len)) {
|
||||
fprintf(stderr, "Failed to send IPC message\n");
|
||||
close(fd);
|
||||
free(payload);
|
||||
return 1;
|
||||
}
|
||||
free(payload);
|
||||
|
||||
/* Receive response */
|
||||
uint32_t resp_type = 0;
|
||||
char *resp_payload = NULL;
|
||||
uint32_t resp_len = 0;
|
||||
|
||||
if (!ipc_recv(fd, &resp_type, &resp_payload, &resp_len)) {
|
||||
fprintf(stderr, "Failed to receive IPC response\n");
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int exit_code = 0;
|
||||
|
||||
if (!quiet && resp_payload) {
|
||||
print_json(resp_payload, pretty);
|
||||
}
|
||||
|
||||
/* Check for server-reported errors */
|
||||
if (resp_payload && msg_type == IPC_RUN_COMMAND) {
|
||||
if (!check_command_success(resp_payload)) {
|
||||
exit_code = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Monitor mode: keep reading events */
|
||||
if (monitor) {
|
||||
free(resp_payload);
|
||||
while (1) {
|
||||
resp_payload = NULL;
|
||||
if (!ipc_recv(fd, &resp_type, &resp_payload,
|
||||
&resp_len)) {
|
||||
break;
|
||||
}
|
||||
if (!quiet && resp_payload) {
|
||||
print_json(resp_payload, pretty);
|
||||
}
|
||||
free(resp_payload);
|
||||
}
|
||||
}
|
||||
|
||||
free(resp_payload);
|
||||
close(fd);
|
||||
return exit_code;
|
||||
}
|
||||
|
|
@ -59,3 +59,11 @@ endif
|
|||
|
||||
clients = files('lab-sensible-terminal')
|
||||
install_data(clients, install_dir: get_option('bindir'))
|
||||
|
||||
jsonc_client = dependency('json-c')
|
||||
executable(
|
||||
'labmsg',
|
||||
files('labmsg.c'),
|
||||
dependencies: [jsonc_client],
|
||||
install: true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ bool action_is_valid(struct action *action);
|
|||
bool action_is_show_menu(struct action *action);
|
||||
|
||||
void action_arg_add_str(struct action *action, const char *key, const char *value);
|
||||
void action_arg_add_int(struct action *action, const char *key, int value);
|
||||
void action_arg_add_actionlist(struct action *action, const char *key);
|
||||
void action_arg_add_querylist(struct action *action, const char *key);
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ struct rcxml {
|
|||
char *config_dir;
|
||||
char *config_file;
|
||||
bool merge_config;
|
||||
char *loaded_config_file;
|
||||
|
||||
/* core */
|
||||
bool xdg_shell_server_side_deco;
|
||||
|
|
|
|||
|
|
@ -9,5 +9,6 @@ struct foreign_toplevel *foreign_toplevel_create(struct view *view);
|
|||
void foreign_toplevel_set_parent(struct foreign_toplevel *toplevel,
|
||||
struct foreign_toplevel *parent);
|
||||
void foreign_toplevel_destroy(struct foreign_toplevel *toplevel);
|
||||
const char *foreign_toplevel_get_identifier(struct foreign_toplevel *toplevel);
|
||||
|
||||
#endif /* LABWC_FOREIGN_TOPLEVEL_H */
|
||||
|
|
|
|||
76
include/ipc.h
Normal file
76
include/ipc.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef LABWC_IPC_H
|
||||
#define LABWC_IPC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <wayland-util.h>
|
||||
|
||||
struct view;
|
||||
struct workspace;
|
||||
|
||||
/* Wire protocol */
|
||||
#define IPC_MAGIC "labwc-ipc"
|
||||
#define IPC_MAGIC_LEN 9
|
||||
#define IPC_HEADER_SIZE (IPC_MAGIC_LEN + 4 + 4) /* 17 bytes */
|
||||
|
||||
/* Message types (same numbering as sway/i3) */
|
||||
enum ipc_msg_type {
|
||||
IPC_RUN_COMMAND = 0,
|
||||
IPC_GET_WORKSPACES = 1,
|
||||
IPC_SUBSCRIBE = 2,
|
||||
IPC_GET_OUTPUTS = 3,
|
||||
IPC_GET_TREE = 4,
|
||||
IPC_GET_MARKS = 5,
|
||||
IPC_GET_BAR_CONFIG = 6,
|
||||
IPC_GET_VERSION = 7,
|
||||
IPC_GET_BINDING_MODES = 8,
|
||||
IPC_GET_CONFIG = 9,
|
||||
IPC_SEND_TICK = 10,
|
||||
IPC_SYNC = 11,
|
||||
IPC_GET_BINDING_STATE = 12,
|
||||
IPC_GET_INPUTS = 100,
|
||||
IPC_GET_SEATS = 101,
|
||||
};
|
||||
|
||||
/* Event types (bit 31 set) */
|
||||
#define IPC_EVENT_FLAG 0x80000000
|
||||
enum ipc_event_type {
|
||||
IPC_EVENT_WORKSPACE = (IPC_EVENT_FLAG | 0),
|
||||
IPC_EVENT_OUTPUT = (IPC_EVENT_FLAG | 1),
|
||||
IPC_EVENT_WINDOW = (IPC_EVENT_FLAG | 3),
|
||||
IPC_EVENT_SHUTDOWN = (IPC_EVENT_FLAG | 6),
|
||||
IPC_EVENT_TICK = (IPC_EVENT_FLAG | 7),
|
||||
};
|
||||
|
||||
/* Subscription bitmask */
|
||||
#define IPC_SUB_WORKSPACE (1 << 0)
|
||||
#define IPC_SUB_OUTPUT (1 << 1)
|
||||
#define IPC_SUB_WINDOW (1 << 3)
|
||||
#define IPC_SUB_SHUTDOWN (1 << 6)
|
||||
#define IPC_SUB_TICK (1 << 7)
|
||||
|
||||
struct ipc_client {
|
||||
struct wl_list link; /* server.ipc_clients */
|
||||
int fd;
|
||||
struct wl_event_source *readable;
|
||||
struct wl_event_source *writable;
|
||||
uint32_t subscriptions;
|
||||
/* Write buffer for partial writes */
|
||||
char *write_buf;
|
||||
size_t write_buf_len;
|
||||
size_t write_buf_cap;
|
||||
};
|
||||
|
||||
/* Server lifecycle */
|
||||
void ipc_init(void);
|
||||
void ipc_finish(void);
|
||||
|
||||
/* Event emitters (called from compositor hooks) */
|
||||
void ipc_event_workspace(const char *change, struct workspace *current,
|
||||
struct workspace *old);
|
||||
void ipc_event_output(const char *change);
|
||||
void ipc_event_window(const char *change, struct view *view);
|
||||
void ipc_event_shutdown(void);
|
||||
|
||||
#endif /* LABWC_IPC_H */
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef LABWC_H
|
||||
#define LABWC_H
|
||||
#include "config.h"
|
||||
#include <wlr/util/box.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "common/set.h"
|
||||
#include "config.h"
|
||||
#include "cycle.h"
|
||||
#include "input/cursor.h"
|
||||
#include "overlay.h"
|
||||
|
|
@ -65,9 +65,9 @@ struct seat {
|
|||
struct input_method_relay *input_method_relay;
|
||||
|
||||
/**
|
||||
* Cursor context saved when a mouse button is pressed on a view/surface.
|
||||
* It is used to send cursor motion events to a surface even though
|
||||
* the cursor has left the surface in the meantime.
|
||||
* Cursor context saved when a mouse button is pressed on a
|
||||
* view/surface. It is used to send cursor motion events to a surface
|
||||
* even though the cursor has left the surface in the meantime.
|
||||
*
|
||||
* This allows to keep dragging a scrollbar or selecting text even
|
||||
* when moving outside of the window.
|
||||
|
|
@ -245,7 +245,8 @@ struct server {
|
|||
*/
|
||||
struct wlr_scene_tree *xdg_popup_tree;
|
||||
#if HAVE_XWAYLAND
|
||||
/* Tree for unmanaged xsurfaces without initialized view (usually popups) */
|
||||
/* Tree for unmanaged xsurfaces without initialized view (usually
|
||||
* popups) */
|
||||
struct wlr_scene_tree *unmanaged_tree;
|
||||
#endif
|
||||
struct wlr_scene_tree *cycle_preview_tree;
|
||||
|
|
@ -254,7 +255,7 @@ struct server {
|
|||
|
||||
/* Workspaces */
|
||||
struct {
|
||||
struct wl_list all; /* struct workspace.link */
|
||||
struct wl_list all; /* struct workspace.link */
|
||||
struct workspace *current;
|
||||
struct workspace *last;
|
||||
struct wlr_ext_workspace_manager_v1 *ext_manager;
|
||||
|
|
@ -324,6 +325,8 @@ struct server {
|
|||
pid_t primary_client_pid;
|
||||
|
||||
char *title_fmt;
|
||||
|
||||
struct wl_list ipc_clients;
|
||||
};
|
||||
|
||||
/* defined in main.c */
|
||||
|
|
@ -406,7 +409,8 @@ void seat_pointer_end_grab(struct seat *seat, struct wlr_surface *surface);
|
|||
*/
|
||||
void seat_focus_lock_surface(struct seat *seat, struct wlr_surface *surface);
|
||||
|
||||
void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer);
|
||||
void seat_set_focus_layer(struct seat *seat,
|
||||
struct wlr_layer_surface_v1 *layer);
|
||||
void seat_output_layout_changed(struct seat *seat);
|
||||
|
||||
/*
|
||||
|
|
@ -457,7 +461,6 @@ void server_start(void);
|
|||
void server_finish(void);
|
||||
|
||||
void create_constraint(struct wl_listener *listener, void *data);
|
||||
void constrain_cursor(struct wlr_pointer_constraint_v1
|
||||
*constraint);
|
||||
void constrain_cursor(struct wlr_pointer_constraint_v1 *constraint);
|
||||
|
||||
#endif /* LABWC_H */
|
||||
|
|
|
|||
|
|
@ -242,6 +242,12 @@ struct view {
|
|||
/* Set temporarily when moving view due to layout change */
|
||||
bool adjusting_for_layout_change;
|
||||
|
||||
/*
|
||||
* Last geometry reported to IPC subscribers. Used to detect
|
||||
* actual position/size changes and emit move/resize events.
|
||||
*/
|
||||
struct wlr_box ipc_last_geo;
|
||||
|
||||
/* used by xdg-shell views */
|
||||
uint32_t pending_configure_serial;
|
||||
struct wl_event_source *pending_configure_timeout;
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ input = dependency('libinput', version: '>=1.26', required: wlroots.get_variable
|
|||
pixman = dependency('pixman-1')
|
||||
math = cc.find_library('m')
|
||||
png = dependency('libpng')
|
||||
jsonc = dependency('json-c')
|
||||
svg = dependency('librsvg-2.0', version: '>=2.46', required: false)
|
||||
sfdo_basedir = dependency(
|
||||
'libsfdo-basedir',
|
||||
|
|
@ -174,6 +175,7 @@ labwc_deps = [
|
|||
pixman,
|
||||
math,
|
||||
png,
|
||||
jsonc,
|
||||
]
|
||||
if have_rsvg
|
||||
labwc_deps += [
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ action_arg_add_bool(struct action *action, const char *key, bool value)
|
|||
wl_list_append(&action->args, &arg->base.link);
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
action_arg_add_int(struct action *action, const char *key, int value)
|
||||
{
|
||||
assert(action);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "config/rcxml.h"
|
||||
#include "dnd.h"
|
||||
#include "labwc.h"
|
||||
#include "ipc.h"
|
||||
#include "layers.h"
|
||||
#include "node.h"
|
||||
#include "output.h"
|
||||
|
|
@ -168,6 +169,8 @@ desktop_focus_view_internal(struct view *view, bool raise, bool allow_delay)
|
|||
struct view *dialog = view_get_modal_dialog(view);
|
||||
set_or_offer_focus(dialog ? dialog : view);
|
||||
|
||||
ipc_event_window("focus", view);
|
||||
|
||||
show_desktop_reset();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include "foreign-toplevel/foreign.h"
|
||||
#include <assert.h>
|
||||
#include <wlr/types/wlr_ext_foreign_toplevel_list_v1.h>
|
||||
#include "common/mem.h"
|
||||
#include "foreign-toplevel/ext-foreign.h"
|
||||
#include "foreign-toplevel/wlr-foreign.h"
|
||||
|
|
@ -27,7 +28,8 @@ foreign_toplevel_create(struct view *view)
|
|||
}
|
||||
|
||||
void
|
||||
foreign_toplevel_set_parent(struct foreign_toplevel *toplevel, struct foreign_toplevel *parent)
|
||||
foreign_toplevel_set_parent(struct foreign_toplevel *toplevel,
|
||||
struct foreign_toplevel *parent)
|
||||
{
|
||||
assert(toplevel);
|
||||
wlr_foreign_toplevel_set_parent(&toplevel->wlr_toplevel,
|
||||
|
|
@ -42,3 +44,17 @@ foreign_toplevel_destroy(struct foreign_toplevel *toplevel)
|
|||
ext_foreign_toplevel_finish(&toplevel->ext_toplevel);
|
||||
free(toplevel);
|
||||
}
|
||||
|
||||
const char *
|
||||
foreign_toplevel_get_identifier(struct foreign_toplevel *toplevel)
|
||||
{
|
||||
if (!toplevel) {
|
||||
return "";
|
||||
}
|
||||
struct wlr_ext_foreign_toplevel_handle_v1 *handle =
|
||||
toplevel->ext_toplevel.handle;
|
||||
if (handle && handle->identifier) {
|
||||
return handle->identifier;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ labwc_sources = files(
|
|||
'edges.c',
|
||||
'idle.c',
|
||||
'interactive.c',
|
||||
'ipc.c',
|
||||
'layers.c',
|
||||
'magnifier.c',
|
||||
'main.c',
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "common/string-helpers.h"
|
||||
#include "config/rcxml.h"
|
||||
#include "labwc.h"
|
||||
#include "ipc.h"
|
||||
#include "layers.h"
|
||||
#include "node.h"
|
||||
#include "output-state.h"
|
||||
|
|
@ -285,6 +286,7 @@ handle_output_destroy(struct wl_listener *listener, void *data)
|
|||
wl_list_remove(&output->destroy.link);
|
||||
wl_list_remove(&output->request_state.link);
|
||||
seat_output_layout_changed(seat);
|
||||
ipc_event_output("destroy");
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) {
|
||||
wlr_scene_node_destroy(&output->layer_tree[i]->node);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
#include "desktop-entry.h"
|
||||
#include "idle.h"
|
||||
#include "input/keyboard.h"
|
||||
#include "ipc.h"
|
||||
#include "labwc.h"
|
||||
#include "layers.h"
|
||||
#include "magnifier.h"
|
||||
|
|
@ -833,6 +834,8 @@ server_start(void)
|
|||
/* Potentially set up the initial fallback output */
|
||||
output_virtual_update_fallback();
|
||||
|
||||
ipc_init();
|
||||
|
||||
if (setenv("WAYLAND_DISPLAY", socket, true) < 0) {
|
||||
wlr_log_errno(WLR_ERROR, "unable to set WAYLAND_DISPLAY");
|
||||
} else {
|
||||
|
|
@ -881,5 +884,7 @@ server_finish(void)
|
|||
workspaces_destroy();
|
||||
wlr_scene_node_destroy(&server.scene->tree.node);
|
||||
|
||||
ipc_finish();
|
||||
|
||||
wl_display_destroy(server.wl_display);
|
||||
}
|
||||
|
|
|
|||
40
src/view.c
40
src/view.c
|
|
@ -19,6 +19,7 @@
|
|||
#include "cycle.h"
|
||||
#include "foreign-toplevel/foreign.h"
|
||||
#include "input/keyboard.h"
|
||||
#include "ipc.h"
|
||||
#include "labwc.h"
|
||||
#include "menu/menu.h"
|
||||
#include "output.h"
|
||||
|
|
@ -80,6 +81,7 @@ struct view_query *
|
|||
view_query_create(void)
|
||||
{
|
||||
struct view_query *query = znew(*query);
|
||||
wl_list_init(&query->link);
|
||||
/* Must be synced with view_matches_rule() in window-rules.c */
|
||||
query->window_type = LAB_WINDOW_TYPE_INVALID;
|
||||
query->maximized = VIEW_AXIS_INVALID;
|
||||
|
|
@ -567,6 +569,25 @@ view_moved(struct view *view)
|
|||
if (rc.resize_indicator && server.grabbed_view == view) {
|
||||
resize_indicator_update(view);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback IPC emission: catch geometry corrections made by
|
||||
* the client on commit (e.g. terminal snapping to char grid).
|
||||
* The primary emission point is in view_move_resize() which
|
||||
* fires immediately using view->pending. The ipc_last_geo
|
||||
* dedup ensures no duplicates when pending == current.
|
||||
*/
|
||||
if (view->mapped) {
|
||||
struct wlr_box *last = &view->ipc_last_geo;
|
||||
struct wlr_box *cur = &view->current;
|
||||
if (cur->x != last->x || cur->y != last->y) {
|
||||
ipc_event_window("move", view);
|
||||
}
|
||||
if (cur->width != last->width || cur->height != last->height) {
|
||||
ipc_event_window("resize", view);
|
||||
}
|
||||
*last = *cur;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -590,6 +611,25 @@ view_move_resize(struct view *view, struct wlr_box geo)
|
|||
if (!view->adjusting_for_layout_change) {
|
||||
view_save_last_placement(view);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit IPC move/resize events based on pending geometry.
|
||||
* This fires immediately when the resize is requested rather
|
||||
* than waiting for the client to commit, giving subscribers
|
||||
* realtime tracking that matches interactive move behaviour.
|
||||
*/
|
||||
if (view->mapped) {
|
||||
struct wlr_box *last = &view->ipc_last_geo;
|
||||
struct wlr_box *pending = &view->pending;
|
||||
if (pending->x != last->x || pending->y != last->y) {
|
||||
ipc_event_window("move", view);
|
||||
}
|
||||
if (pending->width != last->width
|
||||
|| pending->height != last->height) {
|
||||
ipc_event_window("resize", view);
|
||||
}
|
||||
*last = *pending;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "config/rcxml.h"
|
||||
#include "input/keyboard.h"
|
||||
#include "labwc.h"
|
||||
#include "ipc.h"
|
||||
#include "output.h"
|
||||
#include "show-desktop.h"
|
||||
#include "theme.h"
|
||||
|
|
@ -497,6 +498,8 @@ workspaces_switch_to(struct workspace *target, bool update_focus)
|
|||
|
||||
wlr_ext_workspace_handle_v1_set_active(target->ext_workspace, true);
|
||||
|
||||
ipc_event_workspace("focus", target, server.workspaces.last);
|
||||
|
||||
show_desktop_reset();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "config/rcxml.h"
|
||||
#include "decorations.h"
|
||||
#include "foreign-toplevel/foreign.h"
|
||||
#include "ipc.h"
|
||||
#include "labwc.h"
|
||||
#include "menu/menu.h"
|
||||
#include "node.h"
|
||||
|
|
@ -592,6 +593,7 @@ handle_set_title(struct wl_listener *listener, void *data)
|
|||
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
|
||||
|
||||
view_set_title(view, toplevel->title);
|
||||
ipc_event_window("title", view);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -867,6 +869,8 @@ handle_map(struct wl_listener *listener, void *data)
|
|||
|
||||
view_impl_map(view);
|
||||
view->been_mapped = true;
|
||||
ipc_event_window("new", view);
|
||||
view->ipc_last_geo = view->current;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -876,6 +880,7 @@ handle_unmap(struct wl_listener *listener, void *data)
|
|||
if (view->mapped) {
|
||||
view->mapped = false;
|
||||
view_impl_unmap(view);
|
||||
ipc_event_window("close", view);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "config/rcxml.h"
|
||||
#include "config/session.h"
|
||||
#include "foreign-toplevel/foreign.h"
|
||||
#include "ipc.h"
|
||||
#include "labwc.h"
|
||||
#include "node.h"
|
||||
#include "output.h"
|
||||
|
|
@ -580,6 +581,7 @@ handle_set_title(struct wl_listener *listener, void *data)
|
|||
struct view *view = wl_container_of(listener, view, set_title);
|
||||
struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
|
||||
view_set_title(view, xwayland_view->xwayland_surface->title);
|
||||
ipc_event_window("title", view);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -825,6 +827,8 @@ handle_map(struct wl_listener *listener, void *data)
|
|||
|
||||
view_impl_map(view);
|
||||
view->been_mapped = true;
|
||||
ipc_event_window("new", view);
|
||||
view->ipc_last_geo = view->current;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -836,6 +840,7 @@ handle_unmap(struct wl_listener *listener, void *data)
|
|||
}
|
||||
view->mapped = false;
|
||||
view_impl_unmap(view);
|
||||
ipc_event_window("close", view);
|
||||
|
||||
/*
|
||||
* Destroy the content_tree at unmap. Alternatively, we could
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue