diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | LICENSE | 24 | ||||
| -rw-r--r-- | Makefile | 24 | ||||
| -rw-r--r-- | README.md | 102 | ||||
| -rw-r--r-- | main.c | 207 |
5 files changed, 359 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a341b2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +xmagnify
\ No newline at end of file @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2025, Mitja Felicijan <mitja.felicijan@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a9110e7 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +CC = gcc +CFLAGS = -Wall -Wextra -O2 +LDFLAGS = -lX11 -lXfixes -lXrender + +TARGET = xmagnify + +all: $(TARGET) + +$(TARGET): main.o + $(CC) main.o -o $(TARGET) $(LDFLAGS) + +main.o: main.c + $(CC) $(CFLAGS) -c main.c -o main.o + +clean: + rm -f main.o $(TARGET) + +install: $(TARGET) + install -m 755 $(TARGET) /usr/local/bin/ + +uninstall: + rm -f /usr/local/bin/$(TARGET) + +.PHONY: all clean install uninstall
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccb32b8 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# X11 Magnification Tool + +Xmagnify is a real-time screen magnification tool for X11 systems that +provides a zoomed view of the area around your mouse cursor. + +## Features + +| Feature | Description | +|---------|-------------| +| **Real-time zoom** | Live magnification of screen content around cursor | +| **Configurable zoom level** | Adjustable magnification factor via command line | +| **Customizable window size** | Configurable zoom window dimensions | +| **Keyboard controls** | Press `q`, `Q`, or `Escape` to quit | + +## How to use + +> [!IMPORTANT] +> Make sure you have the required dependencies installed before +> building the project. + +First, ensure you have the necessary development libraries installed: + +```sh +# On Debian/Ubuntu +sudo apt-get install libx11-dev libxfixes-dev libxrender-dev + +# On Fedora/RHEL +sudo dnf install libX11-devel libXfixes-devel libXrender-devel + +# On Arch Linux +sudo pacman -S libx11 libxfixes libxrender +``` + +Build the project: + +```sh +make +sudo make install +``` + +Run the magnification tool: + +```sh +xmagnify +``` + +### Command line options + +The tool supports several command line options for customization: + +```sh +xmagnify [OPTIONS] + +Options: + -z, --zoom LEVEL Zoom level (default: 2) + -s, --size SIZE Window size in pixels (default: 600) + -h, --help Show help message + -q, --quit Quit the application +``` + +### Examples + +Basic usage with default settings: +```sh +xmagnify +``` + +Custom zoom level and window size: +```sh +xmagnify --zoom 3 --size 600 +``` + +Short option syntax: +```sh +xmagnify -z 4 -s 500 +``` + +## Building from source + +The project uses a simple Makefile for building. Available targets: + +```sh +make # Build the executable (default) +make clean # Remove build artifacts +sudo make install # Install to /usr/local/bin/ +sudo make uninstall # Remove from /usr/local/bin/ +``` + +### Debugging + +Enable verbose output by modifying the source code or using +debugging tools like `gdb`: + +```sh +gdb ./xmagnify +``` + +## License + +[makext](https://github.com/mitjafelicijan/xmagnfy) was written by [Mitja +Felicijan](https://mitjafelicijan.com) and is released under the BSD +two-clause license, see the LICENSE file for more information.
\ No newline at end of file @@ -0,0 +1,207 @@ +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xfixes.h> +#include <X11/keysym.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> + +#define DEFAULT_ZOOM_LEVEL 2 +#define DEFAULT_WINDOW_SIZE 600 + +Display *display; +Window zoom_window; +int screen; +int screen_width, screen_height; +int running = 1; +int zoom_level = DEFAULT_ZOOM_LEVEL; +int window_size = DEFAULT_WINDOW_SIZE; + +void print_usage(const char *program_name) { + printf("Usage: %s [OPTIONS]\n", program_name); + printf("Options:\n"); + printf(" -z, --zoom LEVEL Zoom level (default: %d)\n", DEFAULT_ZOOM_LEVEL); + printf(" -s, --size SIZE Window size in pixels (default: %d)\n", DEFAULT_WINDOW_SIZE); + printf(" -h, --help Show this help message\n"); + printf(" -q, --quit Quit the application\n"); +} + +void parse_arguments(int argc, char *argv[]) { + int opt; + const char *short_options = "z:s:hq"; + struct option long_options[] = { + {"zoom", required_argument, 0, 'z'}, + {"size", required_argument, 0, 's'}, + {"help", no_argument, 0, 'h'}, + {"quit", no_argument, 0, 'q'}, + {0, 0, 0, 0} + }; + + while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { + switch (opt) { + case 'z': + zoom_level = atoi(optarg); + if (zoom_level <= 0) { + fprintf(stderr, "Error: Zoom level must be positive\n"); + exit(1); + } + break; + case 's': + window_size = atoi(optarg); + if (window_size <= 0) { + fprintf(stderr, "Error: Window size must be positive\n"); + exit(1); + } + break; + case 'h': + print_usage(argv[0]); + exit(0); + case 'q': + exit(0); + default: + print_usage(argv[0]); + exit(1); + } + } +} + +void init_x11() { + display = XOpenDisplay(NULL); + if (!display) { + fprintf(stderr, "Cannot open X display\n"); + exit(1); + } + screen = DefaultScreen(display); + screen_width = DisplayWidth(display, screen); + screen_height = DisplayHeight(display, screen); +} + +void create_zoom_window() { + zoom_window = XCreateSimpleWindow( + display, + RootWindow(display, screen), + 0, 0, + window_size, window_size, + 1, + BlackPixel(display, screen), + WhitePixel(display, screen) + ); + XStoreName(display, zoom_window, "X11 Zoom Tool"); + XSelectInput(display, zoom_window, KeyPressMask); + XMapWindow(display, zoom_window); +} + +void clamp_coordinates(int *x, int *y, int width, int height) { + *x = (*x < 0) ? 0 : *x; + *y = (*y < 0) ? 0 : *y; + *x = (*x > screen_width - width) ? screen_width - width : *x; + *y = (*y > screen_height - height) ? screen_height - height : *y; +} + +void handle_keypress(XEvent *event) { + KeySym keysym = XLookupKeysym(&event->xkey, 0); + if (keysym == XK_Escape || keysym == XK_q || keysym == XK_Q) { + running = 0; + } +} + +void update_zoom() { + XFixesCursorImage *cursor = XFixesGetCursorImage(display); + if (!cursor) { + fprintf(stderr, "Failed to get cursor position\n"); + return; + } + + int capture_width = window_size / zoom_level; + int capture_height = window_size / zoom_level; + int capture_x = cursor->x - capture_width/2; + int capture_y = cursor->y - capture_height/2; + + clamp_coordinates(&capture_x, &capture_y, capture_width, capture_height); + + XImage *src_image = XGetImage( + display, + RootWindow(display, screen), + capture_x, capture_y, + capture_width, capture_height, + AllPlanes, + ZPixmap + ); + + if (!src_image) { + fprintf(stderr, "XGetImage failed\n"); + XFree(cursor); + return; + } + + XImage *dest_image = XCreateImage( + display, + DefaultVisual(display, screen), + DefaultDepth(display, screen), + ZPixmap, + 0, + malloc(window_size * window_size * 4), + window_size, window_size, + 32, + 0 + ); + + for (int y = 0; y < window_size; y++) { + for (int x = 0; x < window_size; x++) { + int src_x = x / zoom_level; + int src_y = y / zoom_level; + XPutPixel(dest_image, x, y, XGetPixel(src_image, src_x, src_y)); + } + } + + GC gc = XCreateGC(display, zoom_window, 0, NULL); + XPutImage( + display, + zoom_window, + gc, + dest_image, + 0, 0, + 0, 0, + window_size, window_size + ); + + XFreeGC(display, gc); + XDestroyImage(src_image); + free(dest_image->data); + dest_image->data = NULL; + XDestroyImage(dest_image); + XFree(cursor); +} + +int main(int argc, char *argv[]) { + parse_arguments(argc, argv); + + init_x11(); + + int event_base, error_base; + if (!XFixesQueryExtension(display, &event_base, &error_base)) { + fprintf(stderr, "XFixes not available\n"); + XCloseDisplay(display); + return 1; + } + + create_zoom_window(); + + while (running) { + while (XPending(display)) { + XEvent event; + XNextEvent(display, &event); + if (event.type == KeyPress) { + handle_keypress(&event); + } + } + + update_zoom(); + XFlush(display); + usleep(16000); + } + + XCloseDisplay(display); + return 0; +} |
