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.
22 #include <X11/keysym.h>
23 #include <X11/XKBlib.h>
24 #include <X11/Xatom.h>
40 extern int XpmCreatePixmapFromData(Display
*, Drawable
, char **, Pixmap
*, Pixmap
*, void *);
41 /* this is the icon from eog project
42 git.gnome.org/browse/eog
48 #define FILE_SEPARATOR '/'
56 const char *APPNAME
= "wmiv";
57 int APPVERSION_MAJOR
= 0;
58 int APPVERSION_MINOR
= 6;
61 float zoom_factor
= 0;
65 Bool fullscreen_flag
= False
;
69 Bool diaporama_flag
= False
;
70 int diaporama_delay
= 5;
73 XTextProperty title_property
;
74 XTextProperty icon_property
;
75 unsigned current_index
= 1;
76 unsigned max_index
= 1;
83 typedef struct link link_t
;
90 typedef struct linked_list
{
100 change_title: used to change window title
101 return EXIT_SUCCESS on success, 1 on failure
103 int change_title(XTextProperty
*prop
, char *filename
) {
104 char *combined_title
= NULL
;
105 if (!asprintf(&combined_title
, "%s - %u/%u - %s", APPNAME
, current_index
, max_index
, filename
))
106 if (!asprintf(&combined_title
, "%s - %u/%u", APPNAME
, current_index
, max_index
))
108 XStringListToTextProperty(&combined_title
, 1, prop
);
109 XSetWMName(dpy
, win
, prop
);
112 free(combined_title
);
117 rescale_image: used to rescale the current image based on the screen size
118 return EXIT_SUCCESS on success
120 int rescale_image() {
121 long final_width
= img
->width
;
122 long final_height
= img
->height
;
124 /* check if there is already a zoom factor applied */
125 if (zoom_factor
!= 0) {
126 final_width
= img
->width
+ (int)(img
->width
* zoom_factor
);
127 final_height
= img
->height
+ (int)(img
->height
* zoom_factor
);
129 if ((max_width
< final_width
) || (max_height
< final_height
)) {
131 if (final_width
> final_height
) {
132 val
= final_height
* max_width
/ final_width
;
133 final_width
= final_width
* val
/ final_height
;
135 if (val
> max_height
) {
136 val
= final_width
* max_height
/ final_height
;
137 final_height
= final_height
* val
/ final_width
;
141 val
= final_width
* max_height
/ final_height
;
142 final_height
= final_height
* val
/ final_width
;
144 if (val
> max_width
) {
145 val
= final_height
* max_width
/ final_width
;
146 final_width
= final_width
* val
/ final_height
;
151 if ((final_width
!= img
->width
) || (final_height
!= img
->height
)) {
152 RImage
*old_img
= img
;
153 img
= RScaleImage(img
, final_width
, final_height
);
158 RReleaseImage(old_img
);
160 if (!RConvertImage(ctx
, img
, &pix
)) {
161 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
168 maximize_image: find the best image size for the current display
169 return EXIT_SUCCESS on success
171 int maximize_image() {
173 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
178 merge_with_background: merge the current image with with a checkboard background
179 return EXIT_SUCCESS on success, 1 on failure
181 int merge_with_background(RImage
*i
) {
184 back
= RCreateImage(i
->width
, i
->height
, True
);
189 RFillImage(back
, &lightGray
);
190 for (x
=0; x
<= i
->width
; x
+=8) {
195 for (; y
<= i
->height
; y
+=16)
196 ROperateRectangle(back
, RAddOperation
, x
,y
,x
+8,y
+8, &darkGray
);
199 RCombineImagesWithOpaqueness(i
, back
, opaq
);
208 draw_failed_image: create a red crossed image to indicate an error loading file
209 return the image on success, NULL on failure
212 RImage
* draw_failed_image() {
213 RImage
*failed_image
= NULL
;
214 XWindowAttributes attr
;
216 if (win
&& (XGetWindowAttributes(dpy
, win
, &attr
) >= 0)) {
217 failed_image
= RCreateImage(attr
.width
, attr
.height
, False
);
219 failed_image
= RCreateImage(50, 50, False
);
224 RFillImage(failed_image
, &black
);
225 ROperateLine(failed_image
, RAddOperation
, 0, 0, failed_image
->width
, failed_image
->height
, &red
);
226 ROperateLine(failed_image
, RAddOperation
, 0, failed_image
->height
, failed_image
->width
, 0, &red
);
232 full_screen: sending event to the window manager to switch from/to full screen mode
233 return EXIT_SUCCESS on success, 1 on failure
238 Atom wm_state
= XInternAtom(dpy
, "_NET_WM_STATE", True
);
239 Atom fullscreen
= XInternAtom(dpy
, "_NET_WM_STATE_FULLSCREEN", True
);
240 long mask
= SubstructureNotifyMask
;
242 if (fullscreen_flag
) {
243 fullscreen_flag
= False
;
246 fullscreen_flag
= True
;
250 memset(&xev
, 0, sizeof(xev
));
251 xev
.type
= ClientMessage
;
252 xev
.xclient
.display
= dpy
;
253 xev
.xclient
.window
= win
;
254 xev
.xclient
.message_type
= wm_state
;
255 xev
.xclient
.format
= 32;
256 xev
.xclient
.data
.l
[0] = fullscreen_flag
;
257 xev
.xclient
.data
.l
[1] = fullscreen
;
259 if (!XSendEvent(dpy
, DefaultRootWindow(dpy
), False
, mask
, &xev
)) {
260 fprintf(stderr
, "Error: sending fullscreen event to xserver\n");
267 zoom_in_out: apply a zoom factor on the current image
268 arg: 1 to zoom in, 0 to zoom out
269 return EXIT_SUCCESS on success, 1 on failure
271 int zoom_in_out(int z
) {
272 RImage
*old_img
= img
;
273 RImage
*tmp
= RLoadImage(ctx
, current_link
->data
, 0);
279 img
= RScaleImage(tmp
, tmp
->width
+ (int)(tmp
->width
* zoom_factor
), tmp
->height
+ (int)(tmp
->height
* zoom_factor
));
286 int new_width
= tmp
->width
+ (int) (tmp
->width
* zoom_factor
);
287 int new_height
= tmp
->height
+ (int)(tmp
->height
* zoom_factor
);
288 if ((new_width
<= 0) || (new_height
<= 0)) {
293 img
= RScaleImage(tmp
, new_width
, new_height
);
299 RReleaseImage(old_img
);
301 XFreePixmap(dpy
, pix
);
303 merge_with_background(img
);
304 if (!RConvertImage(ctx
, img
, &pix
)) {
305 fprintf(stderr
, "%s\n", RMessageForError(RErrorCode
));
308 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
313 zoom_in: transitional fct used to call zoom_in_out with zoom in flag
314 return EXIT_SUCCESS on success, 1 on failure
317 return zoom_in_out(1);
321 zoom_out: transitional fct used to call zoom_in_out with zoom out flag
322 return EXIT_SUCCESS on success, 1 on failure
325 return zoom_in_out(0);
329 change_image: load previous or next image
330 arg: way which could be PREV or NEXT constant
331 return EXIT_SUCCESS on success, 1 on failure
333 int change_image(int way
) {
334 if (img
&& current_link
) {
335 int old_img_width
= img
->width
;
336 int old_img_height
= img
->height
;
341 current_link
= current_link
->next
;
344 current_link
= current_link
->prev
;
347 if (current_link
== NULL
) {
349 current_link
= list
.first
;
352 current_link
= list
.last
;
353 current_index
= max_index
;
357 fprintf(stderr
, "current file is> %s\n", (char *)current_link
->data
);
358 img
= RLoadImage(ctx
, current_link
->data
, 0);
361 fprintf(stderr
, "Error: %s %s\n", (char *)current_link
->data
, RMessageForError(RErrorCode
));
362 img
= draw_failed_image();
364 merge_with_background(img
);
367 if (!fullscreen_flag
) {
368 if ((old_img_width
!= img
->width
) || (old_img_height
!= img
->height
)) {
369 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
371 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
373 change_title(&title_property
, (char *)current_link
->data
);
375 XClearWindow(dpy
, win
);
376 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, max_width
/2-img
->width
/2, max_height
/2-img
->height
/2);
385 diaporama: send a xevent to display the next image at every delay set to diaporama_delay
389 void* diaporama(void *arg
) {
395 event
.root
= DefaultRootWindow(dpy
);
396 event
.subwindow
= None
;
397 event
.time
= CurrentTime
;
402 event
.same_screen
= True
;
403 event
.keycode
= XKeysymToKeycode(dpy
, XK_Right
);
405 event
.type
= KeyPress
;
407 while(diaporama_flag
) {
409 r
= XSendEvent(event
.display
, event
.window
, True
, KeyPressMask
, (XEvent
*)&event
);
411 fprintf(stderr
, "Error sending event\n");
413 /* default sleep time between moving to next image */
414 sleep(diaporama_delay
);
422 linked_list_init: init the linked list
424 void linked_list_init (linked_list_t
*list
) {
425 list
->first
= list
->last
= 0;
430 linked_list_add: add an element to the linked list
431 return EXIT_SUCCESS on success, 1 otherwise
433 int linked_list_add (linked_list_t
*list
, const void *data
) {
436 /* calloc sets the "next" field to zero. */
437 link
= calloc (1, sizeof (link_t
));
439 fprintf (stderr
, "calloc failed.\n");
444 /* Join the two final links together. */
445 list
->last
->next
= link
;
446 link
->prev
= list
->last
;
457 linked_list_free: deallocate the whole linked list
459 void linked_list_free (linked_list_t
*list
) {
462 for (link
= list
->first
; link
; link
= next
) {
463 /* Store the next value so that we don't access freed memory. */
466 free((char *)link
->data
);
472 connect_dir: list and sort by name all files from a given directory
473 arg: the directory path that contains images, the linked list where to add the new file refs
474 return: the first argument of the list or NULL on failure
476 link_t
* connect_dir(char *dirpath
, linked_list_t
*li
) {
479 char path
[PATH_MAX
] = "";
484 dv
= scandir(dirpath
, &dir
, 0, alphasort
);
486 /* maybe it's a file */
487 struct stat stDirInfo
;
488 if (lstat(dirpath
, &stDirInfo
) == 0) {
489 linked_list_add (li
, strdup(dirpath
));
495 for (idx
= 0; idx
< dv
; idx
++) {
496 struct stat stDirInfo
;
497 if (dirpath
[strlen(dirpath
)-1] == FILE_SEPARATOR
)
498 snprintf(path
, PATH_MAX
, "%s%s", dirpath
, dir
[idx
]->d_name
);
500 snprintf(path
, PATH_MAX
, "%s%c%s", dirpath
, FILE_SEPARATOR
, dir
[idx
]->d_name
);
503 if ((lstat(path
, &stDirInfo
) == 0) && !S_ISDIR(stDirInfo
.st_mode
)) {
504 linked_list_add (li
, strdup(path
));
514 int main(int argc
, char **argv
) {
516 RContextAttributes attr
;
519 char *reading_filename
= "";
522 XClassHint
*class_hints
;
523 XSizeHints
*size_hints
;
526 Pixmap icon_pixmap
, icon_shape
;
529 if (!(class_hints
= XAllocClassHint())) {
530 fprintf(stderr
, "Error: failure allocating memory\n");
533 class_hints
->res_name
= (char *)APPNAME
;
534 class_hints
->res_class
= "default";
537 lightGray
.red
= lightGray
.green
= lightGray
.blue
= 211;
538 darkGray
.red
= darkGray
.green
= darkGray
.blue
= 169;
539 lightGray
.alpha
= darkGray
.alpha
= 1;
540 black
.red
= black
.green
= black
.blue
= 0;
542 red
.green
= red
.blue
= 0;
544 if ((option
= getopt(argc
, argv
, "hv")) != -1) {
547 fprintf(stderr
, "Usage: %s [image(s)|directory]\n"
553 "d: launch diaporama mode\n"
556 "right: next image\n"
557 "left: previous image\n"
559 "down: last image\n",
563 fprintf(stderr
, "%s version %d.%d\n", APPNAME
, APPVERSION_MAJOR
, APPVERSION_MINOR
);
570 linked_list_init (&list
);
572 dpy
= XOpenDisplay(NULL
);
574 fprintf(stderr
, "Error: can't open display");
575 linked_list_free (&list
);
579 screen
= DefaultScreen(dpy
);
580 max_width
= DisplayWidth(dpy
, screen
);
581 max_height
= DisplayHeight(dpy
, screen
);
583 attr
.flags
= RC_RenderMode
| RC_ColorsPerChannel
;
584 attr
.render_mode
= RDitheredRendering
;
585 attr
.colors_per_channel
= 4;
586 ctx
= RCreateContext(dpy
, DefaultScreen(dpy
), &attr
);
593 for (file_i
= 1; file_i
< argc
; file_i
++) {
594 current_link
= connect_dir(argv
[file_i
], &list
);
596 reading_filename
= (char *)current_link
->data
;
597 max_index
= list
.count
;
601 img
= RLoadImage(ctx
, reading_filename
, 0);
604 fprintf(stderr
, "Error: %s %s\n", reading_filename
, RMessageForError(RErrorCode
));
605 img
= draw_failed_image();
610 merge_with_background(img
);
614 fprintf(stderr
, "display size: %dx%d\n", max_width
, max_height
);
616 win
= XCreateSimpleWindow(dpy
, DefaultRootWindow(dpy
), 0, 0, img
->width
, img
->height
, 0, 0, BlackPixel(dpy
, screen
));
617 XSelectInput(dpy
, win
, KeyPressMask
|StructureNotifyMask
|ExposureMask
|ButtonPressMask
|FocusChangeMask
);
619 if (!(size_hints
= XAllocSizeHints())) {
620 fprintf(stderr
, "Error: failure allocating memory\n");
623 size_hints
->width
= img
->width
;
624 size_hints
->height
= img
->height
;
626 Atom delWindow
= XInternAtom(dpy
, "WM_DELETE_WINDOW", 0);
627 XSetWMProtocols(dpy
, win
, &delWindow
, 1);
628 change_title(&title_property
, reading_filename
);
630 win_hints
= XAllocWMHints();
632 win_hints
->flags
= StateHint
|InputHint
|WindowGroupHint
;
635 if ((XpmCreatePixmapFromData(dpy
, win
, wmiv_xpm
, &icon_pixmap
, &icon_shape
, NULL
)) == 0) {
636 win_hints
->flags
|= IconPixmapHint
|IconMaskHint
|IconPositionHint
;
637 win_hints
->icon_pixmap
= icon_pixmap
;
638 win_hints
->icon_mask
= icon_shape
;
639 win_hints
->icon_x
= 0;
640 win_hints
->icon_y
= 0;
643 win_hints
->initial_state
= NormalState
;
644 win_hints
->input
= True
;
645 win_hints
->window_group
= win
;
646 XStringListToTextProperty((char **)&APPNAME
, 1, &icon_property
);
647 XSetWMProperties(dpy
, win
, NULL
, &icon_property
, argv
, argc
, size_hints
, win_hints
, class_hints
);
648 if (icon_property
.value
)
649 XFree(icon_property
.value
);
655 XMapWindow(dpy
, win
);
657 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
661 if (e
.type
== ClientMessage
) {
662 if (e
.xclient
.data
.l
[0] == delWindow
)
666 if (e
.type
== FocusIn
) {
670 if (e
.type
== FocusOut
) {
674 if (!fullscreen_flag
&& (e
.type
== Expose
)) {
675 XExposeEvent xev
= e
.xexpose
;
677 XCopyArea(dpy
, pix
, win
, ctx
->copy_gc
, 0, 0, img
->width
, img
->height
, 0, 0);
680 if (!fullscreen_flag
&& e
.type
== ConfigureNotify
) {
681 XConfigureEvent xce
= e
.xconfigure
;
682 if (xce
.width
!= img
->width
|| xce
.height
!= img
->height
) {
683 RImage
*old_img
= img
;
684 img
= RLoadImage(ctx
, current_link
->data
, 0);
686 /* keep the old img and window size */
688 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
690 img
= RScaleImage(img
, xce
.width
, xce
.height
);
693 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
695 merge_with_background(img
);
696 if (RConvertImage(ctx
, img
, &pix
))
697 RReleaseImage(old_img
);
698 XResizeWindow(dpy
, win
, img
->width
, img
->height
);
704 if (fullscreen_flag
&& e
.type
== ConfigureNotify
) {
708 if (e
.type
== ButtonPress
) {
709 switch(e
.xbutton
.button
) {
712 if (img
&& (e
.xbutton
.x
> img
->width
/2))
734 if (e
.type
== KeyPress
) {
735 keysym
= XkbKeycodeToKeysym(dpy
, e
.xkey
.keycode
, 0, e
.xkey
.state
& ShiftMask
?1:0);
737 if (keysym
!= XK_Right
)
738 diaporama_flag
= False
;
749 current_link
= list
.last
;
755 current_link
= list
.first
;
763 if (current_link
&& !diaporama_flag
) {
764 diaporama_flag
= True
;
765 pthread_create(&tid
, NULL
, &diaporama
, NULL
);
767 fprintf(stderr
, "Can't use diaporama mode, need a picture directory\n");
776 if (!fullscreen_flag
) {
778 /* zoom_in will increase the zoom factor by 0.2 */
781 /* we are in fullscreen mode already, want to return to normal size */
803 XFreePixmap(dpy
, pix
);
806 XFreePixmap(dpy
, icon_pixmap
);
808 XFreePixmap(dpy
, icon_shape
);
810 linked_list_free(&list
);
811 RDestroyContext(ctx
);