core/core.c

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <SDL.h>

#include "procfs.h"
#include "memview.h"

static int screen_width = 640;
static int screen_height = 480;

static double scale = 5; 
static double pos_x = 1024;
static double pos_y = 0;

static int mouse_x = 0;
static int mouse_y = 0;

SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;

pid_t pid;
int mem_fd;
struct viewer *viewer;

static void add_scale(double amount) {
	pos_x += mouse_x / scale;
	pos_y += mouse_y / scale;
	scale += amount;
	if (scale > 50) scale = 50;
	else if (scale < 0.5) scale = 0.5;
	pos_x -= mouse_x / scale;
	pos_y -= mouse_y / scale;
}

static void world_pos(double *x, double *y) {
	*x = *x / scale + floor(pos_x);
	*y = *y / scale + floor(pos_y);
}

static void goto_addr(uintptr_t addr) {
	int x, y;
	to_pos(addr, &x, &y);
	pos_x = x; pos_y = y;
	pos_x -= (double) screen_width / 2 * scale;
	pos_y -= (double) screen_height / 2 * scale;
}

static size_t current_map = 0;

static void next_map() {
	struct procfs_map *maps;
	int n = procfs_maps(pid, &maps);
	if (n < 1) return;
	for (int i = 0; i < n; i++) {
		current_map = (current_map + 1) % n;
		if (maps[current_map].prot & PROT_READ)
			break;
	}
	goto_addr(maps[current_map].base);
	free(maps);
}

static void prev_map() {
	struct procfs_map *maps;
	int n = procfs_maps(pid, &maps);
	if (n < 1) return;
	for (int i = 0; i < n; i++) {
		current_map = current_map > 1 ? current_map - 1 : n - 1;
		if (maps[current_map].prot & PROT_READ)
			break;
	}
	goto_addr(maps[current_map].base);
	free(maps);
}

static void report_error() {
	fprintf(stderr, "SDL Error: %s\n", SDL_GetError());
	exit(-1);
}

static void init_sdl(const char *title) {
	if (SDL_Init(SDL_INIT_VIDEO) < 0) report_error();
	window = SDL_CreateWindow(title,
			SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
			screen_width, screen_height,
			SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
	if (window == NULL) report_error();
	renderer = SDL_CreateRenderer(window, -1, 0);
	if (renderer == NULL) report_error();
}

static void deinit_sdl() {
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit();
}

static bool held(SDL_Keycode k) {
	const uint8_t *state = SDL_GetKeyboardState(NULL);
	return state[SDL_GetScancodeFromKey(k)];
}

static void handle_events() {
	bool refresh = false;
	SDL_Event e;
	while (SDL_PollEvent(&e)) {
		switch (e.type) {
			case SDL_QUIT:
				exit(0); break;
			case SDL_WINDOWEVENT:
				switch (e.window.event) {
					case SDL_WINDOWEVENT_RESIZED:
						screen_width = e.window.data1;
						screen_height = e.window.data2;
						SDL_SetWindowSize(window, screen_width, screen_height);
						refresh = true;
						break;
					case SDL_WINDOWEVENT_EXPOSED:
						refresh = true;
						break;
					default:
						break;
				}
				break;
			case SDL_MOUSEMOTION:;
				uint32_t state = e.motion.state;
				if (state & SDL_BUTTON_MMASK ||
						(state & SDL_BUTTON_RMASK && held(SDLK_LSHIFT))) {
					pos_x -= (double) e.motion.xrel / scale;
					pos_y -= (double) e.motion.yrel / scale;
					refresh = true;
				} else if (state & (SDL_BUTTON_LMASK | SDL_BUTTON_RMASK)) {
					double x1 = e.motion.x - e.motion.xrel;
					double y1 = e.motion.y - e.motion.yrel;
					double x2 = e.motion.x; double y2 = e.motion.y;
					world_pos(&x1, &y1); world_pos(&x2, &y2);
					pencil(viewer,
							x1, y1, x2, y2, !(state & SDL_BUTTON_RMASK));
					refresh = true;
				}
				mouse_x = e.motion.x;
				mouse_y = e.motion.y;
				break;
			case SDL_MOUSEBUTTONDOWN:;
				double x = e.button.x; double y = e.button.y;
				world_pos(&x, &y);
				if (e.button.button == SDL_BUTTON_LEFT)
					pencil(viewer, x, y, x, y, true);
				else if (e.button.button == SDL_BUTTON_RIGHT)
					pencil(viewer, x, y, x, y, false);
				break;
			case SDL_MOUSEWHEEL:
				if (e.wheel.y == 0) break;
				add_scale((double) e.wheel.y / 2);
				refresh = true;
				break;
			case SDL_KEYDOWN:
				switch (e.key.keysym.sym) {
					case SDLK_RIGHT:
						next_map();
						refresh = true;
						break;
					case SDLK_LEFT:
						prev_map();
						refresh = true;
						break;
					case SDLK_SPACE:;
						static bool stopped = false;
						kill(pid, stopped ? SIGSTOP : SIGCONT);
						stopped = !stopped;
						break;
					default:
						break;
				}
			default:
				break;
		}
	}
	if (render_pages(viewer, pos_x, pos_y,
			screen_width, screen_height, scale, refresh))
		SDL_RenderPresent(renderer);
}

void cleanup() {
	destroy_viewer(viewer);
	deinit_sdl();
}

int main(int argc, char *argv[]) {
	if (argc < 2) {
		fprintf(stderr, "todo: show usage\n");
		return -1;
	}
	pid = atoi(argv[1]);
	if (pid == 0) {
		fprintf(stderr, "todo: show usage\n");
		return -1;
	}
	mem_fd = procfs_open(pid);
	if (mem_fd == -1) perror("error attaching to process");

	char title[1024];
	snprintf(title, 1024, "%s [%d]", argv[0], pid);
	init_sdl(title);
	viewer = create_viewer(mem_fd, renderer);
	if (!viewer) abort();
	atexit(cleanup);

	prev_map();

	int last_time = 0;
	int current_time = 0;
	while (1) {
		handle_events();
		last_time = current_time;
		current_time = SDL_GetTicks();
		int elapsed = current_time - last_time;
		if (elapsed < 1000 / 30)
			SDL_Delay(1000 / 30 - elapsed);
	}
}