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/keysym.h>
26 #include <X11/XKBlib.h>
27 #include <X11/Xatom.h>
42 #include <libexif/exif-data.h>
50 extern int XpmCreatePixmapFromData(Display
*, Drawable
, char **, Pixmap
*, Pixmap
*, void *);
51 /* this is the icon from eog project
52 git.gnome.org/browse/eog
58 #define FILE_SEPARATOR '/'
66 const char *APPNAME
= "wmiv";
67 int APPVERSION_MAJOR
= 0;
68 int APPVERSION_MINOR
= 7;
71 float zoom_factor
= 0;
75 Bool fullscreen_flag
= False
;
77 Bool back_from_fullscreen
= False
;
80 Bool diaporama_flag
= False
;
81 int diaporama_delay
= 5;
84 XTextProperty title_property
;
85 XTextProperty icon_property
;
86 unsigned current_index
= 1;
87 unsigned max_index
= 1;
94 typedef struct link link_t
;
101 typedef struct linked_list
{
108 link_t
*current_link
;
112 load_oriented_image: used to load an image and optionally
113 get its orientation if libexif is available
114 return the image on success, NULL on failure
116 RImage
*load_oriented_image(RContext
*context
, const char *file
, int index
)
122 image
= RLoadImage(context
, file
, index
);
126 ExifData
*exifData
= exif_data_new_from_file(file
);
128 ExifByteOrder byteOrder
= exif_data_get_byte_order(exifData
);
129 ExifEntry
*exifEntry
= exif_data_get_entry(exifData
, EXIF_TAG_ORIENTATION
);
131 orientation
= exif_get_short(exifEntry
->data
, byteOrder
);
133 exif_data_free(exifData
);
148 if (image
&& (orientation
> 1)) {
150 switch (orientation
) {
152 tmp
= RFlipImage(image
, RHorizontalFlip
);
155 tmp
= RRotateImage(image
, 180);
158 tmp
= RFlipImage(image
, RVerticalFlip
);
162 tmp2
= RFlipImage(image
, RVerticalFlip
);
164 tmp
= RRotateImage(tmp2
, 90);
170 tmp
= RRotateImage(image
, 90);
174 tmp2
= RFlipImage(image
, RVerticalFlip
);
176 tmp
= RRotateImage(tmp2
, 270);
182 tmp
= RRotateImage(image
, 270);
186 RReleaseImage(image
);
195 change_title: used to change window title
196 return EXIT_SUCCESS on success, 1 on failure
198 int change_title(XTextProperty
*prop
, char *filename
)
200 char *combined_title
= NULL
;
201 if (!asprintf(&combined_title
, "%s - %u/%u - %s", APPNAME
, current_index
, max_index
, filename
))
202 if (!asprintf(&combined_title
, "%s - %u/%u", APPNAME
, current_index
, max_index
))
204 XStringListToTextProperty(&combined_title
, 1, prop
);
205 XSetWMName(dpy
, win
, prop
);
208 free(combined_title
);
213 rescale_image: used to rescale the current image based on the screen size
214 return EXIT_SUCCESS on success
216 int rescale_image(void)
218 long final_width
= img
->width
;
219 long final_height
= img
->height
;
221 /* check if there is already a zoom factor applied */
222 if (fabsf(zoom_factor
) <= 0.0f
) {
223 final_width
= img
->width
+ (int)(img
->width
* zoom_factor
);
224 final_height
= img
->height
+ (int)(img
->height
* zoom_factor
);
226 if ((max_width
< final_width
) || (max_height
< final_height
)) {
228 if (final_width
> final_height
) {
229 val
= final_height
* max_width
/ final_width
;
230 final_width
= final_width
* val
/ final_height
;
232 if (val
> max_height
) {
233 val
= final_width
* max_height
/ final_height
;
234 final_height
= final_height
* val
/ final_width
;
238 val
= final_width
* max_height
/ final_height
;
239 final_height
= final_height
* val
/ final_width
;
241 if (val
> max_width
) {
242 val
= final_height
* max_width
/ final_width
;
243 final_width
= final_width
* val
/ final_height
;
248 if ((final_width
!= img
->width
) || (final_height
!= img
->height
)) {
249 RImage
*old_img
= img
;
250 img
= RScaleImage(img
, final_width
, final_height
);
255 RReleaseImage(old_img
);
257 if (!RConvertImage(ctx
, img
, &pix
)) {
258 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
265 maximize_image: find the best image size for the current display
266 return EXIT_SUCCESS on success
268 int maximize_image(void)
271 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
272 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
277 merge_with_background: merge the current image with with a checkboard background
278 return EXIT_SUCCESS on success, 1 on failure
280 int merge_with_background(RImage
*i
)
284 back
= RCreateImage(i
->width
, i
->height
, True
);
289 RFillImage(back
, &lightGray
);
290 for (x
= 0; x
<= i
->width
; x
+= 8) {
295 for (; y
<= i
->height
; y
+= 16)
296 ROperateRectangle(back
, RAddOperation
, x
, y
, x
+8, y
+8, &darkGray
);
299 RCombineImagesWithOpaqueness(i
, back
, opaq
);
308 turn_image: rotate the image by the angle passed
309 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
311 int turn_image(float angle
)
318 tmp
= RRotateImage(img
, angle
);
322 if (!fullscreen_flag
) {
323 if (img
->width
!= tmp
->width
|| img
->height
!= tmp
->height
)
324 XResizeWindow(dpy
, win
, tmp
->width
, tmp
->height
);
331 if (!fullscreen_flag
) {
332 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
334 XClearWindow(dpy
, win
);
335 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
336 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
343 turn_image_right: rotate the image by 90 degree
344 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
346 int turn_image_right(void)
348 return turn_image(90.0);
352 turn_image_left: rotate the image by -90 degree
353 return EXIT_SUCCESS on success, 1 on failure
355 int turn_image_left(void)
357 return turn_image(-90.0);
361 draw_failed_image: create a red crossed image to indicate an error loading file
362 return the image on success, NULL on failure
365 RImage
*draw_failed_image(void)
367 RImage
*failed_image
= NULL
;
368 XWindowAttributes attr
;
370 if (win
&& (XGetWindowAttributes(dpy
, win
, &attr
) >= 0))
371 failed_image
= RCreateImage(attr
.width
, attr
.height
, False
);
373 failed_image
= RCreateImage(50, 50, False
);
377 RFillImage(failed_image
, &black
);
378 ROperateLine(failed_image
, RAddOperation
, 0, 0, failed_image
->width
, failed_image
->height
, &red
);
379 ROperateLine(failed_image
, RAddOperation
, 0, failed_image
->height
, failed_image
->width
, 0, &red
);
385 full_screen: sending event to the window manager to switch from/to full screen mode
386 return EXIT_SUCCESS on success, 1 on failure
388 int full_screen(void)
392 Atom wm_state
= XInternAtom(dpy
, "_NET_WM_STATE", True
);
393 Atom fullscreen
= XInternAtom(dpy
, "_NET_WM_STATE_FULLSCREEN", True
);
394 long mask
= SubstructureNotifyMask
;
396 if (fullscreen_flag
) {
397 fullscreen_flag
= False
;
399 back_from_fullscreen
= True
;
401 fullscreen_flag
= True
;
405 memset(&xev
, 0, sizeof(xev
));
406 xev
.type
= ClientMessage
;
407 xev
.xclient
.display
= dpy
;
408 xev
.xclient
.window
= win
;
409 xev
.xclient
.message_type
= wm_state
;
410 xev
.xclient
.format
= 32;
411 xev
.xclient
.data
.l
[0] = fullscreen_flag
;
412 xev
.xclient
.data
.l
[1] = fullscreen
;
414 if (!XSendEvent(dpy
, DefaultRootWindow(dpy
), False
, mask
, &xev
)) {
415 fprintf(stderr
, "Error: sending fullscreen event to xserver\n");
422 zoom_in_out: apply a zoom factor on the current image
423 arg: 1 to zoom in, 0 to zoom out
424 return EXIT_SUCCESS on success, 1 on failure
426 int zoom_in_out(int z
)
428 RImage
*old_img
= img
;
429 RImage
*tmp
= load_oriented_image(ctx
, current_link
->data
, 0);
435 img
= RScaleImage(tmp
, tmp
->width
+ (int)(tmp
->width
* zoom_factor
),
436 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
);
456 RReleaseImage(old_img
);
458 XFreePixmap(dpy
, pix
);
460 merge_with_background(img
);
461 if (!RConvertImage(ctx
, img
, &pix
)) {
462 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
465 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
470 zoom_in: transitional fct used to call zoom_in_out with zoom in flag
471 return EXIT_SUCCESS on success, 1 on failure
475 return zoom_in_out(1);
479 zoom_out: transitional fct used to call zoom_in_out with zoom out flag
480 return EXIT_SUCCESS on success, 1 on failure
484 return zoom_in_out(0);
488 change_image: load previous or next image
489 arg: way which could be PREV or NEXT constant
490 return EXIT_SUCCESS on success, 1 on failure
492 int change_image(int way
)
494 if (img
&& current_link
) {
495 int old_img_width
= img
->width
;
496 int old_img_height
= img
->height
;
501 current_link
= current_link
->next
;
504 current_link
= current_link
->prev
;
507 if (current_link
== NULL
) {
509 current_link
= list
.first
;
512 current_link
= list
.last
;
513 current_index
= max_index
;
517 fprintf(stderr
, "current file is> %s\n", (char *)current_link
->data
);
518 img
= load_oriented_image(ctx
, current_link
->data
, 0);
521 fprintf(stderr
, "Error: %s %s\n", (char *)current_link
->data
,
522 RMessageForError(RErrorCode
));
523 img
= draw_failed_image();
525 merge_with_background(img
);
528 if (!fullscreen_flag
) {
529 if ((old_img_width
!= img
->width
) || (old_img_height
!= img
->height
))
530 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
532 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
533 change_title(&title_property
, (char *)current_link
->data
);
535 XClearWindow(dpy
, win
);
536 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
537 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
546 diaporama: send a xevent to display the next image at every delay set to diaporama_delay
550 void *diaporama(void *arg
)
557 event
.root
= DefaultRootWindow(dpy
);
558 event
.subwindow
= None
;
559 event
.time
= CurrentTime
;
564 event
.same_screen
= True
;
565 event
.keycode
= XKeysymToKeycode(dpy
, XK_Right
);
567 event
.type
= KeyPress
;
569 while (diaporama_flag
) {
571 r
= XSendEvent(event
.display
, event
.window
, True
, KeyPressMask
, (XEvent
*)&event
);
573 fprintf(stderr
, "Error sending event\n");
575 /* default sleep time between moving to next image */
576 sleep(diaporama_delay
);
584 linked_list_init: init the linked list
586 void linked_list_init(linked_list_t
*list
)
588 list
->first
= list
->last
= 0;
593 linked_list_add: add an element to the linked list
594 return EXIT_SUCCESS on success, 1 otherwise
596 int linked_list_add(linked_list_t
*list
, const void *data
)
600 /* calloc sets the "next" field to zero. */
601 link
= calloc(1, sizeof(link_t
));
603 fprintf(stderr
, "Error: memory allocation failed\n");
608 /* Join the two final links together. */
609 list
->last
->next
= link
;
610 link
->prev
= list
->last
;
621 linked_list_free: deallocate the whole linked list
623 void linked_list_free(linked_list_t
*list
)
627 for (link
= list
->first
; link
; link
= next
) {
628 /* Store the next value so that we don't access freed memory. */
631 free((char *)link
->data
);
637 connect_dir: list and sort by name all files from a given directory
638 arg: the directory path that contains images, the linked list where to add the new file refs
639 return: the first argument of the list or NULL on failure
641 link_t
*connect_dir(char *dirpath
, linked_list_t
*li
)
645 char path
[PATH_MAX
] = "";
650 dv
= scandir(dirpath
, &dir
, 0, alphasort
);
652 /* maybe it's a file */
653 struct stat stDirInfo
;
654 if (lstat(dirpath
, &stDirInfo
) == 0) {
655 linked_list_add(li
, strdup(dirpath
));
661 for (idx
= 0; idx
< dv
; idx
++) {
662 struct stat stDirInfo
;
663 if (dirpath
[strlen(dirpath
)-1] == FILE_SEPARATOR
)
664 snprintf(path
, PATH_MAX
, "%s%s", dirpath
, dir
[idx
]->d_name
);
666 snprintf(path
, PATH_MAX
, "%s%c%s", dirpath
, FILE_SEPARATOR
, dir
[idx
]->d_name
);
669 if ((lstat(path
, &stDirInfo
) == 0) && !S_ISDIR(stDirInfo
.st_mode
))
670 linked_list_add(li
, strdup(path
));
679 int main(int argc
, char **argv
)
682 RContextAttributes attr
;
685 char *reading_filename
= "";
688 XClassHint
*class_hints
;
689 XSizeHints
*size_hints
;
692 Pixmap icon_pixmap
, icon_shape
;
694 class_hints
= XAllocClassHint();
696 fprintf(stderr
, "Error: failure allocating memory\n");
699 class_hints
->res_name
= (char *)APPNAME
;
700 class_hints
->res_class
= "default";
703 lightGray
.red
= lightGray
.green
= lightGray
.blue
= 211;
704 darkGray
.red
= darkGray
.green
= darkGray
.blue
= 169;
705 lightGray
.alpha
= darkGray
.alpha
= 1;
706 black
.red
= black
.green
= black
.blue
= 0;
708 red
.green
= red
.blue
= 0;
710 static struct option long_options
[] = {
711 {"version", no_argument
, 0, 'v'},
712 {"help", no_argument
, 0, 'h'},
715 int option_index
= 0;
717 option
= getopt_long (argc
, argv
, "hv", long_options
, &option_index
);
721 printf("Usage: %s [image(s)|directory]\n"
723 " -h, --help print this help text\n"
724 " -v, --version print version\n"
728 " [Esc] actual size\n"
730 " [D] launch diaporama mode\n"
732 " [L] rotate image on the left\n"
734 " [R] rotate image on the right\n"
735 " [â–¸] next image\n"
736 " [â—‚] previous image\n"
737 " [â–´] first image\n"
738 " [â–¾] last image\n",
742 fprintf(stderr
, "%s version %d.%d\n", APPNAME
, APPVERSION_MAJOR
, APPVERSION_MINOR
);
749 linked_list_init(&list
);
751 dpy
= XOpenDisplay(NULL
);
753 fprintf(stderr
, "Error: can't open display");
754 linked_list_free(&list
);
758 screen
= DefaultScreen(dpy
);
759 max_width
= DisplayWidth(dpy
, screen
);
760 max_height
= DisplayHeight(dpy
, screen
);
762 attr
.flags
= RC_RenderMode
| RC_ColorsPerChannel
;
763 attr
.render_mode
= RDitheredRendering
;
764 attr
.colors_per_channel
= 4;
765 ctx
= RCreateContext(dpy
, DefaultScreen(dpy
), &attr
);
772 for (file_i
= 1; file_i
< argc
; file_i
++) {
773 current_link
= connect_dir(argv
[file_i
], &list
);
775 reading_filename
= (char *)current_link
->data
;
776 max_index
= list
.count
;
780 img
= load_oriented_image(ctx
, reading_filename
, 0);
783 fprintf(stderr
, "Error: %s %s\n", reading_filename
, RMessageForError(RErrorCode
));
784 img
= draw_failed_image();
789 merge_with_background(img
);
793 fprintf(stderr
, "display size: %dx%d\n", max_width
, max_height
);
795 win
= XCreateSimpleWindow(dpy
, DefaultRootWindow(dpy
), 0, 0,
796 img
->width
, img
->height
, 0, 0, BlackPixel(dpy
, screen
));
797 XSelectInput(dpy
, win
, KeyPressMask
|StructureNotifyMask
|ExposureMask
|ButtonPressMask
|FocusChangeMask
);
799 size_hints
= XAllocSizeHints();
801 fprintf(stderr
, "Error: failure allocating memory\n");
804 size_hints
->width
= img
->width
;
805 size_hints
->height
= img
->height
;
807 Atom delWindow
= XInternAtom(dpy
, "WM_DELETE_WINDOW", 0);
808 XSetWMProtocols(dpy
, win
, &delWindow
, 1);
809 change_title(&title_property
, reading_filename
);
811 win_hints
= XAllocWMHints();
813 win_hints
->flags
= StateHint
|InputHint
|WindowGroupHint
;
816 if ((XpmCreatePixmapFromData(dpy
, win
, wmiv_xpm
, &icon_pixmap
, &icon_shape
, NULL
)) == 0) {
817 win_hints
->flags
|= IconPixmapHint
|IconMaskHint
|IconPositionHint
;
818 win_hints
->icon_pixmap
= icon_pixmap
;
819 win_hints
->icon_mask
= icon_shape
;
820 win_hints
->icon_x
= 0;
821 win_hints
->icon_y
= 0;
824 win_hints
->initial_state
= NormalState
;
825 win_hints
->input
= True
;
826 win_hints
->window_group
= win
;
827 XStringListToTextProperty((char **)&APPNAME
, 1, &icon_property
);
828 XSetWMProperties(dpy
, win
, NULL
, &icon_property
, argv
, argc
, size_hints
, win_hints
, class_hints
);
829 if (icon_property
.value
)
830 XFree(icon_property
.value
);
836 XMapWindow(dpy
, win
);
838 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
842 if (e
.type
== ClientMessage
) {
843 if (e
.xclient
.data
.l
[0] == delWindow
)
847 * This break could be related to all ClientMessages or
848 * related to delWindow. Before the patch about this comment
849 * the break was indented with one tab more (at the same level
850 * than "quit = 1;" in the previous line.
854 if (e
.type
== FocusIn
) {
858 if (e
.type
== FocusOut
) {
862 if (!fullscreen_flag
&& (e
.type
== Expose
)) {
863 XExposeEvent xev
= e
.xexpose
;
865 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
868 if (!fullscreen_flag
&& e
.type
== ConfigureNotify
) {
869 XConfigureEvent xce
= e
.xconfigure
;
870 if (xce
.width
!= img
->width
|| xce
.height
!= img
->height
) {
871 RImage
*old_img
= img
;
872 img
= load_oriented_image(ctx
, current_link
->data
, 0);
874 /* keep the old img and window size */
876 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
879 if (!back_from_fullscreen
)
880 /* manually resized window */
881 tmp2
= RScaleImage(img
, xce
.width
, xce
.height
);
883 /* back from fullscreen mode, maybe img was rotated */
885 back_from_fullscreen
= False
;
886 XClearWindow(dpy
, win
);
888 merge_with_background(tmp2
);
889 if (RConvertImage(ctx
, tmp2
, &pix
)) {
890 RReleaseImage(old_img
);
891 img
= RCloneImage(tmp2
);
893 change_title(&title_property
, (char *)current_link
->data
);
895 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
896 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
897 img
->width
, img
->height
, 0, 0);
904 if (fullscreen_flag
&& e
.type
== ConfigureNotify
) {
908 if (e
.type
== ButtonPress
) {
909 switch (e
.xbutton
.button
) {
912 if (img
&& (e
.xbutton
.x
> img
->width
/2))
934 if (e
.type
== KeyPress
) {
935 keysym
= XkbKeycodeToKeysym(dpy
, e
.xkey
.keycode
, 0, e
.xkey
.state
& ShiftMask
?1:0);
937 if (keysym
!= XK_Right
)
938 diaporama_flag
= False
;
949 current_link
= list
.last
;
955 current_link
= list
.first
;
963 if (current_link
&& !diaporama_flag
) {
964 diaporama_flag
= True
;
965 pthread_create(&tid
, NULL
, &diaporama
, NULL
);
967 fprintf(stderr
, "Can't use diaporama mode\n");
976 if (!fullscreen_flag
) {
978 /* zoom_in will increase the zoom factor by 0.2 */
981 /* we are in fullscreen mode already, want to return to normal size */
1009 XFreePixmap(dpy
, pix
);
1012 XFreePixmap(dpy
, icon_pixmap
);
1014 XFreePixmap(dpy
, icon_shape
);
1016 linked_list_free(&list
);
1017 RDestroyContext(ctx
);
1020 return EXIT_SUCCESS
;