tint2/src/taskbar/task.c

792 lines
26 KiB
C

/**************************************************************************
*
* Tint2 : task
*
* Copyright (C) 2007 Pål Staurland (staura@gmail.com)
* Modified (C) 2008 thierry lorthiois (lorthiois@bbsoft.fr) from Omega distribution
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**************************************************************************/
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include "panel.h"
#include "server.h"
#include "task.h"
#include "taskbar.h"
#include "timer.h"
#include "tooltip.h"
#include "window.h"
timeout *urgent_timeout;
GSList *urgent_list;
void task_dump_geometry(void *obj, int indent);
int task_compute_desired_size(void *obj);
char *task_get_tooltip(void *obj)
{
Task *t = (Task *)obj;
return strdup(t->title);
}
Task *add_task(Window win)
{
if (!win)
return NULL;
if (window_is_hidden(win))
return NULL;
XSelectInput(server.display, win, PropertyChangeMask | StructureNotifyMask);
XFlush(server.display);
int monitor = 0;
if (num_panels > 1) {
monitor = get_window_monitor(win);
if (monitor >= num_panels)
monitor = 0;
}
// TODO why do we add the task only to the panel for the current monitor, without checking hide_task_diff_monitor?
Task task_template;
memset(&task_template, 0, sizeof(task_template));
snprintf(task_template.area.name, sizeof(task_template.area.name), "Task %d", (int)win);
task_template.area.has_mouse_over_effect = panel_config.mouse_effects;
task_template.area.has_mouse_press_effect = panel_config.mouse_effects;
task_template.area._dump_geometry = task_dump_geometry;
task_template.area._is_under_mouse = full_width_area_is_under_mouse;
task_template.win = win;
task_template.desktop = get_window_desktop(win);
task_template.area.panel = &panels[monitor];
task_template.current_state = window_is_iconified(win) ? TASK_ICONIFIED : TASK_NORMAL;
get_window_coordinates(win, &task_template.win_x, &task_template.win_y, &task_template.win_w, &task_template.win_h);
// allocate only one title and one icon
// even with task_on_all_desktop and with task_on_all_panel
task_template.title = NULL;
for (int k = 0; k < TASK_STATE_COUNT; ++k) {
task_template.icon[k] = NULL;
}
task_update_title(&task_template);
task_update_icon(&task_template);
snprintf(task_template.area.name,
sizeof(task_template.area.name),
"Task %d %s",
(int)win,
task_template.title ? task_template.title : "null");
// fprintf(stderr, "tint2: %s %d: win = %ld, task = %s\n", __FUNCTION__, __LINE__, win, task_template.title ?
// task_template.title : "??");
// fprintf(stderr, "tint2: new task %s win %u: desktop %d, monitor %d\n", new_task.title, win, new_task.desktop, monitor);
GPtrArray *task_buttons = g_ptr_array_new();
for (int j = 0; j < panels[monitor].num_desktops; j++) {
if (task_template.desktop != ALL_DESKTOPS && task_template.desktop != j)
continue;
Taskbar *taskbar = &panels[monitor].taskbar[j];
Task *task_instance = calloc(1, sizeof(Task));
memcpy(&task_instance->area, &panels[monitor].g_task.area, sizeof(Area));
task_instance->area.has_mouse_over_effect = panel_config.mouse_effects;
task_instance->area.has_mouse_press_effect = panel_config.mouse_effects;
task_instance->area._dump_geometry = task_dump_geometry;
task_instance->area._is_under_mouse = full_width_area_is_under_mouse;
task_instance->area._compute_desired_size = task_compute_desired_size;
task_instance->win = task_template.win;
task_instance->desktop = task_template.desktop;
task_instance->win_x = task_template.win_x;
task_instance->win_y = task_template.win_y;
task_instance->win_w = task_template.win_w;
task_instance->win_h = task_template.win_h;
task_instance->current_state = TASK_UNDEFINED; // to update the current state later in set_task_state...
if (task_instance->desktop == ALL_DESKTOPS && server.desktop != j) {
// fprintf(stderr, "tint2: %s %d: win = %ld hiding task: another desktop\n", __FUNCTION__, __LINE__, win);
task_instance->area.on_screen = always_show_all_desktop_tasks;
}
task_instance->title = task_template.title;
if (panels[monitor].g_task.tooltip_enabled)
task_instance->area._get_tooltip_text = task_get_tooltip;
for (int k = 0; k < TASK_STATE_COUNT; ++k) {
task_instance->icon[k] = task_template.icon[k];
task_instance->icon_hover[k] = task_template.icon_hover[k];
task_instance->icon_press[k] = task_template.icon_press[k];
}
task_instance->icon_width = task_template.icon_width;
task_instance->icon_height = task_template.icon_height;
add_area(&task_instance->area, &taskbar->area);
g_ptr_array_add(task_buttons, task_instance);
}
Window *key = calloc(1, sizeof(Window));
*key = task_template.win;
g_hash_table_insert(win_to_task, key, task_buttons);
set_task_state((Task *)g_ptr_array_index(task_buttons, 0), task_template.current_state);
sort_taskbar_for_win(win);
if (taskbar_mode == MULTI_DESKTOP) {
Panel *panel = (Panel *)task_template.area.panel;
panel->area.resize_needed = TRUE;
}
if (window_is_urgent(win)) {
add_urgent((Task *)g_ptr_array_index(task_buttons, 0));
}
if (hide_taskbar_if_empty)
update_all_taskbars_visibility();
return (Task *)g_ptr_array_index(task_buttons, 0);
}
void task_remove_icon(Task *task)
{
if (!task)
return;
for (int k = 0; k < TASK_STATE_COUNT; ++k) {
if (task->icon[k]) {
imlib_context_set_image(task->icon[k]);
imlib_free_image();
task->icon[k] = 0;
}
if (task->icon_hover[k]) {
imlib_context_set_image(task->icon_hover[k]);
imlib_free_image();
task->icon_hover[k] = 0;
}
if (task->icon_press[k]) {
imlib_context_set_image(task->icon_press[k]);
imlib_free_image();
task->icon_press[k] = 0;
}
}
}
void remove_task(Task *task)
{
if (!task)
return;
// fprintf(stderr, "tint2: %s %d: win = %ld, task = %s\n", __FUNCTION__, __LINE__, task->win, task->title ? task->title :
// "??");
if (taskbar_mode == MULTI_DESKTOP) {
Panel *panel = task->area.panel;
panel->area.resize_needed = 1;
}
Window win = task->win;
// free title and icon just for the first task
// even with task_on_all_desktop and with task_on_all_panel
// fprintf(stderr, "tint2: remove_task %s %d\n", task->title, task->desktop);
if (task->title)
free(task->title);
task_remove_icon(task);
GPtrArray *task_buttons = g_hash_table_lookup(win_to_task, &win);
for (int i = 0; i < task_buttons->len; ++i) {
Task *task2 = g_ptr_array_index(task_buttons, i);
if (task2 == active_task)
active_task = 0;
if (task2 == task_drag)
task_drag = 0;
if (g_slist_find(urgent_list, task2))
del_urgent(task2);
remove_area((Area *)task2);
free(task2);
}
g_hash_table_remove(win_to_task, &win);
if (hide_taskbar_if_empty)
update_all_taskbars_visibility();
}
gboolean task_update_title(Task *task)
{
Panel *panel = task->area.panel;
if (!panel->g_task.has_text && !panel->g_task.tooltip_enabled && taskbar_sort_method != TASKBAR_SORT_TITLE)
return FALSE;
char *name = server_get_property(task->win, server.atom._NET_WM_VISIBLE_NAME, server.atom.UTF8_STRING, 0);
if (!name || !strlen(name)) {
name = server_get_property(task->win, server.atom._NET_WM_NAME, server.atom.UTF8_STRING, 0);
if (!name || !strlen(name)) {
name = server_get_property(task->win, server.atom.WM_NAME, XA_STRING, 0);
}
}
char *title;
if (name && strlen(name)) {
title = strdup(name);
} else {
title = strdup("Untitled");
}
if (name)
XFree(name);
if (task->title) {
// check unecessary title change
if (strcmp(task->title, title) == 0) {
free(title);
return FALSE;
} else {
free(task->title);
}
}
task->title = title;
GPtrArray *task_buttons = get_task_buttons(task->win);
if (task_buttons) {
for (int i = 0; i < task_buttons->len; ++i) {
Task *task2 = g_ptr_array_index(task_buttons, i);
task2->title = task->title;
schedule_redraw(&task2->area);
}
}
return TRUE;
}
void task_update_icon(Task *task)
{
Panel *panel = task->area.panel;
if (!panel->g_task.has_icon)
return;
task_remove_icon(task);
Imlib_Image img = NULL;
if (!img) {
int len;
gulong *data = server_get_property(task->win, server.atom._NET_WM_ICON, XA_CARDINAL, &len);
if (data) {
if (len > 0) {
// get ARGB icon
int w, h;
gulong *tmp_data = get_best_icon(data, get_icon_count(data, len), len, &w, &h, panel->g_task.icon_size1);
if (tmp_data) {
DATA32 icon_data[w * h];
for (int j = 0; j < w * h; ++j)
icon_data[j] = tmp_data[j];
img = imlib_create_image_using_copied_data(w, h, icon_data);
if (0 && img)
fprintf(stderr,
"%s: Got %dx%d icon via _NET_WM_ICON for %s\n",
__FUNCTION__,
w,
h,
task->title ? task->title : "task");
}
}
XFree(data);
}
}
if (!img) {
XWMHints *hints = XGetWMHints(server.display, task->win);
if (hints) {
if (hints->flags & IconPixmapHint && hints->icon_pixmap != 0) {
// get width, height and depth for the pixmap
Window root;
int icon_x, icon_y;
unsigned border_width, bpp;
unsigned w, h;
XGetGeometry(server.display, hints->icon_pixmap, &root, &icon_x, &icon_y, &w, &h, &border_width, &bpp);
imlib_context_set_drawable(hints->icon_pixmap);
img = imlib_create_image_from_drawable(hints->icon_mask, 0, 0, w, h, 0);
if (0 && img)
fprintf(stderr,
"%s: Got %dx%d pixmap icon via WM_HINTS for %s\n",
__FUNCTION__,
w,
h,
task->title ? task->title : "task");
}
XFree(hints);
}
}
if (img == NULL) {
imlib_context_set_image(default_icon);
img = imlib_clone_image();
}
// transform icons
imlib_context_set_image(img);
imlib_image_set_has_alpha(1);
int w = imlib_image_get_width();
int h = imlib_image_get_height();
Imlib_Image orig_image =
imlib_create_cropped_scaled_image(0, 0, w, h, panel->g_task.icon_size1, panel->g_task.icon_size1);
imlib_free_image();
imlib_context_set_image(orig_image);
task->icon_width = imlib_image_get_width();
task->icon_height = imlib_image_get_height();
for (int k = 0; k < TASK_STATE_COUNT; ++k) {
imlib_context_set_image(orig_image);
task->icon[k] = imlib_clone_image();
imlib_context_set_image(task->icon[k]);
DATA32 *data32;
if (panel->g_task.alpha[k] != 100 || panel->g_task.saturation[k] != 0 || panel->g_task.brightness[k] != 0) {
data32 = imlib_image_get_data();
adjust_asb(data32,
task->icon_width,
task->icon_height,
panel->g_task.alpha[k] / 100.0,
panel->g_task.saturation[k] / 100.0,
panel->g_task.brightness[k] / 100.0);
imlib_image_put_back_data(data32);
}
if (panel_config.mouse_effects) {
task->icon_hover[k] = adjust_icon(task->icon[k],
panel_config.mouse_over_alpha,
panel_config.mouse_over_saturation,
panel_config.mouse_over_brightness);
task->icon_press[k] = adjust_icon(task->icon[k],
panel_config.mouse_pressed_alpha,
panel_config.mouse_pressed_saturation,
panel_config.mouse_pressed_brightness);
}
}
imlib_context_set_image(orig_image);
imlib_free_image();
GPtrArray *task_buttons = get_task_buttons(task->win);
if (task_buttons) {
for (int i = 0; i < task_buttons->len; ++i) {
Task *task2 = g_ptr_array_index(task_buttons, i);
task2->icon_width = task->icon_width;
task2->icon_height = task->icon_height;
for (int k = 0; k < TASK_STATE_COUNT; ++k) {
task2->icon[k] = task->icon[k];
task2->icon_hover[k] = task->icon_hover[k];
task2->icon_press[k] = task->icon_press[k];
}
schedule_redraw(&task2->area);
}
}
}
// TODO icons look too large when the panel is large
void draw_task_icon(Task *task, int text_width)
{
if (!task->icon[task->current_state])
return;
// Find pos
Panel *panel = (Panel *)task->area.panel;
if (panel->g_task.centered) {
if (panel->g_task.has_text)
task->_icon_x = (task->area.width - text_width - panel->g_task.icon_size1) / 2;
else
task->_icon_x = (task->area.width - panel->g_task.icon_size1) / 2;
} else {
task->_icon_x = left_border_width(&task->area) + task->area.paddingxlr;
}
// Render
Imlib_Image image;
// Render
if (panel_config.mouse_effects) {
if (task->area.mouse_state == MOUSE_OVER)
image = task->icon_hover[task->current_state];
else if (task->area.mouse_state == MOUSE_DOWN)
image = task->icon_press[task->current_state];
else
image = task->icon[task->current_state];
} else {
image = task->icon[task->current_state];
}
imlib_context_set_image(image);
task->_icon_y = (task->area.height - panel->g_task.icon_size1) / 2;
render_image(task->area.pix, task->_icon_x, task->_icon_y);
}
void draw_task(void *obj, cairo_t *c)
{
Task *task = (Task *)obj;
Panel *panel = (Panel *)task->area.panel;
task->_text_width = 0;
if (panel->g_task.has_text) {
PangoLayout *layout = pango_cairo_create_layout(c);
pango_layout_set_font_description(layout, panel->g_task.font_desc);
pango_layout_set_text(layout, task->title, -1);
pango_layout_set_width(layout, ((Taskbar *)task->area.parent)->text_width * PANGO_SCALE);
pango_layout_set_height(layout, panel->g_task.text_height * PANGO_SCALE);
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
if (panel->g_task.centered)
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
else
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
pango_layout_get_pixel_size(layout, &task->_text_width, &task->_text_height);
task->_text_posy = (panel->g_task.area.height - task->_text_height) / 2.0;
Color *config_text = &panel->g_task.font[task->current_state];
draw_text(layout, c, panel->g_task.text_posx, task->_text_posy, config_text, panel->font_shadow);
g_object_unref(layout);
}
if (panel->g_task.has_icon)
draw_task_icon(task, task->_text_width);
}
void task_dump_geometry(void *obj, int indent)
{
Task *task = (Task *)obj;
Panel *panel = (Panel *)task->area.panel;
fprintf(stderr,
"%*sText: x = %d, y = %d, w = %d, h = %d, align = %s, text = %s\n",
indent,
"",
(int)panel->g_task.text_posx,
(int)task->_text_posy,
task->_text_width,
task->_text_height,
panel->g_task.centered ? "center" : "left",
task->title);
fprintf(stderr,
"%*sIcon: x = %d, y = %d, w = h = %d\n",
indent,
"",
task->_icon_x,
task->_icon_y,
panel->g_task.icon_size1);
}
int task_compute_desired_size(void *obj)
{
Task *task = (Task *)obj;
Panel *panel = (Panel *)task->area.panel;
int size = panel_horizontal ? panel->g_task.maximum_width : panel->g_task.maximum_height;
return size;
}
void on_change_task(void *obj)
{
Task *task = (Task *)obj;
Panel *panel = (Panel *)task->area.panel;
if (task->area.on_screen) {
long value[] = {panel->posx + task->area.posx,
panel->posy + task->area.posy,
task->area.width,
task->area.height};
XChangeProperty(server.display,
task->win,
server.atom._NET_WM_ICON_GEOMETRY,
XA_CARDINAL,
32,
PropModeReplace,
(unsigned char *)value,
4);
} else {
XDeleteProperty(server.display, task->win, server.atom._NET_WM_ICON_GEOMETRY);
}
}
Task *find_active_task(Task *current_task)
{
if (active_task == NULL)
return current_task;
Taskbar *taskbar = (Taskbar *)current_task->area.parent;
GList *l0 = taskbar->area.children;
if (taskbarname_enabled)
l0 = l0->next;
for (; l0; l0 = l0->next) {
Task *task = (Task *)l0->data;
if (task->win == active_task->win)
return task;
}
return current_task;
}
Task *next_task(Task *task)
{
if (!task)
return NULL;
Taskbar *taskbar = task->area.parent;
GList *l0 = taskbar->area.children;
if (taskbarname_enabled)
l0 = l0->next;
GList *lfirst_task = l0;
for (; l0; l0 = l0->next) {
Task *task1 = l0->data;
if (task1 == task) {
l0 = l0->next ? l0->next : lfirst_task;
return l0->data;
}
}
return NULL;
}
Task *prev_task(Task *task)
{
if (!task)
return 0;
Taskbar *taskbar = task->area.parent;
Task *task2 = NULL;
GList *l0 = taskbar->area.children;
if (taskbarname_enabled)
l0 = l0->next;
GList *lfirst_task = l0;
for (; l0; l0 = l0->next) {
Task *task1 = l0->data;
if (task1 == task) {
if (l0 == lfirst_task) {
l0 = g_list_last(l0);
task2 = l0->data;
}
return task2;
}
task2 = task1;
}
return NULL;
}
void reset_active_task()
{
if (active_task) {
set_task_state(active_task, window_is_iconified(active_task->win) ? TASK_ICONIFIED : TASK_NORMAL);
active_task = NULL;
}
Window w1 = get_active_window();
// fprintf(stderr, "tint2: Change active task %ld\n", w1);
if (w1) {
if (!get_task_buttons(w1)) {
Window w2;
while (XGetTransientForHint(server.display, w1, &w2))
w1 = w2;
}
set_task_state((active_task = get_task(w1)), TASK_ACTIVE);
}
}
void set_task_state(Task *task, TaskState state)
{
if (!task || state == TASK_UNDEFINED || state >= TASK_STATE_COUNT)
return;
if (state == TASK_ACTIVE && task->current_state != state) {
clock_gettime(CLOCK_MONOTONIC, &task->last_activation_time);
if (taskbar_sort_method == TASKBAR_SORT_LRU || taskbar_sort_method == TASKBAR_SORT_MRU) {
GPtrArray *task_buttons = get_task_buttons(task->win);
if (task_buttons) {
for (int i = 0; i < task_buttons->len; ++i) {
Task *task1 = g_ptr_array_index(task_buttons, i);
Taskbar *taskbar = (Taskbar *)task1->area.parent;
sort_tasks(taskbar);
}
}
}
}
if (task->current_state != state || hide_task_diff_monitor || hide_task_diff_desktop) {
GPtrArray *task_buttons = get_task_buttons(task->win);
if (task_buttons) {
for (int i = 0; i < task_buttons->len; ++i) {
Task *task1 = g_ptr_array_index(task_buttons, i);
task1->current_state = state;
task1->area.bg = panels[0].g_task.background[state];
free_area_gradient_instances(&task1->area);
instantiate_area_gradients(&task1->area);
schedule_redraw(&task1->area);
if (state == TASK_ACTIVE && g_slist_find(urgent_list, task1))
del_urgent(task1);
gboolean hide = FALSE;
Taskbar *taskbar = (Taskbar *)task1->area.parent;
if (task->desktop == ALL_DESKTOPS && server.desktop != taskbar->desktop) {
// Hide ALL_DESKTOPS task on non-current desktop
hide = !always_show_all_desktop_tasks;
}
if (hide_inactive_tasks) {
// Show only the active task
if (state != TASK_ACTIVE) {
hide = TRUE;
}
}
if (hide_task_diff_desktop) {
if (taskbar->desktop != server.desktop)
hide = TRUE;
}
if (get_window_monitor(task->win) != ((Panel *)task->area.panel)->monitor &&
(hide_task_diff_monitor || num_panels > 1)) {
hide = TRUE;
}
if ((!hide) != task1->area.on_screen) {
task1->area.on_screen = !hide;
schedule_redraw(&task1->area);
Panel *p = (Panel *)task->area.panel;
task->area.resize_needed = TRUE;
p->taskbar->area.resize_needed = TRUE;
p->area.resize_needed = TRUE;
}
}
schedule_panel_redraw();
}
}
}
void blink_urgent(void *arg)
{
GSList *urgent_task = urgent_list;
while (urgent_task) {
Task *t = urgent_task->data;
if (t->urgent_tick < max_tick_urgent) {
if (t->urgent_tick++ % 2)
set_task_state(t, TASK_URGENT);
else
set_task_state(t, window_is_iconified(t->win) ? TASK_ICONIFIED : TASK_NORMAL);
}
urgent_task = urgent_task->next;
}
schedule_panel_redraw();
}
void add_urgent(Task *task)
{
if (!task)
return;
// some programs set urgency hint although they are active
if (active_task && active_task->win == task->win)
return;
task = get_task(task->win); // always add the first task for the task buttons (omnipresent windows)
task->urgent_tick = 0;
if (g_slist_find(urgent_list, task))
return;
// not yet in the list, so we have to add it
urgent_list = g_slist_prepend(urgent_list, task);
if (!urgent_timeout)
urgent_timeout = add_timeout(10, 1000, blink_urgent, 0, &urgent_timeout);
Panel *panel = task->area.panel;
if (panel->is_hidden)
autohide_show(panel);
}
void del_urgent(Task *task)
{
urgent_list = g_slist_remove(urgent_list, task);
if (!urgent_list) {
stop_timeout(urgent_timeout);
urgent_timeout = NULL;
}
}
void task_handle_mouse_event(Task *task, MouseAction action)
{
if (!task)
return;
switch (action) {
case NONE:
break;
case CLOSE:
close_window(task->win);
break;
case TOGGLE:
activate_window(task->win);
break;
case ICONIFY:
XIconifyWindow(server.display, task->win, server.screen);
break;
case TOGGLE_ICONIFY:
if (active_task && task->win == active_task->win)
XIconifyWindow(server.display, task->win, server.screen);
else
activate_window(task->win);
break;
case SHADE:
toggle_window_shade(task->win);
break;
case MAXIMIZE_RESTORE:
toggle_window_maximized(task->win);
break;
case MAXIMIZE:
toggle_window_maximized(task->win);
break;
case RESTORE:
toggle_window_maximized(task->win);
break;
case DESKTOP_LEFT: {
if (task->desktop == 0)
break;
int desktop = task->desktop - 1;
change_window_desktop(task->win, desktop);
if (desktop == server.desktop)
activate_window(task->win);
break;
}
case DESKTOP_RIGHT: {
if (task->desktop == server.num_desktops)
break;
int desktop = task->desktop + 1;
change_window_desktop(task->win, desktop);
if (desktop == server.desktop)
activate_window(task->win);
break;
}
case NEXT_TASK: {
Task *task1 = next_task(find_active_task(task));
activate_window(task1->win);
} break;
case PREV_TASK: {
Task *task1 = prev_task(find_active_task(task));
activate_window(task1->win);
}
}
}
void task_update_desktop(Task *task)
{
// fprintf(stderr, "tint2: %s %d:\n", __FUNCTION__, __LINE__);
Window win = task->win;
remove_task(task);
task = add_task(win);
reset_active_task();
schedule_panel_redraw();
}