Initial commit

This commit is contained in:
Johannes Findeisen 2025-11-23 03:40:08 +01:00
commit 1bf8ddf418
5 changed files with 198 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
tmp/
texec

13
LICENSE Normal file
View file

@ -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.

12
Makefile Normal file
View file

@ -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

0
README.md Normal file
View file

171
texec.c Normal file
View file

@ -0,0 +1,171 @@
/*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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 "<command>" <icon.png>
*
* Left-click: execute command
* Right-click: quit applet
*/
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
//#include <X11/extensions/Xrender.h>
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#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; y<h; y++) {
rows[y] = (png_byte*)malloc(4*w);
}
png_read_image(png, rows);
fclose(fp);
XImage *img = XCreateImage(dpy, CopyFromParent, 24, ZPixmap, 0,
malloc(w*h*4), w, h, 32, 0);
for (int y=0; y<h; y++) {
for (int x=0; x<w; x++) {
png_bytep px = &(rows[y][x*4]);
unsigned long pixel = (px[0]<<16) | (px[1]<<8) | px[2];
XPutPixel(img, x, y, pixel);
}
}
for (int y=0; y<h; y++) {
free(rows[y]);
}
free(rows);
png_destroy_read_struct(&png, &info, NULL);
return img;
}
void draw_icon(Window win, const char *icon_path) {
GC gc = XCreateGC(dpy, win, 0, NULL);
XImage *img = load_png(icon_path);
if (!img) {
fprintf(stderr,"Failed to load PNG icon: %s\n", icon_path);
exit(1);
}
XPutImage(dpy, win, gc, img, 0,0,0,0, img->width, img->height);
XDestroyImage(img);
XFreeGC(dpy,gc);
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr,"Usage: %s <command> <icon.png>\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;
}