feature: create a miraclemsg subproject (#224)

This commit is contained in:
Matthew Kosarek 2024-09-03 15:35:39 -04:00 committed by GitHub
parent 7b5504ff69
commit d24adc7f34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1227 additions and 24 deletions

View File

@ -32,7 +32,7 @@ jobs:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ /usr/bin/g++-13
sudo apt install libmiral-dev libmircommon-internal-dev libmircommon-dev libmirserver-internal-dev \
libgtest-dev libyaml-cpp-dev libglib2.0-dev libevdev-dev nlohmann-json3-dev libnotify-dev pcre2-utils \
libmiroil-dev libmirrenderer-dev libgles2-mesa-dev libmirwayland-dev
libmiroil-dev libmirrenderer-dev libgles2-mesa-dev libmirwayland-dev libjson-c-dev
sudo apt install mir-platform-graphics-virtual xwayland \
mir-platform-graphics-gbm-kms mir-platform-rendering-egl-generic

View File

@ -139,3 +139,4 @@ if(SNAP_BUILD)
endif()
add_subdirectory(tests/)
add_subdirectory(miraclemsg/)

17
miraclemsg/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
find_package(PkgConfig)
pkg_check_modules(JSONC json-c REQUIRED)
add_executable(miraclemsg
ipc.h
ipc_client.cpp ipc_client.h
main.cpp)
target_include_directories(miraclemsg PUBLIC SYSTEM
${JSONC_INCLUDE_DIRS})
target_link_libraries(miraclemsg
${JSONC_LDFLAGS})
install(PROGRAMS ${CMAKE_BINARY_DIR}/bin/miraclemsg
DESTINATION ${CMAKE_INSTALL_BINDIR}
)

19
miraclemsg/LICENSE.sway Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2016-2017 Drew DeVault
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
miraclemsg/README.md Normal file
View File

@ -0,0 +1,4 @@
# miraclemsg
This is a fork of [swaymsg](https://github.com/swaywm/sway/tree/master/swaymsg).
At the moment, it is a direct fork of that project without any changes, however
we may augment the IPC with new calls in the future.

63
miraclemsg/ipc.h Normal file
View File

@ -0,0 +1,63 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Portions of this code originate from swaymsg, licensed under the MIT license.
See the LICENSE.Sway file for details.
**/
#ifndef _SWAY_IPC_H
#define _SWAY_IPC_H
#define event_mask(ev) (1 << (ev & 0x7F))
enum ipc_command_type
{
// i3 command types - see i3's I3_REPLY_TYPE constants
IPC_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,
// sway-specific command types
IPC_GET_INPUTS = 100,
IPC_GET_SEATS = 101,
// Events sent from sway to clients. Events have the highest bits set.
IPC_EVENT_WORKSPACE = ((1 << 31) | 0),
IPC_EVENT_OUTPUT = ((1 << 31) | 1),
IPC_EVENT_MODE = ((1 << 31) | 2),
IPC_EVENT_WINDOW = ((1 << 31) | 3),
IPC_EVENT_BARCONFIG_UPDATE = ((1 << 31) | 4),
IPC_EVENT_BINDING = ((1 << 31) | 5),
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
IPC_EVENT_TICK = ((1 << 31) | 7),
// sway-specific event types
IPC_EVENT_BAR_STATE_UPDATE = ((1 << 31) | 20),
IPC_EVENT_INPUT = ((1 << 31) | 21),
};
#endif

200
miraclemsg/ipc_client.cpp Normal file
View File

@ -0,0 +1,200 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Portions of this code originate from swaymsg, licensed under the MIT license.
See the LICENSE.Sway file for details.
**/
#include "ipc_client.h"
#include <cstdlib>
#include <iostream>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
static const char ipc_magic[] = { 'i', '3', '-', 'i', 'p', 'c' };
#define IPC_HEADER_SIZE (sizeof(ipc_magic) + 8)
char* get_socketpath(void)
{
const char* swaysock = getenv("SWAYSOCK");
if (swaysock)
{
return strdup(swaysock);
}
char* line = NULL;
size_t line_size = 0;
FILE* fp = popen("sway --get-socketpath 2>/dev/null", "r");
if (fp)
{
ssize_t nret = getline(&line, &line_size, fp);
pclose(fp);
if (nret > 0)
{
// remove trailing newline, if there is one
if (line[nret - 1] == '\n')
{
line[nret - 1] = '\0';
}
return line;
}
}
const char* i3sock = getenv("I3SOCK");
if (i3sock)
{
free(line);
return strdup(i3sock);
}
fp = popen("i3 --get-socketpath 2>/dev/null", "r");
if (fp)
{
ssize_t nret = getline(&line, &line_size, fp);
pclose(fp);
if (nret > 0)
{
// remove trailing newline, if there is one
if (line[nret - 1] == '\n')
{
line[nret - 1] = '\0';
}
return line;
}
}
free(line);
return NULL;
}
int ipc_open_socket(const char* socket_path)
{
struct sockaddr_un addr;
int socketfd;
if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
{
std::cerr << "Unable to open Unix socket" << std::endl;
std::abort();
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
int l = sizeof(struct sockaddr_un);
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1)
{
std::cout << "Unable to connect to " << socket_path << std::endl;
}
return socketfd;
}
bool ipc_set_recv_timeout(int socketfd, struct timeval tv)
{
if (setsockopt(socketfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
{
std::cerr << "Failed to set ipc recv timeout" << std::endl;
return false;
}
return true;
}
struct ipc_response* ipc_recv_response(int socketfd)
{
char data[IPC_HEADER_SIZE];
size_t total = 0;
while (total < IPC_HEADER_SIZE)
{
ssize_t received = recv(socketfd, data + total, IPC_HEADER_SIZE - total, 0);
if (received <= 0)
{
std::cerr << "Unable to receive IPC response" << std::endl;
std::abort();
}
total += received;
}
ipc_response* response = static_cast<ipc_response*>(malloc(sizeof(struct ipc_response)));
if (!response)
{
std::cerr << "Unable to allocate memory for IPC response" << std::endl;
return NULL;
}
memcpy(&response->size, data + sizeof(ipc_magic), sizeof(uint32_t));
memcpy(&response->type, data + sizeof(ipc_magic) + sizeof(uint32_t), sizeof(uint32_t));
char* payload = (char*)malloc(response->size + 1);
if (!payload)
{
goto error_2;
}
total = 0;
while (total < response->size)
{
ssize_t received = recv(socketfd, payload + total, response->size - total, 0);
if (received < 0)
{
std::cerr << "Unable to receive IPC response" << std::endl;
std::abort();
}
total += received;
}
payload[response->size] = '\0';
response->payload = payload;
return response;
error_2:
free(response);
error_1:
std::cerr << "Unable to allocate memory for IPC response" << std::endl;
return NULL;
}
void free_ipc_response(struct ipc_response* response)
{
free(response->payload);
free(response);
}
char* ipc_single_command(int socketfd, uint32_t type, const char* payload, uint32_t* len)
{
char data[IPC_HEADER_SIZE];
memcpy(data, ipc_magic, sizeof(ipc_magic));
memcpy(data + sizeof(ipc_magic), len, sizeof(*len));
memcpy(data + sizeof(ipc_magic) + sizeof(*len), &type, sizeof(type));
if (write(socketfd, data, IPC_HEADER_SIZE) == -1)
{
std::cerr << "Unable to send IPC header" << std::endl;
std::abort();
}
if (write(socketfd, payload, *len) == -1)
{
std::cerr << "Unable to send IPC payload" << std::endl;
std::abort();
}
struct ipc_response* resp = ipc_recv_response(socketfd);
char* response = resp->payload;
*len = resp->size;
free(resp);
return response;
}

71
miraclemsg/ipc_client.h Normal file
View File

@ -0,0 +1,71 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Portions of this code originate from swaymsg, licensed under the MIT license.
See the LICENSE.Sway file for details.
**/
#ifndef _SWAY_IPC_CLIENT_H
#define _SWAY_IPC_CLIENT_H
// arbitrary number, it's probably sufficient, higher number = more memory usage
#define JSON_MAX_DEPTH 512
#include <stdbool.h>
#include <stdint.h>
#include <sys/time.h>
#include "ipc.h"
/**
* IPC response including type of IPC response, size of payload and the json
* encoded payload string.
*/
struct ipc_response
{
uint32_t size;
uint32_t type;
char* payload;
};
/**
* Gets the path to the IPC socket from sway.
*/
char* get_socketpath(void);
/**
* Opens the sway socket.
*/
int ipc_open_socket(const char* socket_path);
/**
* Issues a single IPC command and returns the buffer. len will be updated with
* the length of the buffer returned from sway.
*/
char* ipc_single_command(int socketfd, uint32_t type, const char* payload, uint32_t* len);
/**
* Receives a single IPC response and returns an ipc_response.
*/
struct ipc_response* ipc_recv_response(int socketfd);
/**
* Free ipc_response struct
*/
void free_ipc_response(struct ipc_response* response);
/**
* Sets the receive timeout for the IPC socket
*/
bool ipc_set_recv_timeout(int socketfd, struct timeval tv);
#endif

790
miraclemsg/main.cpp Normal file
View File

@ -0,0 +1,790 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Portions of this code originate from swaymsg, licensed under the MIT license.
See the LICENSE.Sway file for details.
**/
#include "ipc_client.h"
#include <ctype.h>
#include <getopt.h>
#include <iostream>
#include <json.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <unistd.h>
void sway_terminate(int exit_code)
{
exit(exit_code);
}
static bool success_object(json_object* result)
{
json_object* success;
if (!json_object_object_get_ex(result, "success", &success))
{
return true;
}
return json_object_get_boolean(success);
}
// Iterate results array and return false if any of them failed
static bool success(json_object* r, bool fallback)
{
if (!json_object_is_type(r, json_type_array))
{
if (json_object_is_type(r, json_type_object))
{
return success_object(r);
}
return fallback;
}
size_t results_len = json_object_array_length(r);
if (!results_len)
{
return fallback;
}
for (size_t i = 0; i < results_len; ++i)
{
json_object* result = json_object_array_get_idx(r, i);
if (!success_object(result))
{
return false;
}
}
return true;
}
static void pretty_print_cmd(json_object* r)
{
if (!success_object(r))
{
json_object* error;
if (!json_object_object_get_ex(r, "error", &error))
{
printf("An unknown error occurred");
}
else
{
printf("Error: %s\n", json_object_get_string(error));
}
}
}
static void pretty_print_workspace(json_object* w)
{
json_object *name, *rect, *visible, *output, *urgent, *layout,
*representation, *focused;
json_object_object_get_ex(w, "name", &name);
json_object_object_get_ex(w, "rect", &rect);
json_object_object_get_ex(w, "visible", &visible);
json_object_object_get_ex(w, "output", &output);
json_object_object_get_ex(w, "urgent", &urgent);
json_object_object_get_ex(w, "layout", &layout);
json_object_object_get_ex(w, "representation", &representation);
json_object_object_get_ex(w, "focused", &focused);
printf(
"Workspace %s%s%s%s\n"
" Output: %s\n"
" Layout: %s\n"
" Representation: %s\n\n",
json_object_get_string(name),
json_object_get_boolean(focused) ? " (focused)" : "",
!json_object_get_boolean(visible) ? " (off-screen)" : "",
json_object_get_boolean(urgent) ? " (urgent)" : "",
json_object_get_string(output),
json_object_get_string(layout),
json_object_get_string(representation));
}
static const char* pretty_type_name(const char* name)
{
// TODO these constants probably belong in the common lib
struct
{
const char* a;
const char* b;
} type_names[] = {
{ "keyboard", "Keyboard" },
{ "pointer", "Mouse" },
{ "touchpad", "Touchpad" },
{ "tablet_pad", "Tablet pad" },
{ "tablet_tool", "Tablet tool" },
{ "touch", "Touch" },
{ "switch", "Switch" },
};
for (size_t i = 0; i < sizeof(type_names) / sizeof(type_names[0]); ++i)
{
if (strcmp(type_names[i].a, name) == 0)
{
return type_names[i].b;
}
}
return name;
}
static void pretty_print_input(json_object* i)
{
json_object *id, *name, *type, *product, *vendor, *kbdlayout, *libinput;
json_object_object_get_ex(i, "identifier", &id);
json_object_object_get_ex(i, "name", &name);
json_object_object_get_ex(i, "type", &type);
json_object_object_get_ex(i, "product", &product);
json_object_object_get_ex(i, "vendor", &vendor);
const char* fmt = "Input device: %s\n"
" Type: %s\n"
" Identifier: %s\n"
" Product ID: %d\n"
" Vendor ID: %d\n";
printf(fmt, json_object_get_string(name),
pretty_type_name(json_object_get_string(type)),
json_object_get_string(id),
json_object_get_int(product),
json_object_get_int(vendor));
if (json_object_object_get_ex(i, "xkb_active_layout_name", &kbdlayout))
{
const char* layout = json_object_get_string(kbdlayout);
printf(" Active Keyboard Layout: %s\n", layout ? layout : "(unnamed)");
}
if (json_object_object_get_ex(i, "libinput", &libinput))
{
json_object* events;
if (json_object_object_get_ex(libinput, "send_events", &events))
{
printf(" Libinput Send Events: %s\n",
json_object_get_string(events));
}
}
printf("\n");
}
char* join_args(char** argv, int argc)
{
int len = 0, i;
for (i = 0; i < argc; ++i)
{
len += strlen(argv[i]) + 1;
}
char* res = (char*)malloc(len);
len = 0;
for (i = 0; i < argc; ++i)
{
strcpy(res + len, argv[i]);
len += strlen(argv[i]);
res[len++] = ' ';
}
res[len - 1] = '\0';
return res;
}
static void pretty_print_seat(json_object* i)
{
json_object *name, *capabilities, *devices;
json_object_object_get_ex(i, "name", &name);
json_object_object_get_ex(i, "capabilities", &capabilities);
json_object_object_get_ex(i, "devices", &devices);
const char* fmt = "Seat: %s\n"
" Capabilities: %d\n";
printf(fmt, json_object_get_string(name),
json_object_get_int(capabilities));
size_t devices_len = json_object_array_length(devices);
if (devices_len > 0)
{
printf(" Devices:\n");
for (size_t i = 0; i < devices_len; ++i)
{
json_object* device = json_object_array_get_idx(devices, i);
json_object* device_name;
json_object_object_get_ex(device, "name", &device_name);
printf(" %s\n", json_object_get_string(device_name));
}
}
printf("\n");
}
static void pretty_print_output(json_object* o)
{
json_object *name, *rect, *focused, *active, *power, *ws, *current_mode, *non_desktop;
json_object_object_get_ex(o, "name", &name);
json_object_object_get_ex(o, "rect", &rect);
json_object_object_get_ex(o, "focused", &focused);
json_object_object_get_ex(o, "active", &active);
json_object_object_get_ex(o, "power", &power);
json_object_object_get_ex(o, "current_workspace", &ws);
json_object_object_get_ex(o, "non_desktop", &non_desktop);
json_object *make, *model, *serial, *scale, *scale_filter, *subpixel,
*transform, *max_render_time, *adaptive_sync_status, *allow_tearing;
json_object_object_get_ex(o, "make", &make);
json_object_object_get_ex(o, "model", &model);
json_object_object_get_ex(o, "serial", &serial);
json_object_object_get_ex(o, "scale", &scale);
json_object_object_get_ex(o, "scale_filter", &scale_filter);
json_object_object_get_ex(o, "subpixel_hinting", &subpixel);
json_object_object_get_ex(o, "transform", &transform);
json_object_object_get_ex(o, "max_render_time", &max_render_time);
json_object_object_get_ex(o, "adaptive_sync_status", &adaptive_sync_status);
json_object_object_get_ex(o, "allow_tearing", &allow_tearing);
json_object *x, *y;
json_object_object_get_ex(rect, "x", &x);
json_object_object_get_ex(rect, "y", &y);
json_object* modes;
json_object_object_get_ex(o, "modes", &modes);
json_object *width, *height, *refresh;
json_object_object_get_ex(o, "current_mode", &current_mode);
json_object_object_get_ex(current_mode, "width", &width);
json_object_object_get_ex(current_mode, "height", &height);
json_object_object_get_ex(current_mode, "refresh", &refresh);
if (json_object_get_boolean(non_desktop))
{
printf(
"Output %s '%s %s %s' (non-desktop)\n",
json_object_get_string(name),
json_object_get_string(make),
json_object_get_string(model),
json_object_get_string(serial));
}
else if (json_object_get_boolean(active))
{
printf(
"Output %s '%s %s %s'%s\n"
" Current mode: %dx%d @ %.3f Hz\n"
" Power: %s\n"
" Position: %d,%d\n"
" Scale factor: %f\n"
" Scale filter: %s\n"
" Subpixel hinting: %s\n"
" Transform: %s\n"
" Workspace: %s\n",
json_object_get_string(name),
json_object_get_string(make),
json_object_get_string(model),
json_object_get_string(serial),
json_object_get_boolean(focused) ? " (focused)" : "",
json_object_get_int(width),
json_object_get_int(height),
(double)json_object_get_int(refresh) / 1000,
json_object_get_boolean(power) ? "on" : "off",
json_object_get_int(x), json_object_get_int(y),
json_object_get_double(scale),
json_object_get_string(scale_filter),
json_object_get_string(subpixel),
json_object_get_string(transform),
json_object_get_string(ws));
int max_render_time_int = json_object_get_int(max_render_time);
printf(" Max render time: ");
printf(max_render_time_int == 0 ? "off\n" : "%d ms\n", max_render_time_int);
printf(" Adaptive sync: %s\n",
json_object_get_string(adaptive_sync_status));
printf(" Allow tearing: %s\n",
json_object_get_boolean(allow_tearing) ? "yes" : "no");
}
else
{
printf(
"Output %s '%s %s %s' (disabled)\n",
json_object_get_string(name),
json_object_get_string(make),
json_object_get_string(model),
json_object_get_string(serial));
}
size_t modes_len = json_object_is_type(modes, json_type_array)
? json_object_array_length(modes)
: 0;
if (modes_len > 0)
{
printf(" Available modes:\n");
for (size_t i = 0; i < modes_len; ++i)
{
json_object* mode = json_object_array_get_idx(modes, i);
json_object *mode_width, *mode_height, *mode_refresh,
*mode_picture_aspect_ratio;
json_object_object_get_ex(mode, "width", &mode_width);
json_object_object_get_ex(mode, "height", &mode_height);
json_object_object_get_ex(mode, "refresh", &mode_refresh);
json_object_object_get_ex(mode, "picture_aspect_ratio",
&mode_picture_aspect_ratio);
printf(" %dx%d @ %.3f Hz", json_object_get_int(mode_width),
json_object_get_int(mode_height),
(double)json_object_get_int(mode_refresh) / 1000);
if (mode_picture_aspect_ratio && strcmp("none", json_object_get_string(mode_picture_aspect_ratio)) != 0)
{
printf(" (%s)", json_object_get_string(mode_picture_aspect_ratio));
}
printf("\n");
}
}
printf("\n");
}
static void pretty_print_version(json_object* v)
{
json_object* ver;
json_object_object_get_ex(v, "human_readable", &ver);
printf("sway version %s\n", json_object_get_string(ver));
}
static void pretty_print_config(json_object* c)
{
json_object* config;
json_object_object_get_ex(c, "config", &config);
printf("%s\n", json_object_get_string(config));
}
static void pretty_print_tree(json_object* obj, int indent)
{
for (int i = 0; i < indent; i++)
{
printf(" ");
}
int id = json_object_get_int(json_object_object_get(obj, "id"));
const char* name = json_object_get_string(json_object_object_get(obj, "name"));
const char* type = json_object_get_string(json_object_object_get(obj, "type"));
const char* shell = json_object_get_string(json_object_object_get(obj, "shell"));
printf("#%d: %s \"%s\"", id, type, name);
if (shell != NULL)
{
int pid = json_object_get_int(json_object_object_get(obj, "pid"));
const char* app_id = json_object_get_string(json_object_object_get(obj, "app_id"));
json_object* window_props_obj = json_object_object_get(obj, "window_properties");
const char* instance = json_object_get_string(json_object_object_get(window_props_obj, "instance"));
const char* class_ = json_object_get_string(json_object_object_get(window_props_obj, "class"));
int x11_id = json_object_get_int(json_object_object_get(obj, "window"));
printf(" (%s, pid: %d", shell, pid);
if (app_id != NULL)
{
printf(", app_id: \"%s\"", app_id);
}
if (instance != NULL)
{
printf(", instance: \"%s\"", instance);
}
if (class_ != NULL)
{
printf(", class: \"%s\"", class_);
}
if (x11_id != 0)
{
printf(", X11 window: 0x%X", x11_id);
}
printf(")");
}
printf("\n");
json_object* nodes_obj = json_object_object_get(obj, "nodes");
size_t len = json_object_array_length(nodes_obj);
for (size_t i = 0; i < len; i++)
{
pretty_print_tree(json_object_array_get_idx(nodes_obj, i), indent + 1);
}
json_object* floating_nodes_obj;
json_bool floating_nodes = json_object_object_get_ex(obj, "floating_nodes", &floating_nodes_obj);
if (floating_nodes)
{
size_t len = json_object_array_length(floating_nodes_obj);
for (size_t i = 0; i < len; i++)
{
pretty_print_tree(json_object_array_get_idx(floating_nodes_obj, i), indent + 1);
}
}
}
static void pretty_print(int type, json_object* resp)
{
switch (type)
{
case IPC_SEND_TICK:
return;
case IPC_GET_VERSION:
pretty_print_version(resp);
return;
case IPC_GET_CONFIG:
pretty_print_config(resp);
return;
case IPC_GET_TREE:
pretty_print_tree(resp, 0);
return;
case IPC_COMMAND:
case IPC_GET_WORKSPACES:
case IPC_GET_INPUTS:
case IPC_GET_OUTPUTS:
case IPC_GET_SEATS:
break;
default:
printf("%s\n", json_object_to_json_string_ext(resp, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
return;
}
json_object* obj;
size_t len = json_object_array_length(resp);
for (size_t i = 0; i < len; ++i)
{
obj = json_object_array_get_idx(resp, i);
switch (type)
{
case IPC_COMMAND:
pretty_print_cmd(obj);
break;
case IPC_GET_WORKSPACES:
pretty_print_workspace(obj);
break;
case IPC_GET_INPUTS:
pretty_print_input(obj);
break;
case IPC_GET_OUTPUTS:
pretty_print_output(obj);
break;
case IPC_GET_SEATS:
pretty_print_seat(obj);
break;
}
}
}
int main(int argc, char** argv)
{
static bool quiet = false;
static bool raw = false;
static bool monitor = false;
char* socket_path = NULL;
char* cmdtype = NULL;
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 }
};
const char* usage = "Usage: swaymsg [options] [message]\n"
"\n"
" -h, --help Show help message and quit.\n"
" -m, --monitor Monitor until killed (-t SUBSCRIBE only)\n"
" -p, --pretty Use pretty output even when not using a tty\n"
" -q, --quiet Be quiet.\n"
" -r, --raw Use raw output even if using a tty\n"
" -s, --socket <socket> Use the specified socket.\n"
" -t, --type <type> Specify the message type.\n"
" -v, --version Show the version number and quit.\n";
raw = !isatty(STDOUT_FILENO);
int c;
while (1)
{
int option_index = 0;
c = getopt_long(argc, argv, "hmpqrs:t:v", long_options, &option_index);
if (c == -1)
{
break;
}
switch (c)
{
case 'm': // Monitor
monitor = true;
break;
case 'p': // Pretty
raw = false;
break;
case 'q': // Quiet
quiet = true;
break;
case 'r': // Raw
raw = true;
break;
case 's': // Socket
socket_path = strdup(optarg);
break;
case 't': // Type
cmdtype = strdup(optarg);
break;
case 'v':
printf("miraclemsg version 0.0.1\n");
exit(EXIT_SUCCESS);
break;
default:
fprintf(stderr, "%s", usage);
exit(EXIT_FAILURE);
}
}
if (!cmdtype)
{
cmdtype = strdup("command");
}
if (!socket_path)
{
socket_path = get_socketpath();
if (!socket_path)
{
if (quiet)
{
exit(EXIT_FAILURE);
}
std::cerr << "Unable to retrieve socket path" << std::endl;
std::abort();
}
}
uint32_t type = IPC_COMMAND;
if (strcasecmp(cmdtype, "command") == 0)
{
type = IPC_COMMAND;
}
else if (strcasecmp(cmdtype, "get_workspaces") == 0)
{
type = IPC_GET_WORKSPACES;
}
else if (strcasecmp(cmdtype, "get_seats") == 0)
{
type = IPC_GET_SEATS;
}
else if (strcasecmp(cmdtype, "get_inputs") == 0)
{
type = IPC_GET_INPUTS;
}
else if (strcasecmp(cmdtype, "get_outputs") == 0)
{
type = IPC_GET_OUTPUTS;
}
else if (strcasecmp(cmdtype, "get_tree") == 0)
{
type = IPC_GET_TREE;
}
else if (strcasecmp(cmdtype, "get_marks") == 0)
{
type = IPC_GET_MARKS;
}
else if (strcasecmp(cmdtype, "get_bar_config") == 0)
{
type = IPC_GET_BAR_CONFIG;
}
else if (strcasecmp(cmdtype, "get_version") == 0)
{
type = IPC_GET_VERSION;
}
else if (strcasecmp(cmdtype, "get_binding_modes") == 0)
{
type = IPC_GET_BINDING_MODES;
}
else if (strcasecmp(cmdtype, "get_binding_state") == 0)
{
type = IPC_GET_BINDING_STATE;
}
else if (strcasecmp(cmdtype, "get_config") == 0)
{
type = IPC_GET_CONFIG;
}
else if (strcasecmp(cmdtype, "send_tick") == 0)
{
type = IPC_SEND_TICK;
}
else if (strcasecmp(cmdtype, "subscribe") == 0)
{
type = IPC_SUBSCRIBE;
}
else
{
if (quiet)
{
exit(EXIT_FAILURE);
}
std::cerr << "Unknown message type: " << cmdtype << std::endl;
std::abort();
}
free(cmdtype);
if (monitor && type != IPC_SUBSCRIBE)
{
if (!quiet)
{
std::cerr << "Monitor can only be used with -t SUBSCRIBE" << std::endl;
}
free(socket_path);
return 1;
}
char* command = NULL;
if (optind < argc)
{
command = join_args(argv + optind, argc - optind);
}
else
{
command = strdup("");
}
int ret = 0;
int socketfd = ipc_open_socket(socket_path);
struct timeval timeout = { .tv_sec = 3, .tv_usec = 0 };
ipc_set_recv_timeout(socketfd, timeout);
uint32_t len = strlen(command);
char* resp = ipc_single_command(socketfd, type, command, &len);
// pretty print the json
json_tokener* tok = json_tokener_new_ex(JSON_MAX_DEPTH);
if (tok == NULL)
{
if (quiet)
{
exit(EXIT_FAILURE);
}
std::cerr << "failed allocating json_tokener" << std::endl;
std::abort();
}
json_object* obj = json_tokener_parse_ex(tok, resp, -1);
enum json_tokener_error err = json_tokener_get_error(tok);
json_tokener_free(tok);
if (obj == NULL || err != json_tokener_success)
{
if (!quiet)
{
std::cerr << "failed to parse payload as json: " << json_tokener_error_desc(err) << std::endl;
}
ret = 1;
}
else
{
if (!success(obj, true))
{
ret = 2;
}
if (!quiet && (type != IPC_SUBSCRIBE || ret != 0))
{
if (raw)
{
printf("%s\n", json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
}
else
{
pretty_print(type, obj);
}
}
json_object_put(obj);
}
free(command);
free(resp);
if (type == IPC_SUBSCRIBE && ret == 0)
{
// Remove the timeout for subscribed events
timeout.tv_sec = 0;
timeout.tv_usec = 0;
ipc_set_recv_timeout(socketfd, timeout);
do
{
struct ipc_response* reply = ipc_recv_response(socketfd);
if (!reply)
{
break;
}
json_tokener* tok = json_tokener_new_ex(JSON_MAX_DEPTH);
if (tok == NULL)
{
if (quiet)
{
exit(EXIT_FAILURE);
}
std::cerr << "failed allocating json_tokener" << std::endl;
std::abort();
}
json_object* obj = json_tokener_parse_ex(tok, reply->payload, -1);
enum json_tokener_error err = json_tokener_get_error(tok);
json_tokener_free(tok);
if (obj == NULL || err != json_tokener_success)
{
if (!quiet)
{
std::cerr << "failed to parse payload as json: " << json_tokener_error_desc(err) << std::endl;
}
ret = 1;
break;
}
else if (quiet)
{
json_object_put(obj);
}
else
{
if (raw)
{
printf("%s\n", json_object_to_json_string(obj));
}
else
{
printf("%s\n", json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
}
fflush(stdout);
json_object_put(obj);
}
free_ipc_response(reply);
} while (monitor);
}
close(socketfd);
free(socket_path);
return ret;
}

View File

@ -33,6 +33,9 @@ apps:
miracle-wm-sensible-terminal:
command: usr/local/bin/miracle-wm-sensible-terminal
miraclemsg:
command: usr/local/bin/miraclemsg
parts:
miracle-wm:
build-attributes:
@ -76,6 +79,7 @@ parts:
- libgles2-mesa-dev
- libmirrenderer-dev
- libmirwayland-dev
- libjson-c-dev
stage-packages:
- libmiral7
- libmiroil5

View File

@ -23,6 +23,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
MIRACLEMSG_LICENSE = """/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Portions of this code originate from swaymsg, licensed under the MIT license.
See the LICENSE.Sway file for details.
**/
"""
parser = argparse.ArgumentParser(description="This tool checks the licenses on CPP files and fixes them if told to do so")
parser.add_argument('--fix', type=bool,
help="Fix the licenses if required")
@ -30,26 +52,37 @@ parser.add_argument('--fix', type=bool,
args = parser.parse_args()
fix = args.fix
root_dir = Path(__file__).parent.parent / "src"
error_files = []
error_data = []
for x in os.listdir(root_dir.as_posix()):
file = root_dir / x
if file.as_posix().endswith(".h") or file.as_posix().endswith(".cpp"):
with open(file, 'r') as original:
content = original.read()
if not content.startswith(LICENSE):
error_files.append(file.as_posix())
error_data.append(content)
to_check = ["src", "miraclemsg"]
if fix is True:
for i in range(0, len(error_files)):
file = error_files[i]
data = error_data[i]
with open(file, 'w') as f:
f.write(LICENSE)
f.write(data)
elif len(error_files) > 0:
print("The following files are missing the GPL License at the top: ")
print(" " + "\n ".join(error_files))
exit(1)
for d in to_check:
root_dir = Path(__file__).parent.parent / d
error_files = []
error_data = []
for x in os.listdir(root_dir.as_posix()):
file = root_dir / x
if file.as_posix().endswith(".h") or file.as_posix().endswith(".cpp"):
with open(file, 'r') as original:
content = original.read()
is_error = False
if d == "miraclemsg":
if not content.startswith(MIRACLEMSG_LICENSE):
is_error = True
elif not content.startswith(LICENSE):
is_error = True
if is_error:
error_files.append(file.as_posix())
error_data.append(content)
if fix is True:
for i in range(0, len(error_files)):
file = error_files[i]
data = error_data[i]
with open(file, 'w') as f:
to_write = MIRACLEMSG_LICENSE if "miraclemsg" in file else LICENSE
f.write(to_write)
f.write(data)
elif len(error_files) > 0:
print("The following files are missing the GPL License at the top: ")
print(" " + "\n ".join(error_files))
exit(1)

View File

@ -1,3 +1,4 @@
#!/bin/bash
find ./src/ -regex '.*\.\(cpp\|h\)' -exec clang-format -style=file -i {} \;
find ./tests/ -regex '.*\.\(cpp\|h\)' -exec clang-format -style=file -i {} \;
find ./tests/ -regex '.*\.\(cpp\|h\)' -exec clang-format -style=file -i {} \;
find ./miraclemsg/ -regex '.*\.\(cpp\|h\)' -exec clang-format -style=file -i {} \;