wip
This commit is contained in:
8
Makefile
8
Makefile
@@ -17,16 +17,20 @@ install: $(BIN)
|
|||||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||||
cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
||||||
chmod 755 $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
chmod 755 $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
||||||
|
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
|
||||||
|
cp -f frcctrlmgr.1 $(DESTDIR)$(MANPREFIX)/man1/
|
||||||
|
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/frcctrlmgr.1
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) \
|
||||||
|
$(DESTDIR)$(MANPREFIX)/man1/frcctrlmgr.1
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJ) $(BIN) $(DIST).tar.gz
|
rm -f $(OBJ) $(BIN) $(DIST).tar.gz
|
||||||
|
|
||||||
dist: clean
|
dist: clean
|
||||||
mkdir -p $(DIST)
|
mkdir -p $(DIST)
|
||||||
cp -R src config.mk Makefile $(DIST)
|
cp -R src config.mk Makefile frcctrlmgr.1 LICENSE README $(DIST)
|
||||||
tar -czf $(DIST).tar.gz $(DIST)
|
tar -czf $(DIST).tar.gz $(DIST)
|
||||||
rm -rf $(DIST)
|
rm -rf $(DIST)
|
||||||
|
|
||||||
|
|||||||
BIN
frcctrlmgr
Executable file
BIN
frcctrlmgr
Executable file
Binary file not shown.
27
frcctrlmgr.1
Normal file
27
frcctrlmgr.1
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.TH VXC "1" "April 2026" "frcctrlmgr v0.1" "User Commands"
|
||||||
|
.SH NAME
|
||||||
|
frcctrlmgr \- manual page for frcctrlmgr v0.1
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B frcctrlmgr
|
||||||
|
[\fI\,OPTION\/\fR]... \fI\,<version> <dir>\/\fR
|
||||||
|
.SH DESCRIPTION
|
||||||
|
A simple way to display the control schema of your robot.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
\fB\-h, \-\-help\fR
|
||||||
|
print this help and exit
|
||||||
|
.TP
|
||||||
|
\fB\-v, \-\-version\fR
|
||||||
|
output version information
|
||||||
|
.TP
|
||||||
|
\fB\-D, --display\fR
|
||||||
|
displays a diagram of the Thrustmaster and the Xbox controller.
|
||||||
|
.TP
|
||||||
|
\fB\-j\fR
|
||||||
|
output the controls in a JSON format.
|
||||||
|
.TP
|
||||||
|
.SH COPYRIGHT
|
||||||
|
Copyright \(co 2025\-2026 fSD.
|
||||||
|
.br
|
||||||
|
This is free software; you are free to change and redistribute it.
|
||||||
|
There is NO WARRANTY, to the extent permitted by law.
|
||||||
289
src/frcctrlmgr.c
289
src/frcctrlmgr.c
@@ -1,22 +1,301 @@
|
|||||||
#include "frcctrlmgr.h"
|
#include "frcctrlmgr.h"
|
||||||
|
|
||||||
char *argv0;
|
char *argv0;
|
||||||
|
bool use_json = false;
|
||||||
|
|
||||||
void usage(int code) {
|
static void usage(int code)
|
||||||
printf("usage: %s: [-h help] [-v]\n", argv0);
|
{
|
||||||
|
printf("usage: %s: [--help] [--diagram] [-vhD]\n", argv0);
|
||||||
exit(code);
|
exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void display_disgrams()
|
||||||
|
{
|
||||||
|
puts("|1|2|3| 1234 |1|2|3|");
|
||||||
|
puts("|4|5|6| |4|5|6|");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void die(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
printf("%s: ", argv0);
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vprintf(fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
putchar('\n');
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *join_path(const char *a, const char *b) {
|
||||||
|
if (!a) a = "";
|
||||||
|
if (!b) b = "";
|
||||||
|
size_t la = strlen(a), lb = strlen(b);
|
||||||
|
int a_has = la > 0 && a[la-1] == '/';
|
||||||
|
int b_has = lb > 0 && b[0] == '/';
|
||||||
|
|
||||||
|
size_t need = la + lb + 2;
|
||||||
|
char *res = malloc(need);
|
||||||
|
if (!res) return NULL;
|
||||||
|
|
||||||
|
if (la == 0) {
|
||||||
|
snprintf(res, need, "%s", b);
|
||||||
|
} else if (lb == 0) {
|
||||||
|
snprintf(res, need, "%s", a);
|
||||||
|
} else if (a_has && b_has) {
|
||||||
|
snprintf(res, need, "%s%s", a, b + 1);
|
||||||
|
} else if (!a_has && !b_has) {
|
||||||
|
snprintf(res, need, "%s/%s", a, b);
|
||||||
|
} else {
|
||||||
|
snprintf(res, need, "%s%s", a, b);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *controller_kind_str(ControllerKind k)
|
||||||
|
{
|
||||||
|
switch (k) {
|
||||||
|
case JOYSTICK: return "JOYSTICK";
|
||||||
|
case XBOX: return "XBOX";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *controller_action_str(ControllerAction a)
|
||||||
|
{
|
||||||
|
switch (a) {
|
||||||
|
case JOYSTICK_ONE: return "JOYSTICK_ONE";
|
||||||
|
case JOYSTICK_TWO: return "JOYSTICK_TWO";
|
||||||
|
case JOYSTICK_THREE: return "JOYSTICK_THREE";
|
||||||
|
case JOYSTICK_FOUR: return "JOYSTICK_FOUR";
|
||||||
|
case JOYSTICK_FIVE: return "JOYSTICK_FIVE";
|
||||||
|
case JOYSTICK_SIX: return "JOYSTICK_SIX";
|
||||||
|
case JOYSTICK_SEVEN: return "JOYSTICK_SEVEN";
|
||||||
|
case JOYSTICK_EIGHT: return "JOYSTICK_EIGHT";
|
||||||
|
case JOYSTICK_NINE: return "JOYSTICK_NINE";
|
||||||
|
case JOYSTICK_TEN: return "JOYSTICK_TEN";
|
||||||
|
case JOYSTICK_ELEVEN: return "JOYSTICK_ELEVEN";
|
||||||
|
case JOYSTICK_TWELVE: return "JOYSTICK_TWELVE";
|
||||||
|
case JOYSTICK_THIRTEEN: return "JOYSTICK_THIRTEEN";
|
||||||
|
case JOYSTICK_FOURTEEN: return "JOYSTICK_FOURTEEN";
|
||||||
|
case JOYSTICK_FIFTEEN: return "JOYSTICK_FIFTEEN";
|
||||||
|
case JOYSTICK_SIXTEEN: return "JOYSTICK_SIXTEEN";
|
||||||
|
case XBOX_BACK: return "XBOX_BACK";
|
||||||
|
case XBOX_START: return "XBOX_START";
|
||||||
|
case XBOX_X: return "XBOX_X";
|
||||||
|
case XBOX_Y: return "XBOX_Y";
|
||||||
|
case XBOX_B: return "XBOX_B";
|
||||||
|
case XBOX_A: return "XBOX_A";
|
||||||
|
case XBOX_LEFTBUMPER: return "XBOX_LEFTBUMPER";
|
||||||
|
case XBOX_RIGHTBUMPER: return "XBOX_RIGHTBUMPER";
|
||||||
|
case XBOX_LEFTSTICK: return "XBOX_LEFTSTICK";
|
||||||
|
case XBOX_RIGHTSTICK: return "XBOX_RIGHTSTICK";
|
||||||
|
case XBOX_LEFTTRIGGER: return "XBOX_LEFTTRIGGER";
|
||||||
|
case XBOX_RIGHTTRIGGER: return "XBOX_RIGHTTRIGGER";
|
||||||
|
case XBOX_UP: return "XBOX_UP";
|
||||||
|
case XBOX_DOWN: return "XBOX_DOWN";
|
||||||
|
case XBOX_LEFT: return "XBOX_LEFT";
|
||||||
|
case XBOX_RIGHT: return "XBOX_RIGHT";
|
||||||
|
case XBOX_NORTH: return "XBOX_NORTH";
|
||||||
|
case XBOX_NORTHEAST: return "XBOX_NORTHEAST";
|
||||||
|
case XBOX_EAST: return "XBOX_EAST";
|
||||||
|
case XBOX_SOUTHEAST: return "XBOX_SOUTHEAST";
|
||||||
|
case XBOX_SOUTH: return "XBOX_SOUTH";
|
||||||
|
case XBOX_SOUTHWEST: return "XBOX_SOUTHWEST";
|
||||||
|
case XBOX_WEST: return "XBOX_WEST";
|
||||||
|
case XBOX_NORTHWEST: return "XBOX_NORTHWEST";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ControllerAction controller_str_action(char *a)
|
||||||
|
{
|
||||||
|
if (strcmp(a, "JOYSTICK_ONE") == 0)
|
||||||
|
return JOYSTICK_ONE;
|
||||||
|
else if (strcmp(a, "JOYSTICK_TWO") == 0)
|
||||||
|
return JOYSTICK_TWO;
|
||||||
|
else if (strcmp(a, "JOYSTICK_THREE") == 0)
|
||||||
|
return JOYSTICK_THREE;
|
||||||
|
else if (strcmp(a, "JOYSTICK_FOUR") == 0)
|
||||||
|
return JOYSTICK_FOUR;
|
||||||
|
else if (strcmp(a, "JOYSTICK_FIVE") == 0)
|
||||||
|
return JOYSTICK_FIVE;
|
||||||
|
else if (strcmp(a, "JOYSTICK_SIX") == 0)
|
||||||
|
return JOYSTICK_SIX;
|
||||||
|
else if (strcmp(a, "JOYSTICK_SEVEN") == 0)
|
||||||
|
return JOYSTICK_SEVEN;
|
||||||
|
else if (strcmp(a, "JOYSTICK_EIGHT") == 0)
|
||||||
|
return JOYSTICK_EIGHT;
|
||||||
|
else if (strcmp(a, "JOYSTICK_NINE") == 0)
|
||||||
|
return JOYSTICK_NINE;
|
||||||
|
else if (strcmp(a, "JOYSTICK_TEN") == 0)
|
||||||
|
return JOYSTICK_TEN;
|
||||||
|
else if (strcmp(a, "JOYSTICK_ELEVEN") == 0)
|
||||||
|
return JOYSTICK_ELEVEN;
|
||||||
|
else if (strcmp(a, "JOYSTICK_TWELVE") == 0)
|
||||||
|
return JOYSTICK_TWELVE;
|
||||||
|
else if (strcmp(a, "JOYSTICK_THIRTEEN") == 0)
|
||||||
|
return JOYSTICK_THIRTEEN;
|
||||||
|
else if (strcmp(a, "JOYSTICK_FOURTEEN") == 0)
|
||||||
|
return JOYSTICK_FOURTEEN;
|
||||||
|
else if (strcmp(a, "JOYSTICK_FIFTEEN") == 0)
|
||||||
|
return JOYSTICK_FIFTEEN;
|
||||||
|
else if (strcmp(a, "JOYSTICK_SIXTEEN") == 0)
|
||||||
|
return JOYSTICK_SIXTEEN;
|
||||||
|
else if (strcmp(a, "XBOX_BACK") == 0)
|
||||||
|
return XBOX_BACK;
|
||||||
|
else if (strcmp(a, "XBOX_START") == 0)
|
||||||
|
return XBOX_START;
|
||||||
|
else if (strcmp(a, "XBOX_X") == 0)
|
||||||
|
return XBOX_X;
|
||||||
|
else if (strcmp(a, "XBOX_Y") == 0)
|
||||||
|
return XBOX_Y;
|
||||||
|
else if (strcmp(a, "XBOX_B") == 0)
|
||||||
|
return XBOX_B;
|
||||||
|
else if (strcmp(a, "XBOX_A") == 0)
|
||||||
|
return XBOX_A;
|
||||||
|
else if (strcmp(a, "XBOX_LEFTBUMPER") == 0)
|
||||||
|
return XBOX_LEFTBUMPER;
|
||||||
|
else if (strcmp(a, "XBOX_RIGHTBUMPER") == 0)
|
||||||
|
return XBOX_RIGHTBUMPER;
|
||||||
|
else if (strcmp(a, "XBOX_LEFTSTICK") == 0)
|
||||||
|
return XBOX_LEFTSTICK;
|
||||||
|
else if (strcmp(a, "XBOX_RIGHTSTICK") == 0)
|
||||||
|
return XBOX_RIGHTSTICK;
|
||||||
|
else if (strcmp(a, "XBOX_LEFTTRIGGER") == 0)
|
||||||
|
return XBOX_LEFTTRIGGER;
|
||||||
|
else if (strcmp(a, "XBOX_RIGHTTRIGGER") == 0)
|
||||||
|
return XBOX_RIGHTTRIGGER;
|
||||||
|
else if (strcmp(a, "XBOX_UP") == 0)
|
||||||
|
return XBOX_UP;
|
||||||
|
else if (strcmp(a, "XBOX_DOWN") == 0)
|
||||||
|
return XBOX_DOWN;
|
||||||
|
else if (strcmp(a, "XBOX_LEFT") == 0)
|
||||||
|
return XBOX_LEFT;
|
||||||
|
else if (strcmp(a, "XBOX_RIGHT") == 0)
|
||||||
|
return XBOX_RIGHT;
|
||||||
|
else if (strcmp(a, "XBOX_NORTH") == 0)
|
||||||
|
return XBOX_NORTH;
|
||||||
|
else if (strcmp(a, "XBOX_NORTHEAST") == 0)
|
||||||
|
return XBOX_NORTHEAST;
|
||||||
|
else if (strcmp(a, "XBOX_EAST") == 0)
|
||||||
|
return XBOX_EAST;
|
||||||
|
else if (strcmp(a, "XBOX_SOUTHEAST") == 0)
|
||||||
|
return XBOX_SOUTHEAST;
|
||||||
|
else if (strcmp(a, "XBOX_SOUTH") == 0)
|
||||||
|
return XBOX_SOUTH;
|
||||||
|
else if (strcmp(a, "XBOX_SOUTHWEST") == 0)
|
||||||
|
return XBOX_SOUTHWEST;
|
||||||
|
else if (strcmp(a, "XBOX_WEST") == 0)
|
||||||
|
return XBOX_WEST;
|
||||||
|
else if (strcmp(a, "XBOX_NORTHWEST") == 0)
|
||||||
|
return XBOX_NORTHWEST;
|
||||||
|
die("%s:%s", __FILE__, __LINE__);
|
||||||
|
return XBOX_A; // dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
int print_control(Control control, bool fmt_json)
|
||||||
|
{
|
||||||
|
if (fmt_json) {
|
||||||
|
return printf(
|
||||||
|
"{\"controller\":\"%s\",\"button\":\"%s\",\"action\":\"%s\"}\n",
|
||||||
|
controller_kind_str(control.controller),
|
||||||
|
controller_action_str(control.button),
|
||||||
|
control.action ? control.action : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return printf(
|
||||||
|
"controller=%s button=%s action=%s\n",
|
||||||
|
controller_kind_str(control.controller),
|
||||||
|
controller_action_str(control.button),
|
||||||
|
control.action ? control.action : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *trim_string(char *str) {
|
||||||
|
if (!str) return NULL;
|
||||||
|
|
||||||
|
size_t len = strlen(str);
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end = len - 1;
|
||||||
|
|
||||||
|
while (isspace(str[start])) start++;
|
||||||
|
while (end > start && isspace(str[end])) end--;
|
||||||
|
|
||||||
|
char *result = malloc(end - start + 2);
|
||||||
|
if (!result) return NULL;
|
||||||
|
|
||||||
|
strncpy(result, str + start, end - start + 1);
|
||||||
|
result[end - start + 1] = '\0';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int read_wpiproject(Control *output, char *root)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(join_path(root, "src/main/java/com/adambots/RobotContainer.java"), "r");
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
bool found_configure_bindings_start = false;
|
||||||
|
int output_i = 0;
|
||||||
|
|
||||||
|
if (!fp)
|
||||||
|
die("failed to read: %s", root);
|
||||||
|
|
||||||
|
while (fgets(buf, sizeof(buf), fp)) {
|
||||||
|
char *p = buf;
|
||||||
|
|
||||||
|
while (*p && isspace((unsigned char)*p))
|
||||||
|
p++;
|
||||||
|
|
||||||
|
if (strncmp(p, "Buttons.", 8) == 0) {
|
||||||
|
unsigned char c = p[8];
|
||||||
|
|
||||||
|
if (c && isupper(c)) {
|
||||||
|
// Buttons.XboxStartButton.onTrue(
|
||||||
|
// 17^
|
||||||
|
ControllerKind controller = XBOX;
|
||||||
|
ControllerAction button = controller_str_action("XBOX_A");
|
||||||
|
print_control((Control){
|
||||||
|
.controller = controller,
|
||||||
|
.button = button,
|
||||||
|
.action = ""
|
||||||
|
}, use_json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
argv0 = argv[0];
|
argv0 = basename(argv[0]);
|
||||||
|
char *project;
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
usage(1);
|
||||||
|
|
||||||
for (int i = 0; i < argc; ++i) {
|
for (int i = 0; i < argc; ++i) {
|
||||||
if (STRCMP("--help"))
|
if (STRCMP("--help"))
|
||||||
usage(0);
|
usage(0);
|
||||||
if (STRCMP("-h"))
|
else if (STRCMP("-h"))
|
||||||
usage(0);
|
usage(0);
|
||||||
if (STRCMP("-v"))
|
else if (STRCMP("-v"))
|
||||||
printf("%s/%s\n", argv0, VERSION);
|
printf("%s/%s\n", argv0, VERSION);
|
||||||
|
else if (STRCMP("-j"))
|
||||||
|
use_json = true;
|
||||||
|
else if (STRCMP("--diagram") || STRCMP("-D"))
|
||||||
|
display_disgrams();
|
||||||
|
else if (argv[i][0] == '-')
|
||||||
|
die("unknown option: %s", argv[i]);
|
||||||
|
else
|
||||||
|
project = argv[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Control *output = malloc(sizeof(Control) * 40);
|
||||||
|
read_wpiproject(output, project);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,72 @@
|
|||||||
|
|
||||||
#define VERSION "0.1"
|
#define VERSION "0.1"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define STRCMP(x) !strcmp(argv[i], x)
|
#define STRCMP(x) !strcmp(argv[i], x)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
JOYSTICK,
|
||||||
|
XBOX,
|
||||||
|
} ControllerKind;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
JOYSTICK_ONE,
|
||||||
|
JOYSTICK_TWO,
|
||||||
|
JOYSTICK_THREE,
|
||||||
|
JOYSTICK_FOUR,
|
||||||
|
JOYSTICK_FIVE,
|
||||||
|
JOYSTICK_SIX,
|
||||||
|
JOYSTICK_SEVEN,
|
||||||
|
JOYSTICK_EIGHT,
|
||||||
|
JOYSTICK_NINE,
|
||||||
|
JOYSTICK_TEN,
|
||||||
|
JOYSTICK_ELEVEN,
|
||||||
|
JOYSTICK_TWELVE,
|
||||||
|
JOYSTICK_THIRTEEN,
|
||||||
|
JOYSTICK_FOURTEEN,
|
||||||
|
JOYSTICK_FIFTEEN,
|
||||||
|
JOYSTICK_SIXTEEN,
|
||||||
|
|
||||||
|
XBOX_BACK,
|
||||||
|
XBOX_START,
|
||||||
|
XBOX_X,
|
||||||
|
XBOX_Y,
|
||||||
|
XBOX_B,
|
||||||
|
XBOX_A,
|
||||||
|
XBOX_LEFTBUMPER,
|
||||||
|
XBOX_RIGHTBUMPER,
|
||||||
|
XBOX_LEFTSTICK,
|
||||||
|
XBOX_RIGHTSTICK,
|
||||||
|
XBOX_LEFTTRIGGER,
|
||||||
|
XBOX_RIGHTTRIGGER,
|
||||||
|
XBOX_UP,
|
||||||
|
XBOX_DOWN,
|
||||||
|
XBOX_LEFT,
|
||||||
|
XBOX_RIGHT,
|
||||||
|
XBOX_NORTH,
|
||||||
|
XBOX_NORTHEAST,
|
||||||
|
XBOX_EAST,
|
||||||
|
XBOX_SOUTHEAST,
|
||||||
|
XBOX_SOUTH,
|
||||||
|
XBOX_SOUTHWEST,
|
||||||
|
XBOX_WEST,
|
||||||
|
XBOX_NORTHWEST,
|
||||||
|
} ControllerAction;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ControllerKind controller;
|
||||||
|
ControllerAction button;
|
||||||
|
char *action;
|
||||||
|
} Control;
|
||||||
|
|
||||||
|
int print_control(Control, bool);
|
||||||
|
int read_wpiproject(Control*, char *);
|
||||||
|
|
||||||
#endif // FRCTRLMGR_H_
|
#endif // FRCTRLMGR_H_
|
||||||
|
|||||||
BIN
src/frcctrlmgr.o
Normal file
BIN
src/frcctrlmgr.o
Normal file
Binary file not shown.
Reference in New Issue
Block a user