toaruos/apps/panel.c

963 lines
26 KiB
C

/**
* @file apps/panel.c
* @brief Panel with widgets. Main desktop interface.
*
* Provides the panel shown at the top of the screen, which
* presents application windows, useful widgets, and a menu
* for launching new apps.
*
* Also provides Alt-Tab app switching and a few other goodies.
*
* @copyright
* 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-2021 K. Lange
*/
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/fswait.h>
#include <sys/shm.h>
/* auto-dep: export-dynamic */
#include <dlfcn.h>
#include <toaru/yutani.h>
#include <toaru/yutani-internal.h>
#include <toaru/graphics.h>
#include <toaru/hashmap.h>
#include <toaru/icon_cache.h>
#include <toaru/menu.h>
#include <toaru/text.h>
/* Several theming defines are in here */
#include <toaru/panel.h>
/* These are local to the core panel, so we don't need to put them in the header */
#define ALTTAB_WIDTH 250
#define ALTTAB_HEIGHT 200
#define ALTTAB_BACKGROUND premultiply(rgba(0,0,0,150))
#define ALTTAB_OFFSET 10
#define ALTTAB_WIN_SIZE 140
#define ALTF2_WIDTH 400
#define ALTF2_HEIGHT 200
/* How many windows we can support in the advertisement lift before truncating it */
#define MAX_WINDOW_COUNT 100
/* Height of the panel window */
#define PANEL_HEIGHT 27
/* How far down dropdown menus should be shown */
#define DROPDOWN_OFFSET PANEL_HEIGHT
/* How much padding should be assured on the left and right of the screen for menus */
#define MENU_PAD 4
static struct PanelContext panel_context;
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 * a2ctx = NULL;
static yutani_window_t * alt_f2 = NULL;
static size_t bg_size;
static char * bg_blob;
/* External interface for widgets */
list_t * window_list = NULL;
yutani_t * yctx;
int width;
int height;
list_t * widgets_enabled = NULL;
/* Windows, indexed by z-order */
struct window_ad * ads_by_z[MAX_WINDOW_COUNT+1] = {NULL};
int active_window = -1;
static int was_tabbing = 0;
static int new_focused = -1;
static void widgets_layout(void);
static int _close_enough(struct yutani_msg_window_mouse_event * me) {
if (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) {
return 1;
}
return 0;
}
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_x_a2(int x) {
return (ALTF2_WIDTH - x) / 2;
}
static volatile int _continue = 1;
static void toggle_hide_panel(void) {
static int panel_hidden = 0;
if (panel_hidden) {
/* Unhide the panel */
for (int i = PANEL_HEIGHT-1; i >= 0; i--) {
yutani_window_move(yctx, panel, 0, -i);
usleep(3000);
}
panel_hidden = 0;
} else {
/* Hide the panel */
for (int i = 1; i <= PANEL_HEIGHT-1; i++) {
yutani_window_move(yctx, panel, 0, -i);
usleep(3000);
}
panel_hidden = 1;
}
}
/* 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;
signal(SIGINT, sig_int);
}
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);
}
}
/* Callback for mouse events */
static void panel_check_click(struct yutani_msg_window_mouse_event * evt) {
static struct PanelWidget * old_target = NULL;
if (evt->wid == panel->wid) {
/* Figure out what widget this belongs to */
struct PanelWidget * target = NULL;
if (evt->new_y >= 0 && evt->new_y < PANEL_HEIGHT) {
foreach(widget_node, widgets_enabled) {
struct PanelWidget * widget = widget_node->value;
if (evt->new_x >= widget->left && evt->new_x < widget->left + widget->width) {
target = widget;
break;
}
}
}
int needs_redraw = 0;
if (evt->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(evt)) {
if (target) needs_redraw |= target->click(target, evt);
} else if (evt->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {
if (target) needs_redraw |= target->right_click(target, evt);
} else if (evt->command == YUTANI_MOUSE_EVENT_MOVE || evt->command == YUTANI_MOUSE_EVENT_ENTER) {
if (old_target && target != old_target) needs_redraw |= old_target->leave(old_target, evt);
if (target && target != old_target) needs_redraw |= target->enter(target, evt);
if (target) needs_redraw |= target->move(target, evt);
old_target = target;
} else if (evt->command == YUTANI_MOUSE_EVENT_LEAVE) {
if (old_target) needs_redraw |= old_target->leave(old_target, evt);
old_target = NULL;
}
if (needs_redraw) redraw();
}
}
static char altf2_buffer[1024] = {0};
static unsigned int altf2_collected = 0;
static void close_altf2(void) {
free(a2ctx->backbuffer);
free(a2ctx);
altf2_buffer[0] = 0;
altf2_collected = 0;
yutani_close(yctx, alt_f2);
alt_f2 = NULL;
}
static void redraw_altf2(void) {
draw_fill(a2ctx, 0);
draw_rounded_rectangle(a2ctx,0,0, ALTF2_WIDTH, ALTF2_HEIGHT, 11, premultiply(rgba(120,120,120,150)));
draw_rounded_rectangle(a2ctx,1,1, ALTF2_WIDTH-2, ALTF2_HEIGHT-2, 10, ALTTAB_BACKGROUND);
tt_set_size(panel_context.font, 20);
int t = tt_string_width(panel_context.font, altf2_buffer);
tt_draw_string(a2ctx, panel_context.font, center_x_a2(t), 80, altf2_buffer, rgb(255,255,255));
flip(a2ctx);
yutani_flip(yctx, alt_f2);
}
static void redraw_alttab(void) {
if (!actx) return;
while (new_focused > -1 && !ads_by_z[new_focused]) {
new_focused--;
}
if (new_focused == -1) {
/* Stop tabbing */
was_tabbing = 0;
free(actx->backbuffer);
free(actx);
actx = NULL;
yutani_close(yctx, alttab);
return;
}
/* How many windows do we have? */
unsigned int window_count = 0;
while (ads_by_z[window_count]) window_count++;
#define ALTTAB_COLUMNS 5
/* How many rows should that be? */
int rows = (window_count - 1) / ALTTAB_COLUMNS + 1;
/* How many columns? */
int columns = (rows == 1) ? window_count : ALTTAB_COLUMNS;
/* How much padding on the last row? */
int last_row = (window_count % columns) ? ((ALTTAB_WIN_SIZE + 20) * (columns - (window_count % columns))) / 2 : 0;
/* Is the window the right size? */
unsigned int expected_width = columns * (ALTTAB_WIN_SIZE + 20) + 40;
unsigned int expected_height = rows * (ALTTAB_WIN_SIZE + 20) + 60;
if (alttab->width != expected_width || alttab->height != expected_height) {
yutani_window_resize(yctx, alttab, expected_width, expected_height);
return;
}
/* Draw the background, right now just a dark semi-transparent box */
draw_fill(actx, 0);
draw_rounded_rectangle(actx,0,0, alttab->width, alttab->height, 11, premultiply(rgba(120,120,120,150)));
draw_rounded_rectangle(actx,1,1, alttab->width-2, alttab->height-2, 10, ALTTAB_BACKGROUND);
for (unsigned int i = 0; i < window_count; ++i) {
if (!ads_by_z[i]) continue;
struct window_ad * ad = ads_by_z[i];
/* Figure out grid alignment for this element */
int pos_x = ((window_count - i - 1) % ALTTAB_COLUMNS) * (ALTTAB_WIN_SIZE + 20) + 20;
int pos_y = ((window_count - i - 1) / ALTTAB_COLUMNS) * (ALTTAB_WIN_SIZE + 20) + 20;
if ((window_count - i - 1) / ALTTAB_COLUMNS == (unsigned int)rows - 1) {
pos_x += last_row;
}
if (i == (unsigned int)new_focused) {
draw_rounded_rectangle(actx, pos_x, pos_y, ALTTAB_WIN_SIZE + 20, ALTTAB_WIN_SIZE + 20, 7, premultiply(rgba(170,170,170,150)));
}
/* try very hard to get a window texture */
char key[1024];
YUTANI_SHMKEY_EXP(yctx->server_ident, key, 1024, ad->bufid);
size_t size = 0;
uint32_t * buf = shm_obtain(key, &size);
if (buf && size >= ad->width * ad->height * 4) {
sprite_t tmp;
tmp.width = ad->width;
tmp.height = ad->height;
tmp.bitmap = buf;
int ox = 0;
int oy = 0;
int sw, sh;
if (tmp.width > tmp.height) {
sw = ALTTAB_WIN_SIZE;
sh = tmp.height * ALTTAB_WIN_SIZE / tmp.width;
oy = (ALTTAB_WIN_SIZE - sh) / 2;
} else {
sh = ALTTAB_WIN_SIZE;
sw = tmp.width * ALTTAB_WIN_SIZE / tmp.height;
ox = (ALTTAB_WIN_SIZE - sw) / 2;
}
draw_sprite_scaled(actx, &tmp,
pos_x + ox + 10,
pos_y + oy + 10,
sw, sh);
shm_release(key);
sprite_t * icon = icon_get_48(ad->icon);
draw_sprite(actx, icon, pos_x + 10 + ALTTAB_WIN_SIZE - 50, pos_y + 10 + ALTTAB_WIN_SIZE - 50);
} else {
sprite_t * icon = icon_get_48(ad->icon);
draw_sprite(actx, icon, pos_x + 10 + (ALTTAB_WIN_SIZE - 48) / 2, pos_y + 10 + (ALTTAB_WIN_SIZE - 48) / 2);
}
}
{
struct window_ad * ad = ads_by_z[new_focused];
int t;
char * title = tt_ellipsify(ad->name, 16, panel_context.font, alttab->width - 20, &t);
tt_set_size(panel_context.font, 16);
tt_draw_string(actx, panel_context.font, center_x_a(t), rows * (ALTTAB_WIN_SIZE + 20) + 44, title, rgb(255,255,255));
free(title);
}
flip(actx);
yutani_window_move(yctx, alttab, center_x(alttab->width), center_y(alttab->height));
yutani_flip(yctx, alttab);
}
static pthread_t _waiter_thread;
static void * logout_prompt_waiter(void * arg) {
if (system("showdialog \"Log Out\" /usr/share/icons/48/exit.png \"Are you sure you want to log out?\"") == 0) {
yutani_session_end(yctx);
_continue = 0;
}
return NULL;
}
void launch_application_menu(struct MenuEntry * self) {
struct MenuEntry_Normal * _self = (void *)self;
if (!strcmp((char *)_self->action,"log-out")) {
/* Spin off a thread for this */
pthread_create(&_waiter_thread, NULL, logout_prompt_waiter, NULL);
} else {
launch_application((char *)_self->action);
}
}
static void handle_key_event(struct yutani_msg_key_event * ke) {
/**
* Is the Alt+F2 command entry window open?
* Then we should capture all typing and use
* direct it to the 'input box'.
*/
if (alt_f2 && ke->wid == alt_f2->wid) {
if (ke->event.action == KEY_ACTION_DOWN) {
/* Escape = cancel */
if (ke->event.keycode == KEY_ESCAPE) {
close_altf2();
return;
}
/* Backspace */
if (ke->event.key == '\b') {
if (altf2_collected) {
altf2_buffer[altf2_collected-1] = '\0';
altf2_collected--;
redraw_altf2();
}
return;
}
/* Enter */
if (ke->event.key == '\n') {
/* execute */
launch_application(altf2_buffer);
close_altf2();
return;
}
/* Some other key */
if (!ke->event.key) {
return;
}
/* Try to add it */
if (altf2_collected < sizeof(altf2_buffer) - 1) {
altf2_buffer[altf2_collected] = ke->event.key;
altf2_collected++;
altf2_buffer[altf2_collected] = 0;
redraw_altf2();
}
}
}
/* Ctrl-Alt-T = Open a new terminal */
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("exec terminal");
return;
}
/* Ctrl-F11 = Toggle visibility of the panel */
if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&
(ke->event.keycode == KEY_F11) &&
(ke->event.action == KEY_ACTION_DOWN)) {
fprintf(stderr, "[panel] Toggling visibility.\n");
toggle_hide_panel();
return;
}
/* Alt-F2 = Show the command entry window */
if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) &&
(ke->event.keycode == KEY_F2) &&
(ke->event.action == KEY_ACTION_DOWN)) {
/* show menu */
if (!alt_f2) {
alt_f2 = yutani_window_create_flags(yctx, ALTF2_WIDTH, ALTF2_HEIGHT, YUTANI_WINDOW_FLAG_BLUR_BEHIND);
yutani_window_update_shape(yctx, alt_f2, 5);
yutani_window_move(yctx, alt_f2, center_x(ALTF2_WIDTH), center_y(ALTF2_HEIGHT));
a2ctx = init_graphics_yutani_double_buffer(alt_f2);
redraw_altf2();
}
}
/* Maybe a plugin wants to handle this key bind */
foreach(widget_node, widgets_enabled) {
struct PanelWidget * widget = widget_node->value;
widget->onkey(widget, ke);
}
/* Releasing Alt when the Alt-Tab switcher is visible */
if ((was_tabbing) && (ke->event.keycode == 0 || ke->event.keycode == KEY_LEFT_ALT) &&
(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);
actx = NULL;
yutani_close(yctx, alttab);
return;
}
/* Alt-Tab = Switch windows */
if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) &&
(ke->event.keycode == '\t') &&
(ke->event.action == KEY_ACTION_DOWN)) {
/* Alt-Tab and Alt-Shift-Tab should go in alternate directions */
int direction = (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) ? 1 : -1;
/* Are there no windows to switch? */
if (window_list->length < 1) return;
/* Figure out the new focused window */
if (was_tabbing) {
new_focused = new_focused + direction;
} else {
new_focused = active_window + direction;
/* Create tab window */
alttab = yutani_window_create_flags(yctx, ALTTAB_WIDTH, ALTTAB_HEIGHT,
YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_NO_ANIMATION | YUTANI_WINDOW_FLAG_BLUR_BEHIND);
yutani_window_update_shape(yctx, alttab, 5);
yutani_set_stack(yctx, alttab, YUTANI_ZORDER_OVERLAY);
/* Initialize graphics context against the window */
actx = init_graphics_yutani_double_buffer(alttab);
}
/* Handle wraparound */
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) {
if (ads_by_z[0]) {
new_focused = 0;
} else {
new_focused = -1;
}
}
was_tabbing = 1;
redraw_alttab();
}
}
void redraw(void) {
memcpy(ctx->backbuffer, bg_blob, bg_size);
foreach(widget_node, widgets_enabled) {
struct PanelWidget * widget = widget_node->value;
gfx_context_t * inner = init_graphics_subregion(ctx, widget->left, 0, widget->width, PANEL_HEIGHT);
widget->draw(widget, inner);
free(inner);
}
/* Flip */
flip(ctx);
yutani_flip(yctx, panel);
}
static void update_window_list(void) {
yutani_query_windows(yctx);
list_t * new_window_list = list_create();
ads_by_z[0] = NULL;
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[0];
ad->icon = &s[wa->icon];
ad->strings = s;
ad->flags = wa->flags;
ad->wid = wa->wid;
ad->bufid = wa->bufid;
ad->width = wa->width;
ad->height = wa->height;
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;
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;
/* And redraw the panel */
redraw();
}
static void redraw_panel_background(gfx_context_t * ctx, int width, int height) {
draw_fill(ctx, rgba(0,0,0,0xF2));
}
static void resize_finish(int xwidth, int xheight) {
yutani_window_resize_accept(yctx, panel, xwidth, xheight);
reinit_graphics_yutani(ctx, panel);
yutani_window_resize_done(yctx, panel);
width = xwidth;
redraw_panel_background(ctx, xwidth, xheight);
/* Copy the prerendered background so we can redraw it quickly */
bg_size = panel->width * panel->height * sizeof(uint32_t);
bg_blob = realloc(bg_blob, bg_size);
memcpy(bg_blob, ctx->backbuffer, bg_size);
widgets_layout();
update_window_list();
}
static void bind_keys(void) {
/* Cltr-Alt-T = launch terminal */
yutani_key_bind(yctx, 't', KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);
/* Alt+Tab = app switcher*/
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);
/* Ctrl-F11 = toggle panel visibility */
yutani_key_bind(yctx, KEY_F11, KEY_MOD_LEFT_CTRL, YUTANI_BIND_STEAL);
/* Alt+F2 = show app runner */
yutani_key_bind(yctx, KEY_F2, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);
/* This lets us receive all just-modifier key releases */
yutani_key_bind(yctx, KEY_LEFT_ALT, 0, YUTANI_BIND_PASSTHROUGH);
}
static void sig_usr2(int sig) {
yutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP);
yutani_flip(yctx, panel);
bind_keys();
signal(SIGUSR2, sig_usr2);
}
static int mouse_event_ignore(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {
return 0;
}
static int widget_enter_generic(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {
this->highlighted = 1; /* Highlighted */
return 1;
}
static int widget_leave_generic(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {
this->highlighted = 0; /* Not highlighted */
return 1;
}
void panel_highlight_widget(struct PanelWidget * this, gfx_context_t * ctx, int active) {
if (this->highlighted || active) {
draw_rounded_rectangle(ctx, 3, 3, ctx->width - 6, ctx->height - 6, 11, premultiply(rgba(120,120,120,active ? 180 : 150)));
}
}
static int widget_draw_generic(struct PanelWidget * this, gfx_context_t * ctx) {
draw_rounded_rectangle(
ctx, 0, 0, ctx->width, ctx->height, 7, premultiply(rgba(120,120,120,150)));
if (this->highlighted) {
draw_rounded_rectangle(
ctx, 1, 1, ctx->width-2, ctx->height-2, 6, premultiply(rgba(120,160,230,220)));
} else {
draw_rounded_rectangle(
ctx, 1, 1, ctx->width-2, ctx->height-2, 6, ALTTAB_BACKGROUND);
}
return 0;
}
static int widget_update_generic(struct PanelWidget * this, int * force_updates) {
return 0;
}
static int widget_onkey_generic(struct PanelWidget * this, struct yutani_msg_key_event * evt) {
return 0;
}
struct PanelWidget * widget_new(void) {
struct PanelWidget * out = calloc(1, sizeof(struct PanelWidget));
out->pctx = &panel_context;
out->draw = widget_draw_generic;
out->click = mouse_event_ignore; /* click_generic */
out->right_click = mouse_event_ignore; /* right_click_generic */
out->leave = widget_leave_generic;
out->enter = widget_enter_generic;
out->update = widget_update_generic;
out->onkey = widget_onkey_generic;
out->move = mouse_event_ignore; /* move_generic */
out->highlighted = 0;
out->fill = 0;
return out;
}
static void widgets_layout(void) {
int total_width = 0;
int flexible_widgets = 0;
foreach(node, widgets_enabled) {
struct PanelWidget * widget = node->value;
if (widget->fill) {
flexible_widgets++;
} else {
total_width += widget->width;
}
}
/* Now lay out the widgets */
int x = 0;
int available = width;
foreach(node, widgets_enabled) {
struct PanelWidget * widget = node->value;
widget->left = x;
if (widget->fill) {
widget->width = (available - total_width) / flexible_widgets;
}
x += widget->width;
}
}
static void update_periodic_widgets(int * force_updates) {
*force_updates = 0;
int needs_layout = 0;
foreach(widget_node, widgets_enabled) {
struct PanelWidget * widget = widget_node->value;
needs_layout |= widget->update(widget, force_updates);
}
if (needs_layout) widgets_layout();
redraw();
}
int panel_menu_show_at(struct MenuList * menu, int x) {
int mwidth, mheight, offset;
/* Calculate the expected size of the menu window. */
menu_calculate_dimensions(menu, &mheight, &mwidth);
if (x - mwidth / 2 < MENU_PAD) {
offset = MENU_PAD;
menu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_LEFT;
} else if (x + mwidth / 2 > width - MENU_PAD) {
offset = width - MENU_PAD - mwidth;
menu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_RIGHT;
} else {
offset = x - mwidth / 2;
menu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_CENTER;
}
menu->flags |= MENU_FLAG_TAIL_POSITION;
menu->tail_offset = x - offset;
/* Prepare the menu, which creates the window. */
menu_prepare(menu, yctx);
/* If we succeeded, move it to the final offset and display it */
if (menu->window) {
yutani_window_move_relative(yctx, menu->window, panel, offset, DROPDOWN_OFFSET);
yutani_flip(yctx,menu->window);
return 0;
}
return 1;
}
int panel_menu_show(struct PanelWidget * this, struct MenuList * menu) {
return panel_menu_show_at(menu, this->left + this->width / 2);
}
int main (int argc, char ** argv) {
if (argc < 2 || strcmp(argv[1],"--really")) {
fprintf(stderr,
"%s: Desktop environment panel / dock\n"
"\n"
" Renders the application menu, window list, widgets,\n"
" alt-tab window switcher, clock, etc.\n"
" You probably don't want to run this directly - it is\n"
" started automatically by the session manager.\n", argv[0]);
return 1;
}
/* Connect to window server */
yctx = yutani_init();
/* Shared fonts */
panel_context.font = tt_font_from_shm("sans-serif");
panel_context.font_bold = tt_font_from_shm("sans-serif.bold");
panel_context.font_mono = tt_font_from_shm("monospace");
panel_context.font_mono_bold = tt_font_from_shm("monospace.bold");
/* For convenience, store the display size */
width = yctx->display_width;
height = yctx->display_height;
panel_context.color_text_normal = rgb(230,230,230);
panel_context.color_text_hilighted = rgb(142,216,255);
panel_context.color_text_focused = rgb(255,255,255);
panel_context.color_icon_normal = rgb(230,230,230);
panel_context.color_special = rgb(93,163,236);
panel_context.font_size_default = 14;
panel_context.extra_widget_spacing = 12;
/* Create the panel window */
panel = yutani_window_create_flags(yctx, width, PANEL_HEIGHT, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_ALT_ANIMATION);
panel_context.basewindow = panel;
/* And move it to the top layer */
yutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP);
yutani_window_update_shape(yctx, panel, YUTANI_SHAPE_THRESHOLD_CLEAR);
/* Initialize graphics context against the window */
ctx = init_graphics_yutani_double_buffer(panel);
/* Draw the background */
redraw_panel_background(ctx, panel->width, panel->height);
/* 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);
signal(SIGUSR2, sig_usr2);
widgets_enabled = list_create();
/* Initialize requested widgets */
const char * widgets_to_load[] = {
"appmenu",
"windowlist",
"volume",
"network",
"weather",
"date",
"clock",
"logout",
NULL
};
for (const char ** widget = widgets_to_load; *widget; widget++) {
char lib_name[200];
char func_name[200];
snprintf(lib_name, 200, "libtoaru_panel_%s.so", *widget);
snprintf(func_name, 200, "widget_init_%s", *widget);
void * lib = dlopen(lib_name, 0);
if (!lib) {
fprintf(stderr, "panel: failed to load widget '%s'\n", *widget);
} else {
void (*widget_init)(void) = dlsym(lib, func_name);
if (!widget_init) {
fprintf(stderr, "panel: failed to initialize widget '%s'\n", *widget);
} else {
widget_init();
}
}
}
/* Lay out the widgets */
int force_updates = 0;
update_periodic_widgets(&force_updates);
widgets_layout();
/* Subscribe to window updates */
yutani_subscribe_windows(yctx);
/* Ask compositor for window list */
update_window_list();
/* Key bindings */
bind_keys();
time_t last_tick = 0;
int fds[1] = {fileno(yctx->sock)};
fprintf(stderr, "entering loop?\n");
while (_continue) {
int index = fswait2(1,fds,force_updates ? 50 : 200); /* ~20 fps? */
if (index == 0) {
/* Respond to Yutani events */
yutani_msg_t * m = yutani_poll(yctx);
while (m) {
menu_process_event(yctx, 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_WELCOME:
{
struct yutani_msg_welcome * mw = (void*)m->data;
width = mw->display_width;
height = mw->display_height;
yutani_window_resize(yctx, panel, mw->display_width, PANEL_HEIGHT);
}
break;
case YUTANI_MSG_RESIZE_OFFER:
{
struct yutani_msg_window_resize * wr = (void*)m->data;
if (wr->wid == panel->wid) {
resize_finish(wr->width, wr->height);
} else if (alttab && wr->wid == alttab->wid) {
yutani_window_resize_accept(yctx, alttab, wr->width, wr->height);
reinit_graphics_yutani(actx, alttab);
redraw_alttab();
yutani_window_resize_done(yctx, alttab);
}
}
break;
default:
break;
}
free(m);
m = yutani_poll_async(yctx);
}
}
struct timeval now;
gettimeofday(&now, NULL);
if (now.tv_sec != last_tick || force_updates) {
last_tick = now.tv_sec;
waitpid(-1, NULL, WNOHANG);
if (was_tabbing) {
redraw_alttab();
}
update_periodic_widgets(&force_updates);
}
}
/* Close the panel window */
yutani_close(yctx, panel);
/* Stop notifying us of window changes */
yutani_unsubscribe_windows(yctx);
return 0;
}