898 lines
22 KiB
C
898 lines
22 KiB
C
/* vim: tabstop=4 shiftwidth=4 noexpandtab
|
|
* This file is part of ToaruOS and is released under the terms
|
|
* of the NCSA / University of Illinois License - see LICENSE.md
|
|
* Copyright (C) 2013-2014 Kevin Lange
|
|
*
|
|
*
|
|
* Yutani Panel
|
|
*
|
|
* Provides a window list and clock as well some simple session management.
|
|
*
|
|
* Future goals:
|
|
* - Applications menu
|
|
* - More session management
|
|
* - Pluggable indicators
|
|
*
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <sys/time.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include "lib/pthread.h"
|
|
#include "lib/yutani.h"
|
|
#include "lib/graphics.h"
|
|
#include "lib/shmemfonts.h"
|
|
#include "lib/hashmap.h"
|
|
#include "lib/spinlock.h"
|
|
|
|
#define PANEL_HEIGHT 28
|
|
#define FONT_SIZE 14
|
|
#define TIME_LEFT 108
|
|
#define DATE_WIDTH 70
|
|
|
|
#define ICON_SIZE 24
|
|
#define GRADIENT_HEIGHT 24
|
|
#define APP_OFFSET 140
|
|
#define TEXT_Y_OFFSET 18
|
|
#define ICON_PADDING 2
|
|
#define MAX_TEXT_WIDTH 120
|
|
#define MIN_TEXT_WIDTH 50
|
|
|
|
#define HILIGHT_COLOR rgb(142,216,255)
|
|
#define FOCUS_COLOR rgb(255,255,255)
|
|
#define TEXT_COLOR rgb(230,230,230)
|
|
|
|
#define GRADIENT_AT(y) premultiply(rgba(72, 167, 255, ((24-(y))*160)/24))
|
|
|
|
#define ALTTAB_WIDTH 250
|
|
#define ALTTAB_HEIGHT 70
|
|
#define ALTTAB_BACKGROUND premultiply(rgba(0,0,0,150))
|
|
#define ALTTAB_OFFSET 10
|
|
|
|
#define MAX_WINDOW_COUNT 100
|
|
|
|
#define TOTAL_CELL_WIDTH (ICON_SIZE + ICON_PADDING * 2 + title_width)
|
|
#define LEFT_BOUND (width - TIME_LEFT - DATE_WIDTH - ICON_PADDING)
|
|
|
|
#define APPMENU_WIDTH 200
|
|
#define APPMENU_PAD_RIGHT 1
|
|
#define APPMENU_PAD_BOTTOM 1
|
|
#define APPMENU_BACKGROUND rgb(255,255,255)
|
|
#define APPMENU_HIGHLIGHT rgb(50,50,200)
|
|
#define APPMENU_ITEM_HEIGHT 24
|
|
|
|
static yutani_t * yctx;
|
|
|
|
static gfx_context_t * ctx = NULL;
|
|
static yutani_window_t * panel = NULL;
|
|
|
|
static gfx_context_t * actx = NULL;
|
|
static yutani_window_t * alttab = NULL;
|
|
|
|
static gfx_context_t * bctx = NULL;
|
|
static yutani_window_t * appmenu = NULL;
|
|
|
|
static int appmenu_item = -1;
|
|
|
|
static list_t * window_list = NULL;
|
|
static volatile int lock = 0;
|
|
static volatile int drawlock = 0;
|
|
|
|
static hashmap_t * icon_cache;
|
|
|
|
static size_t bg_size;
|
|
static char * bg_blob;
|
|
|
|
static int width;
|
|
static int height;
|
|
|
|
static sprite_t * sprite_panel;
|
|
static sprite_t * sprite_logout;
|
|
|
|
static int center_x(int x) {
|
|
return (width - x) / 2;
|
|
}
|
|
|
|
static int center_y(int y) {
|
|
return (height - y) / 2;
|
|
}
|
|
|
|
static int center_x_a(int x) {
|
|
return (ALTTAB_WIDTH - x) / 2;
|
|
}
|
|
|
|
static int center_y_a(int y) {
|
|
return (ALTTAB_HEIGHT - y) / 2;
|
|
}
|
|
|
|
static void redraw(void);
|
|
|
|
static volatile int _continue = 1;
|
|
|
|
struct window_ad {
|
|
yutani_wid_t wid;
|
|
uint32_t flags;
|
|
char * name;
|
|
char * icon;
|
|
char * strings;
|
|
int left;
|
|
};
|
|
|
|
typedef struct {
|
|
char * icon;
|
|
char * appname;
|
|
char * title;
|
|
} application_t;
|
|
|
|
static int appmenu_items_count = 0;
|
|
static application_t * applications = NULL;
|
|
|
|
/* Windows, indexed by list order */
|
|
static struct window_ad * ads_by_l[MAX_WINDOW_COUNT+1] = {NULL};
|
|
/* Windows, indexed by z-order */
|
|
static struct window_ad * ads_by_z[MAX_WINDOW_COUNT+1] = {NULL};
|
|
|
|
static int focused_app = -1;
|
|
static int active_window = -1;
|
|
static int was_tabbing = 0;
|
|
static int new_focused = -1;
|
|
|
|
static int title_width = 0;
|
|
|
|
static sprite_t * icon_get(char * name);
|
|
static void redraw_appmenu(int item);
|
|
|
|
/* Handle SIGINT by telling other threads (clock) to shut down */
|
|
static void sig_int(int sig) {
|
|
printf("Received shutdown signal in panel!\n");
|
|
_continue = 0;
|
|
}
|
|
|
|
static void launch_application(char * app) {
|
|
if (!fork()) {
|
|
printf("Starting %s\n", app);
|
|
char * args[] = {"/bin/sh", "-c", app, NULL};
|
|
execvp(args[0], args);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* Update the hover-focus window */
|
|
static void set_focused(int i) {
|
|
if (focused_app != i) {
|
|
focused_app = i;
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
/* Callback for mouse events */
|
|
static void panel_check_click(struct yutani_msg_window_mouse_event * evt) {
|
|
if (evt->wid == panel->wid) {
|
|
if (evt->command == YUTANI_MOUSE_EVENT_CLICK) {
|
|
/* Up-down click */
|
|
if (evt->new_x >= width - 24 ) {
|
|
yutani_session_end(yctx);
|
|
_continue = 0;
|
|
} else if (evt->new_x < APP_OFFSET) {
|
|
if (!appmenu) {
|
|
appmenu = yutani_window_create(yctx, APPMENU_WIDTH + APPMENU_PAD_RIGHT, APPMENU_ITEM_HEIGHT * appmenu_items_count + APPMENU_PAD_BOTTOM);
|
|
yutani_window_move(yctx, appmenu, 0, PANEL_HEIGHT);
|
|
bctx = init_graphics_yutani_double_buffer(appmenu);
|
|
redraw_appmenu(-1);
|
|
yutani_focus_window(yctx, appmenu->wid);
|
|
} else {
|
|
/* ??? */
|
|
}
|
|
} else {
|
|
for (int i = 0; i < MAX_WINDOW_COUNT; ++i) {
|
|
if (ads_by_l[i] == NULL) break;
|
|
if (evt->new_x >= ads_by_l[i]->left && evt->new_x < ads_by_l[i]->left + TOTAL_CELL_WIDTH) {
|
|
yutani_focus_window(yctx, ads_by_l[i]->wid);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (evt->command == YUTANI_MOUSE_EVENT_MOVE || evt->command == YUTANI_MOUSE_EVENT_ENTER) {
|
|
/* Movement, or mouse entered window */
|
|
if (evt->new_y < PANEL_HEIGHT) {
|
|
for (int i = 0; i < MAX_WINDOW_COUNT; ++i) {
|
|
if (ads_by_l[i] == NULL) {
|
|
set_focused(-1);
|
|
break;
|
|
}
|
|
if (evt->new_x >= ads_by_l[i]->left && evt->new_x < ads_by_l[i]->left + TOTAL_CELL_WIDTH) {
|
|
set_focused(i);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
set_focused(-1);
|
|
}
|
|
} else if (evt->command == YUTANI_MOUSE_EVENT_LEAVE) {
|
|
/* Mouse left panel window */
|
|
set_focused(-1);
|
|
}
|
|
} else {
|
|
if (appmenu && evt->wid == appmenu->wid) {
|
|
/* Do stuff */
|
|
if (evt->command == YUTANI_MOUSE_EVENT_CLICK) {
|
|
if (evt->new_x >= 0 && evt->new_x < appmenu->width && evt->new_y >= 0 && evt->new_y < appmenu->height) {
|
|
int item = evt->new_y / APPMENU_ITEM_HEIGHT;
|
|
launch_application(applications[item].appname);
|
|
yutani_close(yctx, appmenu);
|
|
appmenu = NULL;
|
|
free(bctx->backbuffer);
|
|
free(bctx);
|
|
}
|
|
} else if (evt->command == YUTANI_MOUSE_EVENT_MOVE || evt->command == YUTANI_MOUSE_EVENT_ENTER) {
|
|
if (evt->new_x >= 0 && evt->new_x < appmenu->width && evt->new_y >= 0 && evt->new_y < appmenu->height) {
|
|
int item = evt->new_y / APPMENU_ITEM_HEIGHT;
|
|
if (item != appmenu_item) {
|
|
appmenu_item = item;
|
|
redraw_appmenu(appmenu_item);
|
|
}
|
|
}
|
|
} else if (evt->command == YUTANI_MOUSE_EVENT_LEAVE) {
|
|
if (-1 != appmenu_item) {
|
|
appmenu_item = -1;
|
|
redraw_appmenu(appmenu_item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_focus_event(struct yutani_msg_window_focus_change * wf) {
|
|
|
|
if (appmenu && wf->wid == appmenu->wid && wf->focused == 0) {
|
|
/* Close */
|
|
yutani_close(yctx, appmenu);
|
|
appmenu = NULL;
|
|
free(bctx->backbuffer);
|
|
free(bctx);
|
|
}
|
|
|
|
}
|
|
|
|
static void redraw_alttab(void) {
|
|
/* Draw the background, right now just a dark semi-transparent box */
|
|
draw_fill(actx, ALTTAB_BACKGROUND);
|
|
|
|
if (ads_by_z[new_focused]) {
|
|
struct window_ad * ad = ads_by_z[new_focused];
|
|
|
|
sprite_t * icon = icon_get(ad->icon);
|
|
|
|
/* Draw it, scaled if necessary */
|
|
if (icon->width == 24) {
|
|
draw_sprite(actx, icon, center_x_a(24), ALTTAB_OFFSET);
|
|
} else {
|
|
draw_sprite_scaled(actx, icon, center_x_a(24), ALTTAB_OFFSET, 24, 24);
|
|
}
|
|
|
|
set_font_face(FONT_SANS_SERIF_BOLD);
|
|
set_font_size(14);
|
|
int t = draw_string_width(ad->name);
|
|
|
|
draw_string(actx, center_x_a(t), 24+ALTTAB_OFFSET+16, rgb(255,255,255), ad->name);
|
|
}
|
|
|
|
flip(actx);
|
|
yutani_flip(yctx, alttab);
|
|
}
|
|
|
|
static void handle_key_event(struct yutani_msg_key_event * ke) {
|
|
if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&
|
|
(ke->event.modifiers & KEY_MOD_LEFT_ALT) &&
|
|
(ke->event.keycode == 't') &&
|
|
(ke->event.action == KEY_ACTION_DOWN)) {
|
|
|
|
launch_application("terminal");
|
|
}
|
|
|
|
if ((was_tabbing) && (ke->event.keycode == 0) &&
|
|
(ke->event.modifiers == 0) && (ke->event.action == KEY_ACTION_UP)) {
|
|
|
|
fprintf(stderr, "[panel] Stopping focus new_focused = %d\n", new_focused);
|
|
|
|
struct window_ad * ad = ads_by_z[new_focused];
|
|
|
|
if (!ad) return;
|
|
|
|
yutani_focus_window(yctx, ad->wid);
|
|
was_tabbing = 0;
|
|
new_focused = -1;
|
|
|
|
free(actx->backbuffer);
|
|
free(actx);
|
|
|
|
yutani_close(yctx, alttab);
|
|
|
|
return;
|
|
}
|
|
|
|
if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) &&
|
|
(ke->event.keycode == '\t') &&
|
|
(ke->event.action == KEY_ACTION_DOWN)) {
|
|
|
|
int direction = (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) ? 1 : -1;
|
|
|
|
if (window_list->length < 1) return;
|
|
|
|
if (was_tabbing) {
|
|
new_focused = new_focused + direction;
|
|
} else {
|
|
new_focused = active_window + direction;
|
|
/* Create tab window */
|
|
alttab = yutani_window_create(yctx, ALTTAB_WIDTH, ALTTAB_HEIGHT);
|
|
|
|
/* And move it to the top layer */
|
|
yutani_window_move(yctx, alttab, center_x(ALTTAB_WIDTH), center_y(ALTTAB_HEIGHT));
|
|
|
|
/* Initialize graphics context against the window */
|
|
actx = init_graphics_yutani_double_buffer(alttab);
|
|
}
|
|
|
|
if (new_focused < 0) {
|
|
new_focused = 0;
|
|
for (int i = 0; i < MAX_WINDOW_COUNT; i++) {
|
|
if (ads_by_z[i+1] == NULL) {
|
|
new_focused = i;
|
|
break;
|
|
}
|
|
}
|
|
} else if (ads_by_z[new_focused] == NULL) {
|
|
new_focused = 0;
|
|
}
|
|
|
|
was_tabbing = 1;
|
|
|
|
redraw_alttab();
|
|
}
|
|
|
|
}
|
|
|
|
/* Default search paths for icons, in order of preference */
|
|
static char * icon_directories[] = {
|
|
"/usr/share/icons/24",
|
|
"/usr/share/icons/48",
|
|
"/usr/share/icons",
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* Get an icon from the cache, or if it is not in the cache,
|
|
* load it - or cache the generic icon if we can not find an
|
|
* appropriate matching icon on the filesystem.
|
|
*/
|
|
static sprite_t * icon_get(char * name) {
|
|
|
|
if (!strcmp(name,"")) {
|
|
/* If a window doesn't have an icon set, return the generic icon */
|
|
return hashmap_get(icon_cache, "generic");
|
|
}
|
|
|
|
/* Check the icon cache */
|
|
sprite_t * icon = hashmap_get(icon_cache, name);
|
|
|
|
if (!icon) {
|
|
/* We don't have an icon cached for this identifier, try search */
|
|
int i = 0;
|
|
char path[100];
|
|
while (icon_directories[i]) {
|
|
/* Check each path... */
|
|
sprintf(path, "%s/%s.png", icon_directories[i], name);
|
|
fprintf(stderr, "Checking %s for icon\n", path);
|
|
if (access(path, R_OK) == 0) {
|
|
/* And if we find one, cache it */
|
|
icon = malloc(sizeof(sprite_t));
|
|
load_sprite_png(icon, path);
|
|
hashmap_set(icon_cache, name, icon);
|
|
return icon;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* If we've exhausted our search paths, just return the generic icon */
|
|
icon = hashmap_get(icon_cache, "generic");
|
|
hashmap_set(icon_cache, name, icon);
|
|
}
|
|
|
|
/* We have an icon, return it */
|
|
return icon;
|
|
}
|
|
|
|
static void read_applications(FILE * f) {
|
|
if (!f) {
|
|
/* No applications? */
|
|
applications = malloc(sizeof(application_t));
|
|
applications[0].icon = NULL;
|
|
return;
|
|
}
|
|
char line[2048];
|
|
|
|
int count = 0;
|
|
|
|
while (fgets(line, 2048, f) != NULL) {
|
|
if (strstr(line, "#") == line) continue;
|
|
|
|
char * icon = line;
|
|
char * name = strstr(icon,","); name++;
|
|
char * title = strstr(name, ","); title++;
|
|
|
|
if (!name || !title) {
|
|
continue; /* invalid */
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
fseek(f, 0, SEEK_SET);
|
|
applications = malloc(sizeof(application_t) * (count + 1));
|
|
memset(&applications[count], 0x00, sizeof(application_t));
|
|
|
|
appmenu_items_count = count;
|
|
|
|
int i = 0;
|
|
while (fgets(line, 2048, f) != NULL) {
|
|
if (strstr(line, "#") == line) continue;
|
|
|
|
char * icon = line;
|
|
char * name = strstr(icon,","); name++;
|
|
char * title = strstr(name, ","); title++;
|
|
|
|
if (!name || !title) {
|
|
continue; /* invalid */
|
|
}
|
|
|
|
name[-1] = '\0';
|
|
title[-1] = '\0';
|
|
|
|
char * tmp = strstr(title, "\n");
|
|
if (tmp) *tmp = '\0';
|
|
|
|
fprintf(stderr, "Icon: %s %s %s\n", icon, name, title);
|
|
|
|
applications[i].icon = strdup(icon);
|
|
applications[i].appname = strdup(name);
|
|
applications[i].title = strdup(title);
|
|
|
|
i++;
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
static void redraw_appmenu(int item) {
|
|
draw_fill(bctx, APPMENU_BACKGROUND);
|
|
if (item != -1) {
|
|
for (int i = 0; i < APPMENU_ITEM_HEIGHT; ++i) {
|
|
draw_line(bctx, 0, APPMENU_WIDTH, APPMENU_ITEM_HEIGHT * item + i, APPMENU_ITEM_HEIGHT * item + i, APPMENU_HIGHLIGHT);
|
|
}
|
|
}
|
|
draw_line(bctx, APPMENU_WIDTH, APPMENU_WIDTH, 0, APPMENU_ITEM_HEIGHT * appmenu_items_count, rgb(0,0,0));
|
|
draw_line(bctx, 0, APPMENU_WIDTH, APPMENU_ITEM_HEIGHT * appmenu_items_count, APPMENU_ITEM_HEIGHT * appmenu_items_count, rgb(0,0,0));
|
|
spin_lock(&drawlock);
|
|
for (int i = 0; i < appmenu_items_count; ++i) {
|
|
set_font_face(FONT_SANS_SERIF);
|
|
set_font_size(12);
|
|
|
|
sprite_t * icon = icon_get(applications[i].icon);
|
|
|
|
/* Draw it, scaled if necessary */
|
|
if (icon->width == 24) {
|
|
draw_sprite(bctx, icon, 2, APPMENU_ITEM_HEIGHT * i);
|
|
} else {
|
|
draw_sprite_scaled(bctx, icon, 2, APPMENU_ITEM_HEIGHT * i, 24, 24);
|
|
}
|
|
|
|
uint32_t color = (i == item) ? rgb(255,255,255) : rgb(0,0,0);
|
|
|
|
draw_string(bctx, 30, 18 + APPMENU_ITEM_HEIGHT * i, color, applications[i].title);
|
|
}
|
|
spin_unlock(&drawlock);
|
|
flip(bctx);
|
|
yutani_flip(yctx, appmenu);
|
|
}
|
|
|
|
static void redraw(void) {
|
|
spin_lock(&drawlock);
|
|
|
|
struct timeval now;
|
|
int last = 0;
|
|
struct tm * timeinfo;
|
|
char buffer[80];
|
|
|
|
uint32_t txt_color = TEXT_COLOR;
|
|
int t = 0;
|
|
|
|
/* Redraw the background */
|
|
memcpy(ctx->backbuffer, bg_blob, bg_size);
|
|
|
|
/* Get the current time for the clock */
|
|
gettimeofday(&now, NULL);
|
|
last = now.tv_sec;
|
|
timeinfo = localtime((time_t *)&now.tv_sec);
|
|
|
|
/* Hours : Minutes : Seconds */
|
|
strftime(buffer, 80, "%H:%M:%S", timeinfo);
|
|
set_font_face(FONT_SANS_SERIF_BOLD);
|
|
set_font_size(16);
|
|
draw_string(ctx, width - TIME_LEFT, 19, txt_color, buffer);
|
|
|
|
/* Day-of-week */
|
|
strftime(buffer, 80, "%A", timeinfo);
|
|
set_font_face(FONT_SANS_SERIF);
|
|
set_font_size(9);
|
|
t = draw_string_width(buffer);
|
|
t = (DATE_WIDTH - t) / 2;
|
|
draw_string(ctx, width - TIME_LEFT - DATE_WIDTH + t, 11, txt_color, buffer);
|
|
|
|
/* Month Day */
|
|
strftime(buffer, 80, "%h %e", timeinfo);
|
|
set_font_face(FONT_SANS_SERIF_BOLD);
|
|
set_font_size(9);
|
|
t = draw_string_width(buffer);
|
|
t = (DATE_WIDTH - t) / 2;
|
|
draw_string(ctx, width - TIME_LEFT - DATE_WIDTH + t, 21, txt_color, buffer);
|
|
|
|
/* TODO: Future applications menu */
|
|
set_font_face(FONT_SANS_SERIF_BOLD);
|
|
set_font_size(14);
|
|
draw_string(ctx, 10, 18, appmenu ? HILIGHT_COLOR : txt_color, "Applications");
|
|
|
|
/* Now draw the window list */
|
|
int i = 0, j = 0;
|
|
spin_lock(&lock);
|
|
if (window_list) {
|
|
foreach(node, window_list) {
|
|
struct window_ad * ad = node->value;
|
|
char * s = "";
|
|
char tmp_title[50];
|
|
int w = ICON_SIZE + ICON_PADDING * 2;
|
|
|
|
if (APP_OFFSET + i + w > LEFT_BOUND) {
|
|
break;
|
|
}
|
|
|
|
set_font_face(FONT_SANS_SERIF);
|
|
set_font_size(13);
|
|
|
|
if (title_width > MIN_TEXT_WIDTH) {
|
|
|
|
memset(tmp_title, 0x0, 50);
|
|
int t_l = strlen(ad->name);
|
|
if (t_l > 45) {
|
|
t_l = 45;
|
|
}
|
|
strncpy(tmp_title, ad->name, t_l);
|
|
|
|
while (draw_string_width(tmp_title) > title_width - ICON_PADDING) {
|
|
t_l--;
|
|
tmp_title[t_l] = '.';
|
|
tmp_title[t_l+1] = '.';
|
|
tmp_title[t_l+2] = '.';
|
|
tmp_title[t_l+3] = '\0';
|
|
}
|
|
w += title_width;
|
|
|
|
s = tmp_title;
|
|
}
|
|
|
|
/* Hilight the focused window */
|
|
if (ad->flags & 1) {
|
|
/* This is the focused window */
|
|
for (int y = 0; y < GRADIENT_HEIGHT; ++y) {
|
|
for (int x = APP_OFFSET + i; x < APP_OFFSET + i + w; ++x) {
|
|
GFX(ctx, x, y) = alpha_blend_rgba(GFX(ctx, x, y), GRADIENT_AT(y));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Get the icon for this window */
|
|
sprite_t * icon = icon_get(ad->icon);
|
|
|
|
/* Draw it, scaled if necessary */
|
|
if (icon->width == ICON_SIZE) {
|
|
draw_sprite(ctx, icon, APP_OFFSET + i + ICON_PADDING, 0);
|
|
} else {
|
|
draw_sprite_scaled(ctx, icon, APP_OFFSET + i + ICON_PADDING, 0, ICON_SIZE, ICON_SIZE);
|
|
}
|
|
|
|
if (title_width > MIN_TEXT_WIDTH) {
|
|
/* Then draw the window title, with appropriate color */
|
|
if (j == focused_app) {
|
|
/* Current hilighted - title should be a light blue */
|
|
draw_string(ctx, APP_OFFSET + i + ICON_SIZE + ICON_PADDING * 2, TEXT_Y_OFFSET, HILIGHT_COLOR, s);
|
|
} else {
|
|
if (ad->flags & 1) {
|
|
/* Top window should be white */
|
|
draw_string(ctx, APP_OFFSET + i + ICON_SIZE + ICON_PADDING * 2, TEXT_Y_OFFSET, FOCUS_COLOR, s);
|
|
} else {
|
|
/* Otherwise, off white */
|
|
draw_string(ctx, APP_OFFSET + i + ICON_SIZE + ICON_PADDING * 2, TEXT_Y_OFFSET, txt_color, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* XXX This keeps track of how far left each window list item is
|
|
* so we can map clicks up in the mouse callback. */
|
|
if (j < MAX_WINDOW_COUNT) {
|
|
ads_by_l[j]->left = APP_OFFSET + i;
|
|
}
|
|
j++;
|
|
i += w;
|
|
}
|
|
}
|
|
spin_unlock(&lock);
|
|
|
|
/* Draw the logout button; XXX This should probably have some sort of focus hilight */
|
|
draw_sprite(ctx, sprite_logout, width - 23, 1); /* Logout button */
|
|
|
|
/* Flip */
|
|
flip(ctx);
|
|
yutani_flip(yctx, panel);
|
|
|
|
spin_unlock(&drawlock);
|
|
}
|
|
|
|
static void update_window_list(void) {
|
|
yutani_query_windows(yctx);
|
|
|
|
list_t * new_window_list = list_create();
|
|
|
|
int i = 0;
|
|
while (1) {
|
|
/* We wait for a series of WINDOW_ADVERTISE messsages */
|
|
yutani_msg_t * m = yutani_wait_for(yctx, YUTANI_MSG_WINDOW_ADVERTISE);
|
|
struct yutani_msg_window_advertise * wa = (void*)m->data;
|
|
|
|
if (wa->size == 0) {
|
|
/* A sentinal at the end will have a size of 0 */
|
|
free(m);
|
|
break;
|
|
}
|
|
|
|
/* Store each window advertisement */
|
|
struct window_ad * ad = malloc(sizeof(struct window_ad));
|
|
|
|
char * s = malloc(wa->size);
|
|
memcpy(s, wa->strings, wa->size);
|
|
ad->name = &s[wa->offsets[0]];
|
|
ad->icon = &s[wa->offsets[1]];
|
|
ad->strings = s;
|
|
ad->flags = wa->flags;
|
|
ad->wid = wa->wid;
|
|
|
|
ads_by_z[i] = ad;
|
|
i++;
|
|
ads_by_z[i] = NULL;
|
|
|
|
node_t * next = NULL;
|
|
|
|
/* And insert it, ordered by wid, into the window list */
|
|
foreach(node, new_window_list) {
|
|
struct window_ad * n = node->value;
|
|
|
|
if (n->wid > ad->wid) {
|
|
next = node;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (next) {
|
|
list_insert_before(new_window_list, next, ad);
|
|
} else {
|
|
list_insert(new_window_list, ad);
|
|
}
|
|
free(m);
|
|
}
|
|
active_window = i-1;
|
|
|
|
i = 0;
|
|
/*
|
|
* Update each of the wid entries in our array so we can map
|
|
* clicks to window focus events for each window
|
|
*/
|
|
foreach(node, new_window_list) {
|
|
struct window_ad * ad = node->value;
|
|
if (i < MAX_WINDOW_COUNT) {
|
|
ads_by_l[i] = ad;
|
|
ads_by_l[i+1] = NULL;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* Then free up the old list and replace it with the new list */
|
|
spin_lock(&lock);
|
|
|
|
if (new_window_list->length) {
|
|
int tmp = LEFT_BOUND;
|
|
tmp -= APP_OFFSET;
|
|
tmp -= new_window_list->length * (ICON_SIZE + ICON_PADDING * 2);
|
|
if (tmp < 0) {
|
|
title_width = 0;
|
|
} else {
|
|
title_width = tmp / new_window_list->length;
|
|
if (title_width > MAX_TEXT_WIDTH) {
|
|
title_width = MAX_TEXT_WIDTH;
|
|
}
|
|
if (title_width < MIN_TEXT_WIDTH) {
|
|
title_width = 0;
|
|
}
|
|
}
|
|
} else {
|
|
title_width = 0;
|
|
}
|
|
if (window_list) {
|
|
foreach(node, window_list) {
|
|
struct window_ad * ad = (void*)node->value;
|
|
free(ad->strings);
|
|
free(ad);
|
|
}
|
|
list_free(window_list);
|
|
free(window_list);
|
|
}
|
|
window_list = new_window_list;
|
|
spin_unlock(&lock);
|
|
|
|
/* And redraw the panel */
|
|
redraw();
|
|
}
|
|
|
|
static void * clock_thread(void * garbage) {
|
|
/*
|
|
* This thread just calls redraw every so often so the clock
|
|
* continues to tick. We really shouldn't need this,
|
|
* but our current environment doens't provide timeouts,
|
|
* so we can't just bail out of a yutani poll and redraw...
|
|
*/
|
|
while (_continue) {
|
|
waitpid(-1, NULL, WNOHANG);
|
|
redraw();
|
|
usleep(500000);
|
|
}
|
|
}
|
|
|
|
int main (int argc, char ** argv) {
|
|
/* Connect to window server */
|
|
yctx = yutani_init();
|
|
|
|
/* For convenience, store the display size */
|
|
width = yctx->display_width;
|
|
height = yctx->display_height;
|
|
|
|
/* Initialize fonts. */
|
|
init_shmemfonts();
|
|
set_font_size(14);
|
|
|
|
/* Create the panel window */
|
|
panel = yutani_window_create(yctx, width, PANEL_HEIGHT);
|
|
|
|
/* And move it to the top layer */
|
|
yutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP);
|
|
|
|
/* Initialize graphics context against the window */
|
|
ctx = init_graphics_yutani_double_buffer(panel);
|
|
|
|
/* Clear it out (the compositor should initialize it cleared anyway */
|
|
draw_fill(ctx, rgba(0,0,0,0));
|
|
flip(ctx);
|
|
yutani_flip(yctx, panel);
|
|
|
|
/* Initialize hashmap for icon cache */
|
|
icon_cache = hashmap_create(10);
|
|
|
|
{
|
|
char f_name[256];
|
|
sprintf(f_name, "%s/.menu.desktop", getenv("HOME"));
|
|
FILE * f = fopen(f_name, "r");
|
|
if (!f) {
|
|
f = fopen("/etc/menu.desktop", "r");
|
|
}
|
|
read_applications(f);
|
|
}
|
|
|
|
/* Preload some common icons */
|
|
{ /* Generic fallback icon */
|
|
sprite_t * app_icon = malloc(sizeof(sprite_t));
|
|
load_sprite_png(app_icon, "/usr/share/icons/24/applications-generic.png");
|
|
hashmap_set(icon_cache, "generic", app_icon);
|
|
}
|
|
|
|
{ /* Terminal */
|
|
sprite_t * app_icon = malloc(sizeof(sprite_t));
|
|
load_sprite_png(app_icon, "/usr/share/icons/24/utilities-terminal.png");
|
|
hashmap_set(icon_cache, "utilities-terminal", app_icon);
|
|
}
|
|
|
|
{ /* Draw! icon */
|
|
sprite_t * app_icon = malloc(sizeof(sprite_t));
|
|
load_sprite_png(app_icon, "/usr/share/icons/24/applications-painting.png");
|
|
hashmap_set(icon_cache, "applications-painting", app_icon);
|
|
}
|
|
|
|
/* Load textures for the background and logout button */
|
|
sprite_panel = malloc(sizeof(sprite_t));
|
|
sprite_logout = malloc(sizeof(sprite_t));
|
|
|
|
load_sprite_png(sprite_panel, "/usr/share/panel.png");
|
|
load_sprite_png(sprite_logout, "/usr/share/icons/panel-shutdown.png");
|
|
|
|
/* Draw the background */
|
|
for (uint32_t i = 0; i < width; i += sprite_panel->width) {
|
|
draw_sprite(ctx, sprite_panel, i, 0);
|
|
}
|
|
|
|
/* Copy the prerendered background so we can redraw it quickly */
|
|
bg_size = panel->width * panel->height * sizeof(uint32_t);
|
|
bg_blob = malloc(bg_size);
|
|
memcpy(bg_blob, ctx->backbuffer, bg_size);
|
|
|
|
/* Catch SIGINT */
|
|
signal(SIGINT, sig_int);
|
|
|
|
/* Start clock thread XXX need timeouts in yutani calls */
|
|
pthread_t _clock_thread;
|
|
pthread_create(&_clock_thread, NULL, clock_thread, NULL);
|
|
|
|
/* Subscribe to window updates */
|
|
yutani_subscribe_windows(yctx);
|
|
|
|
/* Ask compositor for window list */
|
|
update_window_list();
|
|
|
|
/* Key bindings */
|
|
|
|
/* Launch terminal */
|
|
yutani_key_bind(yctx, 't', KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);
|
|
|
|
/* Alt+Tab */
|
|
yutani_key_bind(yctx, '\t', KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);
|
|
yutani_key_bind(yctx, '\t', KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT, YUTANI_BIND_STEAL);
|
|
|
|
/* This lets us receive all just-modifier key releases */
|
|
yutani_key_bind(yctx, 0, 0, YUTANI_BIND_PASSTHROUGH);
|
|
|
|
while (_continue) {
|
|
/* Respond to Yutani events */
|
|
yutani_msg_t * m = yutani_poll(yctx);
|
|
if (m) {
|
|
switch (m->type) {
|
|
/* New window information is available */
|
|
case YUTANI_MSG_NOTIFY:
|
|
update_window_list();
|
|
break;
|
|
/* Mouse movement / click */
|
|
case YUTANI_MSG_WINDOW_MOUSE_EVENT:
|
|
panel_check_click((struct yutani_msg_window_mouse_event *)m->data);
|
|
break;
|
|
case YUTANI_MSG_KEY_EVENT:
|
|
handle_key_event((struct yutani_msg_key_event *)m->data);
|
|
break;
|
|
case YUTANI_MSG_WINDOW_FOCUS_CHANGE:
|
|
handle_focus_event((struct yutani_msg_window_focus_change *)m->data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
free(m);
|
|
}
|
|
}
|
|
|
|
/* Close the panel window */
|
|
yutani_close(yctx, panel);
|
|
|
|
/* Stop notifying us of window changes */
|
|
yutani_unsubscribe_windows(yctx);
|
|
|
|
return 0;
|
|
}
|