You've already forked ziomekos
wmpwman v26.10.22
A Window Maker dockapp to help with power management. Co-authored-by: Tomek Wojcik <labs@tomekwojcik.pl> Co-committed-by: Tomek Wojcik <labs@tomekwojcik.pl>
This commit is contained in:
1
packages/wmpwman/.gitignore
vendored
Normal file
1
packages/wmpwman/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/wmpwman
|
||||||
21
packages/wmpwman/Makefile
Normal file
21
packages/wmpwman/Makefile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Co-Authored by: Claude (Sonnet 5.5 Medium)
|
||||||
|
|
||||||
|
CC ?= cc
|
||||||
|
CFLAGS ?= -O2 -Wall -Wextra -Wno-incompatible-pointer-types
|
||||||
|
CFLAGS += $(shell pkg-config --cflags x11 dockapp 2>/dev/null)
|
||||||
|
LDLIBS = $(shell pkg-config --libs x11 xext xpm dockapp 2>/dev/null)
|
||||||
|
|
||||||
|
PREFIX ?= /usr/local
|
||||||
|
|
||||||
|
SOURCES = src/wmpwman.c
|
||||||
|
|
||||||
|
.PHONY: install clean
|
||||||
|
|
||||||
|
wmpwman: $(SOURCES)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
|
install: wmpwman
|
||||||
|
install -D -m 755 wmpwman $(DESTDIR)$(PREFIX)/bin/wmpwman
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f wmpwman
|
||||||
35
packages/wmpwman/README.md
Normal file
35
packages/wmpwman/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# wmpwman
|
||||||
|
|
||||||
|
A Window Maker dockapp to help with power management.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* Xorg-7,
|
||||||
|
* libXext,
|
||||||
|
* libXpm,
|
||||||
|
* [libdockapp v0.7.3](https://www.dockapps.net/libdockapp),
|
||||||
|
* a WM that supports Window Maker dockapps (tested with Window Maker 0.96.0).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Build wmpwman by running the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, as the _root_ user:
|
||||||
|
|
||||||
|
```
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary will be installed in `/usr/local/bin/wmpwman`.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v26.10.22
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
BIN
packages/wmpwman/screenshots/wmpwman.png
Normal file
BIN
packages/wmpwman/screenshots/wmpwman.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
508
packages/wmpwman/src/wmpwman.c
Normal file
508
packages/wmpwman/src/wmpwman.c
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
/*
|
||||||
|
* wmpwman - a Window Maker dockapp to help with power management.
|
||||||
|
* Co-Authored by: Claude (Sonnet 5.5 Medium)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <spawn.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <libdockapp/dockapp.h>
|
||||||
|
|
||||||
|
#define WMPWMAN_VERSION "26.10.22"
|
||||||
|
#define VERSION "wmpwman version v" WMPWMAN_VERSION "\nCreated by Tomek Wójcik | For teh lulz"
|
||||||
|
|
||||||
|
#define DOCK_SIZE 59
|
||||||
|
#define NUM_BUTTONS 3
|
||||||
|
|
||||||
|
#define BTN_X 4
|
||||||
|
#define BTN_W 51
|
||||||
|
#define BTN_H 14
|
||||||
|
#define BTN_GAP 4
|
||||||
|
#define BTN_TOP 4
|
||||||
|
|
||||||
|
#define NO_PRESSED_BUTTON -1
|
||||||
|
|
||||||
|
#define SHUTDOWN_DELAY_MS 5000
|
||||||
|
#define TICK_MS 200
|
||||||
|
|
||||||
|
#pragma mark - Globals
|
||||||
|
|
||||||
|
struct WMPWMan {
|
||||||
|
Pixmap pixmap;
|
||||||
|
Pixmap mask;
|
||||||
|
unsigned short width;
|
||||||
|
unsigned short height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WMPWMan *app;
|
||||||
|
|
||||||
|
static GC graphics_context;
|
||||||
|
|
||||||
|
static unsigned long color_background,
|
||||||
|
color_button,
|
||||||
|
color_button_pressed,
|
||||||
|
color_light,
|
||||||
|
color_dark,
|
||||||
|
color_button_title,
|
||||||
|
color_button_shutdown_background;
|
||||||
|
|
||||||
|
static XFontStruct *font;
|
||||||
|
|
||||||
|
static const char *const button_titles[NUM_BUTTONS] = {
|
||||||
|
"LOCK",
|
||||||
|
"SLEEP",
|
||||||
|
"SHUTDOWN",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const font_candidates[] = {
|
||||||
|
"5x8",
|
||||||
|
"5x7",
|
||||||
|
"6x10",
|
||||||
|
"fixed",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
// button currently held down, or NO_PRESSED_BUTTON
|
||||||
|
static int pressed_button = NO_PRESSED_BUTTON;
|
||||||
|
|
||||||
|
static int is_shutdown_armed;
|
||||||
|
static int last_shutdown_seconds;
|
||||||
|
static struct timespec shutdown_deadline;
|
||||||
|
|
||||||
|
# pragma mark - Helpers
|
||||||
|
|
||||||
|
static unsigned long alloc_color(const char *name) {
|
||||||
|
XColor color, exact;
|
||||||
|
Colormap cmap = DefaultColormap(DADisplay, DefaultScreen(DADisplay));
|
||||||
|
|
||||||
|
if (!XAllocNamedColor(DADisplay, cmap, name, &color, &exact)) {
|
||||||
|
DAWarning("can't allocate color %s", name);
|
||||||
|
return BlackPixel(DADisplay, DefaultScreen(DADisplay));
|
||||||
|
}
|
||||||
|
return color.pixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XFontStruct *load_font(void) {
|
||||||
|
const char *const *name;
|
||||||
|
XFontStruct *result;
|
||||||
|
|
||||||
|
for (name = font_candidates; *name; name++) {
|
||||||
|
result = XLoadQueryFont(DADisplay, *name);
|
||||||
|
if (result) {
|
||||||
|
DAWarning("selected font: %s", *name);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DAWarning("no usable font found, labels disabled");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int calculate_button_y(int i) {
|
||||||
|
return BTN_TOP + i * (BTN_H + BTN_GAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns button index at (x, y), or NO_PRESSED_BUTTON.
|
||||||
|
static int hit_test(int x, int y) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < NUM_BUTTONS; i++) {
|
||||||
|
int button_y = calculate_button_y(i);
|
||||||
|
if (x >= BTN_X && x < BTN_X + BTN_W && y >= button_y && y < button_y + BTN_H) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_PRESSED_BUTTON;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long current_shutdown_deadline_ms(void) {
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
|
||||||
|
return (shutdown_deadline.tv_sec - now.tv_sec) * 1000L
|
||||||
|
+ (shutdown_deadline.tv_nsec - shutdown_deadline.tv_nsec) / 1000000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int current_shutdown_deadline_seconds(void) {
|
||||||
|
return (int)((current_shutdown_deadline_ms() + 999) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void shutdown_arm(void) {
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &shutdown_deadline);
|
||||||
|
shutdown_deadline.tv_sec += SHUTDOWN_DELAY_MS / 1000;
|
||||||
|
is_shutdown_armed = 1;
|
||||||
|
last_shutdown_seconds = current_shutdown_deadline_seconds();
|
||||||
|
DASetTimeout(TICK_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void shutdown_disarm(void) {
|
||||||
|
is_shutdown_armed = 0;
|
||||||
|
DASetTimeout(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_button(int i, int down) {
|
||||||
|
int button_x = BTN_X,
|
||||||
|
button_y = calculate_button_y(i),
|
||||||
|
width = BTN_W,
|
||||||
|
height = BTN_H;
|
||||||
|
|
||||||
|
unsigned long top_left = down ? color_dark : color_light;
|
||||||
|
unsigned long bot_right = down ? color_light : color_dark;
|
||||||
|
|
||||||
|
// face
|
||||||
|
if (i == 2 && is_shutdown_armed) {
|
||||||
|
XSetForeground(
|
||||||
|
DADisplay,
|
||||||
|
graphics_context,
|
||||||
|
color_button_shutdown_background
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
XSetForeground(
|
||||||
|
DADisplay,
|
||||||
|
graphics_context,
|
||||||
|
down ? color_button_pressed : color_button
|
||||||
|
);
|
||||||
|
}
|
||||||
|
XFillRectangle(
|
||||||
|
DADisplay,
|
||||||
|
app->pixmap,
|
||||||
|
graphics_context,
|
||||||
|
button_x,
|
||||||
|
button_y,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
|
||||||
|
// bevel: top & left
|
||||||
|
XSetForeground(DADisplay, graphics_context, top_left);
|
||||||
|
XDrawLine(
|
||||||
|
DADisplay,
|
||||||
|
app->pixmap,
|
||||||
|
graphics_context,
|
||||||
|
button_x,
|
||||||
|
button_y,
|
||||||
|
button_x + width - 1,
|
||||||
|
button_y
|
||||||
|
);
|
||||||
|
XDrawLine(
|
||||||
|
DADisplay,
|
||||||
|
app->pixmap,
|
||||||
|
graphics_context,
|
||||||
|
button_x,
|
||||||
|
button_y,
|
||||||
|
button_x,
|
||||||
|
button_y + height - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
// bevel: bottom & right
|
||||||
|
XSetForeground(DADisplay, graphics_context, bot_right);
|
||||||
|
XDrawLine(
|
||||||
|
DADisplay,
|
||||||
|
app->pixmap,
|
||||||
|
graphics_context,
|
||||||
|
button_x,
|
||||||
|
button_y + height - 1,
|
||||||
|
button_x + width - 1,
|
||||||
|
button_y + height - 1
|
||||||
|
);
|
||||||
|
XDrawLine(
|
||||||
|
DADisplay,
|
||||||
|
app->pixmap,
|
||||||
|
graphics_context,
|
||||||
|
button_x + width - 1,
|
||||||
|
button_y,
|
||||||
|
button_x + width - 1,
|
||||||
|
button_y + height - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (font) {
|
||||||
|
const char *title = button_titles[i];
|
||||||
|
int title_length = (int)strlen(title);
|
||||||
|
int direction, font_ascent, font_descent;
|
||||||
|
XCharStruct overall;
|
||||||
|
int title_x, title_y;
|
||||||
|
|
||||||
|
XTextExtents(
|
||||||
|
font,
|
||||||
|
title,
|
||||||
|
title_length,
|
||||||
|
&direction,
|
||||||
|
&font_ascent,
|
||||||
|
&font_descent,
|
||||||
|
&overall
|
||||||
|
);
|
||||||
|
|
||||||
|
title_x = button_x + (width - (overall.rbearing - overall.lbearing)) / 2 - overall.lbearing;
|
||||||
|
title_y = button_y + (height + overall.ascent - overall.descent) / 2;
|
||||||
|
|
||||||
|
XSetForeground(DADisplay, graphics_context, color_button_title);
|
||||||
|
XDrawString(
|
||||||
|
DADisplay,
|
||||||
|
app->pixmap,
|
||||||
|
graphics_context,
|
||||||
|
title_x,
|
||||||
|
title_y,
|
||||||
|
title,
|
||||||
|
title_length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redraw(void) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
XSetForeground(DADisplay, graphics_context, color_background);
|
||||||
|
XFillRectangle(
|
||||||
|
DADisplay,
|
||||||
|
app->pixmap,
|
||||||
|
graphics_context,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
DOCK_SIZE,
|
||||||
|
DOCK_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i = 0; i < NUM_BUTTONS; i++) {
|
||||||
|
draw_button(i, i == pressed_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
DASetPixmap(app->pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void spawn_detached(char const* argv[]) {
|
||||||
|
posix_spawn_file_actions_t file_actions;
|
||||||
|
posix_spawnattr_t attributes;
|
||||||
|
sigset_t sigdefault;
|
||||||
|
pid_t pid;
|
||||||
|
int return_code;
|
||||||
|
|
||||||
|
posix_spawn_file_actions_init(&file_actions);
|
||||||
|
posix_spawn_file_actions_addopen(
|
||||||
|
&file_actions, 0, "/dev/null", O_RDONLY, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
sigemptyset(&sigdefault);
|
||||||
|
sigaddset(&sigdefault, SIGCHLD);
|
||||||
|
|
||||||
|
posix_spawnattr_init(&attributes);
|
||||||
|
posix_spawnattr_setsigdefault(&attributes, &sigdefault);
|
||||||
|
posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETSIGDEF);
|
||||||
|
|
||||||
|
return_code = posix_spawnp(
|
||||||
|
&pid, argv[0], &file_actions, &attributes, argv, environ
|
||||||
|
);
|
||||||
|
if (return_code != 0) {
|
||||||
|
DAWarning("can't spawn %s: %s", argv[0], strerror(return_code));
|
||||||
|
}
|
||||||
|
|
||||||
|
posix_spawn_file_actions_destroy(&file_actions);
|
||||||
|
posix_spawnattr_destroy(&attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_shutdown(void) {
|
||||||
|
DAWarning("do_shutdown()");
|
||||||
|
|
||||||
|
char const *argv[] = {
|
||||||
|
"sudo",
|
||||||
|
"/usr/sbin/poweroff",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
spawn_detached(argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
# pragma mark - Button handlers
|
||||||
|
|
||||||
|
static void on_button_lock(void) {
|
||||||
|
DAWarning("on_button_lock");
|
||||||
|
|
||||||
|
char const *argv[] = {
|
||||||
|
"xscreensaver-command",
|
||||||
|
"-display", DisplayString(DADisplay),
|
||||||
|
"-lock",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
spawn_detached(argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_button_sleep(void) {
|
||||||
|
DAWarning("on_button_sleep");
|
||||||
|
|
||||||
|
char const *argv[] = {
|
||||||
|
"sudo",
|
||||||
|
"/usr/sbin/pm-suspend",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
spawn_detached(argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_button_shutdown(void) {
|
||||||
|
DAWarning("on_button_shutdown");
|
||||||
|
|
||||||
|
if (is_shutdown_armed) {
|
||||||
|
shutdown_disarm();
|
||||||
|
} else {
|
||||||
|
shutdown_arm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void (*const handlers[NUM_BUTTONS])(void) = {
|
||||||
|
on_button_lock, on_button_sleep, on_button_shutdown
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma mark - libdockapp handlers
|
||||||
|
|
||||||
|
static void on_dockapp_button_press(int button, int state, int x, int y) {
|
||||||
|
(void)state;
|
||||||
|
|
||||||
|
if (button != Button1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pressed_button = hit_test(x, y);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_dockapp_button_release(int button, int state, int x, int y) {
|
||||||
|
int released_on;
|
||||||
|
(void)state;
|
||||||
|
|
||||||
|
if (button != Button1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
released_on = hit_test(x, y);
|
||||||
|
|
||||||
|
// Fire only if released over the same button that was pressed.
|
||||||
|
if (pressed_button >= 0 && pressed_button == released_on) {
|
||||||
|
handlers[pressed_button]();
|
||||||
|
}
|
||||||
|
|
||||||
|
pressed_button = NO_PRESSED_BUTTON;
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_dockapp_mouse_leave(void) {
|
||||||
|
// Cancel a press if the pointer leaves the dockapp.
|
||||||
|
if (pressed_button != NO_PRESSED_BUTTON) {
|
||||||
|
pressed_button = NO_PRESSED_BUTTON;
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_dockapp_destroy(void) {
|
||||||
|
if (font) {
|
||||||
|
XFreeFont((DADisplay), font);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphics_context) {
|
||||||
|
XFreeGC(DADisplay, graphics_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app->pixmap) {
|
||||||
|
XFreePixmap(DADisplay, app->pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_dockapp_timeout(void) {
|
||||||
|
DAWarning("on_dockapp_timeout(): %d", current_shutdown_deadline_ms());
|
||||||
|
if (!is_shutdown_armed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_shutdown_deadline_ms() <= 0) {
|
||||||
|
shutdown_disarm();
|
||||||
|
redraw();
|
||||||
|
do_shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_shutdown_deadline_seconds() != last_shutdown_seconds) {
|
||||||
|
last_shutdown_seconds = current_shutdown_deadline_seconds();
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Main
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
XSizeHints *hints;
|
||||||
|
|
||||||
|
DASetExpectedVersion(20050716);
|
||||||
|
|
||||||
|
DACallbacks callbacks = {
|
||||||
|
.destroy = on_dockapp_destroy,
|
||||||
|
.buttonPress = on_dockapp_button_press,
|
||||||
|
.buttonRelease = on_dockapp_button_release,
|
||||||
|
.leave = on_dockapp_mouse_leave,
|
||||||
|
.timeout = on_dockapp_timeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
app = calloc(1, sizeof(struct WMPWMan));
|
||||||
|
|
||||||
|
DAParseArguments(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
"A Window Maker dockapp to help with power management",
|
||||||
|
VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
DAInitialize("", argv[0], DOCK_SIZE, DOCK_SIZE, argc, argv);
|
||||||
|
|
||||||
|
app->pixmap = DAMakePixmap();
|
||||||
|
|
||||||
|
graphics_context = XCreateGC(DADisplay, app->pixmap, 0, NULL);
|
||||||
|
|
||||||
|
color_background = alloc_color("#8f909f");
|
||||||
|
color_button = alloc_color("#4b5263");
|
||||||
|
color_button_pressed = alloc_color("#363b47");
|
||||||
|
color_light = alloc_color("white");
|
||||||
|
color_dark = alloc_color("black");
|
||||||
|
color_button_title = alloc_color("white");
|
||||||
|
color_button_shutdown_background = alloc_color("#dc3545");
|
||||||
|
|
||||||
|
DASetCallbacks(&callbacks);
|
||||||
|
|
||||||
|
font = load_font();
|
||||||
|
if (font) {
|
||||||
|
XSetFont(DADisplay, graphics_context, font->fid);
|
||||||
|
}
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
|
||||||
|
hints = XAllocSizeHints();
|
||||||
|
if (hints) {
|
||||||
|
hints->flags |= PMinSize | PMaxSize;
|
||||||
|
hints->min_width = DOCK_SIZE;
|
||||||
|
hints->max_width = DOCK_SIZE;
|
||||||
|
hints->min_height = DOCK_SIZE;
|
||||||
|
hints->max_height = DOCK_SIZE;
|
||||||
|
XSetWMNormalHints(DADisplay, DAWindow, hints);
|
||||||
|
XFree(hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
signal(SIGCHLD, SIG_IGN);
|
||||||
|
|
||||||
|
DAShow();
|
||||||
|
|
||||||
|
// Omitting this causes the event loop to spin? :dunno:
|
||||||
|
DASetTimeout(-1);
|
||||||
|
|
||||||
|
DAEventLoop();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user