2 * Window Maker window manager
4 * Copyright (c) 2014-2023 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";
68 float zoom_factor
= 0;
72 Bool fullscreen_flag
= False
;
74 Bool back_from_fullscreen
= False
;
77 Bool diaporama_flag
= False
;
78 int diaporama_delay
= 5;
81 XTextProperty title_property
;
82 XTextProperty icon_property
;
83 unsigned current_index
= 1;
84 unsigned max_index
= 1;
91 typedef struct link link_t
;
98 typedef struct linked_list
{
105 link_t
*current_link
;
109 load_oriented_image: used to load an image and optionally
110 get its orientation if libexif is available
111 return the image on success, NULL on failure
113 RImage
*load_oriented_image(RContext
*context
, const char *file
, int index
)
119 image
= RLoadImage(context
, file
, index
);
123 ExifData
*exifData
= exif_data_new_from_file(file
);
125 ExifByteOrder byteOrder
= exif_data_get_byte_order(exifData
);
126 ExifEntry
*exifEntry
= exif_data_get_entry(exifData
, EXIF_TAG_ORIENTATION
);
128 orientation
= exif_get_short(exifEntry
->data
, byteOrder
);
130 exif_data_free(exifData
);
145 if (image
&& (orientation
> 1)) {
147 switch (orientation
) {
149 tmp
= RFlipImage(image
, RHorizontalFlip
);
152 tmp
= RRotateImage(image
, 180);
155 tmp
= RFlipImage(image
, RVerticalFlip
);
159 tmp2
= RFlipImage(image
, RVerticalFlip
);
161 tmp
= RRotateImage(tmp2
, 90);
167 tmp
= RRotateImage(image
, 90);
171 tmp2
= RFlipImage(image
, RVerticalFlip
);
173 tmp
= RRotateImage(tmp2
, 270);
179 tmp
= RRotateImage(image
, 270);
183 RReleaseImage(image
);
192 change_title: used to change window title
193 return EXIT_SUCCESS on success, 1 on failure
195 int change_title(XTextProperty
*prop
, char *filename
)
197 char *combined_title
= NULL
;
198 if (!asprintf(&combined_title
, "%s - %u/%u - %s", APPNAME
, current_index
, max_index
, filename
))
199 if (!asprintf(&combined_title
, "%s - %u/%u", APPNAME
, current_index
, max_index
))
201 XStringListToTextProperty(&combined_title
, 1, prop
);
202 XSetWMName(dpy
, win
, prop
);
205 free(combined_title
);
210 rescale_image: used to rescale the current image based on the screen size
211 return EXIT_SUCCESS on success
213 int rescale_image(void)
215 long final_width
= img
->width
;
216 long final_height
= img
->height
;
218 /* check if there is already a zoom factor applied */
219 if (fabsf(zoom_factor
) <= 0.0f
) {
220 final_width
= img
->width
+ (int)(img
->width
* zoom_factor
);
221 final_height
= img
->height
+ (int)(img
->height
* zoom_factor
);
223 if ((max_width
< final_width
) || (max_height
< final_height
)) {
225 if (final_width
> final_height
) {
226 val
= final_height
* max_width
/ final_width
;
227 final_width
= final_width
* val
/ final_height
;
229 if (val
> max_height
) {
230 val
= final_width
* max_height
/ final_height
;
231 final_height
= final_height
* val
/ final_width
;
235 val
= final_width
* max_height
/ final_height
;
236 final_height
= final_height
* val
/ final_width
;
238 if (val
> max_width
) {
239 val
= final_height
* max_width
/ final_width
;
240 final_width
= final_width
* val
/ final_height
;
245 if ((final_width
!= img
->width
) || (final_height
!= img
->height
)) {
246 RImage
*old_img
= img
;
247 img
= RScaleImage(img
, final_width
, final_height
);
252 RReleaseImage(old_img
);
254 if (!RConvertImage(ctx
, img
, &pix
)) {
255 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
262 maximize_image: find the best image size for the current display
263 return EXIT_SUCCESS on success
265 int maximize_image(void)
268 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
269 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
274 merge_with_background: merge the current image with with a checkboard background
275 return EXIT_SUCCESS on success, 1 on failure
277 int merge_with_background(RImage
*i
)
281 back
= RCreateImage(i
->width
, i
->height
, True
);
286 RFillImage(back
, &lightGray
);
287 for (x
= 0; x
<= i
->width
; x
+= 8) {
292 for (; y
<= i
->height
; y
+= 16)
293 ROperateRectangle(back
, RAddOperation
, x
, y
, x
+8, y
+8, &darkGray
);
296 RCombineImagesWithOpaqueness(i
, back
, opaq
);
305 turn_image: rotate the image by the angle passed
306 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
308 int turn_image(float angle
)
315 tmp
= RRotateImage(img
, angle
);
319 if (!fullscreen_flag
) {
320 if (img
->width
!= tmp
->width
|| img
->height
!= tmp
->height
)
321 XResizeWindow(dpy
, win
, tmp
->width
, tmp
->height
);
328 if (!fullscreen_flag
) {
329 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
331 XClearWindow(dpy
, win
);
332 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
333 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
340 turn_image_right: rotate the image by 90 degree
341 return EXIT_SUCCESS on success, EXIT_FAILURE on failure
343 int turn_image_right(void)
345 return turn_image(90.0);
349 turn_image_left: rotate the image by -90 degree
350 return EXIT_SUCCESS on success, 1 on failure
352 int turn_image_left(void)
354 return turn_image(-90.0);
358 draw_failed_image: create a red crossed image to indicate an error loading file
359 return the image on success, NULL on failure
362 RImage
*draw_failed_image(void)
364 RImage
*failed_image
= NULL
;
365 XWindowAttributes attr
;
367 if (win
&& (XGetWindowAttributes(dpy
, win
, &attr
) >= 0))
368 failed_image
= RCreateImage(attr
.width
, attr
.height
, False
);
370 failed_image
= RCreateImage(50, 50, False
);
374 RFillImage(failed_image
, &black
);
375 ROperateLine(failed_image
, RAddOperation
, 0, 0, failed_image
->width
, failed_image
->height
, &red
);
376 ROperateLine(failed_image
, RAddOperation
, 0, failed_image
->height
, failed_image
->width
, 0, &red
);
382 full_screen: sending event to the window manager to switch from/to full screen mode
383 return EXIT_SUCCESS on success, 1 on failure
385 int full_screen(void)
389 Atom wm_state
= XInternAtom(dpy
, "_NET_WM_STATE", True
);
390 Atom fullscreen
= XInternAtom(dpy
, "_NET_WM_STATE_FULLSCREEN", True
);
391 long mask
= SubstructureNotifyMask
;
393 if (fullscreen_flag
) {
394 fullscreen_flag
= False
;
396 back_from_fullscreen
= True
;
398 fullscreen_flag
= True
;
402 memset(&xev
, 0, sizeof(xev
));
403 xev
.type
= ClientMessage
;
404 xev
.xclient
.display
= dpy
;
405 xev
.xclient
.window
= win
;
406 xev
.xclient
.message_type
= wm_state
;
407 xev
.xclient
.format
= 32;
408 xev
.xclient
.data
.l
[0] = fullscreen_flag
;
409 xev
.xclient
.data
.l
[1] = fullscreen
;
411 if (!XSendEvent(dpy
, DefaultRootWindow(dpy
), False
, mask
, &xev
)) {
412 fprintf(stderr
, "Error: sending fullscreen event to xserver\n");
419 zoom_in_out: apply a zoom factor on the current image
420 arg: 1 to zoom in, 0 to zoom out
421 return EXIT_SUCCESS on success, 1 on failure
423 int zoom_in_out(int z
)
425 RImage
*old_img
= img
;
426 RImage
*tmp
= load_oriented_image(ctx
, current_link
->data
, 0);
432 img
= RScaleImage(tmp
, tmp
->width
+ (int)(tmp
->width
* zoom_factor
),
433 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
);
455 RReleaseImage(old_img
);
457 XFreePixmap(dpy
, pix
);
459 merge_with_background(img
);
460 if (!RConvertImage(ctx
, img
, &pix
)) {
461 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
464 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
469 zoom_in: transitional fct used to call zoom_in_out with zoom in flag
470 return EXIT_SUCCESS on success, 1 on failure
474 return zoom_in_out(1);
478 zoom_out: transitional fct used to call zoom_in_out with zoom out flag
479 return EXIT_SUCCESS on success, 1 on failure
483 return zoom_in_out(0);
487 change_image: load previous or next image
488 arg: way which could be PREV or NEXT constant
489 return EXIT_SUCCESS on success, 1 on failure
491 int change_image(int way
)
493 if (img
&& current_link
) {
494 int old_img_width
= img
->width
;
495 int old_img_height
= img
->height
;
500 current_link
= current_link
->next
;
503 current_link
= current_link
->prev
;
506 if (current_link
== NULL
) {
508 current_link
= list
.first
;
511 current_link
= list
.last
;
512 current_index
= max_index
;
516 fprintf(stderr
, "current file is> %s\n", (char *)current_link
->data
);
517 img
= load_oriented_image(ctx
, current_link
->data
, 0);
520 fprintf(stderr
, "Error: %s %s\n", (char *)current_link
->data
,
521 RMessageForError(RErrorCode
));
522 img
= draw_failed_image();
524 merge_with_background(img
);
527 if (!fullscreen_flag
) {
528 if ((old_img_width
!= img
->width
) || (old_img_height
!= img
->height
))
529 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
531 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
532 change_title(&title_property
, (char *)current_link
->data
);
534 XClearWindow(dpy
, win
);
535 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
536 img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
545 diaporama: send a xevent to display the next image at every delay set to diaporama_delay
549 void *diaporama(void *arg
)
556 event
.root
= DefaultRootWindow(dpy
);
557 event
.subwindow
= None
;
558 event
.time
= CurrentTime
;
563 event
.same_screen
= True
;
564 event
.keycode
= XKeysymToKeycode(dpy
, XK_Right
);
566 event
.type
= KeyPress
;
568 while (diaporama_flag
) {
570 r
= XSendEvent(event
.display
, event
.window
, True
, KeyPressMask
, (XEvent
*)&event
);
572 fprintf(stderr
, "Error sending event\n");
574 /* default sleep time between moving to next image */
575 sleep(diaporama_delay
);
583 linked_list_init: init the linked list
585 void linked_list_init(linked_list_t
*list
)
587 list
->first
= list
->last
= 0;
592 linked_list_add: add an element to the linked list
593 return EXIT_SUCCESS on success, 1 otherwise
595 int linked_list_add(linked_list_t
*list
, const void *data
)
599 /* calloc sets the "next" field to zero. */
600 link
= calloc(1, sizeof(link_t
));
602 fprintf(stderr
, "Error: memory allocation failed\n");
607 /* Join the two final links together. */
608 list
->last
->next
= link
;
609 link
->prev
= list
->last
;
620 linked_list_free: deallocate the whole linked list
622 void linked_list_free(linked_list_t
*list
)
626 for (link
= list
->first
; link
; link
= next
) {
627 /* Store the next value so that we don't access freed memory. */
630 free((char *)link
->data
);
636 connect_dir: list and sort by name all files from a given directory
637 arg: the directory path that contains images, the linked list where to add the new file refs
638 return: the first argument of the list or NULL on failure
640 link_t
*connect_dir(char *dirpath
, linked_list_t
*li
)
644 char path
[PATH_MAX
] = "";
649 dv
= scandir(dirpath
, &dir
, 0, alphasort
);
651 /* maybe it's a file */
652 struct stat stDirInfo
;
653 if (lstat(dirpath
, &stDirInfo
) == 0) {
654 linked_list_add(li
, strdup(dirpath
));
660 for (idx
= 0; idx
< dv
; idx
++) {
661 struct stat stDirInfo
;
662 if (dirpath
[strlen(dirpath
)-1] == FILE_SEPARATOR
)
663 snprintf(path
, PATH_MAX
, "%s%s", dirpath
, dir
[idx
]->d_name
);
665 snprintf(path
, PATH_MAX
, "%s%c%s", dirpath
, FILE_SEPARATOR
, dir
[idx
]->d_name
);
668 if ((lstat(path
, &stDirInfo
) == 0) && !S_ISDIR(stDirInfo
.st_mode
))
669 linked_list_add(li
, strdup(path
));
678 int main(int argc
, char **argv
)
681 RContextAttributes attr
= {};
684 char *reading_filename
= "";
687 XClassHint
*class_hints
;
688 XSizeHints
*size_hints
;
691 Pixmap icon_pixmap
, icon_shape
;
693 class_hints
= XAllocClassHint();
695 fprintf(stderr
, "Error: failure allocating memory\n");
698 class_hints
->res_name
= (char *)APPNAME
;
699 class_hints
->res_class
= "default";
702 lightGray
.red
= lightGray
.green
= lightGray
.blue
= 211;
703 darkGray
.red
= darkGray
.green
= darkGray
.blue
= 169;
704 lightGray
.alpha
= darkGray
.alpha
= 1;
705 black
.red
= black
.green
= black
.blue
= 0;
707 red
.green
= red
.blue
= 0;
709 static struct option long_options
[] = {
710 {"version", no_argument
, 0, 'v'},
711 {"help", no_argument
, 0, 'h'},
714 int option_index
= 0;
716 option
= getopt_long (argc
, argv
, "hv", long_options
, &option_index
);
720 printf("Usage: %s [image(s)|directory]\n"
722 " -h, --help print this help text\n"
723 " -v, --version print version\n"
727 " [Esc] actual size\n"
729 " [D] launch diaporama mode\n"
731 " [L] rotate image on the left\n"
733 " [R] rotate image on the right\n"
734 " [â–¸] next image\n"
735 " [â—‚] previous image\n"
736 " [â–´] first image\n"
737 " [â–¾] last image\n",
741 printf("%s version %s\n", APPNAME
, VERSION
);
748 linked_list_init(&list
);
750 dpy
= XOpenDisplay(NULL
);
752 fprintf(stderr
, "Error: can't open display\n");
753 linked_list_free(&list
);
757 screen
= DefaultScreen(dpy
);
758 max_width
= DisplayWidth(dpy
, screen
);
759 max_height
= DisplayHeight(dpy
, screen
);
761 attr
.flags
= RC_RenderMode
| RC_ColorsPerChannel
;
762 attr
.render_mode
= RDitheredRendering
;
763 attr
.colors_per_channel
= 4;
764 ctx
= RCreateContext(dpy
, DefaultScreen(dpy
), &attr
);
771 for (file_i
= 1; file_i
< argc
; file_i
++) {
772 current_link
= connect_dir(argv
[file_i
], &list
);
774 reading_filename
= (char *)current_link
->data
;
775 max_index
= list
.count
;
779 img
= load_oriented_image(ctx
, reading_filename
, 0);
782 fprintf(stderr
, "Error: %s %s\n", reading_filename
, RMessageForError(RErrorCode
));
783 img
= draw_failed_image();
788 merge_with_background(img
);
792 fprintf(stderr
, "display size: %dx%d\n", max_width
, max_height
);
794 win
= XCreateSimpleWindow(dpy
, DefaultRootWindow(dpy
), 0, 0,
795 img
->width
, img
->height
, 0, 0, BlackPixel(dpy
, screen
));
796 XSelectInput(dpy
, win
, KeyPressMask
|StructureNotifyMask
|ExposureMask
|ButtonPressMask
|FocusChangeMask
);
798 size_hints
= XAllocSizeHints();
800 fprintf(stderr
, "Error: failure allocating memory\n");
803 size_hints
->width
= img
->width
;
804 size_hints
->height
= img
->height
;
806 Atom delWindow
= XInternAtom(dpy
, "WM_DELETE_WINDOW", 0);
807 XSetWMProtocols(dpy
, win
, &delWindow
, 1);
808 change_title(&title_property
, reading_filename
);
810 win_hints
= XAllocWMHints();
812 win_hints
->flags
= StateHint
|InputHint
|WindowGroupHint
;
815 if ((XpmCreatePixmapFromData(dpy
, win
, wmiv_xpm
, &icon_pixmap
, &icon_shape
, NULL
)) == 0) {
816 win_hints
->flags
|= IconPixmapHint
|IconMaskHint
|IconPositionHint
;
817 win_hints
->icon_pixmap
= icon_pixmap
;
818 win_hints
->icon_mask
= icon_shape
;
819 win_hints
->icon_x
= 0;
820 win_hints
->icon_y
= 0;
823 win_hints
->initial_state
= NormalState
;
824 win_hints
->input
= True
;
825 win_hints
->window_group
= win
;
826 XStringListToTextProperty((char **)&APPNAME
, 1, &icon_property
);
827 XSetWMProperties(dpy
, win
, NULL
, &icon_property
, argv
, argc
, size_hints
, win_hints
, class_hints
);
828 if (icon_property
.value
)
829 XFree(icon_property
.value
);
835 XMapWindow(dpy
, win
);
837 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
841 if (e
.type
== ClientMessage
) {
842 if (e
.xclient
.data
.l
[0] == delWindow
)
846 * This break could be related to all ClientMessages or
847 * related to delWindow. Before the patch about this comment
848 * the break was indented with one tab more (at the same level
849 * than "quit = 1;" in the previous line.
853 if (e
.type
== FocusIn
) {
857 if (e
.type
== FocusOut
) {
861 if (!fullscreen_flag
&& (e
.type
== Expose
)) {
862 XExposeEvent xev
= e
.xexpose
;
864 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
867 if (!fullscreen_flag
&& e
.type
== ConfigureNotify
) {
868 XConfigureEvent xce
= e
.xconfigure
;
869 if (xce
.width
!= img
->width
|| xce
.height
!= img
->height
) {
870 RImage
*old_img
= img
;
871 img
= load_oriented_image(ctx
, current_link
->data
, 0);
873 /* keep the old img and window size */
875 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
878 if (!back_from_fullscreen
)
879 /* manually resized window */
880 tmp2
= RScaleImage(img
, xce
.width
, xce
.height
);
882 /* back from fullscreen mode, maybe img was rotated */
884 back_from_fullscreen
= False
;
885 XClearWindow(dpy
, win
);
887 merge_with_background(tmp2
);
888 if (RConvertImage(ctx
, tmp2
, &pix
)) {
889 RReleaseImage(old_img
);
890 img
= RCloneImage(tmp2
);
892 change_title(&title_property
, (char *)current_link
->data
);
894 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
895 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0,
896 img
->width
, img
->height
, 0, 0);
903 if (fullscreen_flag
&& e
.type
== ConfigureNotify
) {
907 if (e
.type
== ButtonPress
) {
908 switch (e
.xbutton
.button
) {
911 if (img
&& (e
.xbutton
.x
> img
->width
/2))
933 if (e
.type
== KeyPress
) {
934 keysym
= W_KeycodeToKeysym(dpy
, e
.xkey
.keycode
, e
.xkey
.state
& ShiftMask
?1:0);
936 if (keysym
!= XK_Right
)
937 diaporama_flag
= False
;
948 current_link
= list
.last
;
954 current_link
= list
.first
;
962 if (current_link
&& !diaporama_flag
) {
963 diaporama_flag
= True
;
964 pthread_create(&tid
, NULL
, &diaporama
, NULL
);
966 fprintf(stderr
, "Can't use diaporama mode\n");
975 if (!fullscreen_flag
) {
977 /* zoom_in will increase the zoom factor by 0.2 */
980 /* we are in fullscreen mode already, want to return to normal size */
1008 XFreePixmap(dpy
, pix
);
1011 XFreePixmap(dpy
, icon_pixmap
);
1013 XFreePixmap(dpy
, icon_shape
);
1015 linked_list_free(&list
);
1016 RDestroyContext(ctx
);
1019 return EXIT_SUCCESS
;