commit 1bf8ddf418c7c436538e021db8c6f1942afff0af Author: hanez Date: Sun Nov 23 03:40:08 2025 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1b55b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tmp/ +texec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d2c57e8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2025 Johannes Findeisen + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2822c09 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +all: + gcc -o texec texec.c -lX11 -lpng + +clean: + rm -rf texec + +install: + install -g 0 -o 0 -m 0655 ./texec /usr/bin/ + +uninstall: + rm -rf /usr/bin/texec + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/texec.c b/texec.c new file mode 100644 index 0000000..d88f4e4 --- /dev/null +++ b/texec.c @@ -0,0 +1,171 @@ +/* + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * .c - Minimal X11 tray applet with mandatory PNG icon (32x32) + * + * Compile: + * gcc -o gexec_xembed gexec_xembed.c -lX11 -lXrender -lpng + * + * Usage: + * ./gexec_xembed "" + * + * Left-click: execute command + * Right-click: quit applet + */ + +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include + +#define WIDTH 24 +#define HEIGHT 24 + +static Display *dpy; +static int screen; +static Window icon_win; +static char *g_command; + +void execute_command() { + if (!g_command) return; + pid_t pid = fork(); + if (pid == 0) { + setsid(); + execl("/bin/sh", "sh", "-c", g_command, (char *)NULL); + _exit(127); + } +} + +/* Minimal PNG loader into XImage */ +XImage *load_png(const char *filename) { + FILE *fp = fopen(filename, "rb"); + if (!fp) return NULL; + + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL); + png_infop info = png_create_info_struct(png); + if (setjmp(png_jmpbuf(png))) return NULL; + + png_init_io(png, fp); + png_read_info(png, info); + + int w = png_get_image_width(png, info); + int h = png_get_image_height(png, info); + png_byte color_type = png_get_color_type(png, info); + png_byte bit_depth = png_get_bit_depth(png, info); + + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + png_read_update_info(png, info); + + png_bytep *rows = (png_bytep*)malloc(sizeof(png_bytep) * h); + for (int y=0; ywidth, img->height); + XDestroyImage(img); + XFreeGC(dpy,gc); +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr,"Usage: %s \n",argv[0]); + return 1; + } + g_command = argv[1]; + char *icon_path = argv[2]; + + signal(SIGCHLD, SIG_IGN); + + dpy = XOpenDisplay(NULL); + if (!dpy) { + fprintf(stderr,"Cannot open display\n"); + return 1; + } + screen = DefaultScreen(dpy); + + Atom _NET_SYSTEM_TRAY_S0 = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False); + Window tray_win = XGetSelectionOwner(dpy, _NET_SYSTEM_TRAY_S0); + if (tray_win == None) { + fprintf(stderr,"No system tray found\n"); + return 1; + } + + icon_win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), + 0,0, WIDTH, HEIGHT, 1, + BlackPixel(dpy,screen), + WhitePixel(dpy,screen)); + XSelectInput(dpy, icon_win, ExposureMask | ButtonPressMask); + + // Send dock request + XEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = icon_win; + ev.xclient.message_type = XInternAtom(dpy,"_NET_SYSTEM_TRAY_OPCODE",False); + ev.xclient.format = 32; + ev.xclient.data.l[0] = CurrentTime; + ev.xclient.data.l[1] = 0; // SYSTEM_TRAY_REQUEST_DOCK + ev.xclient.data.l[2] = icon_win; + XSendEvent(dpy, tray_win, False, NoEventMask, &ev); + + XMapWindow(dpy, icon_win); + draw_icon(icon_win, icon_path); + + XEvent xev; + while (1) { + XNextEvent(dpy, &xev); + if (xev.xany.window == icon_win) { + if (xev.type == Expose) draw_icon(icon_win, icon_path); + else if (xev.type == ButtonPress) { + if (xev.xbutton.button == 1) execute_command(); // left-click + else if (xev.xbutton.button == 3) break; // right-click: quit + } + } + } + + XDestroyWindow(dpy, icon_win); + XCloseDisplay(dpy); + return 0; +} +