Initial commit
This commit is contained in:
commit
1bf8ddf418
5 changed files with 198 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
tmp/
|
||||||
|
texec
|
||||||
13
LICENSE
Normal file
13
LICENSE
Normal 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
12
Makefile
Normal 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
0
README.md
Normal file
171
texec.c
Normal file
171
texec.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue