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>
40 #include <libexif/exif-data.h>
48 extern int XpmCreatePixmapFromData(Display
*, Drawable
, char **, Pixmap
*, Pixmap
*, void *);
49 /* this is the icon from eog project
50 git.gnome.org/browse/eog
56 #define FILE_SEPARATOR '/'
64 const char *APPNAME
= "wmiv";
65 int APPVERSION_MAJOR
= 0;
66 int APPVERSION_MINOR
= 7;
69 float zoom_factor
= 0;
73 Bool fullscreen_flag
= False
;
75 Bool back_from_fullscreen
= False
;
78 Bool diaporama_flag
= False
;
79 int diaporama_delay
= 5;
82 XTextProperty title_property
;
83 XTextProperty icon_property
;
84 unsigned current_index
= 1;
85 unsigned max_index
= 1;
92 typedef struct link link_t
;
99 typedef struct linked_list
{
106 link_t
*current_link
;
110 load_oriented_image: used to load an image and optionally
111 get its orientation if libexif is available
112 return the image on success, NULL on failure
114 RImage
*load_oriented_image(RContext
*context
, const char *file
, int index
)
120 image
= RLoadImage(context
, file
, index
);
124 ExifData
*exifData
= exif_data_new_from_file(file
);
126 ExifByteOrder byteOrder
= exif_data_get_byte_order(exifData
);
127 ExifEntry
*exifEntry
= exif_data_get_entry(exifData
, EXIF_TAG_ORIENTATION
);
129 orientation
= exif_get_short(exifEntry
->data
, byteOrder
);
131 exif_data_free(exifData
);
146 if (image
&& (orientation
> 1)) {
148 switch (orientation
) {
150 tmp
= RFlipImage(image
, RHorizontalFlip
);
153 tmp
= RRotateImage(image
, 180);
156 tmp
= RFlipImage(image
, RVerticalFlip
);
160 tmp2
= RFlipImage(image
, RVerticalFlip
);
162 tmp
= RRotateImage(tmp2
, 90);
168 tmp
= RRotateImage(image
, 90);
172 tmp2
= RFlipImage(image
, RVerticalFlip
);
174 tmp
= RRotateImage(tmp2
, 270);
180 tmp
= RRotateImage(image
, 270);
184 RReleaseImage(image
);
193 change_title: used to change window title
194 return EXIT_SUCCESS on success, 1 on failure
196 int change_title(XTextProperty
*prop
, char *filename
)
198 char *combined_title
= NULL
;
199 if (!asprintf(&combined_title
, "%s - %u/%u - %s", APPNAME
, current_index
, max_index
, filename
))
200 if (!asprintf(&combined_title
, "%s - %u/%u", APPNAME
, current_index
, max_index
))
202 XStringListToTextProperty(&combined_title
, 1, prop
);
203 XSetWMName(dpy
, win
, prop
);
206 free(combined_title
);
211 rescale_image: used to rescale the current image based on the screen size
212 return EXIT_SUCCESS on success
214 int rescale_image(void)
216 long final_width
= img
->width
;
217 long final_height
= img
->height
;
219 /* check if there is already a zoom factor applied */
220 if (zoom_factor
!= 0) {
221 final_width
= img
->width
+ (int)(img
->width
* zoom_factor
);
222 final_height
= img
->height
+ (int)(img
->height
* zoom_factor
);
224 if ((max_width
< final_width
) || (max_height
< final_height
)) {
226 if (final_width
> final_height
) {
227 val
= final_height
* max_width
/ final_width
;
228 final_width
= final_width
* val
/ final_height
;
230 if (val
> max_height
) {
231 val
= final_width
* max_height
/ final_height
;
232 final_height
= final_height
* val
/ final_width
;
236 val
= final_width
* max_height
/ final_height
;
237 final_height
= final_height
* val
/ final_width
;
239 if (val
> max_width
) {
240 val
= final_height
* max_width
/ final_width
;
241 final_width
= final_width
* val
/ final_height
;
246 if ((final_width
!= img
->width
) || (final_height
!= img
->height
)) {
247 RImage
*old_img
= img
;
248 img
= RScaleImage(img
, final_width
, final_height
);
253 RReleaseImage(old_img
);
255 if (!RConvertImage(ctx
, img
, &pix
)) {
256 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
263 maximize_image: find the best image size for the current display
264 return EXIT_SUCCESS on success
266 int maximize_image(void)
269 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
270 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
275 merge_with_background: merge the current image with with a checkboard background
276 return EXIT_SUCCESS on success, 1 on failure
278 int merge_with_background(RImage
*i
)
282 back
= RCreateImage(i
->width
, i
->height
, True
);
287 RFillImage(back
, &lightGray
);
288 for (x
= 0; x
<= i
->width
; x
+= 8) {
293 for (; y
<= i
->height
; y
+= 16)
294 ROperateRectangle(back
, RAddOperation
, x
, y
, x
+8, y
+8, &darkGray
);
297 RCombineImagesWithOpaqueness(i
, back
, opaq
);
306 turn_image: rotate the image by the angle passed
307 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
309 int turn_image(float angle
)
316 tmp
= RRotateImage(img
, angle
);
320 if (!fullscreen_flag
) {
321 if (img
->width
!= tmp
->width
|| img
->height
!= tmp
->height
)
322 XResizeWindow(dpy
, win
, tmp
->width
, tmp
->height
);
329 if (!fullscreen_flag
) {
330 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
332 XClearWindow(dpy
, win
);
333 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
334 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
341 turn_image_right: rotate the image by 90 degree
342 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
344 int turn_image_right(void)
346 return turn_image(90.0);
350 turn_image_left: rotate the image by -90 degree
351 return EXIT_SUCCESS on success, 1 on failure
353 int turn_image_left(void)
355 return turn_image(-90.0);
359 draw_failed_image: create a red crossed image to indicate an error loading file
360 return the image on success, NULL on failure
363 RImage
*draw_failed_image(void)
365 RImage
*failed_image
= NULL
;
366 XWindowAttributes attr
;
368 if (win
&& (XGetWindowAttributes(dpy
, win
, &attr
) >= 0))
369 failed_image
= RCreateImage(attr
.width
, attr
.height
, False
);
371 failed_image
= RCreateImage(50, 50, False
);
375 RFillImage(failed_image
, &black
);
376 ROperateLine(failed_image
, RAddOperation
, 0, 0, failed_image
->width
, failed_image
->height
, &red
);
377 ROperateLine(failed_image
, RAddOperation
, 0, failed_image
->height
, failed_image
->width
, 0, &red
);
383 full_screen: sending event to the window manager to switch from/to full screen mode
384 return EXIT_SUCCESS on success, 1 on failure
386 int full_screen(void)
390 Atom wm_state
= XInternAtom(dpy
, "_NET_WM_STATE", True
);
391 Atom fullscreen
= XInternAtom(dpy
, "_NET_WM_STATE_FULLSCREEN", True
);
392 long mask
= SubstructureNotifyMask
;
394 if (fullscreen_flag
) {
395 fullscreen_flag
= False
;
397 back_from_fullscreen
= True
;
399 fullscreen_flag
= True
;
403 memset(&xev
, 0, sizeof(xev
));
404 xev
.type
= ClientMessage
;
405 xev
.xclient
.display
= dpy
;
406 xev
.xclient
.window
= win
;
407 xev
.xclient
.message_type
= wm_state
;
408 xev
.xclient
.format
= 32;
409 xev
.xclient
.data
.l
[0] = fullscreen_flag
;
410 xev
.xclient
.data
.l
[1] = fullscreen
;
412 if (!XSendEvent(dpy
, DefaultRootWindow(dpy
), False
, mask
, &xev
)) {
413 fprintf(stderr
, "Error: sending fullscreen event to xserver\n");
420 zoom_in_out: apply a zoom factor on the current image
421 arg: 1 to zoom in, 0 to zoom out
422 return EXIT_SUCCESS on success, 1 on failure
424 int zoom_in_out(int z
)
426 RImage
*old_img
= img
;
427 RImage
*tmp
= load_oriented_image(ctx
, current_link
->data
, 0);
433 img
= RScaleImage(tmp
, tmp
->width
+ (int)(tmp
->width
* zoom_factor
),
434 tmp
->height
+ (int)(tmp
->height
* zoom_factor
));
441 int new_width
= tmp
->width
+ (int) (tmp
->width
* zoom_factor
);
442 int new_height
= tmp
->height
+ (int)(tmp
->height
* zoom_factor
);
443 if ((new_width
<= 0) || (new_height
<= 0)) {
448 img
= RScaleImage(tmp
, new_width
, new_height
);
454 RReleaseImage(old_img
);
456 XFreePixmap(dpy
, pix
);
458 merge_with_background(img
);
459 if (!RConvertImage(ctx
, img
, &pix
)) {
460 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
463 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
468 zoom_in: transitional fct used to call zoom_in_out with zoom in flag
469 return EXIT_SUCCESS on success, 1 on failure
473 return zoom_in_out(1);
477 zoom_out: transitional fct used to call zoom_in_out with zoom out flag
478 return EXIT_SUCCESS on success, 1 on failure
482 return zoom_in_out(0);
486 change_image: load previous or next image
487 arg: way which could be PREV or NEXT constant
488 return EXIT_SUCCESS on success, 1 on failure
490 int change_image(int way
)
492 if (img
&& current_link
) {
493 int old_img_width
= img
->width
;
494 int old_img_height
= img
->height
;
499 current_link
= current_link
->next
;
502 current_link
= current_link
->prev
;
505 if (current_link
== NULL
) {
507 current_link
= list
.first
;
510 current_link
= list
.last
;
511 current_index
= max_index
;
515 fprintf(stderr
, "current file is> %s\n", (char *)current_link
->data
);
516 img
= load_oriented_image(ctx
, current_link
->data
, 0);
519 fprintf(stderr
, "Error: %s %s\n", (char *)current_link
->data
,
520 RMessageForError(RErrorCode
));
521 img
= draw_failed_image();
523 merge_with_background(img
);
526 if (!fullscreen_flag
) {
527 if ((old_img_width
!= img
->width
) || (old_img_height
!= img
->height
))
528 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
530 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
531 change_title(&title_property
, (char *)current_link
->data
);
533 XClearWindow(dpy
, win
);
534 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
535 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
544 diaporama: send a xevent to display the next image at every delay set to diaporama_delay
548 void *diaporama(void *arg
)
555 event
.root
= DefaultRootWindow(dpy
);
556 event
.subwindow
= None
;
557 event
.time
= CurrentTime
;
562 event
.same_screen
= True
;
563 event
.keycode
= XKeysymToKeycode(dpy
, XK_Right
);
565 event
.type
= KeyPress
;
567 while (diaporama_flag
) {
569 r
= XSendEvent(event
.display
, event
.window
, True
, KeyPressMask
, (XEvent
*)&event
);
571 fprintf(stderr
, "Error sending event\n");
573 /* default sleep time between moving to next image */
574 sleep(diaporama_delay
);
582 linked_list_init: init the linked list
584 void linked_list_init(linked_list_t
*list
)
586 list
->first
= list
->last
= 0;
591 linked_list_add: add an element to the linked list
592 return EXIT_SUCCESS on success, 1 otherwise
594 int linked_list_add(linked_list_t
*list
, const void *data
)
598 /* calloc sets the "next" field to zero. */
599 link
= calloc(1, sizeof(link_t
));
601 fprintf(stderr
, "Error: memory allocation failed\n");
606 /* Join the two final links together. */
607 list
->last
->next
= link
;
608 link
->prev
= list
->last
;
619 linked_list_free: deallocate the whole linked list
621 void linked_list_free(linked_list_t
*list
)
625 for (link
= list
->first
; link
; link
= next
) {
626 /* Store the next value so that we don't access freed memory. */
629 free((char *)link
->data
);
635 connect_dir: list and sort by name all files from a given directory
636 arg: the directory path that contains images, the linked list where to add the new file refs
637 return: the first argument of the list or NULL on failure
639 link_t
*connect_dir(char *dirpath
, linked_list_t
*li
)
643 char path
[PATH_MAX
] = "";
648 dv
= scandir(dirpath
, &dir
, 0, alphasort
);
650 /* maybe it's a file */
651 struct stat stDirInfo
;
652 if (lstat(dirpath
, &stDirInfo
) == 0) {
653 linked_list_add(li
, strdup(dirpath
));
659 for (idx
= 0; idx
< dv
; idx
++) {
660 struct stat stDirInfo
;
661 if (dirpath
[strlen(dirpath
)-1] == FILE_SEPARATOR
)
662 snprintf(path
, PATH_MAX
, "%s%s", dirpath
, dir
[idx
]->d_name
);
664 snprintf(path
, PATH_MAX
, "%s%c%s", dirpath
, FILE_SEPARATOR
, dir
[idx
]->d_name
);
667 if ((lstat(path
, &stDirInfo
) == 0) && !S_ISDIR(stDirInfo
.st_mode
))
668 linked_list_add(li
, strdup(path
));
677 int main(int argc
, char **argv
)
680 RContextAttributes attr
;
683 char *reading_filename
= "";
686 XClassHint
*class_hints
;
687 XSizeHints
*size_hints
;
690 Pixmap icon_pixmap
, icon_shape
;
692 class_hints
= XAllocClassHint();
694 fprintf(stderr
, "Error: failure allocating memory\n");
697 class_hints
->res_name
= (char *)APPNAME
;
698 class_hints
->res_class
= "default";
701 lightGray
.red
= lightGray
.green
= lightGray
.blue
= 211;
702 darkGray
.red
= darkGray
.green
= darkGray
.blue
= 169;
703 lightGray
.alpha
= darkGray
.alpha
= 1;
704 black
.red
= black
.green
= black
.blue
= 0;
706 red
.green
= red
.blue
= 0;
708 option
= getopt(argc
, argv
, "hv");
712 fprintf(stderr
, "Usage: %s [image(s)|directory]\n"
718 "d: launch diaporama mode\n"
720 "l: rotate image on the left\n"
722 "r: rotate image on the right\n"
723 "right: next image\n"
724 "left: previous image\n"
726 "down: last image\n",
730 fprintf(stderr
, "%s version %d.%d\n", APPNAME
, APPVERSION_MAJOR
, APPVERSION_MINOR
);
737 linked_list_init(&list
);
739 dpy
= XOpenDisplay(NULL
);
741 fprintf(stderr
, "Error: can't open display");
742 linked_list_free(&list
);
746 screen
= DefaultScreen(dpy
);
747 max_width
= DisplayWidth(dpy
, screen
);
748 max_height
= DisplayHeight(dpy
, screen
);
750 attr
.flags
= RC_RenderMode
| RC_ColorsPerChannel
;
751 attr
.render_mode
= RDitheredRendering
;
752 attr
.colors_per_channel
= 4;
753 ctx
= RCreateContext(dpy
, DefaultScreen(dpy
), &attr
);
760 for (file_i
= 1; file_i
< argc
; file_i
++) {
761 current_link
= connect_dir(argv
[file_i
], &list
);
763 reading_filename
= (char *)current_link
->data
;
764 max_index
= list
.count
;
768 img
= load_oriented_image(ctx
, reading_filename
, 0);
771 fprintf(stderr
, "Error: %s %s\n", reading_filename
, RMessageForError(RErrorCode
));
772 img
= draw_failed_image();
777 merge_with_background(img
);
781 fprintf(stderr
, "display size: %dx%d\n", max_width
, max_height
);
783 win
= XCreateSimpleWindow(dpy
, DefaultRootWindow(dpy
), 0, 0,
784 img
->width
, img
->height
, 0, 0, BlackPixel(dpy
, screen
));
785 XSelectInput(dpy
, win
, KeyPressMask
|StructureNotifyMask
|ExposureMask
|ButtonPressMask
|FocusChangeMask
);
787 size_hints
= XAllocSizeHints();
789 fprintf(stderr
, "Error: failure allocating memory\n");
792 size_hints
->width
= img
->width
;
793 size_hints
->height
= img
->height
;
795 Atom delWindow
= XInternAtom(dpy
, "WM_DELETE_WINDOW", 0);
796 XSetWMProtocols(dpy
, win
, &delWindow
, 1);
797 change_title(&title_property
, reading_filename
);
799 win_hints
= XAllocWMHints();
801 win_hints
->flags
= StateHint
|InputHint
|WindowGroupHint
;
804 if ((XpmCreatePixmapFromData(dpy
, win
, wmiv_xpm
, &icon_pixmap
, &icon_shape
, NULL
)) == 0) {
805 win_hints
->flags
|= IconPixmapHint
|IconMaskHint
|IconPositionHint
;
806 win_hints
->icon_pixmap
= icon_pixmap
;
807 win_hints
->icon_mask
= icon_shape
;
808 win_hints
->icon_x
= 0;
809 win_hints
->icon_y
= 0;
812 win_hints
->initial_state
= NormalState
;
813 win_hints
->input
= True
;
814 win_hints
->window_group
= win
;
815 XStringListToTextProperty((char **)&APPNAME
, 1, &icon_property
);
816 XSetWMProperties(dpy
, win
, NULL
, &icon_property
, argv
, argc
, size_hints
, win_hints
, class_hints
);
817 if (icon_property
.value
)
818 XFree(icon_property
.value
);
824 XMapWindow(dpy
, win
);
826 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
830 if (e
.type
== ClientMessage
) {
831 if (e
.xclient
.data
.l
[0] == delWindow
)
835 if (e
.type
== FocusIn
) {
839 if (e
.type
== FocusOut
) {
843 if (!fullscreen_flag
&& (e
.type
== Expose
)) {
844 XExposeEvent xev
= e
.xexpose
;
846 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
849 if (!fullscreen_flag
&& e
.type
== ConfigureNotify
) {
850 XConfigureEvent xce
= e
.xconfigure
;
851 if (xce
.width
!= img
->width
|| xce
.height
!= img
->height
) {
852 RImage
*old_img
= img
;
853 img
= load_oriented_image(ctx
, current_link
->data
, 0);
855 /* keep the old img and window size */
857 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
860 if (!back_from_fullscreen
)
861 /* manually resized window */
862 tmp2
= RScaleImage(img
, xce
.width
, xce
.height
);
864 /* back from fullscreen mode, maybe img was rotated */
866 back_from_fullscreen
= False
;
867 XClearWindow(dpy
, win
);
869 merge_with_background(tmp2
);
870 if (RConvertImage(ctx
, tmp2
, &pix
)) {
871 RReleaseImage(old_img
);
872 img
= RCloneImage(tmp2
);
874 change_title(&title_property
, (char *)current_link
->data
);
876 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
877 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
878 img
->width
, img
->height
, 0, 0);
885 if (fullscreen_flag
&& e
.type
== ConfigureNotify
) {
889 if (e
.type
== ButtonPress
) {
890 switch (e
.xbutton
.button
) {
893 if (img
&& (e
.xbutton
.x
> img
->width
/2))
915 if (e
.type
== KeyPress
) {
916 keysym
= XkbKeycodeToKeysym(dpy
, e
.xkey
.keycode
, 0, e
.xkey
.state
& ShiftMask
?1:0);
918 if (keysym
!= XK_Right
)
919 diaporama_flag
= False
;
930 current_link
= list
.last
;
936 current_link
= list
.first
;
944 if (current_link
&& !diaporama_flag
) {
945 diaporama_flag
= True
;
946 pthread_create(&tid
, NULL
, &diaporama
, NULL
);
948 fprintf(stderr
, "Can't use diaporama mode\n");
957 if (!fullscreen_flag
) {
959 /* zoom_in will increase the zoom factor by 0.2 */
962 /* we are in fullscreen mode already, want to return to normal size */
990 XFreePixmap(dpy
, pix
);
993 XFreePixmap(dpy
, icon_pixmap
);
995 XFreePixmap(dpy
, icon_shape
);
997 linked_list_free(&list
);
998 RDestroyContext(ctx
);
1001 return EXIT_SUCCESS
;