/************************************************************************** * * 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 #include #include #include #include #include #include #include #include "panel.h" #include "server.h" #include "task.h" #include "taskbar.h" #include "timer.h" #include "tooltip.h" #include "window.h" Timer urgent_timer; GSList *urgent_list; void task_dump_geometry(void *obj, int indent); int task_compute_desired_size(void *obj); void task_refresh_thumbnail(Task *task); void task_get_content_color(void *obj, Color *color); char *task_get_tooltip(void *obj) { Task *t = (Task *)obj; return strdup(t->title); } cairo_surface_t *task_get_thumbnail(void *obj) { if (!panel_config.g_task.thumbnail_enabled) return NULL; Task *t = (Task *)obj; if (!t->thumbnail) task_refresh_thumbnail(t); taskbar_start_thumbnail_timer(THUMB_MODE_TOOLTIP_WINDOW); return t->thumbnail; } 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.area._get_content_color = task_get_content_color; 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"); // get application name // use res_class property of WM_CLASS as res_name is easily overridable by user XClassHint *classhint = XAllocClassHint(); if (classhint && XGetClassHint(server.display, win, classhint)) task_template.application = strdup(classhint->res_class); else task_template.application = strdup("Untitled"); if (classhint) { if (classhint->res_name) XFree(classhint->res_name); if (classhint->res_class) XFree(classhint->res_class); XFree(classhint); } GPtrArray *task_buttons = g_ptr_array_new(); for (int j = 0; j < panels[monitor].num_desktops; j++) { 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->area._get_content_color = task_get_content_color; 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... task_instance->title = task_template.title; task_instance->application = task_template.application; if (panels[monitor].g_task.tooltip_enabled) { task_instance->area._get_tooltip_text = task_get_tooltip; task_instance->area._get_tooltip_image = task_get_thumbnail; } task_instance->icon_color = task_template.icon_color; task_instance->icon_color_hover = task_template.icon_color_hover; task_instance->icon_color_press = task_template.icon_color_press; 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; if (taskbar_mode == MULTI_DESKTOP) { Panel *panel = task->area.panel; panel->area.resize_needed = 1; } Window win = task->win; // free title, icon and application name just for the first task // even with task_on_all_desktop and with task_on_all_panel if (task->title) free(task->title); if (task->thumbnail) cairo_surface_destroy(task->thumbnail); if (task->application) free(task->application); 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); if (g_tooltip.area == &task2->area) tooltip_hide(NULL); 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; } Imlib_Image task_get_icon(Window win, int icon_size) { Imlib_Image img = NULL; if (!img) { int len; gulong *data = server_get_property(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, icon_size); if (tmp_data) { int array_size = w * h; // imlib needs the array in DATA32 type // using malloc for the array to protect from stack overflow DATA32 *icon_data = (DATA32*) g_try_malloc(sizeof(*icon_data) * array_size); if (icon_data) { for (int j = 0; j < array_size; ++j) icon_data[j] = tmp_data[j]; img = imlib_create_image_using_copied_data(w, h, icon_data); g_free(icon_data); } } } XFree(data); } } if (!img) { XWMHints *hints = XGetWMHints(server.display, 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); } XFree(hints); } } if (img == NULL) { imlib_context_set_image(default_icon); img = imlib_clone_image(); } return img; } void task_set_icon_color(Task *task, Imlib_Image icon) { get_image_mean_color(icon, &task->icon_color); if (panel_config.mouse_effects) { task->icon_color_hover = task->icon_color; adjust_color(&task->icon_color_hover, panel_config.mouse_over_alpha, panel_config.mouse_over_saturation, panel_config.mouse_over_brightness); task->icon_color_press = task->icon_color; adjust_color(&task->icon_color_press, panel_config.mouse_pressed_alpha, panel_config.mouse_pressed_saturation, panel_config.mouse_pressed_brightness); } } void task_update_icon(Task *task) { Panel *panel = task->area.panel; if (!panel->g_task.has_icon) { if (panel_config.g_task.has_content_tint) { Imlib_Image img = task_get_icon(task->win, panel->g_task.icon_size1); task_set_icon_color(task, img); imlib_context_set_image(img); imlib_free_image(); } return; } task_remove_icon(task); Imlib_Image img = task_get_icon(task->win, panel->g_task.icon_size1); task_set_icon_color(task, img); // 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) { task->icon[k] = adjust_icon(orig_image, panel->g_task.alpha[k], panel->g_task.saturation[k], panel->g_task.brightness[k]); 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 = (Task *)g_ptr_array_index(task_buttons, i); task2->icon_width = task->icon_width; task2->icon_height = task->icon_height; task2->icon_color = task->icon_color; task2->icon_color_hover = task->icon_color_hover; task2->icon_color_press = task->icon_color_press; 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 * panel->scale; } // 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) { PangoContext *context = pango_cairo_create_context(c); pango_cairo_context_set_resolution(context, 96 * panel->scale); PangoLayout *layout = pango_layout_new(context); 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 + TINT2_PANGO_SLACK) * 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 ? layout : NULL); g_object_unref(layout); g_object_unref(context); } 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, "tint2: %*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, "tint2: %*sIcon: x = %d, y = %d, w = h = %d\n", indent, "", task->_icon_x, task->_icon_y, panel->g_task.icon_size1); } void task_get_content_color(void *obj, Color *color) { Task *task = (Task *)obj; Color *content_color = NULL; if (panel_config.mouse_effects) { if (task->area.mouse_state == MOUSE_OVER) content_color = &task->icon_color_hover; else if (task->area.mouse_state == MOUSE_DOWN) content_color = &task->icon_color_press; else content_color = &task->icon_color; } else { content_color = &task->icon_color; } if (content_color) *color = *content_color; } 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) * panel->scale; 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(); 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 task_refresh_thumbnail(Task *task) { if (!panel_config.g_task.thumbnail_enabled) return; if (task->current_state == TASK_ICONIFIED) return; Panel *panel = (Panel*)task->area.panel; double now = get_time(); if (now - task->thumbnail_last_update < 0.1) return; if (debug_thumbnails) fprintf(stderr, "tint2: thumbnail for window: %s" RESET "\n", task->title ? task->title : ""); cairo_surface_t *thumbnail = get_window_thumbnail(task->win, panel_config.g_task.thumbnail_width * panel->scale); if (!thumbnail) return; if (task->thumbnail) cairo_surface_destroy(task->thumbnail); task->thumbnail = thumbnail; task->thumbnail_last_update = get_time(); if (debug_thumbnails) fprintf(stderr, YELLOW "tint2: %s took %f ms (window: %s)" RESET "\n", __func__, 1000 * (task->thumbnail_last_update - now), task->title ? task->title : ""); if (g_tooltip.mapped && (g_tooltip.area == &task->area)) { tooltip_update_contents_for(&task->area); tooltip_update(); } } void set_task_state(Task *task, TaskState state) { if (!task || state == TASK_UNDEFINED || state >= TASK_STATE_COUNT) return; if (!task->thumbnail) task_refresh_thumbnail(task); if (state == TASK_ACTIVE) { // For active windows, we get the thumbnail twice with a small delay in between. // This is because they sometimes redraw their windows slowly. taskbar_start_thumbnail_timer(THUMB_MODE_ACTIVE_WINDOW); } 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 = TRUE; /* don't need this logic */ /* 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_ICONIFIED) { hide = FALSE; } /*nor this logic*/ /*} 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 = task1->current_state == TASK_ICONIFIED; 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_timer.enabled_) change_timer(&urgent_timer, true, 10, 1000, blink_urgent, 0); 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_timer(&urgent_timer); } 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 { change_window_desktop(task->win, server.desktop); 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) { Window win = task->win; remove_task(task); task = add_task(win); reset_active_task(); schedule_panel_redraw(); }