file-browser.c 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399
  1. /* vim: tabstop=4 shiftwidth=4 noexpandtab
  2. * This file is part of ToaruOS and is released under the terms
  3. * of the NCSA / University of Illinois License - see LICENSE.md
  4. * Copyright (C) 2018 K. Lange
  5. *
  6. * file-browser - Graphical file manager.
  7. *
  8. * Based on the original Python implementation and inspired by
  9. * Nautilus and Thunar. Also provides a "wallpaper" mode for
  10. * managing the desktop backgrond.
  11. */
  12. #include <stdio.h>
  13. #include <unistd.h>
  14. #include <dirent.h>
  15. #include <time.h>
  16. #include <math.h>
  17. #include <libgen.h>
  18. #include <signal.h>
  19. #include <sys/stat.h>
  20. #include <sys/time.h>
  21. #include <sys/wait.h>
  22. #include <toaru/yutani.h>
  23. #include <toaru/graphics.h>
  24. #include <toaru/decorations.h>
  25. #include <toaru/menu.h>
  26. #include <toaru/icon_cache.h>
  27. #include <toaru/list.h>
  28. #include <toaru/sdf.h>
  29. #include <toaru/button.h>
  30. #include <toaru/jpeg.h>
  31. #include <toaru/trace.h>
  32. #define TRACE_APP_NAME "file-browser"
  33. #define APPLICATION_TITLE "File Browser"
  34. #define SCROLL_AMOUNT 120
  35. #define WALLPAPER_PATH "/usr/share/wallpaper.jpg"
  36. struct File {
  37. char name[256]; /* Displayed name (icon label) */
  38. char icon[256]; /* Icon identifier */
  39. char link[256]; /* Link target for symlinks */
  40. char launcher[256]; /* Launcher spec */
  41. char filename[256]; /* Actual filename for launchers */
  42. int type; /* File type: 0 = normal, 1 = directory, 2 = launcher */
  43. int selected; /* Selection status */
  44. };
  45. static yutani_t * yctx;
  46. static yutani_window_t * main_window;
  47. static gfx_context_t * ctx;
  48. static int application_running = 1; /* Big loop exit condition */
  49. static int show_hidden = 0; /* Whether or not show hidden files */
  50. static int scroll_offset = 0; /* How far the icon view should be scrolled */
  51. static int available_height = 0; /* How much space is available in the main window for the icon view */
  52. static int is_desktop_background = 0; /* If we're in desktop background mode */
  53. static int menu_bar_height = MENU_BAR_HEIGHT + 36; /* Height of the menu bar, if present - it's not in desktop mode */
  54. static sprite_t * wallpaper_buffer = NULL; /* Prebaked wallpaper texture */
  55. static char title[512]; /* Application title bar */
  56. static int FILE_HEIGHT = 80; /* Height of one row of icons */
  57. static int FILE_WIDTH = 100; /* Width of one column of icons */
  58. static int FILE_PTR_WIDTH = 1; /* How many icons wide the display should be */
  59. static sprite_t * contents_sprite = NULL; /* Icon view rendering context */
  60. static gfx_context_t * contents = NULL; /* Icon view rendering context */
  61. static char * current_directory = NULL; /* Current directory path */
  62. static int hilighted_offset = -1; /* Which file is hovered by the mouse */
  63. static struct File ** file_pointers = NULL; /* List of file pointers */
  64. static ssize_t file_pointers_len = 0; /* How many files are in the current list */
  65. static uint64_t last_click = 0; /* For double click */
  66. static int last_click_offset = -1; /* So that clicking two different things quickly doesn't count as a double click */
  67. static int modifiers = 0; /* For ctrl-click */
  68. static int _button_hilights[4] = {3,3,3,3};
  69. static int _button_disabled[4] = {1,1,0,0};
  70. static int _button_hover = -1;
  71. /* Menu bar entries */
  72. static struct menu_bar menu_bar = {0};
  73. static struct menu_bar_entries menu_entries[] = {
  74. {"File", "file"},
  75. {"Edit", "edit"},
  76. {"View", "view"},
  77. {"Go", "go"},
  78. {"Help", "help"},
  79. {NULL, NULL},
  80. };
  81. static struct MenuList * context_menu = NULL;
  82. /**
  83. * Accurate time comparison.
  84. *
  85. * These methods were taken from the compositor and
  86. * allow us to time double-clicks accurately.
  87. */
  88. static uint64_t precise_current_time(void) {
  89. struct timeval t;
  90. gettimeofday(&t, NULL);
  91. time_t sec_diff = t.tv_sec;
  92. suseconds_t usec_diff = t.tv_usec;
  93. return (uint64_t)(sec_diff * 1000 + usec_diff / 1000);
  94. }
  95. static uint64_t precise_time_since(uint64_t start_time) {
  96. uint32_t now = precise_current_time();
  97. uint32_t diff = now - start_time; /* Milliseconds */
  98. return diff;
  99. }
  100. /**
  101. * When in desktop mode, we fake decoration boundaries to
  102. * position the icon view correctly. When in normal mode,
  103. * we just passt through the actual bounds.
  104. */
  105. static int _decor_get_bounds(yutani_window_t * win, struct decor_bounds * bounds) {
  106. if (is_desktop_background) {
  107. memset(bounds, 0, sizeof(struct decor_bounds));
  108. bounds->top_height = 54;
  109. bounds->left_width = 20;
  110. return 0;
  111. }
  112. return decor_get_bounds(win, bounds);
  113. }
  114. /**
  115. * This should probably be in a yutani core library...
  116. *
  117. * If a down and up event were close enough together to be considered a click.
  118. */
  119. static int _close_enough(struct yutani_msg_window_mouse_event * me) {
  120. 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) {
  121. return 1;
  122. }
  123. return 0;
  124. }
  125. /**
  126. * Clear out the space for an icon.
  127. * We clear to transparent so that the desktop background can be shown in desktop mode.
  128. */
  129. static void clear_offset(int offset) {
  130. /* From the flat array offset, figure out the x/y offset. */
  131. int offset_y = offset / FILE_PTR_WIDTH;
  132. int offset_x = offset % FILE_PTR_WIDTH;
  133. draw_rectangle_solid(contents, offset_x * FILE_WIDTH, offset_y * FILE_HEIGHT, FILE_WIDTH, FILE_HEIGHT, rgba(0,0,0,0));
  134. }
  135. /**
  136. * Draw an icon view entry
  137. */
  138. static void draw_file(struct File * f, int offset) {
  139. /* From the flat array offset, figure out the x/y offset. */
  140. int offset_y = offset / FILE_PTR_WIDTH;
  141. int offset_x = offset % FILE_PTR_WIDTH;
  142. int x = offset_x * FILE_WIDTH;
  143. int y = offset_y * FILE_HEIGHT;
  144. /* Load the icon sprite from the cache */
  145. sprite_t * icon = icon_get_48(f->icon);
  146. /* If the display name is too long to fit, cut it with an ellipsis. */
  147. int len = strlen(f->name);
  148. char * name = malloc(len + 4);
  149. memcpy(name, f->name, len + 1);
  150. int name_width;
  151. while ((name_width = draw_sdf_string_width(name, 16, SDF_FONT_THIN)) > FILE_WIDTH - 8 /* Padding */) {
  152. len--;
  153. name[len+0] = '.';
  154. name[len+1] = '.';
  155. name[len+2] = '.';
  156. name[len+3] = '\0';
  157. }
  158. /* Draw the icon */
  159. int center_x_icon = (FILE_WIDTH - icon->width) / 2;
  160. int center_x_text = (FILE_WIDTH - name_width) / 2;
  161. draw_sprite(contents, icon, center_x_icon + x, y + 2);
  162. if (f->selected) {
  163. /* If this file is selected, paint the icon blue... */
  164. if (main_window->focused) {
  165. draw_sprite_alpha_paint(contents, icon, center_x_icon + x, y + 2, 0.5, rgb(72,167,255));
  166. }
  167. /* And draw the name with a blue background and white text */
  168. draw_rounded_rectangle(contents, center_x_text + x - 2, y + 54, name_width + 6, 20, 3, rgb(72,167,255));
  169. draw_sdf_string(contents, center_x_text + x, y + 54, name, 16, rgb(255,255,255), SDF_FONT_THIN);
  170. } else {
  171. if (is_desktop_background) {
  172. /* If this is the desktop view, white text with a drop shadow */
  173. draw_sdf_string_stroke(contents, center_x_text + x + 1, y + 55, name, 16, rgba(0,0,0,120), SDF_FONT_THIN, 1.7, 0.5);
  174. draw_sdf_string(contents, center_x_text + x, y + 54, name, 16, rgb(255,255,255), SDF_FONT_THIN);
  175. } else {
  176. /* Otherwise, black text */
  177. draw_sdf_string(contents, center_x_text + x, y + 54, name, 16, rgb(0,0,0), SDF_FONT_THIN);
  178. }
  179. }
  180. if (offset == hilighted_offset) {
  181. /* The hovered icon should have some added brightness, so paint it white */
  182. draw_sprite_alpha_paint(contents, icon, center_x_icon + x, y + 2, 0.3, rgb(255,255,255));
  183. }
  184. if (f->link[0]) {
  185. /* For symlinks, draw an indicator */
  186. sprite_t * arrow = icon_get_16("forward");
  187. draw_sprite(contents, arrow, center_x_icon + 32 + x, y + 32);
  188. }
  189. free(name);
  190. }
  191. /**
  192. * Get file from array offset, with bounds check
  193. */
  194. static struct File * get_file_at_offset(int offset) {
  195. if (offset >= 0 && offset < file_pointers_len) {
  196. return file_pointers[offset];
  197. }
  198. return NULL;
  199. }
  200. /**
  201. * Redraw all icon view entries
  202. */
  203. static void redraw_files(void) {
  204. /* Fill to blank */
  205. draw_fill(contents, rgba(0,0,0,0));
  206. for (int i = 0; i < file_pointers_len; ++i) {
  207. draw_file(file_pointers[i], i);
  208. }
  209. }
  210. /**
  211. * Set the application title.
  212. */
  213. static void set_title(char * directory) {
  214. /* Do nothing in desktop mode to avoid advertisement. */
  215. if (is_desktop_background) return;
  216. /* If the directory name is set... */
  217. if (directory) {
  218. sprintf(title, "%s - " APPLICATION_TITLE, directory);
  219. } else {
  220. /* Otherwise, just "File Browser" */
  221. sprintf(title, APPLICATION_TITLE);
  222. }
  223. /* Advertise to the panel */
  224. yutani_window_advertise_icon(yctx, main_window, title, "folder");
  225. }
  226. /**
  227. * Check if a file name ends with an extension.
  228. *
  229. * Can also be used for exact matches.
  230. */
  231. static int has_extension(struct File * f, char * extension) {
  232. int i = strlen(f->name);
  233. int j = strlen(extension);
  234. do {
  235. if (f->name[i] != (extension)[j]) break;
  236. if (j == 0) return 1;
  237. if (i == 0) break;
  238. i--;
  239. j--;
  240. } while (1);
  241. return 0;
  242. }
  243. static list_t * history_back;
  244. static list_t * history_forward;
  245. /**
  246. * Read the contents of a directory into the icon view.
  247. */
  248. static void load_directory(const char * path, int modifies_history) {
  249. /* Free the current icon view entries */
  250. if (file_pointers) {
  251. for (int i = 0; i < file_pointers_len; ++i) {
  252. free(file_pointers[i]);
  253. }
  254. free(file_pointers);
  255. }
  256. DIR * dirp = opendir(path);
  257. if (!dirp) {
  258. /**
  259. * TODO: This should probably show a dialog and then reload the current directory,
  260. * or maybe we should be checking this before clearing the current file pointers.
  261. */
  262. file_pointers = NULL;
  263. file_pointers_len = 0;
  264. return;
  265. }
  266. if (modifies_history) {
  267. /* Clear forward history */
  268. list_destroy(history_forward);
  269. list_free(history_forward);
  270. free(history_forward);
  271. history_forward = list_create();
  272. /* Append current pointer */
  273. if (current_directory) {
  274. list_insert(history_back, strdup(current_directory));
  275. }
  276. }
  277. if (current_directory) {
  278. free(current_directory);
  279. }
  280. _button_disabled[0] = !(history_back->length);
  281. _button_disabled[1] = !(history_forward->length);
  282. _button_disabled[2] = 0;
  283. _button_disabled[3] = 0;
  284. char * home = getenv("HOME");
  285. if (home && !strcmp(path, home)) {
  286. /* If the current directory is the user's homedir, present it that way in the title */
  287. set_title("Home");
  288. _button_disabled[3] = 1;
  289. } else if (!strcmp(path, "/")) {
  290. set_title("File System");
  291. _button_disabled[2] = 1;
  292. } else {
  293. /* Otherwise use just the directory base name */
  294. char * tmp = strdup(path);
  295. char * base = basename(tmp);
  296. set_title(base);
  297. free(tmp);
  298. }
  299. /* If we ended up in a path with //two/initial/slashes, fix that. */
  300. if (path[0] == '/' && path[1] == '/') {
  301. current_directory = strdup(path+1);
  302. } else {
  303. current_directory = strdup(path);
  304. }
  305. /* TODO: Show relative time informaton... */
  306. #if 0
  307. /* Get the current time */
  308. struct tm * timeinfo;
  309. struct timeval now;
  310. gettimeofday(&now, NULL); //time(NULL);
  311. timeinfo = localtime((time_t *)&now.tv_sec);
  312. int this_year = timeinfo->tm_year;
  313. #endif
  314. list_t * file_list = list_create();
  315. struct dirent * ent = readdir(dirp);
  316. while (ent != NULL) {
  317. if (ent->d_name[0] == '.' &&
  318. (ent->d_name[1] == '\0' ||
  319. (ent->d_name[1] == '.' &&
  320. ent->d_name[2] == '\0'))) {
  321. /* skip . and .. */
  322. ent = readdir(dirp);
  323. continue;
  324. }
  325. if (show_hidden || (ent->d_name[0] != '.')) {
  326. /* Set display name from file name */
  327. struct File * f = malloc(sizeof(struct File));
  328. sprintf(f->name, "%s", ent->d_name); /* snprintf? copy min()? */
  329. struct stat statbuf;
  330. struct stat statbufl;
  331. /* Calculate absolute path to file */
  332. char tmp[strlen(path)+strlen(ent->d_name)+2];
  333. sprintf(tmp, "%s/%s", path, ent->d_name);
  334. lstat(tmp, &statbuf);
  335. /* Read link target for symlinks */
  336. if (S_ISLNK(statbuf.st_mode)) {
  337. memcpy(&statbufl, &statbuf, sizeof(struct stat));
  338. stat(tmp, &statbuf);
  339. readlink(tmp, f->link, 256);
  340. } else {
  341. f->link[0] = '\0';
  342. }
  343. f->launcher[0] = '\0';
  344. f->selected = 0;
  345. if (S_ISDIR(statbuf.st_mode)) {
  346. /* Directory */
  347. sprintf(f->icon, "folder");
  348. f->type = 1;
  349. } else {
  350. /* Regular file */
  351. /* Default regular files to open in bim */
  352. sprintf(f->launcher, "exec terminal bim");
  353. if (is_desktop_background && has_extension(f, ".launcher")) {
  354. /* In desktop mode, read launchers specially */
  355. FILE * file = fopen(tmp,"r");
  356. char tbuf[1024];
  357. while (!feof(file)) {
  358. fgets(tbuf, 1024, file);
  359. char * nl = strchr(tbuf,'\n');
  360. if (nl) *nl = '\0';
  361. char * eq = strchr(tbuf,'=');
  362. if (!eq) continue;
  363. *eq = '\0'; eq++;
  364. if (!strcmp(tbuf, "icon")) {
  365. sprintf(f->icon, "%s", eq);
  366. } else if (!strcmp(tbuf, "run")) {
  367. sprintf(f->launcher, "%s #", eq);
  368. } else if (!strcmp(tbuf, "title")) {
  369. sprintf(f->name, eq);
  370. }
  371. }
  372. sprintf(f->filename, "%s", ent->d_name);
  373. f->type = 2;
  374. } else {
  375. /* Handle various file types */
  376. if (has_extension(f, ".c")) {
  377. sprintf(f->icon, "c");
  378. } else if (has_extension(f, ".h")) {
  379. sprintf(f->icon, "h");
  380. } else if (has_extension(f, ".bmp")) {
  381. sprintf(f->icon, "image");
  382. sprintf(f->launcher, "exec imgviewer");
  383. } else if (has_extension(f, ".sdf") || has_extension(f, ".ttf")) {
  384. sprintf(f->icon, "font");
  385. /* TODO: Font viewer for SDF and TrueType */
  386. } else if (has_extension(f, ".tgz") || has_extension(f, ".tar") || has_extension(f, ".tar.gz")) {
  387. /* Or dozens of others... */
  388. sprintf(f->icon, "package");
  389. /* TODO: Archive tool? Extract locally? */
  390. } else if (has_extension(f, ".sh")) {
  391. sprintf(f->icon, "sh");
  392. if (statbuf.st_mode & 0111) {
  393. /* Make executable */
  394. sprintf(f->launcher, "SELF");
  395. }
  396. } else if (statbuf.st_mode & 0111) {
  397. /* Executable files - use their name for their icon, and launch themselves. */
  398. sprintf(f->icon, "%s", f->name);
  399. sprintf(f->launcher, "SELF");
  400. } else {
  401. sprintf(f->icon, "file");
  402. }
  403. f->type = 0;
  404. }
  405. }
  406. list_insert(file_list, f);
  407. }
  408. ent = readdir(dirp);
  409. }
  410. closedir(dirp);
  411. /* Store the entries in a flat array. */
  412. file_pointers = malloc(sizeof(struct File *) * file_list->length);
  413. file_pointers_len = file_list->length;
  414. int i = 0;
  415. foreach (node, file_list) {
  416. file_pointers[i] = node->value;
  417. i++;
  418. }
  419. /* Free our temporary linked list */
  420. list_free(file_list);
  421. free(file_list);
  422. /* Sort files */
  423. int comparator(const void * c1, const void * c2) {
  424. const struct File * f1 = *(const struct File **)(c1);
  425. const struct File * f2 = *(const struct File **)(c2);
  426. /* Launchers before directories before files */
  427. if (f1->type > f2->type) return -1;
  428. if (f2->type > f1->type) return 1;
  429. /* Launchers sorted by filename, not by display name */
  430. if (f1->type == 2 && f2->type == 2) {
  431. return strcmp(f1->filename, f2->filename);
  432. }
  433. /* Files sorted by name */
  434. return strcmp(f1->name, f2->name);
  435. }
  436. qsort(file_pointers, file_pointers_len, sizeof(struct File *), comparator);
  437. /* Reset scroll offset when navigating */
  438. scroll_offset = 0;
  439. }
  440. /**
  441. * Resize and redraw the icon view */
  442. static void reinitialize_contents(void) {
  443. /* If there already is a context, free it. */
  444. if (contents) {
  445. free(contents);
  446. }
  447. /* If there already is a context buffer, free it. */
  448. if (contents_sprite) {
  449. sprite_free(contents_sprite);
  450. }
  451. /* Get window bounds to determine how wide we can make our icon view */
  452. struct decor_bounds bounds;
  453. _decor_get_bounds(main_window, &bounds);
  454. if (is_desktop_background) {
  455. /**
  456. * TODO: Actually calculate an optimal FILE_PTR_WIDTH or fix this to
  457. * work properly with vertical rows of files
  458. */
  459. FILE_PTR_WIDTH = 1;
  460. } else {
  461. FILE_PTR_WIDTH = (ctx->width - bounds.width) / FILE_WIDTH;
  462. }
  463. /* Calculate required height to fit files */
  464. int calculated_height = (file_pointers_len / FILE_PTR_WIDTH + 1) * FILE_HEIGHT;
  465. /* Create buffer */
  466. contents_sprite = create_sprite(FILE_PTR_WIDTH * FILE_WIDTH, calculated_height, ALPHA_EMBEDDED);
  467. contents = init_graphics_sprite(contents_sprite);
  468. /* Draw file entries */
  469. redraw_files();
  470. }
  471. /**
  472. * Redraw the entire window.
  473. */
  474. static void redraw_window(void) {
  475. if (!is_desktop_background) {
  476. /* Clear to white and draw decorations */
  477. draw_fill(ctx, rgb(255,255,255));
  478. render_decorations(main_window, ctx, title);
  479. } else {
  480. /* Draw wallpaper in desktop mode */
  481. draw_sprite(ctx, wallpaper_buffer, 0, 0);
  482. }
  483. struct decor_bounds bounds;
  484. _decor_get_bounds(main_window, &bounds);
  485. if (!is_desktop_background) {
  486. /* Position, size, and draw the menu bar */
  487. menu_bar.x = bounds.left_width;
  488. menu_bar.y = bounds.top_height;
  489. menu_bar.width = ctx->width - bounds.width;
  490. menu_bar.window = main_window;
  491. menu_bar_render(&menu_bar, ctx);
  492. /* Draw toolbar */
  493. uint32_t gradient_top = rgb(59,59,59);
  494. uint32_t gradient_bot = rgb(40,40,40);
  495. for (int i = 0; i < 37; ++i) {
  496. uint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / 36);
  497. draw_rectangle(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT + i,
  498. ctx->width - bounds.width, 1, c);
  499. }
  500. int x = 0;
  501. int i = 0;
  502. #define draw_button(label) do { \
  503. struct TTKButton _up = {bounds.left_width + 2 + x,bounds.top_height + MENU_BAR_HEIGHT + 2,32,32,"\033" label,_button_hilights[i] | (_button_disabled[i] << 8)}; \
  504. ttk_button_draw(ctx, &_up); \
  505. x += 34; i++; } while (0)
  506. draw_button("back");
  507. draw_button("forward");
  508. draw_button("up");
  509. draw_button("home");
  510. struct gradient_definition edge = {28, bounds.top_height + MENU_BAR_HEIGHT + 3, rgb(90,90,90), rgb(110,110,110)};
  511. draw_rounded_rectangle_pattern(ctx, bounds.left_width + 2 + x + 1, bounds.top_height + MENU_BAR_HEIGHT + 4, main_window->width - bounds.width - x - 6, 26, 4, gfx_vertical_gradient_pattern, &edge);
  512. draw_rounded_rectangle(ctx, bounds.left_width + 2 + x + 2, bounds.top_height + MENU_BAR_HEIGHT + 5, main_window->width - bounds.width - x - 8, 24, 3, rgb(250,250,250));
  513. int max_width = main_window->width - bounds.width - x - 12;
  514. int len = strlen(current_directory);
  515. char * name = malloc(len + 4);
  516. memcpy(name, current_directory, len + 1);
  517. int name_width;
  518. while ((name_width = draw_sdf_string_width(name, 16, SDF_FONT_THIN)) > max_width) {
  519. len--;
  520. name[len+0] = '.';
  521. name[len+1] = '.';
  522. name[len+2] = '.';
  523. name[len+3] = '\0';
  524. }
  525. draw_sdf_string(ctx, bounds.left_width + 2 + x + 5, bounds.top_height + MENU_BAR_HEIGHT + 8, name, 16, rgb(0,0,0), SDF_FONT_THIN);
  526. }
  527. /* Draw the icon view, clipped to the viewport and scrolled appropriately. */
  528. gfx_clear_clip(ctx);
  529. gfx_add_clip(ctx, bounds.left_width, bounds.top_height + menu_bar_height, ctx->width - bounds.width, available_height);
  530. draw_sprite(ctx, contents_sprite, bounds.left_width, bounds.top_height + menu_bar_height - scroll_offset);
  531. gfx_clear_clip(ctx);
  532. gfx_add_clip(ctx, 0, 0, ctx->width, ctx->height);
  533. /* Flip graphics context and inform compositor */
  534. flip(ctx);
  535. yutani_flip(yctx, main_window);
  536. }
  537. /**
  538. * Loads and bakes the wallpaper to the appropriate size.
  539. */
  540. static void draw_background(int width, int height) {
  541. /* If the wallpaper is already loaded, free it. */
  542. if (wallpaper_buffer) {
  543. sprite_free(wallpaper_buffer);
  544. }
  545. /* Open the wallpaper */
  546. sprite_t * wallpaper = malloc(sizeof(sprite_t));
  547. load_sprite_jpg(wallpaper, WALLPAPER_PATH);
  548. wallpaper->alpha = 0;
  549. fprintf(stderr, "done?\n");
  550. /* Create a new buffer to hold the baked wallpaper */
  551. wallpaper_buffer = create_sprite(width, height, 0);
  552. gfx_context_t * ctx = init_graphics_sprite(wallpaper_buffer);
  553. /* Calculate the appropriate scaled size to fit the screen. */
  554. float x = (float)width / (float)wallpaper->width;
  555. float y = (float)height / (float)wallpaper->height;
  556. int nh = (int)(x * (float)wallpaper->height);
  557. int nw = (int)(y * (float)wallpaper->width);
  558. /* Clear to black to avoid odd transparency issues along edges */
  559. draw_fill(ctx, rgb(0,0,0));
  560. /* Scale the wallpaper into the buffer. */
  561. if (nw == wallpaper->width && nh == wallpaper->height) {
  562. /* No scaling necessary */
  563. draw_sprite(ctx, wallpaper, 0, 0);
  564. } else if (nw >= width) {
  565. /* Scaled wallpaper is wider, height should match. */
  566. draw_sprite_scaled(ctx, wallpaper, ((int)width - nw) / 2, 0, nw+2, height);
  567. } else {
  568. /* Scaled wallpaper is taller, width should match. */
  569. draw_sprite_scaled(ctx, wallpaper, 0, ((int)height - nh) / 2, width+2, nh);
  570. }
  571. /* Free the original wallpaper. */
  572. sprite_free(wallpaper);
  573. free(ctx);
  574. }
  575. /**
  576. * Resize window when asked by the compositor.
  577. */
  578. static void resize_finish(int w, int h) {
  579. if (w < 300 || h < 300) {
  580. yutani_window_resize_offer(yctx, main_window, w < 300 ? 300 : w, h < 300 ? 300 : h);
  581. return;
  582. }
  583. int width_changed = (main_window->width != (unsigned int)w);
  584. yutani_window_resize_accept(yctx, main_window, w, h);
  585. reinit_graphics_yutani(ctx, main_window);
  586. struct decor_bounds bounds;
  587. _decor_get_bounds(main_window, &bounds);
  588. /* Recalculate available size */
  589. available_height = ctx->height - menu_bar_height - bounds.height;
  590. /* If the width changed, we need to rebuild the icon view */
  591. if (width_changed) {
  592. reinitialize_contents();
  593. }
  594. /* Make sure we're not scrolled weirdly after resizing */
  595. if (available_height > contents->height) {
  596. scroll_offset = 0;
  597. } else {
  598. if (scroll_offset > contents->height - available_height) {
  599. scroll_offset = contents->height - available_height;
  600. }
  601. }
  602. /* If the desktop background changes size, we have to reload and rescale the wallpaper */
  603. if (is_desktop_background) {
  604. draw_background(w, h);
  605. }
  606. /* Redraw */
  607. redraw_window();
  608. yutani_window_resize_done(yctx, main_window);
  609. yutani_flip(yctx, main_window);
  610. }
  611. /* TODO: We don't have an input box yet. */
  612. #if 0
  613. static void _menu_action_input_path(struct MenuEntry * entry) {
  614. }
  615. #endif
  616. /* File > Exit */
  617. static void _menu_action_exit(struct MenuEntry * entry) {
  618. application_running = 0;
  619. }
  620. /* Go > ... generic handler */
  621. static void _menu_action_navigate(struct MenuEntry * entry) {
  622. /* go to entry->action */
  623. struct MenuEntry_Normal * _entry = (void*)entry;
  624. load_directory(_entry->action, 1);
  625. reinitialize_contents();
  626. redraw_window();
  627. }
  628. /* Go > Up */
  629. static void _menu_action_up(struct MenuEntry * entry) {
  630. /* go up */
  631. char * tmp = strdup(current_directory);
  632. char * dir = dirname(tmp);
  633. load_directory(dir, 1);
  634. reinitialize_contents();
  635. redraw_window();
  636. }
  637. /* [Context] > Refresh */
  638. static void _menu_action_refresh(struct MenuEntry * entry) {
  639. char * tmp = strdup(current_directory);
  640. load_directory(tmp, 0);
  641. reinitialize_contents();
  642. redraw_window();
  643. }
  644. /* Help > Contents */
  645. static void _menu_action_help(struct MenuEntry * entry) {
  646. /* show help documentation */
  647. system("help-browser file-browser.trt &");
  648. redraw_window();
  649. }
  650. /* [Context] > Copy */
  651. static void _menu_action_copy(struct MenuEntry * entry) {
  652. size_t output_size = 0;
  653. /* Calculate required space for the clipboard */
  654. int base_is_root = !strcmp(current_directory, "/"); /* avoid redundant slash */
  655. for (int i = 0; i < file_pointers_len; ++i) {
  656. if (file_pointers[i]->selected) {
  657. output_size += strlen(current_directory) + !base_is_root + strlen(file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name) + 1; /* base / file \n */
  658. }
  659. }
  660. /* Nothing to copy? */
  661. if (!output_size) return;
  662. /* Create the clipboard contents as a LF-separated list of absolute paths */
  663. char * clipboard = malloc(output_size);
  664. clipboard[0] = '\0';
  665. for (int i = 0; i < file_pointers_len; ++i) {
  666. if (file_pointers[i]->selected) {
  667. strcat(clipboard, current_directory);
  668. if (!base_is_root) { strcat(clipboard, "/"); }
  669. strcat(clipboard, file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name);
  670. strcat(clipboard, "\n");
  671. }
  672. }
  673. if (clipboard[output_size-1] == '\n') {
  674. /* Remove trailing line feed */
  675. clipboard[output_size-1] = '\0';
  676. }
  677. yutani_set_clipboard(yctx, clipboard);
  678. free(clipboard);
  679. }
  680. static void _menu_action_paste(struct MenuEntry * entry) {
  681. yutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD);
  682. }
  683. /* Help > About File Browser */
  684. static void _menu_action_about(struct MenuEntry * entry) {
  685. /* Show About dialog */
  686. char about_cmd[1024] = "\0";
  687. strcat(about_cmd, "about \"About File Browser\" /usr/share/icons/48/folder.bmp \"ToaruOS File Browser\" \"(C) 2018 K. Lange\n-\nPart of ToaruOS, which is free software\nreleased under the NCSA/University of Illinois\nlicense.\n-\n%https://toaruos.org\n%https://gitlab.com/toaruos\" ");
  688. char coords[100];
  689. sprintf(coords, "%d %d &", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2);
  690. strcat(about_cmd, coords);
  691. system(about_cmd);
  692. redraw_window();
  693. }
  694. /**
  695. * Generic application launcher - like system(), but without the wait.
  696. * Also sets the working directory to the currently-opened directory.
  697. */
  698. static void launch_application(char * app) {
  699. if (!fork()) {
  700. if (current_directory) chdir(current_directory);
  701. char * tmp = malloc(strlen(app) + 10);
  702. sprintf(tmp, "%s", app);
  703. char * args[] = {"/bin/sh", "-c", tmp, NULL};
  704. execvp(args[0], args);
  705. exit(1);
  706. }
  707. }
  708. /* Generic handler for various launcher menus */
  709. static void launch_application_menu(struct MenuEntry * self) {
  710. struct MenuEntry_Normal * _self = (void *)self;
  711. launch_application((char *)_self->action);
  712. }
  713. /**
  714. * Perform the appropriate action to open a File
  715. */
  716. static void open_file(struct File * f) {
  717. if (f->type == 1) {
  718. char tmp[1024];
  719. if (is_desktop_background) {
  720. /* Always open directories in new file browser windows when launched from desktop */
  721. sprintf(tmp,"file-browser \"%s/%s\"", current_directory, f->name);
  722. launch_application(tmp);
  723. } else {
  724. /* In normal mode, navigate to this directory. */
  725. sprintf(tmp,"%s/%s", current_directory, f->name);
  726. load_directory(tmp, 1);
  727. reinitialize_contents();
  728. redraw_window();
  729. }
  730. } else if (f->launcher[0]) {
  731. char tmp[4096];
  732. if (!strcmp(f->launcher, "SELF")) {
  733. /* "SELF" launchers are for binaries. */
  734. sprintf(tmp, "exec ./%s", f->name);
  735. } else {
  736. /* Other launchers shouuld take file names as arguments.
  737. * NOTE: If you don't want the file name, you can append # to your launcher.
  738. * Since it's parsed by the shell, this will yield a comment.
  739. */
  740. sprintf(tmp, "%s \"%s\"", f->launcher, f->name);
  741. }
  742. launch_application(tmp);
  743. }
  744. }
  745. /* [Context] > Open */
  746. static void _menu_action_open(struct MenuEntry * self) {
  747. for (int i = 0; i < file_pointers_len; ++i) {
  748. if (file_pointers[i]->selected) {
  749. open_file(file_pointers[i]);
  750. }
  751. }
  752. }
  753. /* [Context] > Edit in Bim */
  754. static void _menu_action_edit(struct MenuEntry * self) {
  755. for (int i = 0; i < file_pointers_len; ++i) {
  756. if (file_pointers[i]->selected) {
  757. char tmp[1024];
  758. sprintf(tmp, "exec terminal bim \"%s\"", file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name);
  759. launch_application(tmp);
  760. }
  761. }
  762. }
  763. /* View > (Show/Hide) Hidden Files */
  764. static void _menu_action_toggle_hidden(struct MenuEntry * self) {
  765. show_hidden = !show_hidden;
  766. menu_update_title(self, show_hidden ? "Hide Hidden Files" : "Show Hidden Files");
  767. _menu_action_refresh(NULL);
  768. }
  769. static void _menu_action_select_all(struct MenuEntry * self) {
  770. for (int i = 0; i < file_pointers_len; ++i) {
  771. file_pointers[i]->selected = 1;
  772. }
  773. reinitialize_contents();
  774. redraw_window();
  775. }
  776. static void handle_clipboard(char * contents) {
  777. fprintf(stderr, "Received clipboard:\n%s\n",contents);
  778. char * file = contents;
  779. while (file && *file) {
  780. char * next_file = strchr(file, '\n');
  781. if (next_file) {
  782. *next_file = '\0';
  783. next_file++;
  784. }
  785. /* determine if the destination already exists */
  786. char * cheap_basename = strrchr(file, '/');
  787. if (!cheap_basename) cheap_basename = file;
  788. else cheap_basename++;
  789. char destination[4096];
  790. sprintf(destination, "%s/%s", current_directory, cheap_basename);
  791. struct stat statbuf;
  792. if (!stat(destination, &statbuf)) {
  793. char message[4096];
  794. sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.bmp \"Not overwriting file '%s'.\"", cheap_basename);
  795. launch_application(message);
  796. } else {
  797. char cp[1024];
  798. sprintf(cp, "cp -r \"%s\" \"%s\"", file, current_directory);
  799. if (system(cp)) {
  800. char message[4096];
  801. sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.bmp \"Error copying file '%s'.\"", cheap_basename);
  802. launch_application(message);
  803. }
  804. }
  805. file = next_file;
  806. }
  807. _menu_action_refresh(NULL);
  808. }
  809. /**
  810. * Toggle the selected status of the highlighted icon.
  811. *
  812. * When Ctrl is held, the current selection is maintained.
  813. */
  814. static void toggle_selected(int hilighted_offset, int modifiers) {
  815. struct File * f = get_file_at_offset(hilighted_offset);
  816. /* No file at this offset, do nothing. */
  817. if (!f) return;
  818. /* Toggle selection of the current file */
  819. f->selected = !f->selected;
  820. /* If Ctrl wasn't held, unselect everything else. */
  821. if (!(modifiers & KEY_MOD_LEFT_CTRL)) {
  822. for (int i = 0; i < file_pointers_len; ++i) {
  823. if (file_pointers[i] != f && file_pointers[i]->selected) {
  824. file_pointers[i]->selected = 0;
  825. clear_offset(i);
  826. draw_file(file_pointers[i], i);
  827. }
  828. }
  829. }
  830. /* Redraw the file */
  831. clear_offset(hilighted_offset);
  832. draw_file(f, hilighted_offset);
  833. /* And repaint the window */
  834. redraw_window();
  835. }
  836. static int _down_button = -1;
  837. static void _set_hilight(int index, int hilight) {
  838. int _update = 0;
  839. if (_button_hover != index || (_button_hover == index && index != -1 && _button_hilights[index] != hilight)) {
  840. if (_button_hover != -1 && _button_hilights[_button_hover] != 3) {
  841. _button_hilights[_button_hover] = 3;
  842. _update = 1;
  843. }
  844. _button_hover = index;
  845. if (index != -1 && !_button_disabled[index]) {
  846. _button_hilights[_button_hover] = hilight;
  847. _update = 1;
  848. }
  849. if (_update) {
  850. redraw_window();
  851. }
  852. }
  853. }
  854. static void _handle_button_press(int index) {
  855. if (index != -1 && _button_disabled[index]) return; /* can't click disabled buttons */
  856. switch (index) {
  857. case 0:
  858. /* Back */
  859. if (history_back->length) {
  860. list_insert(history_forward, strdup(current_directory));
  861. node_t * next = list_pop(history_back);
  862. load_directory(next->value, 0);
  863. free(next->value);
  864. free(next);
  865. reinitialize_contents();
  866. redraw_window();
  867. }
  868. break;
  869. case 1:
  870. /* Forward */
  871. if (history_forward->length) {
  872. list_insert(history_back, strdup(current_directory));
  873. node_t * next = list_pop(history_forward);
  874. load_directory(next->value, 0);
  875. free(next->value);
  876. free(next);
  877. reinitialize_contents();
  878. redraw_window();
  879. }
  880. break;
  881. case 2:
  882. /* Up */
  883. _menu_action_up(NULL);
  884. break;
  885. case 3:
  886. /* Home */
  887. {
  888. struct MenuEntry_Normal _fake = {.action = getenv("HOME") };
  889. _menu_action_navigate(&_fake);
  890. }
  891. break;
  892. default:
  893. /* ??? */
  894. break;
  895. }
  896. }
  897. /**
  898. * Desktop mode responsds to sig_usr2 by returning to
  899. * the bottom of the Z-order stack.
  900. */
  901. static void sig_usr2(int sig) {
  902. yutani_set_stack(yctx, main_window, YUTANI_ZORDER_BOTTOM);
  903. yutani_flip(yctx, main_window);
  904. signal(SIGUSR2, sig_usr2);
  905. _menu_action_refresh(NULL);
  906. }
  907. int main(int argc, char * argv[]) {
  908. yctx = yutani_init();
  909. init_decorations();
  910. int arg_ind = 1;
  911. TRACE("Starting");
  912. if (argc > 1 && !strcmp(argv[1], "--wallpaper")) {
  913. is_desktop_background = 1;
  914. menu_bar_height = 0;
  915. signal(SIGUSR2, sig_usr2);
  916. TRACE("Drawing background");
  917. draw_background(yctx->display_width, yctx->display_height);
  918. TRACE("Creating window");
  919. main_window = yutani_window_create(yctx, yctx->display_width, yctx->display_height);
  920. TRACE("Moving");
  921. yutani_window_move(yctx, main_window, 0, 0);
  922. TRACE("Setting stack");
  923. yutani_set_stack(yctx, main_window, YUTANI_ZORDER_BOTTOM);
  924. arg_ind++;
  925. TRACE("Pid");
  926. FILE * f = fopen("/var/run/.wallpaper.pid", "w");
  927. fprintf(f, "%d\n", getpid());
  928. fclose(f);
  929. } else {
  930. main_window = yutani_window_create(yctx, 800, 600);
  931. yutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2);
  932. }
  933. if (arg_ind < argc) {
  934. chdir(argv[arg_ind]);
  935. }
  936. ctx = init_graphics_yutani_double_buffer(main_window);
  937. struct decor_bounds bounds;
  938. _decor_get_bounds(main_window, &bounds);
  939. set_title(NULL);
  940. menu_bar.entries = menu_entries;
  941. menu_bar.redraw_callback = redraw_window;
  942. menu_bar.set = menu_set_create();
  943. struct MenuList * m = menu_create(); /* File */
  944. menu_insert(m, menu_create_normal("exit",NULL,"Exit", _menu_action_exit));
  945. menu_set_insert(menu_bar.set, "file", m);
  946. m = menu_create();
  947. menu_insert(m, menu_create_normal(NULL,NULL,"Copy",_menu_action_copy));
  948. menu_insert(m, menu_create_normal(NULL,NULL,"Paste",_menu_action_paste));
  949. menu_insert(m, menu_create_separator());
  950. menu_insert(m, menu_create_normal(NULL,NULL,"Select all",_menu_action_select_all));
  951. menu_set_insert(menu_bar.set, "edit", m);
  952. m = menu_create();
  953. menu_insert(m, menu_create_normal("refresh",NULL,"Refresh", _menu_action_refresh));
  954. menu_insert(m, menu_create_separator());
  955. menu_insert(m, menu_create_normal(NULL,NULL,"Show Hidden Files", _menu_action_toggle_hidden));
  956. menu_set_insert(menu_bar.set, "view", m);
  957. m = menu_create(); /* Go */
  958. /* TODO implement input dialog for Path... */
  959. #if 0
  960. menu_insert(m, menu_create_normal("open",NULL,"Path...", _menu_action_input_path));
  961. menu_insert(m, menu_create_separator());
  962. #endif
  963. menu_insert(m, menu_create_normal("home",getenv("HOME"),"Home",_menu_action_navigate));
  964. menu_insert(m, menu_create_normal(NULL,"/","File System",_menu_action_navigate));
  965. menu_insert(m, menu_create_normal("up",NULL,"Up",_menu_action_up));
  966. menu_set_insert(menu_bar.set, "go", m);
  967. m = menu_create();
  968. menu_insert(m, menu_create_normal("help",NULL,"Contents",_menu_action_help));
  969. menu_insert(m, menu_create_separator());
  970. menu_insert(m, menu_create_normal("star",NULL,"About " APPLICATION_TITLE,_menu_action_about));
  971. menu_set_insert(menu_bar.set, "help", m);
  972. available_height = ctx->height - menu_bar_height - bounds.height;
  973. context_menu = menu_create(); /* Right-click menu */
  974. menu_insert(context_menu, menu_create_normal(NULL,NULL,"Open",_menu_action_open));
  975. menu_insert(context_menu, menu_create_normal(NULL,NULL,"Edit in Bim",_menu_action_edit));
  976. menu_insert(context_menu, menu_create_separator());
  977. menu_insert(context_menu, menu_create_normal(NULL,NULL,"Copy",_menu_action_copy));
  978. menu_insert(context_menu, menu_create_normal(NULL,NULL,"Paste",_menu_action_paste));
  979. menu_insert(context_menu, menu_create_separator());
  980. if (!is_desktop_background) {
  981. menu_insert(context_menu, menu_create_normal("up",NULL,"Up",_menu_action_up));
  982. }
  983. menu_insert(context_menu, menu_create_normal("refresh",NULL,"Refresh",_menu_action_refresh));
  984. menu_insert(context_menu, menu_create_normal("utilities-terminal","terminal","Open Terminal",launch_application_menu));
  985. history_back = list_create();
  986. history_forward = list_create();
  987. /* Load the current working directory */
  988. char tmp[1024];
  989. getcwd(tmp, 1024);
  990. load_directory(tmp, 1);
  991. /* Draw files */
  992. reinitialize_contents();
  993. redraw_window();
  994. while (application_running) {
  995. waitpid(-1, NULL, WNOHANG);
  996. yutani_msg_t * m = yutani_poll(yctx);
  997. while (m) {
  998. if (menu_process_event(yctx, m)) {
  999. redraw_window();
  1000. }
  1001. switch (m->type) {
  1002. case YUTANI_MSG_WELCOME:
  1003. if (is_desktop_background) {
  1004. yutani_window_resize_offer(yctx, main_window, yctx->display_width, yctx->display_height);
  1005. }
  1006. break;
  1007. case YUTANI_MSG_KEY_EVENT:
  1008. {
  1009. struct yutani_msg_key_event * ke = (void*)m->data;
  1010. modifiers = ke->event.modifiers;
  1011. if (!is_desktop_background) {
  1012. if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {
  1013. _menu_action_exit(NULL);
  1014. }
  1015. }
  1016. }
  1017. break;
  1018. case YUTANI_MSG_WINDOW_FOCUS_CHANGE:
  1019. {
  1020. struct yutani_msg_window_focus_change * wf = (void*)m->data;
  1021. yutani_window_t * win = hashmap_get(yctx->windows, (void*)wf->wid);
  1022. if (win == main_window) {
  1023. win->focused = wf->focused;
  1024. redraw_files();
  1025. redraw_window();
  1026. }
  1027. }
  1028. break;
  1029. case YUTANI_MSG_RESIZE_OFFER:
  1030. {
  1031. struct yutani_msg_window_resize * wr = (void*)m->data;
  1032. if (wr->wid == main_window->wid) {
  1033. resize_finish(wr->width, wr->height);
  1034. }
  1035. }
  1036. break;
  1037. case YUTANI_MSG_CLIPBOARD:
  1038. {
  1039. struct yutani_msg_clipboard * cb = (void *)m->data;
  1040. char * selection_text;
  1041. if (*cb->content == '\002') {
  1042. int size = atoi(&cb->content[2]);
  1043. FILE * clipboard = yutani_open_clipboard(yctx);
  1044. selection_text = malloc(size + 1);
  1045. fread(selection_text, 1, size, clipboard);
  1046. selection_text[size] = '\0';
  1047. fclose(clipboard);
  1048. } else {
  1049. selection_text = malloc(cb->size+1);
  1050. memcpy(selection_text, cb->content, cb->size);
  1051. selection_text[cb->size] = '\0';
  1052. }
  1053. handle_clipboard(selection_text);
  1054. free(selection_text);
  1055. }
  1056. break;
  1057. case YUTANI_MSG_WINDOW_MOUSE_EVENT:
  1058. {
  1059. struct yutani_msg_window_mouse_event * me = (void*)m->data;
  1060. yutani_window_t * win = hashmap_get(yctx->windows, (void*)me->wid);
  1061. struct decor_bounds bounds;
  1062. _decor_get_bounds(win, &bounds);
  1063. if (win == main_window) {
  1064. int result = decor_handle_event(yctx, m);
  1065. switch (result) {
  1066. case DECOR_CLOSE:
  1067. _menu_action_exit(NULL);
  1068. break;
  1069. case DECOR_RIGHT:
  1070. /* right click in decoration, show appropriate menu */
  1071. decor_show_default_menu(main_window, main_window->x + me->new_x, main_window->y + me->new_y);
  1072. break;
  1073. default:
  1074. /* Other actions */
  1075. break;
  1076. }
  1077. /* Menu bar */
  1078. menu_bar_mouse_event(yctx, main_window, &menu_bar, me, me->new_x, me->new_y);
  1079. if (menu_bar_height &&
  1080. me->new_y > (int)(bounds.top_height + menu_bar_height - 36) &&
  1081. me->new_y < (int)(bounds.top_height + menu_bar_height) &&
  1082. me->new_x > (int)(bounds.left_width) &&
  1083. me->new_x < (int)(main_window->width - bounds.right_width)) {
  1084. int x = me->new_x - bounds.left_width - 2;
  1085. if (x >= 0) {
  1086. int i = x / 34;
  1087. if (i < 4) {
  1088. if (me->command == YUTANI_MOUSE_EVENT_DOWN) {
  1089. _set_hilight(i, 2);
  1090. _down_button = i;
  1091. } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {
  1092. if (_down_button != -1 && _down_button == i) {
  1093. _handle_button_press(i);
  1094. _set_hilight(i, 1);
  1095. }
  1096. _down_button = -1;
  1097. } else {
  1098. if (!(me->buttons & YUTANI_MOUSE_BUTTON_LEFT)) {
  1099. _set_hilight(i, 1);
  1100. } else {
  1101. if (_down_button == i) {
  1102. _set_hilight(i, 2);
  1103. } else if (_down_button != -1) {
  1104. _set_hilight(_down_button, 3);
  1105. }
  1106. }
  1107. }
  1108. } else {
  1109. _set_hilight(-1,0);
  1110. }
  1111. }
  1112. } else {
  1113. if (_button_hover != -1) {
  1114. _button_hilights[_button_hover] = 3;
  1115. _button_hover = -1;
  1116. redraw_window(); /* Double redraw ??? */
  1117. }
  1118. }
  1119. if (me->new_y > (int)(bounds.top_height + menu_bar_height) &&
  1120. me->new_y < (int)(main_window->height - bounds.bottom_height) &&
  1121. me->new_x > (int)(bounds.left_width) &&
  1122. me->new_x < (int)(main_window->width - bounds.right_width) &&
  1123. me->command != YUTANI_MOUSE_EVENT_LEAVE) {
  1124. if (me->buttons & YUTANI_MOUSE_SCROLL_UP) {
  1125. /* Scroll up */
  1126. scroll_offset -= SCROLL_AMOUNT;
  1127. if (scroll_offset < 0) {
  1128. scroll_offset = 0;
  1129. }
  1130. redraw_window();
  1131. } else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) {
  1132. if (available_height > contents->height) {
  1133. scroll_offset = 0;
  1134. } else {
  1135. scroll_offset += SCROLL_AMOUNT;
  1136. if (scroll_offset > contents->height - available_height) {
  1137. scroll_offset = contents->height - available_height;
  1138. }
  1139. }
  1140. redraw_window();
  1141. }
  1142. /* Get offset into contents */
  1143. int y_into = me->new_y - bounds.top_height - menu_bar_height + scroll_offset;
  1144. int x_into = me->new_x - bounds.left_width;
  1145. int offset = (y_into / FILE_HEIGHT) * FILE_PTR_WIDTH + x_into / FILE_WIDTH;
  1146. if (x_into > FILE_PTR_WIDTH * FILE_WIDTH) {
  1147. offset = -1;
  1148. }
  1149. if (offset != hilighted_offset) {
  1150. int old_offset = hilighted_offset;
  1151. hilighted_offset = offset;
  1152. if (old_offset != -1) {
  1153. clear_offset(old_offset);
  1154. struct File * f = get_file_at_offset(old_offset);
  1155. if (f) {
  1156. clear_offset(old_offset);
  1157. draw_file(f, old_offset);
  1158. }
  1159. }
  1160. struct File * f = get_file_at_offset(hilighted_offset);
  1161. if (f) {
  1162. clear_offset(hilighted_offset);
  1163. draw_file(f, hilighted_offset);
  1164. }
  1165. redraw_window();
  1166. }
  1167. if (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) {
  1168. struct File * f = get_file_at_offset(hilighted_offset);
  1169. if (f) {
  1170. if (last_click_offset == hilighted_offset && precise_time_since(last_click) < 400) {
  1171. open_file(f);
  1172. last_click = 0;
  1173. } else {
  1174. last_click = precise_current_time();
  1175. last_click_offset = hilighted_offset;
  1176. toggle_selected(hilighted_offset, modifiers);
  1177. }
  1178. } else {
  1179. if (!(modifiers & KEY_MOD_LEFT_CTRL)) {
  1180. for (int i = 0; i < file_pointers_len; ++i) {
  1181. if (file_pointers[i]->selected) {
  1182. file_pointers[i]->selected = 0;
  1183. clear_offset(i);
  1184. draw_file(file_pointers[i], i);
  1185. }
  1186. }
  1187. redraw_window();
  1188. }
  1189. }
  1190. } else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {
  1191. if (!context_menu->window) {
  1192. struct File * f = get_file_at_offset(hilighted_offset);
  1193. if (f && !f->selected) {
  1194. toggle_selected(hilighted_offset, modifiers);
  1195. }
  1196. menu_show(context_menu, main_window->ctx);
  1197. yutani_window_move(main_window->ctx, context_menu->window, me->new_x + main_window->x, me->new_y + main_window->y);
  1198. }
  1199. }
  1200. } else {
  1201. int old_offset = hilighted_offset;
  1202. hilighted_offset = -1;
  1203. if (old_offset != -1) {
  1204. clear_offset(old_offset);
  1205. struct File * f = get_file_at_offset(old_offset);
  1206. if (f) {
  1207. clear_offset(old_offset);
  1208. draw_file(f, old_offset);
  1209. }
  1210. redraw_window();
  1211. }
  1212. }
  1213. }
  1214. }
  1215. break;
  1216. case YUTANI_MSG_WINDOW_CLOSE:
  1217. case YUTANI_MSG_SESSION_END:
  1218. _menu_action_exit(NULL);
  1219. break;
  1220. default:
  1221. break;
  1222. }
  1223. free(m);
  1224. m = yutani_poll_async(yctx);
  1225. }
  1226. }
  1227. }