2 * Window Maker window manager
4 * Copyright (c) 2014 Window Maker Team - David Maciejak
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #if !defined(_GNU_SOURCE)
25 #include <X11/Xatom.h>
27 #include <WINGs/WINGsP.h>
41 #include <libexif/exif-data.h>
49 extern int XpmCreatePixmapFromData(Display
*, Drawable
, char **, Pixmap
*, Pixmap
*, void *);
50 /* this is the icon from eog project
51 git.gnome.org/browse/eog
57 #define FILE_SEPARATOR '/'
65 const char *APPNAME
= "wmiv";
66 int APPVERSION_MAJOR
= 0;
67 int APPVERSION_MINOR
= 7;
70 float zoom_factor
= 0;
74 Bool fullscreen_flag
= False
;
76 Bool back_from_fullscreen
= False
;
79 Bool diaporama_flag
= False
;
80 int diaporama_delay
= 5;
83 XTextProperty title_property
;
84 XTextProperty icon_property
;
85 unsigned current_index
= 1;
86 unsigned max_index
= 1;
93 typedef struct link link_t
;
100 typedef struct linked_list
{
107 link_t
*current_link
;
111 load_oriented_image: used to load an image and optionally
112 get its orientation if libexif is available
113 return the image on success, NULL on failure
115 RImage
*load_oriented_image(RContext
*context
, const char *file
, int index
)
121 image
= RLoadImage(context
, file
, index
);
125 ExifData
*exifData
= exif_data_new_from_file(file
);
127 ExifByteOrder byteOrder
= exif_data_get_byte_order(exifData
);
128 ExifEntry
*exifEntry
= exif_data_get_entry(exifData
, EXIF_TAG_ORIENTATION
);
130 orientation
= exif_get_short(exifEntry
->data
, byteOrder
);
132 exif_data_free(exifData
);
147 if (image
&& (orientation
> 1)) {
149 switch (orientation
) {
151 tmp
= RFlipImage(image
, RHorizontalFlip
);
154 tmp
= RRotateImage(image
, 180);
157 tmp
= RFlipImage(image
, RVerticalFlip
);
161 tmp2
= RFlipImage(image
, RVerticalFlip
);
163 tmp
= RRotateImage(tmp2
, 90);
169 tmp
= RRotateImage(image
, 90);
173 tmp2
= RFlipImage(image
, RVerticalFlip
);
175 tmp
= RRotateImage(tmp2
, 270);
181 tmp
= RRotateImage(image
, 270);
185 RReleaseImage(image
);
194 change_title: used to change window title
195 return EXIT_SUCCESS on success, 1 on failure
197 int change_title(XTextProperty
*prop
, char *filename
)
199 char *combined_title
= NULL
;
200 if (!asprintf(&combined_title
, "%s - %u/%u - %s", APPNAME
, current_index
, max_index
, filename
))
201 if (!asprintf(&combined_title
, "%s - %u/%u", APPNAME
, current_index
, max_index
))
203 XStringListToTextProperty(&combined_title
, 1, prop
);
204 XSetWMName(dpy
, win
, prop
);
207 free(combined_title
);
212 rescale_image: used to rescale the current image based on the screen size
213 return EXIT_SUCCESS on success
215 int rescale_image(void)
217 long final_width
= img
->width
;
218 long final_height
= img
->height
;
220 /* check if there is already a zoom factor applied */
221 if (fabsf(zoom_factor
) <= 0.0f
) {
222 final_width
= img
->width
+ (int)(img
->width
* zoom_factor
);
223 final_height
= img
->height
+ (int)(img
->height
* zoom_factor
);
225 if ((max_width
< final_width
) || (max_height
< final_height
)) {
227 if (final_width
> final_height
) {
228 val
= final_height
* max_width
/ final_width
;
229 final_width
= final_width
* val
/ final_height
;
231 if (val
> max_height
) {
232 val
= final_width
* max_height
/ final_height
;
233 final_height
= final_height
* val
/ final_width
;
237 val
= final_width
* max_height
/ final_height
;
238 final_height
= final_height
* val
/ final_width
;
240 if (val
> max_width
) {
241 val
= final_height
* max_width
/ final_width
;
242 final_width
= final_width
* val
/ final_height
;
247 if ((final_width
!= img
->width
) || (final_height
!= img
->height
)) {
248 RImage
*old_img
= img
;
249 img
= RScaleImage(img
, final_width
, final_height
);
254 RReleaseImage(old_img
);
256 if (!RConvertImage(ctx
, img
, &pix
)) {
257 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
264 maximize_image: find the best image size for the current display
265 return EXIT_SUCCESS on success
267 int maximize_image(void)
270 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
271 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
276 merge_with_background: merge the current image with with a checkboard background
277 return EXIT_SUCCESS on success, 1 on failure
279 int merge_with_background(RImage
*i
)
283 back
= RCreateImage(i
->width
, i
->height
, True
);
288 RFillImage(back
, &lightGray
);
289 for (x
= 0; x
<= i
->width
; x
+= 8) {
294 for (; y
<= i
->height
; y
+= 16)
295 ROperateRectangle(back
, RAddOperation
, x
, y
, x
+8, y
+8, &darkGray
);
298 RCombineImagesWithOpaqueness(i
, back
, opaq
);
307 turn_image: rotate the image by the angle passed
308 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
310 int turn_image(float angle
)
317 tmp
= RRotateImage(img
, angle
);
321 if (!fullscreen_flag
) {
322 if (img
->width
!= tmp
->width
|| img
->height
!= tmp
->height
)
323 XResizeWindow(dpy
, win
, tmp
->width
, tmp
->height
);
330 if (!fullscreen_flag
) {
331 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
333 XClearWindow(dpy
, win
);
334 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
335 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
342 turn_image_right: rotate the image by 90 degree
343 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
345 int turn_image_right(void)
347 return turn_image(90.0);
351 turn_image_left: rotate the image by -90 degree
352 return EXIT_SUCCESS on success, 1 on failure
354 int turn_image_left(void)
356 return turn_image(-90.0);
360 draw_failed_image: create a red crossed image to indicate an error loading file
361 return the image on success, NULL on failure
364 RImage
*draw_failed_image(void)
366 RImage
*failed_image
= NULL
;
367 XWindowAttributes attr
;
369 if (win
&& (XGetWindowAttributes(dpy
, win
, &attr
) >= 0))
370 failed_image
= RCreateImage(attr
.width
, attr
.height
, False
);
372 failed_image
= RCreateImage(50, 50, False
);
376 RFillImage(failed_image
, &black
);
377 ROperateLine(failed_image
, RAddOperation
, 0, 0, failed_image
->width
, failed_image
->height
, &red
);
378 ROperateLine(failed_image
, RAddOperation
, 0, failed_image
->height
, failed_image
->width
, 0, &red
);
384 full_screen: sending event to the window manager to switch from/to full screen mode
385 return EXIT_SUCCESS on success, 1 on failure
387 int full_screen(void)
391 Atom wm_state
= XInternAtom(dpy
, "_NET_WM_STATE", True
);
392 Atom fullscreen
= XInternAtom(dpy
, "_NET_WM_STATE_FULLSCREEN", True
);
393 long mask
= SubstructureNotifyMask
;
395 if (fullscreen_flag
) {
396 fullscreen_flag
= False
;
398 back_from_fullscreen
= True
;
400 fullscreen_flag
= True
;
404 memset(&xev
, 0, sizeof(xev
));
405 xev
.type
= ClientMessage
;
406 xev
.xclient
.display
= dpy
;
407 xev
.xclient
.window
= win
;
408 xev
.xclient
.message_type
= wm_state
;
409 xev
.xclient
.format
= 32;
410 xev
.xclient
.data
.l
[0] = fullscreen_flag
;
411 xev
.xclient
.data
.l
[1] = fullscreen
;
413 if (!XSendEvent(dpy
, DefaultRootWindow(dpy
), False
, mask
, &xev
)) {
414 fprintf(stderr
, "Error: sending fullscreen event to xserver\n");
421 zoom_in_out: apply a zoom factor on the current image
422 arg: 1 to zoom in, 0 to zoom out
423 return EXIT_SUCCESS on success, 1 on failure
425 int zoom_in_out(int z
)
427 RImage
*old_img
= img
;
428 RImage
*tmp
= load_oriented_image(ctx
, current_link
->data
, 0);
434 img
= RScaleImage(tmp
, tmp
->width
+ (int)(tmp
->width
* zoom_factor
),
435 tmp
->height
+ (int)(tmp
->height
* zoom_factor
));
443 int new_width
= tmp
->width
+ (int) (tmp
->width
* zoom_factor
);
444 int new_height
= tmp
->height
+ (int)(tmp
->height
* zoom_factor
);
445 if ((new_width
<= 0) || (new_height
<= 0)) {
450 img
= RScaleImage(tmp
, new_width
, new_height
);
457 RReleaseImage(old_img
);
459 XFreePixmap(dpy
, pix
);
461 merge_with_background(img
);
462 if (!RConvertImage(ctx
, img
, &pix
)) {
463 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
466 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
471 zoom_in: transitional fct used to call zoom_in_out with zoom in flag
472 return EXIT_SUCCESS on success, 1 on failure
476 return zoom_in_out(1);
480 zoom_out: transitional fct used to call zoom_in_out with zoom out flag
481 return EXIT_SUCCESS on success, 1 on failure
485 return zoom_in_out(0);
489 change_image: load previous or next image
490 arg: way which could be PREV or NEXT constant
491 return EXIT_SUCCESS on success, 1 on failure
493 int change_image(int way
)
495 if (img
&& current_link
) {
496 int old_img_width
= img
->width
;
497 int old_img_height
= img
->height
;
502 current_link
= current_link
->next
;
505 current_link
= current_link
->prev
;
508 if (current_link
== NULL
) {
510 current_link
= list
.first
;
513 current_link
= list
.last
;
514 current_index
= max_index
;
518 fprintf(stderr
, "current file is> %s\n", (char *)current_link
->data
);
519 img
= load_oriented_image(ctx
, current_link
->data
, 0);
522 fprintf(stderr
, "Error: %s %s\n", (char *)current_link
->data
,
523 RMessageForError(RErrorCode
));
524 img
= draw_failed_image();
526 merge_with_background(img
);
529 if (!fullscreen_flag
) {
530 if ((old_img_width
!= img
->width
) || (old_img_height
!= img
->height
))
531 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
533 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
534 change_title(&title_property
, (char *)current_link
->data
);
536 XClearWindow(dpy
, win
);
537 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
538 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
547 diaporama: send a xevent to display the next image at every delay set to diaporama_delay
551 void *diaporama(void *arg
)
558 event
.root
= DefaultRootWindow(dpy
);
559 event
.subwindow
= None
;
560 event
.time
= CurrentTime
;
565 event
.same_screen
= True
;
566 event
.keycode
= XKeysymToKeycode(dpy
, XK_Right
);
568 event
.type
= KeyPress
;
570 while (diaporama_flag
) {
572 r
= XSendEvent(event
.display
, event
.window
, True
, KeyPressMask
, (XEvent
*)&event
);
574 fprintf(stderr
, "Error sending event\n");
576 /* default sleep time between moving to next image */
577 sleep(diaporama_delay
);
585 linked_list_init: init the linked list
587 void linked_list_init(linked_list_t
*list
)
589 list
->first
= list
->last
= 0;
594 linked_list_add: add an element to the linked list
595 return EXIT_SUCCESS on success, 1 otherwise
597 int linked_list_add(linked_list_t
*list
, const void *data
)
601 /* calloc sets the "next" field to zero. */
602 link
= calloc(1, sizeof(link_t
));
604 fprintf(stderr
, "Error: memory allocation failed\n");
609 /* Join the two final links together. */
610 list
->last
->next
= link
;
611 link
->prev
= list
->last
;
622 linked_list_free: deallocate the whole linked list
624 void linked_list_free(linked_list_t
*list
)
628 for (link
= list
->first
; link
; link
= next
) {
629 /* Store the next value so that we don't access freed memory. */
632 free((char *)link
->data
);
638 connect_dir: list and sort by name all files from a given directory
639 arg: the directory path that contains images, the linked list where to add the new file refs
640 return: the first argument of the list or NULL on failure
642 link_t
*connect_dir(char *dirpath
, linked_list_t
*li
)
646 char path
[PATH_MAX
] = "";
651 dv
= scandir(dirpath
, &dir
, 0, alphasort
);
653 /* maybe it's a file */
654 struct stat stDirInfo
;
655 if (lstat(dirpath
, &stDirInfo
) == 0) {
656 linked_list_add(li
, strdup(dirpath
));
662 for (idx
= 0; idx
< dv
; idx
++) {
663 struct stat stDirInfo
;
664 if (dirpath
[strlen(dirpath
)-1] == FILE_SEPARATOR
)
665 snprintf(path
, PATH_MAX
, "%s%s", dirpath
, dir
[idx
]->d_name
);
667 snprintf(path
, PATH_MAX
, "%s%c%s", dirpath
, FILE_SEPARATOR
, dir
[idx
]->d_name
);
670 if ((lstat(path
, &stDirInfo
) == 0) && !S_ISDIR(stDirInfo
.st_mode
))
671 linked_list_add(li
, strdup(path
));
680 int main(int argc
, char **argv
)
683 RContextAttributes attr
= {};
686 char *reading_filename
= "";
689 XClassHint
*class_hints
;
690 XSizeHints
*size_hints
;
693 Pixmap icon_pixmap
, icon_shape
;
695 class_hints
= XAllocClassHint();
697 fprintf(stderr
, "Error: failure allocating memory\n");
700 class_hints
->res_name
= (char *)APPNAME
;
701 class_hints
->res_class
= "default";
704 lightGray
.red
= lightGray
.green
= lightGray
.blue
= 211;
705 darkGray
.red
= darkGray
.green
= darkGray
.blue
= 169;
706 lightGray
.alpha
= darkGray
.alpha
= 1;
707 black
.red
= black
.green
= black
.blue
= 0;
709 red
.green
= red
.blue
= 0;
711 static struct option long_options
[] = {
712 {"version", no_argument
, 0, 'v'},
713 {"help", no_argument
, 0, 'h'},
716 int option_index
= 0;
718 option
= getopt_long (argc
, argv
, "hv", long_options
, &option_index
);
722 printf("Usage: %s [image(s)|directory]\n"
724 " -h, --help print this help text\n"
725 " -v, --version print version\n"
729 " [Esc] actual size\n"
731 " [D] launch diaporama mode\n"
733 " [L] rotate image on the left\n"
735 " [R] rotate image on the right\n"
736 " [â–¸] next image\n"
737 " [â—‚] previous image\n"
738 " [â–´] first image\n"
739 " [â–¾] last image\n",
743 fprintf(stderr
, "%s version %d.%d\n", APPNAME
, APPVERSION_MAJOR
, APPVERSION_MINOR
);
750 linked_list_init(&list
);
752 dpy
= XOpenDisplay(NULL
);
754 fprintf(stderr
, "Error: can't open display\n");
755 linked_list_free(&list
);
759 screen
= DefaultScreen(dpy
);
760 max_width
= DisplayWidth(dpy
, screen
);
761 max_height
= DisplayHeight(dpy
, screen
);
763 attr
.flags
= RC_RenderMode
| RC_ColorsPerChannel
;
764 attr
.render_mode
= RDitheredRendering
;
765 attr
.colors_per_channel
= 4;
766 ctx
= RCreateContext(dpy
, DefaultScreen(dpy
), &attr
);
773 for (file_i
= 1; file_i
< argc
; file_i
++) {
774 current_link
= connect_dir(argv
[file_i
], &list
);
776 reading_filename
= (char *)current_link
->data
;
777 max_index
= list
.count
;
781 img
= load_oriented_image(ctx
, reading_filename
, 0);
784 fprintf(stderr
, "Error: %s %s\n", reading_filename
, RMessageForError(RErrorCode
));
785 img
= draw_failed_image();
790 merge_with_background(img
);
794 fprintf(stderr
, "display size: %dx%d\n", max_width
, max_height
);
796 win
= XCreateSimpleWindow(dpy
, DefaultRootWindow(dpy
), 0, 0,
797 img
->width
, img
->height
, 0, 0, BlackPixel(dpy
, screen
));
798 XSelectInput(dpy
, win
, KeyPressMask
|StructureNotifyMask
|ExposureMask
|ButtonPressMask
|FocusChangeMask
);
800 size_hints
= XAllocSizeHints();
802 fprintf(stderr
, "Error: failure allocating memory\n");
805 size_hints
->width
= img
->width
;
806 size_hints
->height
= img
->height
;
808 Atom delWindow
= XInternAtom(dpy
, "WM_DELETE_WINDOW", 0);
809 XSetWMProtocols(dpy
, win
, &delWindow
, 1);
810 change_title(&title_property
, reading_filename
);
812 win_hints
= XAllocWMHints();
814 win_hints
->flags
= StateHint
|InputHint
|WindowGroupHint
;
817 if ((XpmCreatePixmapFromData(dpy
, win
, wmiv_xpm
, &icon_pixmap
, &icon_shape
, NULL
)) == 0) {
818 win_hints
->flags
|= IconPixmapHint
|IconMaskHint
|IconPositionHint
;
819 win_hints
->icon_pixmap
= icon_pixmap
;
820 win_hints
->icon_mask
= icon_shape
;
821 win_hints
->icon_x
= 0;
822 win_hints
->icon_y
= 0;
825 win_hints
->initial_state
= NormalState
;
826 win_hints
->input
= True
;
827 win_hints
->window_group
= win
;
828 XStringListToTextProperty((char **)&APPNAME
, 1, &icon_property
);
829 XSetWMProperties(dpy
, win
, NULL
, &icon_property
, argv
, argc
, size_hints
, win_hints
, class_hints
);
830 if (icon_property
.value
)
831 XFree(icon_property
.value
);
837 XMapWindow(dpy
, win
);
839 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
843 if (e
.type
== ClientMessage
) {
844 if (e
.xclient
.data
.l
[0] == delWindow
)
848 * This break could be related to all ClientMessages or
849 * related to delWindow. Before the patch about this comment
850 * the break was indented with one tab more (at the same level
851 * than "quit = 1;" in the previous line.
855 if (e
.type
== FocusIn
) {
859 if (e
.type
== FocusOut
) {
863 if (!fullscreen_flag
&& (e
.type
== Expose
)) {
864 XExposeEvent xev
= e
.xexpose
;
866 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
869 if (!fullscreen_flag
&& e
.type
== ConfigureNotify
) {
870 XConfigureEvent xce
= e
.xconfigure
;
871 if (xce
.width
!= img
->width
|| xce
.height
!= img
->height
) {
872 RImage
*old_img
= img
;
873 img
= load_oriented_image(ctx
, current_link
->data
, 0);
875 /* keep the old img and window size */
877 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
880 if (!back_from_fullscreen
)
881 /* manually resized window */
882 tmp2
= RScaleImage(img
, xce
.width
, xce
.height
);
884 /* back from fullscreen mode, maybe img was rotated */
886 back_from_fullscreen
= False
;
887 XClearWindow(dpy
, win
);
889 merge_with_background(tmp2
);
890 if (RConvertImage(ctx
, tmp2
, &pix
)) {
891 RReleaseImage(old_img
);
892 img
= RCloneImage(tmp2
);
894 change_title(&title_property
, (char *)current_link
->data
);
896 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
897 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
898 img
->width
, img
->height
, 0, 0);
905 if (fullscreen_flag
&& e
.type
== ConfigureNotify
) {
909 if (e
.type
== ButtonPress
) {
910 switch (e
.xbutton
.button
) {
913 if (img
&& (e
.xbutton
.x
> img
->width
/2))
935 if (e
.type
== KeyPress
) {
936 keysym
= W_KeycodeToKeysym(dpy
, e
.xkey
.keycode
, e
.xkey
.state
& ShiftMask
?1:0);
938 if (keysym
!= XK_Right
)
939 diaporama_flag
= False
;
950 current_link
= list
.last
;
956 current_link
= list
.first
;
964 if (current_link
&& !diaporama_flag
) {
965 diaporama_flag
= True
;
966 pthread_create(&tid
, NULL
, &diaporama
, NULL
);
968 fprintf(stderr
, "Can't use diaporama mode\n");
977 if (!fullscreen_flag
) {
979 /* zoom_in will increase the zoom factor by 0.2 */
982 /* we are in fullscreen mode already, want to return to normal size */
1010 XFreePixmap(dpy
, pix
);
1013 XFreePixmap(dpy
, icon_pixmap
);
1015 XFreePixmap(dpy
, icon_shape
);
1017 linked_list_free(&list
);
1018 RDestroyContext(ctx
);
1021 return EXIT_SUCCESS
;