file-browser.c 44 KB

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