summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2025-07-18 20:26:12 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2025-07-18 20:26:12 +0200
commit5f696d942879a93a37b11d7b454233132262168d (patch)
tree3e49483518e1dd0314674a874ebf09d1b48f8590
downloadxmagnify-5f696d942879a93a37b11d7b454233132262168d.tar.gz
Engage!
-rw-r--r--.gitignore2
-rw-r--r--LICENSE24
-rw-r--r--Makefile24
-rw-r--r--README.md102
-rw-r--r--main.c207
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..23f05ad
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..334e318
--- /dev/null
+++ b/main.c
@@ -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;
+}