2012-10-22 06:52:53 +04:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <syscall.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
2014-04-16 06:45:56 +04:00
|
|
|
#include <signal.h>
|
2014-03-17 02:13:27 +04:00
|
|
|
#include <fcntl.h>
|
2012-10-22 06:52:53 +04:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <time.h>
|
2012-12-08 12:25:16 +04:00
|
|
|
#include <math.h>
|
2012-10-22 06:52:53 +04:00
|
|
|
#include <assert.h>
|
2014-04-16 06:45:56 +04:00
|
|
|
#include <getopt.h>
|
2012-10-22 06:52:53 +04:00
|
|
|
#include <sys/stat.h>
|
2014-04-16 06:45:56 +04:00
|
|
|
|
2012-10-22 06:52:53 +04:00
|
|
|
#include <cairo.h>
|
|
|
|
|
|
|
|
#include "lib/graphics.h"
|
|
|
|
#include "lib/pthread.h"
|
2014-04-16 06:45:56 +04:00
|
|
|
#include "lib/mouse.h"
|
2012-10-22 06:52:53 +04:00
|
|
|
#include "lib/kbd.h"
|
2014-04-16 06:45:56 +04:00
|
|
|
#include "lib/pex.h"
|
|
|
|
#include "lib/yutani.h"
|
|
|
|
#include "lib/hashmap.h"
|
|
|
|
#include "lib/list.h"
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
#include "yutani_int.h"
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
struct {
|
|
|
|
int nested;
|
|
|
|
int nest_width;
|
|
|
|
int nest_height;
|
|
|
|
} yutani_options = {
|
|
|
|
.nested = 0,
|
|
|
|
.nest_width = 0,
|
|
|
|
.nest_height = 0
|
|
|
|
};
|
2013-07-23 09:42:24 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static int usage(char * argv[]) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Yutani - Window Compositor\n"
|
|
|
|
"\n"
|
|
|
|
"usage: %s [-n [-g WxH]] [-h]\n"
|
|
|
|
"\n"
|
|
|
|
" -n --nested \033[3mRun in a window.\033[0m\n"
|
|
|
|
" -h --help \033[3mShow this help message.\033[0m\n"
|
|
|
|
" -g --geometry \033[3mSet the size of the server framebuffer.\033[0m\n"
|
|
|
|
"\n"
|
|
|
|
" Yutani is the standard system compositor.\n"
|
|
|
|
"\n",
|
|
|
|
argv[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
2013-05-30 08:31:59 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/**
|
|
|
|
* Parse arguments
|
|
|
|
*/
|
|
|
|
static int parse_args(int argc, char * argv[], int * out) {
|
|
|
|
static struct option long_opts[] = {
|
|
|
|
{"nest", no_argument, 0, 'n'},
|
|
|
|
{"geometry", required_argument, 0, 'g'},
|
|
|
|
{"help", no_argument, 0, 'h'},
|
|
|
|
{0,0,0,0}
|
|
|
|
};
|
|
|
|
|
|
|
|
int index, c;
|
|
|
|
while ((c = getopt_long(argc, argv, "hg:n", long_opts, &index)) != -1) {
|
|
|
|
if (!c) {
|
|
|
|
if (long_opts[index].flag == 0) {
|
|
|
|
c = long_opts[index].val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch (c) {
|
|
|
|
case 'h':
|
|
|
|
return usage(argv);
|
|
|
|
case 'n':
|
|
|
|
yutani_options.nested = 1;
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
{
|
|
|
|
char * c = strstr(optarg, "x");
|
|
|
|
if (c) {
|
|
|
|
*c = '\0';
|
|
|
|
c++;
|
|
|
|
yutani_options.nest_width = atoi(optarg);
|
|
|
|
yutani_options.nest_height = atoi(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Unrecognized option: %c\n", c);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*out = optind;
|
|
|
|
return 0;
|
|
|
|
}
|
2013-05-22 08:11:30 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int32_t min(int32_t a, int32_t b) {
|
|
|
|
return (a < b) ? a : b;
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int32_t max(int32_t a, int32_t b) {
|
|
|
|
return (a > b) ? a : b;
|
|
|
|
}
|
2012-11-22 11:29:27 +04:00
|
|
|
|
|
|
|
static void spin_lock(int volatile * lock) {
|
|
|
|
while(__sync_lock_test_and_set(lock, 0x01)) {
|
|
|
|
syscall_yield();
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
2012-11-22 11:29:27 +04:00
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2012-11-22 11:29:27 +04:00
|
|
|
static void spin_unlock(int volatile * lock) {
|
|
|
|
__sync_lock_release(lock);
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static int next_buf_id(void) {
|
|
|
|
static int _next = 1;
|
|
|
|
return _next++;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static int next_wid(void) {
|
|
|
|
static int _next = 1;
|
|
|
|
return _next++;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void device_to_window(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y) {
|
|
|
|
*out_x = x - window->x;
|
|
|
|
*out_y = y - window->y;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (!window->rotation) return;
|
2012-11-22 11:29:27 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
double t_x = *out_x - (window->width / 2);
|
|
|
|
double t_y = *out_y - (window->height / 2);
|
2012-11-22 11:29:27 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
double s = sin(-M_PI * (window->rotation/ 180.0));
|
|
|
|
double c = cos(-M_PI * (window->rotation/ 180.0));
|
2012-11-22 11:29:27 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
double n_x = t_x * c - t_y * s;
|
|
|
|
double n_y = t_x * s + t_y * c;
|
2012-11-22 11:29:27 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
*out_x = (int32_t)n_x + (window->width / 2);
|
|
|
|
*out_y = (int32_t)n_y + (window->height / 2);
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void window_to_device(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y) {
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (!window->rotation) {
|
|
|
|
*out_x = window->x + x;
|
|
|
|
*out_y = window->y + y;
|
|
|
|
return;
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
double t_x = x - (window->width / 2);
|
|
|
|
double t_y = y - (window->height / 2);
|
|
|
|
|
|
|
|
double s = sin(M_PI * (window->rotation/ 180.0));
|
|
|
|
double c = cos(M_PI * (window->rotation/ 180.0));
|
|
|
|
|
|
|
|
double n_x = t_x * c - t_y * s;
|
|
|
|
double n_y = t_x * s + t_y * c;
|
|
|
|
|
|
|
|
*out_x = (int32_t)n_x + (window->width / 2) + window->x;
|
|
|
|
*out_y = (int32_t)n_y + (window->height / 2) + window->y;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void rebalance_windows(yutani_globals_t * yg) {
|
2012-10-22 06:52:53 +04:00
|
|
|
uint32_t i = 1;
|
2014-04-16 06:45:56 +04:00
|
|
|
for (; i < YUTANI_ZORDER_TOP; ++i) {
|
|
|
|
if (!yg->zlist[i]) break;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
uint32_t j = i + 1;
|
2014-04-16 06:45:56 +04:00
|
|
|
for (; j < YUTANI_ZORDER_TOP; ++j) {
|
|
|
|
if (!yg->zlist[j]) break;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
if (j == i + 1) {
|
|
|
|
return;
|
|
|
|
} else {
|
2014-04-16 06:45:56 +04:00
|
|
|
for (j = i; j < YUTANI_ZORDER_TOP; ++j) {
|
|
|
|
yg->zlist[j] = yg->zlist[j+1];
|
|
|
|
if (yg->zlist[j+1] == NULL) return;
|
|
|
|
yg->zlist[j]->z = j;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void reorder_window(yutani_globals_t * yg, yutani_server_window_t * window, uint16_t new_zed) {
|
2012-10-22 06:52:53 +04:00
|
|
|
if (!window) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int z = window->z;
|
|
|
|
window->z = new_zed;
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (yg->zlist[z] == window) {
|
|
|
|
yg->zlist[z] = NULL;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (new_zed == 0 || new_zed == YUTANI_ZORDER_TOP) {
|
|
|
|
yg->zlist[new_zed] = window;
|
2012-10-22 06:52:53 +04:00
|
|
|
if (z != new_zed) {
|
2014-04-16 06:45:56 +04:00
|
|
|
rebalance_windows(yg);
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (yg->zlist[new_zed] != window) {
|
|
|
|
reorder_window(yg, yg->zlist[new_zed], new_zed + 1);
|
|
|
|
yg->zlist[new_zed ] = window;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
if (z != new_zed) {
|
2014-04-16 06:45:56 +04:00
|
|
|
rebalance_windows(yg);
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void unorder_window(yutani_globals_t * yg, yutani_server_window_t * w) {
|
|
|
|
if (yg->zlist[w->z] == w) {
|
|
|
|
yg->zlist[w->z] = NULL;
|
2012-11-20 09:27:24 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
rebalance_windows(yg);
|
2012-11-20 09:27:24 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void make_top(yutani_globals_t * yg, yutani_server_window_t * w) {
|
|
|
|
unsigned short index = w->z;
|
2012-11-24 12:18:14 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (index == YUTANI_ZORDER_BOTTOM) return;
|
|
|
|
if (index == YUTANI_ZORDER_TOP) return;
|
2012-11-24 12:18:14 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
unsigned short highest = 0;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
for (unsigned int i = 0; i <= YUTANI_ZORDER_MAX; ++i) {
|
|
|
|
if (yg->zlist[i]) {
|
|
|
|
yutani_server_window_t * win = yg->zlist[i];
|
2013-05-30 08:31:59 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (win == w) continue;
|
|
|
|
if (win->z == YUTANI_ZORDER_BOTTOM) continue;
|
|
|
|
if (win->z == YUTANI_ZORDER_TOP) continue;
|
|
|
|
if (highest < win->z) highest = win->z;
|
|
|
|
if (win->z > w->z) continue;
|
|
|
|
}
|
2013-05-30 08:31:59 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
reorder_window(yg, w, highest + 1);
|
2013-05-30 08:31:59 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void set_focused_window(yutani_globals_t * yg, yutani_server_window_t * w) {
|
|
|
|
if (w == yg->focused_window) {
|
|
|
|
return; /* Already focused */
|
2013-05-30 08:31:59 +04:00
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (yg->focused_window) {
|
|
|
|
/* XXX Send focus change to old focused window */
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_focus_change(yg->focused_window->wid, 0);
|
|
|
|
pex_send(yg->server, yg->focused_window->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
2013-05-30 08:31:59 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->focused_window = w;
|
|
|
|
if (w) {
|
|
|
|
/* XXX Send focus change to new focused window */
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_focus_change(w->wid, 1);
|
|
|
|
pex_send(yg->server, w->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
make_top(yg, w);
|
|
|
|
} else {
|
|
|
|
/* XXX */
|
|
|
|
yg->focused_window = yg->zlist[0];
|
2013-05-30 08:31:59 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static yutani_server_window_t * get_focused(yutani_globals_t * yg) {
|
|
|
|
if (yg->focused_window) return yg->focused_window;
|
|
|
|
return yg->zlist[0];
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int best_z_option(yutani_globals_t * yg) {
|
|
|
|
for (int i = 1; i < YUTANI_ZORDER_TOP; ++i) {
|
|
|
|
if (!yg->zlist[i]) return i;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
return -1;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static yutani_server_window_t * server_window_create(yutani_globals_t * yg, int width, int height, uint32_t owner) {
|
|
|
|
yutani_server_window_t * win = malloc(sizeof(yutani_server_window_t));
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
win->wid = next_wid();
|
|
|
|
win->owner = owner;
|
|
|
|
list_insert(yg->windows, win);
|
|
|
|
hashmap_set(yg->wids_to_windows, (void*)win->wid, win);
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
win->x = 0;
|
|
|
|
win->y = 0;
|
|
|
|
win->z = best_z_option(yg);
|
|
|
|
yg->zlist[win->z] = win;
|
|
|
|
win->width = width;
|
|
|
|
win->height = height;
|
|
|
|
win->bufid = next_buf_id();
|
|
|
|
win->rotation = 0;
|
2014-04-17 10:50:57 +04:00
|
|
|
win->newbufid = 0;
|
2014-04-18 11:18:19 +04:00
|
|
|
win->name = NULL;
|
2014-04-20 03:59:32 +04:00
|
|
|
win->anim_mode = YUTANI_EFFECT_FADE_IN;
|
|
|
|
win->anim_start = yg->tick_count;
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
char key[1024];
|
|
|
|
YUTANI_SHMKEY(key, 1024, win);
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
size_t size = (width * height * 4);
|
|
|
|
win->buffer = (uint8_t *)syscall_shm_obtain(key, &size);
|
|
|
|
return win;
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-17 10:50:57 +04:00
|
|
|
static uint32_t server_window_resize(yutani_globals_t * yg, yutani_server_window_t * win, int width, int height) {
|
|
|
|
/* A client has accepted our offer, let's make a buffer for them */
|
|
|
|
if (win->newbufid) {
|
|
|
|
/* Already in the middle of an accept/done, bail */
|
|
|
|
return win->newbufid;
|
|
|
|
}
|
|
|
|
win->newbufid = next_buf_id();
|
|
|
|
|
|
|
|
{
|
|
|
|
char key[1024];
|
|
|
|
YUTANI_SHMKEY_EXP(key, 1024, win->newbufid);
|
|
|
|
|
|
|
|
size_t size = (width * height * 4);
|
|
|
|
win->newbuffer = (uint8_t *)syscall_shm_obtain(key, &size);
|
|
|
|
}
|
|
|
|
|
|
|
|
return win->newbufid;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void server_window_resize_finish(yutani_globals_t * yg, yutani_server_window_t * win, int width, int height) {
|
|
|
|
if (!win->newbufid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
char key[1024];
|
|
|
|
YUTANI_SHMKEY_EXP(key, 1024, win->bufid);
|
|
|
|
syscall_shm_release(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
win->width = width;
|
|
|
|
win->height = height;
|
|
|
|
|
|
|
|
win->bufid = win->newbufid;
|
|
|
|
win->newbufid = 0;
|
|
|
|
|
|
|
|
win->buffer = win->newbuffer;
|
|
|
|
win->newbuffer = NULL;
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/**
|
|
|
|
* Mouse input thread
|
|
|
|
*
|
|
|
|
* Reads the kernel mouse device and converts
|
|
|
|
* mouse clicks and movements into event objects
|
|
|
|
* to send to the core compositor.
|
|
|
|
*/
|
|
|
|
void * mouse_input(void * garbage) {
|
|
|
|
int mfd = open("/dev/mouse", O_RDONLY);
|
2013-05-22 08:11:30 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yutani_t * y = yutani_init();
|
|
|
|
mouse_device_packet_t packet;
|
2013-05-22 08:11:30 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
while (1) {
|
|
|
|
int r = read(mfd, (char *)&packet, sizeof(mouse_device_packet_t));
|
|
|
|
if (r > 0) {
|
|
|
|
yutani_msg_t * m = yutani_msg_build_mouse_event(0, &packet);
|
|
|
|
int result = yutani_msg_send(y, m);
|
|
|
|
free(m);
|
2013-05-30 08:31:59 +04:00
|
|
|
}
|
2013-05-22 08:11:30 +04:00
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/**
|
|
|
|
* Keyboard input thread
|
|
|
|
*
|
|
|
|
* Reads the kernel keyboard device and converts
|
|
|
|
* key presses into event objects to send to the
|
|
|
|
* core compositor.
|
|
|
|
*/
|
|
|
|
void * keyboard_input(void * garbage) {
|
|
|
|
int kfd = open("/dev/kbd", O_RDONLY);
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yutani_t * y = yutani_init();
|
|
|
|
key_event_t event;
|
|
|
|
key_event_state_t state = {0};
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
while (1) {
|
|
|
|
char buf[1];
|
|
|
|
int r = read(kfd, buf, 1);
|
|
|
|
if (r > 0) {
|
|
|
|
kbd_scancode(&state, buf[0], &event);
|
|
|
|
yutani_msg_t * m = yutani_msg_build_key_event(0, &event, &state);
|
|
|
|
int result = yutani_msg_send(y, m);
|
|
|
|
free(m);
|
2012-12-08 12:25:16 +04:00
|
|
|
}
|
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
#define FONT_PATH "/usr/share/fonts/"
|
|
|
|
#define FONT(a,b) {YUTANI_SERVER_IDENTIFIER ".fonts." a, FONT_PATH b}
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
struct font_def {
|
|
|
|
char * identifier;
|
|
|
|
char * path;
|
|
|
|
};
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static struct font_def fonts[] = {
|
|
|
|
FONT("sans-serif", "DejaVuSans.ttf"),
|
|
|
|
FONT("sans-serif.bold", "DejaVuSans-Bold.ttf"),
|
|
|
|
FONT("sans-serif.italic", "DejaVuSans-Oblique.ttf"),
|
|
|
|
FONT("sans-serif.bolditalic", "DejaVuSans-BoldOblique.ttf"),
|
|
|
|
FONT("monospace", "DejaVuSansMono.ttf"),
|
|
|
|
FONT("monospace.bold", "DejaVuSansMono-Bold.ttf"),
|
|
|
|
FONT("monospace.italic", "DejaVuSansMono-Oblique.ttf"),
|
|
|
|
FONT("monospace.bolditalic", "DejaVuSansMono-BoldOblique.ttf"),
|
|
|
|
{NULL, NULL}
|
|
|
|
};
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static char * precache_shmfont(char * ident, char * name) {
|
|
|
|
FILE * f = fopen(name, "r");
|
|
|
|
size_t s = 0;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
|
|
s = ftell(f);
|
|
|
|
fseek(f, 0, SEEK_SET);
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
size_t shm_size = s;
|
|
|
|
char * font = (char *)syscall_shm_obtain(ident, &shm_size); //malloc(s);
|
|
|
|
assert((shm_size >= s) && "shm_obtain returned too little memory to load a font into!");
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
fread(font, s, 1, f);
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
fclose(f);
|
|
|
|
return font;
|
|
|
|
}
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void load_fonts(void) {
|
|
|
|
int i = 0;
|
|
|
|
while (fonts[i].identifier) {
|
|
|
|
fprintf(stderr, "[compositor] Loading font %s -> %s\n", fonts[i].path, fonts[i].identifier);
|
|
|
|
precache_shmfont(fonts[i].identifier, fonts[i].path);
|
|
|
|
++i;
|
2012-12-08 12:25:16 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void draw_cursor(yutani_globals_t * yg, int x, int y) {
|
|
|
|
draw_sprite(yg->backend_ctx, &yg->mouse_sprite, x / MOUSE_SCALE - MOUSE_OFFSET_X, y / MOUSE_SCALE - MOUSE_OFFSET_Y);
|
2012-12-08 12:25:16 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void yutani_add_clip(yutani_globals_t * yg, double x, double y, double w, double h) {
|
|
|
|
cairo_rectangle(yg->framebuffer_ctx, x, y, w, h);
|
|
|
|
cairo_rectangle(yg->real_ctx, x, y, w, h);
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void save_cairo_states(yutani_globals_t * yg) {
|
|
|
|
cairo_save(yg->framebuffer_ctx);
|
|
|
|
cairo_save(yg->selectbuffer_ctx);
|
|
|
|
cairo_save(yg->real_ctx);
|
|
|
|
}
|
2012-12-11 08:38:34 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void restore_cairo_states(yutani_globals_t * yg) {
|
|
|
|
cairo_restore(yg->framebuffer_ctx);
|
|
|
|
cairo_restore(yg->selectbuffer_ctx);
|
|
|
|
cairo_restore(yg->real_ctx);
|
|
|
|
}
|
2012-12-11 08:38:34 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void yutani_set_clip(yutani_globals_t * yg) {
|
|
|
|
cairo_clip(yg->framebuffer_ctx);
|
|
|
|
cairo_clip(yg->real_ctx);
|
|
|
|
}
|
2012-12-11 08:38:34 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yutani_server_window_t * top_at(yutani_globals_t * yg, uint16_t x, uint16_t y) {
|
|
|
|
uint32_t c = ((uint32_t *)yg->select_framebuffer)[(yg->width * y + x)];
|
|
|
|
yutani_wid_t w = (_RED(c) << 16) | (_GRE(c) << 8) | (_BLU(c));
|
|
|
|
return hashmap_get(yg->wids_to_windows, (void *)w);
|
|
|
|
}
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void set_focused_at(yutani_globals_t * yg, int x, int y) {
|
|
|
|
yutani_server_window_t * n_focused = top_at(yg, x, y);
|
|
|
|
set_focused_window(yg, n_focused);
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static int window_is_top(yutani_globals_t * yg, yutani_server_window_t * window) {
|
|
|
|
/* For now, just use simple z-order */
|
|
|
|
return window->z == YUTANI_ZORDER_TOP;
|
|
|
|
}
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static int window_is_bottom(yutani_globals_t * yg, yutani_server_window_t * window) {
|
|
|
|
/* For now, just use simple z-order */
|
|
|
|
return window->z == YUTANI_ZORDER_BOTTOM;
|
2013-06-19 10:43:50 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static int yutani_blit_window(yutani_globals_t * yg, yutani_server_window_t * window, int x, int y) {
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Obtain the previously initialized cairo contexts */
|
|
|
|
cairo_t * cr = yg->framebuffer_ctx;
|
|
|
|
cairo_t * cs = yg->selectbuffer_ctx;
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Window stride is always 4 bytes per pixel... */
|
|
|
|
int stride = window->width * 4;
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Initialize a cairo surface object for this window */
|
|
|
|
cairo_surface_t * surf = cairo_image_surface_create_for_data(
|
|
|
|
window->buffer, CAIRO_FORMAT_ARGB32, window->width, window->height, stride);
|
2013-06-28 11:15:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Save cairo contexts for both rendering and selectbuffer */
|
|
|
|
cairo_save(cr);
|
|
|
|
cairo_save(cs);
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/*
|
|
|
|
* Offset the rendering context appropriately for the position of the window
|
|
|
|
* based on the modifier paramters
|
|
|
|
*/
|
|
|
|
cairo_translate(cr, x, y);
|
|
|
|
cairo_translate(cs, x, y);
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Top and bottom windows can not be rotated. */
|
|
|
|
if (!window_is_top(yg, window) && !window_is_bottom(yg, window)) {
|
|
|
|
/* Calcuate radians from degrees */
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* XXX Window rotation is disabled until damage rects can take it into account */
|
|
|
|
if (window->rotation != 0) {
|
|
|
|
double r = M_PI * (((double)window->rotation) / 180.0);
|
2013-06-28 11:15:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Rotate the render context about the center of the window */
|
|
|
|
cairo_translate(cr, (int)( window->width / 2), (int)( (int)window->height / 2));
|
|
|
|
cairo_rotate(cr, r);
|
|
|
|
cairo_translate(cr, (int)(-window->width / 2), (int)(-window->height / 2));
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Rotate the selectbuffer context about the center of the window */
|
|
|
|
cairo_translate(cs, (int)( window->width / 2), (int)( window->height / 2));
|
|
|
|
cairo_rotate(cs, r);
|
|
|
|
cairo_translate(cs, (int)(-window->width / 2), (int)(-window->height / 2));
|
|
|
|
|
|
|
|
/* Prefer faster filter when rendering rotated windows */
|
|
|
|
cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_FAST);
|
2012-11-24 12:18:14 +04:00
|
|
|
}
|
|
|
|
}
|
2014-04-20 03:59:32 +04:00
|
|
|
if (window->anim_mode) {
|
|
|
|
int frame = yg->tick_count - window->anim_start;
|
|
|
|
if (frame >= yutani_animation_lengths[window->anim_mode]) {
|
|
|
|
/* XXX handle animation-end things like cleanup of closing windows */
|
|
|
|
if (window->anim_mode == YUTANI_EFFECT_FADE_OUT) {
|
|
|
|
window_actually_close(yg, window);
|
|
|
|
goto draw_finish;
|
|
|
|
}
|
|
|
|
window->anim_mode = 0;
|
|
|
|
window->anim_start = 0;
|
|
|
|
goto draw_window;
|
|
|
|
} else {
|
|
|
|
switch (window->anim_mode) {
|
|
|
|
case YUTANI_EFFECT_FADE_OUT:
|
|
|
|
{
|
|
|
|
frame = 256 - frame;
|
|
|
|
}
|
|
|
|
case YUTANI_EFFECT_FADE_IN:
|
|
|
|
{
|
|
|
|
double x = 0.75 + ((double)frame / 256.0) * 0.25;
|
|
|
|
int t_x = (window->width * (1.0 - x)) / 2;
|
|
|
|
int t_y = (window->height * (1.0 - x)) / 2;
|
|
|
|
|
|
|
|
if (!window_is_top(yg, window) && !window_is_bottom(yg, window)) {
|
|
|
|
cairo_translate(cr, t_x, t_y);
|
|
|
|
cairo_scale(cr, x, x);
|
|
|
|
}
|
|
|
|
|
|
|
|
cairo_set_source_surface(cr, surf, 0, 0);
|
|
|
|
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST);
|
|
|
|
cairo_paint_with_alpha(cr, (double)frame/256.0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
draw_window:
|
|
|
|
/* Paint window */
|
|
|
|
cairo_set_source_surface(cr, surf, 0, 0);
|
|
|
|
cairo_paint(cr);
|
|
|
|
}
|
2012-11-24 12:18:14 +04:00
|
|
|
|
2014-04-20 03:59:32 +04:00
|
|
|
draw_finish:
|
2012-12-10 11:06:42 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Clean up */
|
|
|
|
cairo_surface_destroy(surf);
|
2013-05-30 08:31:59 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Paint select buffer */
|
|
|
|
cairo_set_operator(cs, CAIRO_OPERATOR_SOURCE);
|
|
|
|
cairo_set_source_rgb(cs,
|
|
|
|
((window->wid & 0xFF0000) >> 16) / 255.0,
|
|
|
|
((window->wid & 0xFF00) >> 8) / 255.0,
|
|
|
|
((window->wid & 0xFF) >> 0) / 255.0
|
|
|
|
);
|
|
|
|
cairo_rectangle(cs, 0, 0, window->width, window->height);
|
|
|
|
cairo_set_antialias(cs, CAIRO_ANTIALIAS_NONE);
|
|
|
|
cairo_fill(cs);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Restore context stack */
|
|
|
|
cairo_restore(cr);
|
|
|
|
cairo_restore(cs);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
#ifdef YUTANI_DEBUG_WINDOW_BOUNDS
|
|
|
|
cairo_save(cr);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int32_t t_x, t_y;
|
|
|
|
int32_t s_x, s_y;
|
|
|
|
int32_t r_x, r_y;
|
|
|
|
int32_t q_x, q_y;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
window_to_device(window, 0, 0, &t_x, &t_y);
|
|
|
|
window_to_device(window, window->width, window->height, &s_x, &s_y);
|
|
|
|
window_to_device(window, 0, window->height, &r_x, &r_y);
|
|
|
|
window_to_device(window, window->width, 0, &q_x, &q_y);
|
|
|
|
cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.7);
|
|
|
|
cairo_set_line_width(cr, 2.0);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
cairo_move_to(cr, t_x, t_y);
|
|
|
|
cairo_line_to(cr, s_x, s_y);
|
|
|
|
cairo_stroke(cr);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
cairo_move_to(cr, r_x, r_y);
|
|
|
|
cairo_line_to(cr, q_x, q_y);
|
|
|
|
cairo_stroke(cr);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
cairo_restore(cr);
|
|
|
|
#endif
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
return 0;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-17 11:24:54 +04:00
|
|
|
static void draw_resizing_box(yutani_globals_t * yg) {
|
|
|
|
cairo_t * cr = yg->framebuffer_ctx;
|
|
|
|
cairo_save(cr);
|
|
|
|
|
|
|
|
int32_t t_x, t_y;
|
|
|
|
int32_t s_x, s_y;
|
|
|
|
int32_t r_x, r_y;
|
|
|
|
int32_t q_x, q_y;
|
|
|
|
|
|
|
|
window_to_device(yg->resizing_window, 0, 0, &t_x, &t_y);
|
|
|
|
window_to_device(yg->resizing_window, yg->resizing_w, yg->resizing_h, &s_x, &s_y);
|
|
|
|
window_to_device(yg->resizing_window, 0, yg->resizing_h, &r_x, &r_y);
|
|
|
|
window_to_device(yg->resizing_window, yg->resizing_w, 0, &q_x, &q_y);
|
|
|
|
cairo_set_line_width(cr, 2.0);
|
|
|
|
|
|
|
|
cairo_move_to(cr, t_x, t_y);
|
|
|
|
cairo_line_to(cr, q_x, q_y);
|
|
|
|
cairo_line_to(cr, s_x, s_y);
|
|
|
|
cairo_line_to(cr, r_x, r_y);
|
|
|
|
cairo_line_to(cr, t_x, t_y);
|
|
|
|
cairo_close_path(cr);
|
|
|
|
cairo_stroke_preserve(cr);
|
|
|
|
cairo_set_source_rgba(cr, 0.33, 0.55, 1.0, 0.5);
|
|
|
|
cairo_fill(cr);
|
|
|
|
cairo_set_source_rgba(cr, 0.0, 0.4, 1.0, 0.9);
|
|
|
|
cairo_stroke(cr);
|
|
|
|
|
|
|
|
cairo_restore(cr);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void redraw_windows(yutani_globals_t * yg) {
|
|
|
|
/* Save the cairo contexts so we can apply clipping */
|
|
|
|
save_cairo_states(yg);
|
|
|
|
int has_updates = 0;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* We keep our own temporary mouse coordinates as they may change while we're drawing. */
|
|
|
|
int tmp_mouse_x = yg->mouse_x;
|
|
|
|
int tmp_mouse_y = yg->mouse_y;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* If the mouse has moved, that counts as two damage regions */
|
|
|
|
if ((yg->last_mouse_x != tmp_mouse_x) || (yg->last_mouse_y != tmp_mouse_y)) {
|
|
|
|
has_updates = 2;
|
|
|
|
yutani_add_clip(yg, yg->last_mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->last_mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, 64, 64);
|
|
|
|
yutani_add_clip(yg, tmp_mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, tmp_mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, 64, 64);
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->last_mouse_x = tmp_mouse_x;
|
|
|
|
yg->last_mouse_y = tmp_mouse_y;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-20 03:59:32 +04:00
|
|
|
yg->tick_count += 10;
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i <= YUTANI_ZORDER_MAX; ++i) {
|
|
|
|
yutani_server_window_t * w = yg->zlist[i];
|
|
|
|
if (w) {
|
|
|
|
if (w->anim_mode > 0) {
|
|
|
|
mark_window(yg,w);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Calculate damage regions from currently queued updates */
|
|
|
|
spin_lock(&yg->update_list_lock);
|
|
|
|
while (yg->update_list->length) {
|
|
|
|
node_t * win = list_dequeue(yg->update_list);
|
|
|
|
yutani_damage_rect_t * rect = (void *)win->value;
|
2012-11-22 11:29:27 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* We add a clip region for each window in the update queue */
|
|
|
|
has_updates = 1;
|
|
|
|
yutani_add_clip(yg, rect->x, rect->y, rect->width, rect->height);
|
|
|
|
free(rect);
|
|
|
|
free(win);
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
spin_unlock(&yg->update_list_lock);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Render */
|
|
|
|
if (has_updates) {
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yutani_set_clip(yg);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/*
|
|
|
|
* In theory, we should restrict this to windows within the clip region,
|
|
|
|
* but calculating that may be more trouble than it's worth;
|
|
|
|
* we also need to render windows in stacking order...
|
|
|
|
*/
|
|
|
|
for (unsigned int i = 0; i <= YUTANI_ZORDER_MAX; ++i) {
|
|
|
|
if (yg->zlist[i]) {
|
|
|
|
yutani_blit_window(yg, yg->zlist[i], yg->zlist[i]->x, yg->zlist[i]->y);
|
|
|
|
}
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-17 11:24:54 +04:00
|
|
|
if (yg->resizing_window) {
|
|
|
|
/* Draw box */
|
|
|
|
draw_resizing_box(yg);
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/*
|
|
|
|
* Draw the cursor.
|
|
|
|
* We may also want to draw other compositor elements, like effects, but those
|
|
|
|
* can also go in the stack order of the windows.
|
|
|
|
*/
|
|
|
|
draw_cursor(yg, tmp_mouse_x, tmp_mouse_y);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/*
|
|
|
|
* Flip the updated areas. This minimizes writes to video memory,
|
|
|
|
* which is very important on real hardware where these writes are slow.
|
|
|
|
*/
|
|
|
|
cairo_set_operator(yg->real_ctx, CAIRO_OPERATOR_SOURCE);
|
|
|
|
cairo_translate(yg->real_ctx, 0, 0);
|
|
|
|
cairo_set_source_surface(yg->real_ctx, yg->framebuffer_surface, 0, 0);
|
|
|
|
cairo_paint(yg->real_ctx);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Restore the cairo contexts to reset clip regions */
|
|
|
|
restore_cairo_states(yg);
|
2012-11-24 12:18:14 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
void yutani_cairo_init(yutani_globals_t * yg) {
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, yg->width);
|
|
|
|
yg->framebuffer_surface = cairo_image_surface_create_for_data(
|
|
|
|
yg->backend_framebuffer, CAIRO_FORMAT_ARGB32, yg->width, yg->height, stride);
|
|
|
|
yg->real_surface = cairo_image_surface_create_for_data(
|
|
|
|
yg->backend_ctx->buffer, CAIRO_FORMAT_ARGB32, yg->width, yg->height, stride);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->select_framebuffer = malloc(YUTANI_BYTE_DEPTH * yg->width * yg->height);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->selectbuffer_surface = cairo_image_surface_create_for_data(
|
|
|
|
yg->select_framebuffer, CAIRO_FORMAT_ARGB32, yg->width, yg->height, stride);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->framebuffer_ctx = cairo_create(yg->framebuffer_surface);
|
|
|
|
yg->selectbuffer_ctx = cairo_create(yg->selectbuffer_surface);
|
|
|
|
yg->real_ctx = cairo_create(yg->real_surface);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->update_list = list_create();
|
|
|
|
yg->update_list_lock = 0;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
void * redraw(void * in) {
|
|
|
|
yutani_globals_t * yg = in;
|
|
|
|
while (1) {
|
|
|
|
/*
|
|
|
|
* Perform whatever redraw work is required.
|
|
|
|
*/
|
|
|
|
redraw_windows(yg);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Attempt to run at about 60fps...
|
|
|
|
* we should actually see how long it took to render so
|
|
|
|
* we can sleep *less* if it took a long time to render
|
|
|
|
* this particular frame. We are definitely not
|
|
|
|
* going to run at 60fps unless there's nothing to do
|
|
|
|
* (and even then we've wasted cycles checking).
|
|
|
|
*/
|
|
|
|
usleep(16666);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mark_window(yutani_globals_t * yg, yutani_server_window_t * window) {
|
|
|
|
yutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t));
|
|
|
|
|
|
|
|
if (window->rotation == 0) {
|
|
|
|
rect->x = window->x;
|
|
|
|
rect->y = window->y;
|
|
|
|
rect->width = window->width;
|
|
|
|
rect->height = window->height;
|
|
|
|
} else {
|
|
|
|
int32_t ul_x, ul_y;
|
|
|
|
int32_t ll_x, ll_y;
|
|
|
|
int32_t ur_x, ur_y;
|
|
|
|
int32_t lr_x, lr_y;
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
window_to_device(window, 0, 0, &ul_x, &ul_y);
|
|
|
|
window_to_device(window, 0, window->height, &ll_x, &ll_y);
|
|
|
|
window_to_device(window, window->width, 0, &ur_x, &ur_y);
|
|
|
|
window_to_device(window, window->width, window->height, &lr_x, &lr_y);
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Calculate bounds */
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int32_t left_bound = min(min(ul_x, ll_x), min(ur_x, lr_x));
|
|
|
|
int32_t top_bound = min(min(ul_y, ll_y), min(ur_y, lr_y));
|
2012-12-08 12:25:16 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int32_t right_bound = max(max(ul_x, ll_x), max(ur_x, lr_x));
|
|
|
|
int32_t bottom_bound = max(max(ul_y, ll_y), max(ur_y, lr_y));
|
2012-12-10 11:06:42 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
rect->x = left_bound;
|
|
|
|
rect->y = top_bound;
|
|
|
|
rect->width = right_bound - left_bound;
|
|
|
|
rect->height = bottom_bound - top_bound;
|
2013-05-22 08:11:30 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
spin_lock(&yg->update_list_lock);
|
|
|
|
list_insert(yg->update_list, rect);
|
|
|
|
spin_unlock(&yg->update_list_lock);
|
|
|
|
}
|
2013-05-22 08:11:30 +04:00
|
|
|
|
2014-04-16 11:00:52 +04:00
|
|
|
static void mark_window_relative(yutani_globals_t * yg, yutani_server_window_t * window, int32_t x, int32_t y, int32_t width, int32_t height) {
|
|
|
|
yutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t));
|
|
|
|
|
|
|
|
if (window->rotation == 0) {
|
|
|
|
rect->x = window->x + x;
|
|
|
|
rect->y = window->y + y;
|
|
|
|
rect->width = width;
|
|
|
|
rect->height = height;
|
|
|
|
} else {
|
|
|
|
int32_t ul_x, ul_y;
|
|
|
|
int32_t ll_x, ll_y;
|
|
|
|
int32_t ur_x, ur_y;
|
|
|
|
int32_t lr_x, lr_y;
|
|
|
|
|
|
|
|
window_to_device(window, x, y, &ul_x, &ul_y);
|
|
|
|
window_to_device(window, x, y + height, &ll_x, &ll_y);
|
|
|
|
window_to_device(window, x + width, y, &ur_x, &ur_y);
|
|
|
|
window_to_device(window, x + width, y + height, &lr_x, &lr_y);
|
|
|
|
|
|
|
|
/* Calculate bounds */
|
|
|
|
|
|
|
|
int32_t left_bound = min(min(ul_x, ll_x), min(ur_x, lr_x));
|
|
|
|
int32_t top_bound = min(min(ul_y, ll_y), min(ur_y, lr_y));
|
|
|
|
|
|
|
|
int32_t right_bound = max(max(ul_x, ll_x), max(ur_x, lr_x));
|
|
|
|
int32_t bottom_bound = max(max(ul_y, ll_y), max(ur_y, lr_y));
|
|
|
|
|
|
|
|
rect->x = left_bound;
|
|
|
|
rect->y = top_bound;
|
|
|
|
rect->width = right_bound - left_bound;
|
|
|
|
rect->height = bottom_bound - top_bound;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock(&yg->update_list_lock);
|
|
|
|
list_insert(yg->update_list, rect);
|
|
|
|
spin_unlock(&yg->update_list_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void mark_region(yutani_globals_t * yg, int x, int y, int width, int height) {
|
|
|
|
yutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t));
|
|
|
|
rect->x = x;
|
|
|
|
rect->y = y;
|
|
|
|
rect->width = width;
|
|
|
|
rect->height = height;
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
spin_lock(&yg->update_list_lock);
|
|
|
|
list_insert(yg->update_list, rect);
|
|
|
|
spin_unlock(&yg->update_list_lock);
|
|
|
|
}
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
|
2014-04-20 03:59:32 +04:00
|
|
|
static void window_mark_for_close(yutani_globals_t * yg, yutani_server_window_t * w) {
|
|
|
|
w->anim_mode = YUTANI_EFFECT_FADE_OUT;
|
|
|
|
w->anim_start = yg->tick_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void window_actually_close(yutani_globals_t * yg, yutani_server_window_t * w) {
|
|
|
|
/* XXX free window */
|
|
|
|
hashmap_remove(yg->wids_to_windows, (void *)w->wid);
|
|
|
|
list_remove(yg->windows, list_index_of(yg->windows, w));
|
|
|
|
mark_window(yg, w);
|
|
|
|
unorder_window(yg, w);
|
|
|
|
if (w == yg->focused_window) {
|
|
|
|
yg->focused_window = NULL;
|
|
|
|
}
|
|
|
|
yutani_msg_t * response = yutani_msg_build_notify();
|
|
|
|
foreach(node, yg->window_subscribers) {
|
|
|
|
uint32_t subscriber = (uint32_t)node->value;
|
|
|
|
pex_send(yg->server, subscriber, response->size, (char *)response);
|
|
|
|
}
|
|
|
|
free(response);
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void handle_key_event(yutani_globals_t * yg, struct yutani_msg_key_event * ke) {
|
|
|
|
yutani_server_window_t * focused = get_focused(yg);
|
|
|
|
memcpy(&yg->kbd_state, &ke->state, sizeof(key_event_state_t));
|
|
|
|
if (focused) {
|
|
|
|
if ((ke->event.action == KEY_ACTION_DOWN) &&
|
|
|
|
(ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&
|
2014-04-16 10:26:27 +04:00
|
|
|
(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&
|
2014-04-16 06:45:56 +04:00
|
|
|
(ke->event.keycode == 'z')) {
|
|
|
|
mark_window(yg,focused);
|
|
|
|
focused->rotation -= 5;
|
|
|
|
mark_window(yg,focused);
|
|
|
|
return;
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
if ((ke->event.action == KEY_ACTION_DOWN) &&
|
|
|
|
(ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&
|
2014-04-16 10:26:27 +04:00
|
|
|
(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&
|
2014-04-16 06:45:56 +04:00
|
|
|
(ke->event.keycode == 'x')) {
|
|
|
|
mark_window(yg,focused);
|
|
|
|
focused->rotation += 5;
|
|
|
|
mark_window(yg,focused);
|
|
|
|
return;
|
2012-12-08 12:25:16 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
if ((ke->event.action == KEY_ACTION_DOWN) &&
|
|
|
|
(ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&
|
2014-04-16 10:26:27 +04:00
|
|
|
(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&
|
2014-04-16 06:45:56 +04:00
|
|
|
(ke->event.keycode == 'c')) {
|
|
|
|
mark_window(yg,focused);
|
|
|
|
focused->rotation = 0;
|
|
|
|
mark_window(yg,focused);
|
|
|
|
return;
|
2013-07-23 09:42:24 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yutani_msg_t * response = yutani_msg_build_key_event(focused->wid, &ke->event, &ke->state);
|
|
|
|
pex_send(yg->server, focused->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
2013-06-19 10:43:50 +04:00
|
|
|
|
2012-12-08 12:25:16 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/* Other events? */
|
2012-12-08 12:25:16 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
static void handle_mouse_event(yutani_globals_t * yg, struct yutani_msg_mouse_event * me) {
|
|
|
|
yg->mouse_x += me->event.x_difference * 3;
|
|
|
|
yg->mouse_y -= me->event.y_difference * 3;
|
2012-12-10 11:06:42 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (yg->mouse_x < 0) yg->mouse_x = 0;
|
|
|
|
if (yg->mouse_y < 0) yg->mouse_y = 0;
|
|
|
|
if (yg->mouse_x > (yg->width) * MOUSE_SCALE) yg->mouse_x = (yg->width) * MOUSE_SCALE;
|
|
|
|
if (yg->mouse_y > (yg->height) * MOUSE_SCALE) yg->mouse_y = (yg->height) * MOUSE_SCALE;
|
2012-12-10 11:06:42 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
switch (yg->mouse_state) {
|
|
|
|
case YUTANI_MOUSE_STATE_NORMAL:
|
|
|
|
{
|
|
|
|
if ((me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT) && (yg->kbd_state.k_alt)) {
|
|
|
|
set_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);
|
|
|
|
yg->mouse_window = get_focused(yg);
|
|
|
|
if (yg->mouse_window) {
|
|
|
|
if (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP) {
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;
|
|
|
|
yg->mouse_window = NULL;
|
|
|
|
} else {
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_MOVING;
|
|
|
|
yg->mouse_init_x = yg->mouse_x;
|
|
|
|
yg->mouse_init_y = yg->mouse_y;
|
|
|
|
yg->mouse_win_x = yg->mouse_window->x;
|
|
|
|
yg->mouse_win_y = yg->mouse_window->y;
|
|
|
|
make_top(yg, yg->mouse_window);
|
|
|
|
}
|
2014-04-02 08:39:08 +04:00
|
|
|
}
|
2014-04-17 11:24:54 +04:00
|
|
|
} else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_MIDDLE) && (yg->kbd_state.k_alt)) {
|
|
|
|
set_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);
|
|
|
|
yg->mouse_window = get_focused(yg);
|
|
|
|
if (yg->mouse_window) {
|
|
|
|
if (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP) {
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;
|
|
|
|
yg->mouse_window = NULL;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "[yutani-server] resize starting for wid=%d\n", yg->mouse_window -> wid);
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_RESIZING;
|
|
|
|
yg->mouse_init_x = yg->mouse_x;
|
|
|
|
yg->mouse_init_y = yg->mouse_y;
|
|
|
|
yg->mouse_win_x = yg->mouse_window->x;
|
|
|
|
yg->mouse_win_y = yg->mouse_window->y;
|
|
|
|
yg->resizing_window = yg->mouse_window;
|
|
|
|
yg->resizing_w = yg->mouse_window->width;
|
|
|
|
yg->resizing_h = yg->mouse_window->height;
|
|
|
|
make_top(yg, yg->mouse_window);
|
|
|
|
}
|
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
} else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT) && (!yg->kbd_state.k_alt)) {
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_DRAGGING;
|
|
|
|
set_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);
|
|
|
|
yg->mouse_window = get_focused(yg);
|
|
|
|
yg->mouse_moved = 0;
|
|
|
|
yg->mouse_drag_button = YUTANI_MOUSE_BUTTON_LEFT;
|
|
|
|
device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y);
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_mouse_event(yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_DOWN);
|
|
|
|
pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
} else {
|
|
|
|
/* XXX Arbitrary mouse movement, not dragging */
|
2012-12-10 11:06:42 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MOUSE_STATE_MOVING:
|
|
|
|
{
|
|
|
|
if (!(me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT)) {
|
|
|
|
yg->mouse_window = NULL;
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;
|
2012-12-10 11:06:42 +04:00
|
|
|
} else {
|
2014-04-16 06:45:56 +04:00
|
|
|
mark_window(yg, yg->mouse_window);
|
|
|
|
yg->mouse_window->x = yg->mouse_win_x + (yg->mouse_x - yg->mouse_init_x) / MOUSE_SCALE;
|
|
|
|
yg->mouse_window->y = yg->mouse_win_y + (yg->mouse_y - yg->mouse_init_y) / MOUSE_SCALE;
|
|
|
|
mark_window(yg, yg->mouse_window);
|
2012-12-10 11:06:42 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MOUSE_STATE_DRAGGING:
|
|
|
|
{
|
|
|
|
if (!(me->event.buttons & yg->mouse_drag_button)) {
|
|
|
|
/* Mouse released */
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;
|
|
|
|
int32_t old_x = yg->mouse_click_x;
|
|
|
|
int32_t old_y = yg->mouse_click_y;
|
|
|
|
device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y);
|
|
|
|
if (!yg->mouse_moved) {
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_mouse_event(yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_CLICK);
|
|
|
|
pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
} else {
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_mouse_event(yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, old_x, old_y, me->event.buttons, YUTANI_MOUSE_EVENT_RAISE);
|
|
|
|
pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
2012-12-08 12:25:16 +04:00
|
|
|
}
|
2012-12-10 11:06:42 +04:00
|
|
|
} else {
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_DRAGGING;
|
|
|
|
yg->mouse_moved = 1;
|
|
|
|
int32_t old_x = yg->mouse_click_x;
|
|
|
|
int32_t old_y = yg->mouse_click_y;
|
|
|
|
device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y);
|
|
|
|
if (old_x != yg->mouse_click_x || old_y != yg->mouse_click_y) {
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_mouse_event(yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, old_x, old_y, me->event.buttons, YUTANI_MOUSE_EVENT_DRAG);
|
|
|
|
pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
2012-12-10 11:06:42 +04:00
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MOUSE_STATE_RESIZING:
|
|
|
|
{
|
2014-04-17 11:24:54 +04:00
|
|
|
int width_diff = (yg->mouse_x - yg->mouse_init_x) / MOUSE_SCALE;
|
|
|
|
int height_diff = (yg->mouse_y - yg->mouse_init_y) / MOUSE_SCALE;
|
|
|
|
|
|
|
|
mark_window_relative(yg, yg->mouse_window, -2, -2, yg->resizing_w + 10, yg->resizing_h + 10);
|
|
|
|
|
|
|
|
yg->resizing_w = yg->resizing_window->width + width_diff;
|
|
|
|
yg->resizing_h = yg->resizing_window->height + height_diff;
|
2014-04-15 08:03:23 +04:00
|
|
|
|
2014-04-17 11:24:54 +04:00
|
|
|
mark_window_relative(yg, yg->mouse_window, -2, -2, yg->resizing_w + 10, yg->resizing_h + 10);
|
|
|
|
|
|
|
|
if (!(me->event.buttons & YUTANI_MOUSE_BUTTON_MIDDLE)) {
|
|
|
|
fprintf(stderr, "[yutani-server] resize complete, now %d x %d\n", yg->resizing_w, yg->resizing_h);
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_resize(YUTANI_MSG_RESIZE_OFFER, yg->resizing_window->wid, yg->resizing_w, yg->resizing_h, 0);
|
|
|
|
pex_send(yg->server, yg->resizing_window->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
yg->resizing_window = NULL;
|
|
|
|
yg->mouse_window = NULL;
|
|
|
|
yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* XXX ? */
|
|
|
|
break;
|
2013-07-23 09:42:24 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
/**
|
|
|
|
* main
|
|
|
|
*/
|
|
|
|
int main(int argc, char * argv[]) {
|
2013-07-23 09:42:24 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
int argx = 0;
|
|
|
|
int results = parse_args(argc, argv, &argx);
|
|
|
|
if (results) return results;
|
2013-05-30 08:31:59 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yutani_globals_t * yg = malloc(sizeof(yutani_globals_t));
|
|
|
|
memset(yg, 0x00, sizeof(yutani_globals_t));
|
|
|
|
yg->backend_ctx = init_graphics_fullscreen_double_buffer();
|
2013-05-30 08:31:59 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
if (!yg->backend_ctx) {
|
|
|
|
free(yg);
|
|
|
|
fprintf(stderr, "%s: Failed to open framebuffer, bailing.\n", argv[0]);
|
|
|
|
return 1;
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->width = yg->backend_ctx->width;
|
|
|
|
yg->height = yg->backend_ctx->height;
|
EXPERIMENTAL: Rotating windows.
* Dumb bounding box top_at logic replaced with select buffer
* Select buffer rendered through cairo with AA disabled
Using rectangles for window shapes - this should easily
be expandable to 1bpp bitmaps. Currently, the select buffer
is very inefficient, using twice the space it needs (plus,
it's double buffered, so in reality, 4x the space needed),
however, it's also very accurate and fast, and I like that.
* Window rotation is controlled through Ctrl+Shift+{z,x,c} where
z = rotate 1 degree left
x = rotate 1 degree right
c = reset rotation
* Input is remapped based on window rotation, so you *can* use the
draw app, and it is totally epic.
2013-04-02 12:26:32 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
draw_fill(yg->backend_ctx, rgb(0,0,0));
|
|
|
|
flip(yg->backend_ctx);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->backend_framebuffer = yg->backend_ctx->backbuffer;
|
|
|
|
yg->pex_endpoint = "compositor";
|
|
|
|
setenv("DISPLAY", yg->pex_endpoint, 1);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
FILE * server = pex_bind(yg->pex_endpoint);
|
|
|
|
yg->server = server;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
fprintf(stderr, "[yutani] Loading fonts...\n");
|
2012-11-22 11:29:27 +04:00
|
|
|
load_fonts();
|
2014-04-16 06:45:56 +04:00
|
|
|
fprintf(stderr, "[yutani] Done.\n");
|
|
|
|
|
|
|
|
load_sprite_png(&yg->mouse_sprite, "/usr/share/arrow.png");
|
|
|
|
yg->last_mouse_x = 0;
|
|
|
|
yg->last_mouse_y = 0;
|
|
|
|
yg->mouse_x = yg->width * MOUSE_SCALE / 2;
|
|
|
|
yg->mouse_y = yg->height * MOUSE_SCALE / 2;
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yg->windows = list_create();
|
|
|
|
yg->wids_to_windows = hashmap_create_int(10);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-18 11:18:19 +04:00
|
|
|
yg->window_subscribers = list_create();
|
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
yutani_cairo_init(yg);
|
2013-05-30 08:31:59 +04:00
|
|
|
|
2012-12-10 11:06:42 +04:00
|
|
|
pthread_t mouse_thread;
|
2014-04-16 06:45:56 +04:00
|
|
|
pthread_create(&mouse_thread, NULL, mouse_input, NULL);
|
2012-12-10 11:06:42 +04:00
|
|
|
|
|
|
|
pthread_t keyboard_thread;
|
|
|
|
pthread_create(&keyboard_thread, NULL, keyboard_input, NULL);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
2014-04-16 06:45:56 +04:00
|
|
|
pthread_t render_thread;
|
|
|
|
pthread_create(&render_thread, NULL, redraw, yg);
|
2012-10-22 06:52:53 +04:00
|
|
|
|
|
|
|
if (!fork()) {
|
2014-04-16 06:45:56 +04:00
|
|
|
fprintf(stderr, "Have %d args, argx=%d\n", argc, argx);
|
|
|
|
if (argx < argc) {
|
|
|
|
fprintf(stderr, "Starting %s\n", argv[argx]);
|
|
|
|
execvp(argv[argx], &argv[argx]);
|
|
|
|
} else {
|
2013-06-28 11:33:03 +04:00
|
|
|
char * args[] = {"/bin/glogin", NULL};
|
|
|
|
execvp(args[0], args);
|
|
|
|
}
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
2014-04-16 06:45:56 +04:00
|
|
|
pex_packet_t * p = calloc(PACKET_SIZE, 1);
|
|
|
|
pex_listen(server, p);
|
|
|
|
|
|
|
|
yutani_msg_t * m = (yutani_msg_t *)p->data;
|
|
|
|
|
|
|
|
if (m->magic != YUTANI_MSG__MAGIC) {
|
|
|
|
fprintf(stderr, "[yutani-server] Message has bad magic. (Should eject client, but will instead skip this message.) 0x%x\n", m->magic);
|
|
|
|
free(p);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(m->type) {
|
2014-04-18 11:18:19 +04:00
|
|
|
case YUTANI_MSG_HELLO:
|
|
|
|
{
|
|
|
|
fprintf(stderr, "[yutani-server] And hello to you, %08x!\n", p->source);
|
|
|
|
yutani_msg_t * response = yutani_msg_build_welcome(yg->width, yg->height);
|
|
|
|
pex_send(server, p->source, response->size, (char *)response);
|
|
|
|
free(response);
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_WINDOW_NEW:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_new * wn = (void *)m->data;
|
|
|
|
fprintf(stderr, "[yutani-server] Client %08x requested a new window (%dx%d).\n", p->source, wn->width, wn->height);
|
|
|
|
yutani_server_window_t * w = server_window_create(yg, wn->width, wn->height, p->source);
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_init(w->wid, w->width, w->height, w->bufid);
|
|
|
|
pex_send(server, p->source, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
|
|
|
|
response = yutani_msg_build_notify();
|
|
|
|
foreach(node, yg->window_subscribers) {
|
|
|
|
uint32_t subscriber = (uint32_t)node->value;
|
|
|
|
pex_send(server, subscriber, response->size, (char *)response);
|
|
|
|
}
|
|
|
|
free(response);
|
2014-04-16 11:00:52 +04:00
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_FLIP:
|
|
|
|
{
|
|
|
|
struct yutani_msg_flip * wf = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wf->wid);
|
|
|
|
if (w) {
|
|
|
|
mark_window(yg, w);
|
|
|
|
}
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_FLIP_REGION:
|
|
|
|
{
|
|
|
|
struct yutani_msg_flip_region * wf = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wf->wid);
|
|
|
|
if (w) {
|
|
|
|
mark_window_relative(yg, w, wf->x, wf->y, wf->width, wf->height);
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_KEY_EVENT:
|
|
|
|
{
|
|
|
|
/* XXX Verify this is from a valid device client */
|
|
|
|
struct yutani_msg_key_event * ke = (void *)m->data;
|
|
|
|
handle_key_event(yg, ke);
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_MOUSE_EVENT:
|
|
|
|
{
|
|
|
|
/* XXX Verify this is from a valid device client */
|
|
|
|
struct yutani_msg_mouse_event * me = (void *)m->data;
|
|
|
|
handle_mouse_event(yg, me);
|
2014-04-17 10:50:57 +04:00
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_WINDOW_MOVE:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_move * wm = (void *)m->data;
|
|
|
|
fprintf(stderr, "[yutani-server] %08x wanted to move window %d\n", p->source, wm->wid);
|
|
|
|
yutani_server_window_t * win = hashmap_get(yg->wids_to_windows, (void*)wm->wid);
|
|
|
|
if (win) {
|
2014-04-19 06:45:55 +04:00
|
|
|
mark_window(yg, win);
|
2014-04-18 11:18:19 +04:00
|
|
|
win->x = wm->x;
|
|
|
|
win->y = wm->y;
|
2014-04-19 06:45:55 +04:00
|
|
|
mark_window(yg, win);
|
2014-04-18 11:18:19 +04:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "[yutani-server] %08x wanted to move window %d, but I can't find it?\n", p->source, wm->wid);
|
|
|
|
}
|
2014-04-17 10:50:57 +04:00
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_WINDOW_CLOSE:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_close * wc = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wc->wid);
|
|
|
|
if (w) {
|
2014-04-20 03:59:32 +04:00
|
|
|
window_mark_for_close(yg, w);
|
2014-04-18 11:18:19 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MSG_WINDOW_STACK:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_stack * ws = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)ws->wid);
|
|
|
|
if (w) {
|
|
|
|
reorder_window(yg, w, ws->z);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MSG_RESIZE_REQUEST:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_resize * wr = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wr->wid);
|
|
|
|
if (w) {
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_resize(YUTANI_MSG_RESIZE_OFFER, w->wid, wr->width, wr->height, 0);
|
|
|
|
pex_send(server, p->source, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MSG_RESIZE_OFFER:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_resize * wr = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wr->wid);
|
|
|
|
if (w) {
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_resize(YUTANI_MSG_RESIZE_OFFER, w->wid, wr->width, wr->height, 0);
|
|
|
|
pex_send(server, p->source, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MSG_RESIZE_ACCEPT:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_resize * wr = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wr->wid);
|
|
|
|
if (w) {
|
|
|
|
uint32_t newbufid = server_window_resize(yg, w, wr->width, wr->height);
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_resize(YUTANI_MSG_RESIZE_BUFID, w->wid, wr->width, wr->height, newbufid);
|
|
|
|
pex_send(server, p->source, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MSG_RESIZE_DONE:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_resize * wr = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wr->wid);
|
|
|
|
if (w) {
|
|
|
|
server_window_resize_finish(yg, w, wr->width, wr->height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MSG_QUERY_WINDOWS:
|
|
|
|
{
|
|
|
|
for (unsigned int i = 0; i <= YUTANI_ZORDER_MAX; ++i) {
|
|
|
|
if (yg->zlist[i] && yg->zlist[i]->name) {
|
|
|
|
fprintf(stderr, "[yutani-server] informing client about window %d with name %s\n", yg->zlist[i]->wid, yg->zlist[i]->name);
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_advertise(yg->zlist[i]->wid, yg->zlist[i]->name);
|
|
|
|
pex_send(server, p->source, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
yutani_msg_t * response = yutani_msg_build_window_advertise(0, NULL);
|
2014-04-17 10:50:57 +04:00
|
|
|
pex_send(server, p->source, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_SUBSCRIBE:
|
|
|
|
{
|
|
|
|
foreach(node, yg->window_subscribers) {
|
|
|
|
if ((uint32_t)node->value == p->source) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
list_insert(yg->window_subscribers, (void*)p->source);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YUTANI_MSG_UNSUBSCRIBE:
|
|
|
|
{
|
|
|
|
node_t * node = list_find(yg->window_subscribers, (void*)p->source);
|
|
|
|
if (node) {
|
|
|
|
list_delete(yg->window_subscribers, node);
|
|
|
|
}
|
2014-04-17 10:50:57 +04:00
|
|
|
}
|
2014-04-18 11:18:19 +04:00
|
|
|
break;
|
|
|
|
case YUTANI_MSG_WINDOW_ADVERTISE:
|
|
|
|
{
|
|
|
|
struct yutani_msg_window_advertise * wa = (void *)m->data;
|
|
|
|
yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)wa->wid);
|
|
|
|
if (w) {
|
|
|
|
if (w->name) {
|
|
|
|
free(w->name);
|
|
|
|
}
|
|
|
|
if (wa->size == 0) {
|
|
|
|
w->name = NULL;
|
|
|
|
} else {
|
|
|
|
char * t = malloc(wa->size+1);
|
|
|
|
memcpy(t, wa->name, wa->size+1);
|
|
|
|
w->name = t;
|
|
|
|
}
|
|
|
|
yutani_msg_t * response = yutani_msg_build_notify();
|
|
|
|
foreach(node, yg->window_subscribers) {
|
|
|
|
uint32_t subscriber = (uint32_t)node->value;
|
|
|
|
pex_send(server, subscriber, response->size, (char *)response);
|
|
|
|
}
|
|
|
|
free(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2014-04-19 06:23:45 +04:00
|
|
|
case YUTANI_MSG_SESSION_END:
|
|
|
|
{
|
|
|
|
for (unsigned int i = 0; i <= YUTANI_ZORDER_MAX; ++i) {
|
|
|
|
if (yg->zlist[i]) {
|
|
|
|
yutani_msg_t * response = yutani_msg_build_session_end();
|
|
|
|
pex_send(server, yg->zlist[i]->owner, response->size, (char *)response);
|
|
|
|
free(response);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2014-04-18 11:18:19 +04:00
|
|
|
default:
|
|
|
|
{
|
|
|
|
fprintf(stderr, "[yutani-server] Unknown type!\n");
|
|
|
|
}
|
|
|
|
break;
|
2014-04-16 06:45:56 +04:00
|
|
|
}
|
|
|
|
free(p);
|
2012-10-22 06:52:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|