diff options
author | John Ankarstr\xf6m <john@ankarstrom.se> | 2021-05-29 12:54:47 +0200 |
---|---|---|
committer | John Ankarstr\xf6m <john@ankarstrom.se> | 2021-05-29 13:18:40 +0200 |
commit | a041d9898e6d699bd8c0c25482ec574feb03c547 (patch) | |
tree | 7f094e33fb530152c3ab6238ce7300750b47addb | |
download | jwm-a041d9898e6d699bd8c0c25482ec574feb03c547.tar.gz |
First commit
This is the original state of the released tarball for JWM 1.8,
which will serve as my starting point for further modifications.
-rw-r--r-- | Doxyfile | 1237 | ||||
-rw-r--r-- | LICENSE | 340 | ||||
-rw-r--r-- | Makefile.in | 53 | ||||
-rw-r--r-- | README | 24 | ||||
-rw-r--r-- | config.h.in | 145 | ||||
-rw-r--r-- | configure.in | 388 | ||||
-rw-r--r-- | example.jwmrc | 161 | ||||
-rw-r--r-- | jwm.1.in | 1185 | ||||
-rw-r--r-- | package/irix/Makefile.in | 18 | ||||
-rw-r--r-- | package/irix/jwm.idb.in | 3 | ||||
-rw-r--r-- | package/irix/jwm.spec.in | 23 | ||||
-rw-r--r-- | package/slackware/Makefile.in | 27 | ||||
-rw-r--r-- | package/slackware/slack-desc.in | 20 | ||||
-rw-r--r-- | package/slackware/slackware.jwmrc | 111 | ||||
-rw-r--r-- | package/solaris/Makefile.in | 28 | ||||
-rw-r--r-- | package/solaris/pkginfo.in | 9 | ||||
-rw-r--r-- | package/solaris/prototype.in | 4 | ||||
-rw-r--r-- | package/solaris/solaris.jwmrc | 111 | ||||
-rw-r--r-- | src/Makefile.in | 38 | ||||
-rw-r--r-- | src/border.c | 753 | ||||
-rw-r--r-- | src/border.h | 80 | ||||
-rw-r--r-- | src/button.c | 212 | ||||
-rw-r--r-- | src/button.h | 63 | ||||
-rw-r--r-- | src/client.c | 1423 | ||||
-rw-r--r-- | src/client.h | 285 | ||||
-rw-r--r-- | src/clock.c | 332 | ||||
-rw-r--r-- | src/clock.h | 41 | ||||
-rw-r--r-- | src/color.c | 606 | ||||
-rw-r--r-- | src/color.h | 91 | ||||
-rw-r--r-- | src/command.c | 124 | ||||
-rw-r--r-- | src/command.h | 36 | ||||
-rw-r--r-- | src/confirm.c | 412 | ||||
-rw-r--r-- | src/confirm.h | 36 | ||||
-rw-r--r-- | src/cursor.c | 313 | ||||
-rw-r--r-- | src/cursor.h | 44 | ||||
-rw-r--r-- | src/debug.c | 396 | ||||
-rw-r--r-- | src/debug.h | 104 | ||||
-rw-r--r-- | src/desktop.c | 239 | ||||
-rw-r--r-- | src/desktop.h | 60 | ||||
-rw-r--r-- | src/dock.c | 602 | ||||
-rw-r--r-- | src/dock.h | 43 | ||||
-rw-r--r-- | src/error.c | 108 | ||||
-rw-r--r-- | src/error.h | 38 | ||||
-rw-r--r-- | src/event.c | 1138 | ||||
-rw-r--r-- | src/event.h | 28 | ||||
-rw-r--r-- | src/font.c | 295 | ||||
-rw-r--r-- | src/font.h | 43 | ||||
-rw-r--r-- | src/group.c | 299 | ||||
-rw-r--r-- | src/group.h | 48 | ||||
-rw-r--r-- | src/help.c | 84 | ||||
-rw-r--r-- | src/help.h | 26 | ||||
-rw-r--r-- | src/hint.c | 970 | ||||
-rw-r--r-- | src/hint.h | 173 | ||||
-rw-r--r-- | src/icon.c | 726 | ||||
-rw-r--r-- | src/icon.h | 110 | ||||
-rw-r--r-- | src/image.c | 362 | ||||
-rw-r--r-- | src/image.h | 47 | ||||
-rw-r--r-- | src/jwm.h | 128 | ||||
-rw-r--r-- | src/jxlib.h | 420 | ||||
-rw-r--r-- | src/key.c | 452 | ||||
-rw-r--r-- | src/key.h | 57 | ||||
-rw-r--r-- | src/lex.c | 572 | ||||
-rw-r--r-- | src/lex.h | 115 | ||||
-rw-r--r-- | src/main.c | 507 | ||||
-rw-r--r-- | src/main.h | 56 | ||||
-rw-r--r-- | src/match.c | 80 | ||||
-rw-r--r-- | src/match.h | 21 | ||||
-rw-r--r-- | src/menu.c | 799 | ||||
-rw-r--r-- | src/menu.h | 89 | ||||
-rw-r--r-- | src/misc.c | 196 | ||||
-rw-r--r-- | src/misc.h | 37 | ||||
-rw-r--r-- | src/move.c | 729 | ||||
-rw-r--r-- | src/move.h | 61 | ||||
-rw-r--r-- | src/outline.c | 73 | ||||
-rw-r--r-- | src/outline.h | 32 | ||||
-rw-r--r-- | src/pager.c | 331 | ||||
-rw-r--r-- | src/pager.h | 27 | ||||
-rw-r--r-- | src/parse.c | 1551 | ||||
-rw-r--r-- | src/parse.h | 19 | ||||
-rw-r--r-- | src/place.c | 647 | ||||
-rw-r--r-- | src/place.h | 34 | ||||
-rw-r--r-- | src/popup.c | 232 | ||||
-rw-r--r-- | src/popup.h | 35 | ||||
-rw-r--r-- | src/render.c | 226 | ||||
-rw-r--r-- | src/render.h | 25 | ||||
-rw-r--r-- | src/resize.c | 491 | ||||
-rw-r--r-- | src/resize.h | 42 | ||||
-rw-r--r-- | src/root.c | 315 | ||||
-rw-r--r-- | src/root.h | 65 | ||||
-rw-r--r-- | src/screen.c | 138 | ||||
-rw-r--r-- | src/screen.h | 53 | ||||
-rw-r--r-- | src/status.c | 274 | ||||
-rw-r--r-- | src/status.h | 55 | ||||
-rw-r--r-- | src/swallow.c | 274 | ||||
-rw-r--r-- | src/swallow.h | 43 | ||||
-rw-r--r-- | src/taskbar.c | 936 | ||||
-rw-r--r-- | src/taskbar.h | 64 | ||||
-rw-r--r-- | src/theme.c | 88 | ||||
-rw-r--r-- | src/theme.h | 31 | ||||
-rw-r--r-- | src/timing.c | 71 | ||||
-rw-r--r-- | src/timing.h | 43 | ||||
-rw-r--r-- | src/tray.c | 1104 | ||||
-rw-r--r-- | src/tray.h | 214 | ||||
-rw-r--r-- | src/traybutton.c | 454 | ||||
-rw-r--r-- | src/traybutton.h | 51 | ||||
-rw-r--r-- | src/winmenu.c | 327 | ||||
-rw-r--r-- | src/winmenu.h | 37 | ||||
-rw-r--r-- | src/x.xpm | 32 | ||||
-rw-r--r-- | todo.txt | 799 |
109 files changed, 28267 insertions, 0 deletions
diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..dc247e7 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1237 @@ +# Doxyfile 1.4.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = "JWM" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = "doc" + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = "src" + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +FILE_PATTERNS = "*.h" + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = USE_FRIBID USE_ICONS USE_PNG USE_SHAPE USE_XFT USE_XINERAMA USE_XPM USE_XRENDER + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = NO + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = NO + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = NO + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..6c50f45 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,53 @@ + +SYSCONF = @SYSCONF@ +MANDIR = @MANDIR@ +VERSION = @VERSION@ + +all: + cd src ; $(MAKE) all + +install: all + cd src ; $(MAKE) install ; cd .. + install -d $(SYSCONF) + install -m 644 example.jwmrc $(SYSCONF)/system.jwmrc + install -d $(MANDIR)/man1 + install -m 644 jwm.1 $(MANDIR)/man1/jwm.1 + +depend: + cd src ; $(MAKE) depend + +tarball: + rm -f ../jwm-$(VERSION).tar.bz2 ; + rm -fr ../jwm-$(VERSION) ; + cp -r ../jwm ../jwm-$(VERSION) ; + (cd ../jwm-$(VERSION) && $(MAKE) distclean) ; + (cd .. && tar -cf jwm-$(VERSION).tar jwm-$(VERSION)); + rm -fr ../jwm-$(VERSION) ; + (cd .. && bzip2 jwm-$(VERSION).tar) + +irix-package: all + (cd package/irix && $(MAKE)) + +slackware-package: all + (cd package/slackware && $(MAKE)) + +solaris-package: all + (cd package/solaris && $(MAKE)) + +clean: + (cd src && $(MAKE) clean) + for x in package/* ; do \ + (cd $$x && $(MAKE) clean ) ; \ + done + rm -rf doc + +distclean: clean + rm -f *~ config.cache config.log config.status config.h ; + rm -f Makefile src/Makefile jwm.tardist jwm.1 ; + rm -f Makefile.bak src/Makefile.bak ; + rm -fr autom4te.cache ; + rm -fr `find . \( -name .svn -o -name .gdb_history \) -print` ; + for x in package/* ; do \ + (cd $$x && $(MAKE) distclean ) ; \ + done + @@ -0,0 +1,24 @@ +JWM +See LICENSE for license information. + +> Requirements +To build JWM you will need a C compiler (gcc works), X11, and the +"development headers" for X11 and Xlib. +If available and not disabled at compile time, JWM will also use +the following libraries: + libfribidi for bi-directional text support. + libXext for the shape extension. + libXext for the render extension. + libXinerama for multiple head support. + libXpm for XPM icons. + libpng for PNG icons. + libxft for antialiased and true type fonts. + +> Installation +Run "./configure --help" for configuration options. +Run "./configure [options]" +Run "make" to build JWM. +Run "make install" to install JWM. + +For more information see http://joewing.net + diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..fc3f81f --- /dev/null +++ b/config.h.in @@ -0,0 +1,145 @@ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define to debug JWM */ +#undef DEBUG + +/* Define to disable confirm dialogs */ +#undef DISABLE_CONFIRM + +/* Define to 1 if you have the <alloca.h> header file. */ +#undef HAVE_ALLOCA_H + +/* Define to 1 if you have the <ctype.h> header file. */ +#undef HAVE_CTYPE_H + +/* Define to 1 if you have the <ft2build.h> header file. */ +#undef HAVE_FT2BUILD_H + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the <signal.h> header file. */ +#undef HAVE_SIGNAL_H + +/* Define to 1 if you have the <stdarg.h> header file. */ +#undef HAVE_STDARG_H + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdio.h> header file. */ +#undef HAVE_STDIO_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the <sys/select.h> header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/time.h> header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <sys/wait.h> header file. */ +#undef HAVE_SYS_WAIT_H + +/* Define to 1 if you have the <time.h> header file. */ +#undef HAVE_TIME_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the <X11/cursorfont.h> header file. */ +#undef HAVE_X11_CURSORFONT_H + +/* Define to 1 if you have the <X11/extensions/Xrender.h> header file. */ +#undef HAVE_X11_EXTENSIONS_XRENDER_H + +/* Define to 1 if you have the <X11/keysym.h> header file. */ +#undef HAVE_X11_KEYSYM_H + +/* Define to 1 if you have the <X11/Xatom.h> header file. */ +#undef HAVE_X11_XATOM_H + +/* Define to 1 if you have the <X11/Xlib.h> header file. */ +#undef HAVE_X11_XLIB_H + +/* Define to 1 if you have the <X11/xpm.h> header file. */ +#undef HAVE_X11_XPM_H + +/* Define to 1 if you have the <X11/Xproto.h> header file. */ +#undef HAVE_X11_XPROTO_H + +/* Define to 1 if you have the <X11/Xresource.h> header file. */ +#undef HAVE_X11_XRESOURCE_H + +/* Define to 1 if you have the <X11/Xutil.h> header file. */ +#undef HAVE_X11_XUTIL_H + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* default system configuration path */ +#undef SYSTEM_CONFIG + +/* Define to use FriBidi */ +#undef USE_FRIBIDI + +/* Define to enable icon support */ +#undef USE_ICONS + +/* Define to use libpng */ +#undef USE_PNG + +/* Define to enable the X shape extension */ +#undef USE_SHAPE + +/* Define to enable Xft */ +#undef USE_XFT + +/* Define to enable Xinerama */ +#undef USE_XINERAMA + +/* Define to enable XPM support */ +#undef USE_XPM + +/* Define to enable the XRender extension */ +#undef USE_XRENDER + +/* Define for single UNIX conformance */ +#undef _XOPEN_SOURCE + +/* Define for timeval on IRIX 6.2 */ +#undef _XOPEN_SOURCE_EXTENDED + +/* Define for timeval on Solaris 2.5.1 */ +#undef __EXTENSIONS__ diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..86e3843 --- /dev/null +++ b/configure.in @@ -0,0 +1,388 @@ +############################################################################ +# JWM autoconf. +############################################################################ + +AC_INIT(jwm, 1.8, joewing@joewing.net) +AC_PREREQ(2.57) +AC_CONFIG_SRCDIR([src]) +AC_CONFIG_HEADER([config.h]) +AC_LANG(C) + +AC_PROG_CC +AC_PROG_CPP + +############################################################################ +# Check if we need _XOPEN_SOURCE +############################################################################ +AC_MSG_CHECKING([if _XOPEN_SOURCE should be defined]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#define _XOPEN_SOURCE 600L +#include <unistd.h> +]])], [use_xopen_source="yes"], [use_xopen_source="no"]) +AC_MSG_RESULT([$use_xopen_source]) + +if test $use_xopen_source = "yes"; then + + AC_DEFINE(_XOPEN_SOURCE, 600L, [Define for single UNIX conformance]) + + # Needed for IRIX 6.2 so that struct timeval is declared. + AC_DEFINE(_XOPEN_SOURCE_EXTENDED, 1, [Define for timeval on IRIX 6.2]) + + # Needed for Solaris 2.5.1 so that struct timeval is declared. + AC_DEFINE(__EXTENSIONS__, 1, [Define for timeval on Solaris 2.5.1]) + +fi + +############################################################################ +# Check for X11 +############################################################################ +AC_PATH_X + +if test ! "$no_x" = "yes" ; then + if test ! x"$x_libraries" = x ; then + LDFLAGS="$LDFLAGS -L$x_libraries" + fi + if test ! x"$x_includes" = x ; then + CFLAGS="$CFLAGS -I$x_includes" + fi +else + AC_MSG_ERROR([Could not find X11]) +fi + +AC_CHECK_LIB([X11], XOpenDisplay, + [ LDFLAGS="$LDFLAGS -lX11" ], + [ AC_MSG_ERROR([libX11 not found]) ]) + +############################################################################ +# Check for necessary include files. +############################################################################ +AC_CHECK_HEADERS([stdarg.h stdio.h stdlib.h ctype.h], [], + [ AC_MSG_ERROR([one or more necessary header files not found]) ]) + +AC_CHECK_HEADERS([sys/select.h signal.h unistd.h time.h sys/wait.h sys/time.h]) + +AC_CHECK_HEADERS([alloca.h]) + +AC_CHECK_HEADERS([X11/Xlib.h], [], + [ AC_MSG_ERROR([Xlib.h could not be found]) ]) + +AC_CHECK_HEADERS([X11/Xutil.h X11/cursorfont.h X11/Xproto.h \ + X11/Xatom.h X11/keysym.h X11/Xresource.h], [], [], + [ +#include <X11/Xlib.h> + ]) + +############################################################################ +# Check for pkg-config. +############################################################################ + +AC_DEFUN([JWM_PKGCONFIG_EXISTS], +[ + AC_MSG_CHECKING([for pkg-config]) + if test -x `which pkg-config` ; then + PKGCONFIG="pkg-config" + fi + AC_MSG_RESULT($PKGCONFIG) +]) + +AC_DEFUN([JWM_PKGCONFIG], +[ + AC_REQUIRE([JWM_PKGCONFIG_EXISTS]) + if test "x$PKGCONFIG" != "x" ; then + AC_MSG_CHECKING([if pkg-config knows about $2]) + if test `$PKGCONFIG $2 ; echo $?` -eq 0 ; then + $1="yes" + else + $1="no" + fi + AC_MSG_RESULT($$1) + else + $1="no" + fi +]) + +JWM_PKGCONFIG([use_pkgconfig_png], [libpng]) +JWM_PKGCONFIG([use_pkgconfig_xft], [xft]) +JWM_PKGCONFIG([use_pkgconfig_xrender], [xrender]) +JWM_PKGCONFIG([use_pkgconfig_fribidi], [fribidi]) + +############################################################################ +# Check if confirm dialogs should be used. +############################################################################ +AC_ARG_ENABLE(confirm, + AC_HELP_STRING([--disable-confirm], [don't enable confirm dialogs]) ) +if test "$enable_confirm" = "no" ; then + AC_DEFINE(DISABLE_CONFIRM, 1, [Define to disable confirm dialogs]) +else + enable_confirm="yes" +fi + +############################################################################ +# Check if icon support was requested. +############################################################################ +AC_ARG_ENABLE(icons, + AC_HELP_STRING([--disable-icons], [don't enable icon support]) ) +if test "$enable_icons" != "no" ; then + enable_icons="yes" + AC_DEFINE(USE_ICONS, 1, [Define to enable icon support] ) +fi + +############################################################################ +# Check if PNG support was requested and available. +############################################################################ +AC_ARG_ENABLE(png, + AC_HELP_STRING([--disable-png], [don't support PNG images]) ) +if test "$enable_png" != "no" ; then + + if test "$use_pkgconfig_png" = "yes" ; then + PNG_CFLAGS=`$PKGCONFIG --cflags libpng` + PNG_LDFLAGS=`$PKGCONFIG --libs libpng` + elif test -x `which libpng-config` ; then + PNG_CFLAGS=`libpng-config --cflags` + PNG_LDFLAGS=`libpng-config --libs` + else + PNG_LDFLAGS="-lpng -lz -lm" + fi + +fi +if test "$enable_png" != "no" ; then + AC_CHECK_LIB(png, png_read_image, + [ LDFLAGS="$LDFLAGS $PNG_LDFLAGS" + CFLAGS="$CFLAGS $PNG_CFLAGS" + enable_png="yes" + AC_DEFINE(USE_PNG, 1, [Define to use libpng]) ], + [ enable_png="no" + AC_MSG_WARN([unable to use libpng, PNG support disabled]) ], + [ $PNG_LDFLAGS ]) +fi + +############################################################################ +# Check if XFT support was requested and available. +############################################################################ +AC_ARG_ENABLE(xft, + AC_HELP_STRING([--disable-xft], [don't use Xft]) ) +if test "$enable_xft" != "no"; then + + if test "$use_pkgconfig_xft" = "yes" ; then + XFT_CFLAGS=`$PKGCONFIG --cflags xft` + XFT_LDFLAGS=`$PKGCONFIG --libs xft` + elif test -x `which xft-config` ; then + XFT_CFLAGS=`xft-config --cflags` + XFT_LDFLAGS=`xft-config --libs` + else + XFT_LDFLAGS="-lXft" + fi + +fi +if test "$enable_xft" != "no" ; then + AC_CHECK_LIB(Xft, XftFontOpenName, + [ LDFLAGS="$LDFLAGS $XFT_LDFLAGS" + CFLAGS="$CFLAGS $XFT_CFLAGS" + enable_xft="yes" + AC_DEFINE(USE_XFT, 1, [Define to enable Xft]) ], + [ enable_xft="no" + AC_MSG_WARN([unable to use Xft]) ], + [ $XFT_LDFLAGS ]) +fi +if test "$enable_xft" != "no" ; then + AC_CHECK_HEADERS([ft2build.h]) +fi + +############################################################################ +# Check if support for the XRENDER extension was requested and available. +############################################################################ +AC_ARG_ENABLE(xrender, + AC_HELP_STRING([--disable-xrender], [don't use the XRender extension]) ) +if test "$enable_xrender" != "no"; then + + if test "$use_pkgconfig_xrender" = "yes" ; then + XRENDER_CFLAGS=`$PKGCONFIG --cflags xrender` + XRENDER_LDFLAGS=`$PKGCONFIG --libs xrender` + else + XRENDER_LDFLAGS="-lXrender" + fi + + AC_CHECK_HEADERS([X11/extensions/Xrender.h], [], + [ + enable_xrender="no"; + AC_MSG_WARN([unable to use X11/extensions/XRender.h]) + ]) + +fi +if test "$enable_xrender" != "no" ; then + AC_CHECK_LIB(Xrender, XRenderComposite, + [ LDFLAGS="$LDFLAGS $XRENDER_LDFLAGS" + CFLAGS="$CFLAGS $XRENDER_CFLAGS" + enable_xrender="yes" + AC_DEFINE(USE_XRENDER, 1, [Define to enable the XRender extension]) ], + [ enable_xrender="no" + AC_MSG_WARN([unable to use the XRender extension]) ], + [ $XRENDER_LDFLAGS ]) +fi + +############################################################################ +# Check if FriBidi support was requested and available. +############################################################################ +AC_ARG_ENABLE(fribidi, + AC_HELP_STRING([--disable-fribidi], + [disable bi-directional unicode support]) ) +if test "$enable_fribidi" != "no" ; then + + if test "$use_pkgconfig_fribidi" = "yes" ; then + FRIBIDI_CFLAGS=`$PKGCONFIG --cflags fribidi` + FRIBIDI_LDFLAGS=`$PKGCONFIG --libs fribidi` + elif test -x `which fribidi-config` ; then + FRIBIDI_CFLAGS=`fribidi-config --cflags` + FRIBIDI_LDFLAGS=`fribidi-config --libs` + else + FRIBIDI_LDFLAGS="-lfribidi" + fi + +fi +if test "$enable_fribidi" != "no" ; then + AC_CHECK_LIB(fribidi, fribidi_log2vis, + [ LDFLAGS="$LDFLAGS $FRIBIDI_LDFLAGS" + CFLAGS="$CFLAGS $FRIBIDI_CFLAGS" + enable_fribidi="yes" + AC_DEFINE(USE_FRIBIDI, 1, [Define to use FriBidi]) ], + [ enable_fribidi="no" + AC_MSG_WARN([unable to use FriBidi]) ], + [ $FRIBIDI_LDFLAGS ]) +fi + +############################################################################ +# Check if XPM support was requested and available. +############################################################################ +AC_ARG_ENABLE(xpm, + AC_HELP_STRING([--disable-xpm], [don't support XPM images]) ) +if test "$enable_xpm" != "no"; then + AC_CHECK_HEADERS([X11/xpm.h], [], + [ enable_xpm="no"; + AC_MSG_WARN([unable to use X11/xpm.h]) ]) +fi +if test "$enable_xpm" != "no"; then + AC_CHECK_DECL(XpmAllocColor, [], + [ enable_xpm="no" + AC_MSG_WARN([XPM library too old]) ], + [ +#include <X11/xpm.h> +]) +fi +if test "$enable_xpm" != "no"; then + AC_CHECK_LIB(Xpm, XpmReadFileToImage, + [ LDFLAGS="$LDFLAGS -lXpm"; + enable_xpm="yes" + AC_DEFINE(USE_XPM, 1, [Define to enable XPM support]) ], + [ enable_xpm="no" + AC_MSG_WARN([unable to use libXpm]) ]) +fi + +############################################################################ +# Check if support for the shape extension was requested and available. +############################################################################ +AC_ARG_ENABLE(shape, + AC_HELP_STRING([--disable-shape], [don't use the X shape extension]) ) +if test "$enable_shape" != "no"; then + AC_CHECK_LIB(Xext, XShapeCombineRectangles, + [ LDFLAGS="$LDFLAGS -lXext" + enable_shape="yes" + AC_DEFINE(USE_SHAPE, 1, [Define to enable the X shape extension]) ], + [ enable_shape="no" + AC_MSG_WARN([unable to use the X shape extension]) ]) +fi + +############################################################################ +# Check if support for Xinerama was requested and available. +############################################################################ +AC_ARG_ENABLE(xinerama, + AC_HELP_STRING([--disable-xinerama], [don't use Xinerama]) ) +if test "$enable_xinerama" != "no"; then + AC_CHECK_LIB(Xinerama, XineramaQueryExtension, + [ LDFLAGS="$LDFLAGS -lXinerama" + enable_xinerama="yes" + AC_DEFINE(USE_XINERAMA, 1, [Define to enable Xinerama]) ], + [ enable_xinerama="no" + AC_MSG_WARN([unable to use Xinerama]) ]) +fi + +############################################################################ +# Check if debug mode was requested. +############################################################################ +AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug], [use this to debug JWM]) ) +if test "$enable_debug" = "yes"; then + AC_DEFINE(DEBUG, 1, [Define to debug JWM]) + CFLAGS="$CFLAGS -Wall -g -DDEBUG" + LDFLAGS="$LDFLAGS -g" +else + enable_debug="no" +fi + +############################################################################ +# Create the output files. +############################################################################ +if test "$prefix" = "NONE" ; then + PREFIX="$ac_default_prefix" + prefix="$ac_default_prefix" +else + PREFIX="$prefix" +fi + +if test "$exec_prefix" = "NONE" ; then + exec_prefix="$PREFIX" +fi + +if test "$sysconfdir" = "" ; then + sysconfdir="$ac_default_sysconfdir" +fi + +BINDIR=`eval echo \""$bindir"\"` +SYSCONF=`eval echo \"$sysconfdir\"` +MANDIR=`eval echo \"$mandir\"` + +AC_DEFINE_UNQUOTED(SYSTEM_CONFIG, "$SYSCONF/system.jwmrc", + [default system configuration path]) + +AC_SUBST(CFLAGS) +AC_SUBST(LDFLAGS) +AC_SUBST(VERSION, "$PACKAGE_VERSION") +AC_SUBST(INSTVERSION, `echo $PACKAGE_VERSION | tr -d .`) +AC_SUBST(BINDIR) +AC_SUBST(MANDIR) +AC_SUBST(DATE, `date "+%Y-%m-%d"`) +AC_SUBST(SYSCONF, "$SYSCONF") + +AC_OUTPUT( + + Makefile src/Makefile jwm.1 + + package/irix/jwm.spec package/irix/jwm.idb package/irix/Makefile + + package/solaris/Makefile package/solaris/pkginfo package/solaris/prototype + + package/slackware/Makefile package/slackware/slack-desc + +) + +############################################################################ +# Display the status. +############################################################################ + +echo "Compiler: $CC" +echo "Compile flags: $CFLAGS" +echo "Link flags: $LDFLAGS" +echo +echo "Options" +echo +echo " Confirm: $enable_confirm" +echo " Icon: $enable_icons" +echo " PNG: $enable_png" +echo " XPM: $enable_xpm" +echo " XFT: $enable_xft" +echo " XRender: $enable_xrender" +echo " FriBidi: $enable_fribidi" +echo " Shape: $enable_shape" +echo " Xinerama: $enable_xinerama" +echo " Debug: $enable_debug" +echo + diff --git a/example.jwmrc b/example.jwmrc new file mode 100644 index 0000000..deae8c0 --- /dev/null +++ b/example.jwmrc @@ -0,0 +1,161 @@ +<?xml version="1.0"?> + +<JWM> + + <!-- The root menu, if this is undefined you will not get a menu. --> + <!-- Additional RootMenu attributes: onroot, labeled, label --> + <RootMenu height="32"> + <Program icon="rxvt.png" label="Terminal">xterm</Program> + + <!-- Addititional Menu attributes: height, labeled --> + <Menu icon="folder.png" label="Applications"> + <Program icon="dia.png" label="Dia">dia</Program> + <Program icon="firefox.png" label="Firefox">firefox</Program> + <Program icon="gaim.png" label="Gaim">gaim</Program> + <Program icon="gftp.png" label="gFTP">gftp</Program> + <Program icon="gimp.png" label="Gimp">gimp</Program> + <Program icon="gtk-gnutella.png" label="gtk-gnutella"> + gtk-gnutella + </Program> + <Program icon="gxine.png" label="gxine">gxine</Program> + <Program icon="xmms.xpm" label="XMMS">xmms</Program> + </Menu> + <Menu icon="folder.png" label="Utilities"> + <Program icon="xcalc.png">xcalc</Program> + <Program icon="xfontsel.png">xfontsel</Program> + <Program icon="xmag.png">xmag</Program> + <Program icon="xprop.png" label="xprop"> + xprop | xmessage -file - + </Program> + </Menu> + <!-- <Desktops label="Desktops" icon="desktops.png"/> --> + <Separator/> + <Restart label="Restart" icon="restart.png"/> + <Exit label="Exit" confirm="true" icon="exit.png"/> + </RootMenu> + + <Group> + <Class>Gaim</Class> + <Option>sticky</Option> + </Group> + + <Group> + <Class>xmms</Class> + <Option>icon:xmms.xpm</Option> + </Group> + + <!-- Additional tray attributes: autohide, width, border, layer, layout --> + <Tray x="0" y="-1" height="32"> + + <!-- Additional TrayButton attribute: label --> + <TrayButton label="JWM"/> + + <!-- Additional Pager attributes; width, height --> + <Pager/> + + <!-- Additional TaskList attribute: maxwidth --> + <TaskList/> + + <!-- Additional Swallow attribute: height --> + <Swallow name="xload" width="64"> + xload -nolabel -bg black -fg red -hl white + </Swallow> + <Clock>xclock</Clock> + </Tray> + + <!-- Visual Styles --> + + <BorderStyle> + <Font>FreeSans-12:bold</Font> + <Width>5</Width> + <Height>20</Height> + <Foreground>black</Foreground> + <Background>gray90</Background> + <ActiveForeground>white</ActiveForeground> + <ActiveBackground>#4A5966</ActiveBackground> + </BorderStyle> + + <TaskListStyle> + <Font>FreeSans-12:bold</Font> + <ActiveForeground>white</ActiveForeground> + <ActiveBackground>#8899A6</ActiveBackground> + </TaskListStyle> + + <!-- Additional TrayStyle attribute: insert --> + <TrayStyle> + <Font>FreeSans-12:bold</Font> + <Background>gray90</Background> + <Foreground>black</Foreground> + </TrayStyle> + + <PagerStyle> + <Outline>black</Outline> + <Foreground>gray90</Foreground> + <Background>#888888</Background> + <ActiveForeground>#8899AA</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </PagerStyle> + + <MenuStyle> + <Font>FreeSans-12:bold</Font> + <Foreground>black</Foreground> + <Background>gray90</Background> + <ActiveForeground>white</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </MenuStyle> + + <PopupStyle> + <Font>FreeSans-10</Font> + <Outline>black</Outline> + <Foreground>black</Foreground> + <Background>yellow</Background> + </PopupStyle> + + <IconPath>$HOME/.icons</IconPath> + + <StartupCommand> + xli -onroot /export0/images/formulae.jpg + </StartupCommand> + + <!-- Virtual Desktops --> + <!-- Name tags can be contained within Desktops for desktop names. --> + <Desktops count="4"/> + + <!-- Double click speed (in milliseconds) --> + <DoubleClickSpeed>400</DoubleClickSpeed> + + <!-- Double click delta (in pixels) --> + <DoubleClickDelta>2</DoubleClickDelta> + + <!-- The focus model (sloppy or click) --> + <FocusModel>sloppy</FocusModel> + + <!-- The snap mode (none, screen, or border) --> + <SnapMode distance="10">border</SnapMode> + + <!-- The move mode (outline or opaque) --> + <MoveMode>opaque</MoveMode> + + <!-- The resize mode (outline or opaque) --> + <ResizeMode>opaque</ResizeMode> + + <!-- Key bindings --> + <Key key="Up">up</Key> + <Key key="Down">down</Key> + <Key key="Right">right</Key> + <Key key="Left">left</Key> + <Key key="h">left</Key> + <Key key="j">down</Key> + <Key key="k">up</Key> + <Key key="l">right</Key> + <Key key="Return">select</Key> + <Key key="Escape">escape</Key> + + <Key mask="A" key="Tab">next</Key> + <Key mask="A" key="F4">close</Key> + <Key mask="A" key="#">desktop#</Key> + <Key mask="A" key="F1">root:1</Key> + <Key mask="A" key="F2">window</Key> + +</JWM> + diff --git a/jwm.1.in b/jwm.1.in new file mode 100644 index 0000000..109960b --- /dev/null +++ b/jwm.1.in @@ -0,0 +1,1185 @@ +./" +./" groff -man -Tascii jwm.1 +./" + +.TH jwm 1 "@DATE@" "v@VERSION@" +.SH NAME +JWM - Joe's Window Manager + +.SH SYNOPSIS +.BR jwm " [options]" +.SH DESCRIPTION +JWM is a window manager for the X11 Window System. + +.SH OPTIONS +\fB\-display\fP \fIdisplay\fP +.RS +This option specifies the display to use; see \fBX\fP(1). +.RE +.P +.B "-exit" +.RS +Exit JWM by sending _JWM_EXIT to the root window. +.RE +.P +.B "-h" +.RS +Display a help message and exit. +.RE +.P +.B "-p" +.RS +Parse the configuration file and exit. +It is a good idea to use this after making modifications to the configuration +file to ensure there are no errors. +.RE +.P +.B "-restart" +.RS +Restart JWM by sending _JWM_RESTART to the root window. +.RE +.P +.B "-v" +.RS +Display version information and exit. +.RE + +.SH FILES +.IP "@SYSCONF@/system.jwmrc" +The default JWM configuration file. +.IP "~/.jwmrc" +Local configuration file. Copy the default configuration file to this +location to make user-specific changes. + +.SH CONFIGURATION +.B OVERVIEW +.RS +Configuration of JWM is done by editing ".jwmrc". This file is XML +making it easy to edit, either by hand or programmatically. The +example.jwmrc gives an example configuration file. +Before restarting JWM, it is a good idea to run "jwm -p" to make +sure the configuration file is free of errors. Otherwise you may end up +without a root menu. +.RE +.P +.B "ROOT MENU" +.RS +The root menu in JWM is the primary way of starting programs. +It also provides a way to restart or exit the window manager. +The outer most tag is \fBRootMenu\fP. The following attributes are +supported: +.P +\fBonroot\fP \fIlist\fP +.RS +Determine which buttons on the root window activate the menu. +This is a list of integers specifying buttons. The default is "123". +Note that multiple root menus may be specified by using different +buttons for different menus. The range of possible button values is +\fB0\fP to \fB9\fP inclusive. +.RE +.P +\fBheight\fP \fIint\fP +.RS +Height of each menu item in pixels. 0 indicates the largest menu item +will determine the height. The default is 0. +.RE +.P +\fBlabeled\fP \fIbool\fP +.RS +Determines if a label appears at the top of the menu. Default is false. +.RE +.P +\fBlabel\fP \fIstring\fP +.RS +The label to display at the top of the menu. Default is "JWM". +.RE +.P +Within the \fBRootMenu\fP tag, the following tags are supported: +.P +.B Menu +.RS +This tag creates a submenu item. Any of the tags allowed within the +\fBRootMenu\fP tag, including the \fBMenu\fP tag are allowed within this +element. The following attributes are supported: +.P +\fBheight\fP \fIint\fP +.RS +Height of each menu item in pixels. 0 indicates the largest menu item +will determine the height. The default is inherited from the parent menu. +.RE +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. No default. +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this menu. No default. +.RE +.P +\fBlabeled\fP \fIbool\fP +.RS +Determines if a label appears at the top of the menu. Default is false. +.RE +.RE +.P +.B Include +.RS +Include the contents of a file into the menu structure. The file must +start with a "Menu" tag. The file is specified by the text of the tag. +If the text starts with "exec:" then the output of a program is used. +.RE +.P +.B Program +.RS +The \fBProgram\fP tag provides a way to start an external program. The text +in this tag is the command used to start he program. +The following attributes are supported: +.P +\fBlabel\fP \fIstring\fP +.RS +The label to display. Default is the text of the tag. +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use. No default. +.RE +.RE +.P +.B Separator +.RS +This tag simply puts a line in the menu allowing menu divisions. +No text or attributes are used. +.RE +.P +.B Desktops +.RS +Add a desktop menu. This will add a submenu with a list of desktops that +can be used to change the current desktop. +The following attributes are supported: +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use for the menu. The default is "Desktops". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B SendTo +.RS +Add a "send to" menu to the menu. After selecting an item from this menu, +a window may be selected to send that window to the selected desktop. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "SendTo". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Stick +.RS +Add a stick/unstick window operation to the menu. After selecting this +item a window may be selected to toggle the sticky state of that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Stick". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Maximize +.RS +Add a maximize window operation to the menu. After selecting this +item a window may be selected to toggle the maximized state of that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Maximize". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Minimize +.RS +Add a minimize window operation to the menu. After selecting this +item a window may be selected to minimize that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Minimize". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Shade +.RS +Add a shade/unshade window operation to the menu. After selecting this +item a window may be selected to toggle the shaded status of that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Shade". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Move +.RS +Add a move window operation to the menu. After selecting this +item a window may be selected to move that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Move". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Resize +.RS +Add a resize window operation to the menu. After selecting this +item a window may be selected to resize that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Resize". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Kill +.RS +Add a kill window operation to the menu. After selecting this +item a window may be selected to kill that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Kill". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Close +.RS +Add a close window operation to the menu. After selecting this +item a window may be selected to close that window. +The following attributes are supported +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Close". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use for this item. No default. +.RE +.RE +.P +.B Restart +.RS +This tag adds a menu item to restart the window manager. +The following attributes are supported: +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Restart". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use. No default. +.RE +.RE +.P +.B Exit +.RS +This tag adds a menu item to exit the window manager. If text is +present within this tag, it is interpreted as a command to run when JWM +exits. This can be used to start another window manager. +The following attributes are supported: +.P +\fBlabel\fP \fIstring\fP +.RS +The label to use. The default is "Exit". +.RE +.P +\fBicon\fP \fIstring\fP +.RS +The icon to use. No default. +.RE +.P +\fBconfirm\fP \fIbool\fP +.RS +Determine if a confirm dialog appears before exiting. Default is true. +.RE +.P +Note that confirm dialogs can be disabled completely at the compile-time. +.RE +.RE + +.B TRAYS +.RS +One or more trays may be created via the \fBTray\fP tag. +This tag supports the following attributes: +.P +\fBautohide\fP \fIbool\fP +.RS +Allows this tray to hide itself when not activated. Default is false. +.RE +.P +\fBx\fP \fIint\fP +.RS +The x-coordinate of the tray. This may be negative to indicate an offset +from the right of the screen. +.RE +.P +\fBy\fP \fIint\fP +.RS +The y-coordinate of the tray. This may be negative to indicate an offset +from the bottom of the screen. +.RE +.P +\fBwidth\fP \fIint\fP +.RS +The width of the tray. 0 indicates that the tray should compute an +optimal width depending on what it contains and the layout. 0 is the +default. +.RE +.P +\fBheight\fP \fIint\fP +.RS +The height of the tray. 0 indicates that the tray should compute an +optimal height depending on what it contains and the layout. 0 is the +default. +.RE +.P +\fBborder\fP \fIint\fP +.RS +The width of the border. The default is 1. Valid values are between 0 and 32 +inclusive. +.RE +.P +\fBlayer\fP \fIint\fP +.RS +The layer of the tray. The default is 8. Valid values are between 0 and +12 inclusive. +.RE +.P +\fBlayout\fP { \fBvertical\fP | \fBhorizontal\fP } +.RS +The layout of the tray. The default is horizontal. +.RE +.P +\fBvalign\fP { \fBfixed\fP | \fBtop\fP | \fBcenter\fP | \fBbottom\fP } +.RS +The vertical alignment of the tray. The default is \fBfixed\fP. +.RE +.P +\fBhalign\fP { \fBfixed\fP | \fBleft\fP | \fBcenter\fP | \fBright\fP } +.RS +The horizontal alignment of the tray. The default is \fBfixed\fP. +.RE +.P +Within this tag the following tags are supported: +.P +.B Clock +.RS +Add a clock to the tray. The text of this tag is a command to run +when the clock is clicked. This tag supports the following attributes: +.P +\fBformat\fP \fIstring\fP +.RS +The format of the clock. See \fBstrftime\fP(3). +.RE +.P +\fBwidth\fP \fIint\fP +.RS +The width of the clock. 0 indicates that the width should be determined +from the length of the text to be displayed. +.RE +.P +\fBheight\fP \fIint\fP +.RS +The height of the clock. 0 indicates that the height should be determined +from the font used. +.RE +.RE +.P +.B Dock +.RS +Add a dock for system notifications. This can be used by those programs +that use the _NET_SYSTEM_TRAY_Sn selection. The size of the Dock is +dynamic based on the size of the tray and the number of items contained. +Only one Dock is allowed per instance of JWM. +.RE +.P +.B Pager +.RS +Add a pager to the tray. This tag supports the following attributes: +.P +\fBwidth\fP \fIint\fP +.RS +The width of the pager. 0 indicates that the width should be determined +from the tray. 0 is the default. +.RE +.P +\fBheight\fP \fIint\fP +.RS +The height of the pager. 0 indicates that the height should be determined +from the tray. 0 is the default. +.RE +.RE +.P +.B Swallow +.RS +Swallow a program into the tray. The text of this tag gives the +command to run. +This tag supports the following attributes: +.P +\fBname\fP \fIstring\fP +.RS +The name of the program to swallow. This attribute is required. +.RE +.P +\fBwidth\fP \fIint\fP +.RS +The width of the swallowed program. 0 indicates that the width should +be determined from the tray and size requested from the program. 0 is +the default. +.RE +.P +\fBheight\fP \fIint\fP +.RS +The height of the swallowed program. 0 indicates that the height should +be determined from the tray and the size requested from the program. 0 is +the default. +.RE +.RE +.P +.B TaskList +.RS +Add a task list to the tray. +This tag supports the following attribute: +.P +\fBmaxwidth\fP \fIint\fP +.RS +The maximum width of an item in the task list. 0 indicates no maximum. +The default is 0. +.RE +.RE +.P +.B TrayButton +.RS +Add a button to the tray. The text of this tag determines what action to +take when the button is clicked. The following actions are supported: +.P +\fBroot:\fP\fIn\fP +.RS +Show root menu \fIn\fP. +Note that the default TrayButton action is \fBroot:1\fP. +.RE +.P +\fBexec:\fP \fIstring\fP +.RS +Execute a command. +.RE +.P +\fBshowdesktop\fP +.RS +Minimize all windows on the current desktop. +.RE +.P +This tag supports the following attributes: +.P +\fBlabel\fP \fIstring\fP +.RS +A label to display. No default. +.RE +.P +\fBpopup\fP \fIstring\fP +.RS +A string to be displayed for a popup. This will default to the value +specified for \fBlabel\fP, if provided. If neither \fBpopup\fP nor +\fBlabel\fP are specified no popup will be shown. +.RE +.P +\fBicon\fP \fIstring\fP +.RS +An icon to display. No default. +.RE +.RE +.RE + +.B INCLUDES +.RS +Other configuration files may be included under the JWM tag via the +\fBInclude\fP tag. The text of this tag specifies the location of an +additional configuration file. The path may be relative to the loacation +JWM was started, an absolute path, or a path referencing an environment +variable (using '$'). The format of the configuration file is the same as +the main configuration file. +.RE + +.B "GROUP SETTINGS" +.RS +Program groups allow one to specify options which apply to a group of +programs by name and/or class. A program group is created with the +\fBGroup\fP tag. As many program groups can be created as desired. Within the +\fBGroup\fP tag the following tags are supported: +.P +.B Name +.RS +The title of a program to match to be in this group. This field is case +sensitive. a wild card, \fB*\fP, may be used. +.RE +.B Class +.RS +The window class for a program to match to be in this group. This field is +case sensitive. A wild card, \fB*\fP, may be used. +.RE +.B Option +.RS +An option for this group. Possible options are given below: +.P +.B border +.RS +Causes windows in this group to have a border. +.RE +.P +\fBdesktop:\fP\fI#\fP +.RS +The desktop on which windows in this group will be started. +.RE +.P +\fBicon:\fP\fIimage.xpm\fP +.RS +The icon to be used for windows in this group. +.RE +.P +.\fBlayer:\fP\fI#\fP +.RS +The layer on which windows in this group will be started. +.RE +.P +.B maximized +.RS +Make windows in this group initially maximized. +.RE +.P +.B minimized +.RS +Make windows in this group initially minimized. +.RE +.P +.B noborder +.RS +Causes windows in this group to be displayed without a border. +.RE +.P +.B nolist +.RS +Causes the tray to ignore windows in this group. +.RE +.P +.B notitle +.RS +Causes windows in this group to be displayed without a title bar. +.RE +.P +.B pignore +.RS +Ignore initial window position requested by program. +.RE +.P +.B shaded +.RS +Make windows in this group initially shaded. +.RE +.P +.B sticky +.RS +Make windows in this group sticky. +.RE +.P +.B title +.RS +Causes windows in this group to have a title bar. +.RE +.RE +.RE + +.B "BORDER STYLE" +.RS +The \fBBorderStyle\fP tag controls the look of window borders. +Within this tag, the following tags are supported: +.P +.B Font +.RS +The font used for title bars. See Fonts section for more information +.RE +.P +.B Width +.RS +The width of window borders in pixels. The default is 5, the minimum is 3, +and the maximum is 32. +.RE +.P +.B Height +.RS +The height of window title bars in pixels. The default is 21, the minimum +is 2, and the maximum is 64. +.RE +.P +.B Foreground +.RS +The color of text on inactive title bars. See the \fBCOLORS\fP section for +more information. +.RE +.P +.B Background +.RS +The border background color for inactive borders. See the \fBCOLORS\fP +section for more information. +.RE +.P +.B ActiveForeground +.RS +The color of text on active title bars. See the \fBCOLORS\fP section for +more information. +.RE +.P +.B ActiveBackground +.RS +The border background color for active borders. See the \fBCOLORS\fP +section for more information. +.RE +.RE + +.B "TRAY STYLE" +.RS +The \fBTrayStyle\fP tag controls the look of trays. +Within this tag the following tag is supported: +.P +.B Font +.RS +The default tray font to use. See the \fBFONTS\fP section for more +information. +.RE +.P +.B Foreground +.RS +The default foreground color. See the \fBCOLORS\fP section for +more information. +.RE +.P +.B Background +.RS +The default background color. See the \fBCOLORS\fP section for +more information. +.RE +.RE + +.B "TASK LIST STYLE" +.RS +The \fBTaskListStyle\fP tag controls the look of task lists. +This tag supports the following attribute: +.P +\fBinsert\fP \fImode\fP +.RS +This determines how new items are added to the task list. Valid options +are \fBleft\fP and \fBright\fP. The default is \fBright\fP. +.RE +.P +Within this tag the following tags are supported: +.P +.B Font +.RS +The font used for program names. See the \fBFONTS\fP section for more +information. +.RE +.P +.B Foreground +.RS +The foreground color of the task list. See the \fBCOLORS\fP section for +more information. +.RE +.P +.B Background +.RS +The background color of the task list. See the \fBCOLORS\fP section for +more information. +.RE +.P +.B ActiveForeground +.RS +The foreground color of an active item on the task list. See the \fBCOLORS\fP +section for more information. +.RE +.P +.B ActiveBackground +.RS +The background color of an active item on the task list. See the \fBCOLORS\fP +section for more information. +.RE +.RE + +.B "CLOCK STYLE" +.RS +The \fBClockStyle\fP tag controls the look of clocks. +Within this tag, the following tags are supported. +.P +.B Font +.RS +The font used. See the \fBFONTS\fP section for more information. +.RE +.P +.B Foreground +.RS +The color of the text. See the \fBCOLORS\fP section for more information. +.RE +.P +.B Background +.RS +The background color. See the \fBCOLORS\fP section for more information. +.RE +.RE + +.B "PAGER STYLE" +.RS +The \fBPagerStyle\fP tag controls the look of pagers. +Within this tag, the following tags are supported: +.P +.B Outline +.RS +The color of the outline around windows shown in the pager. See the +\fBCOLORS\fP section for more information. +.RE +.P +.B Foreground +.RS +The color of inactive windows shown in the pager. See the \fBCOLORS\fP +section for more information. +.RE +.P +.B Background +.RS +The background color of inactive desktops shown in the pager. See the +\fBCOLORS\fP section for more information. +.RE +.P +.B ActiveForeground +.RS +The color of active windows shown in the pager. See the \fBCOLORS\fP section +for more information. +.RE +.P +.B ActiveBackground +.RS +The color of active desktops shown in the pager. See the \fBCOLORS\fP +section for more information. +.RE +.RE + +.B "MENU STYLE" +.RS +The \fBMenuStyle\fP tag controls the look of the menus in JWM +(this includes the root menu and window menus). +Within this tag the following tags are supported: +.P +.B Font +.RS +The font used on menus See the \fBFONTS\fP section for more information. +.RE +.P +.B Foreground +.RS +The text color of inactive menu items. See the \fBCOLORS\fP section for more +information. +.RE +.P +.B Background +.RS +The background color of inactive menu items. See the \fBCOLORS\fPsection for +more information. +.RE +.P +.B ActiveForeground +.RS +The text color of active menu items. See the \fBCOLORS\fP section for more +information. +.RE +.P +.B ActiveBackground +.RS +Text background color of active menu items. See the \fBCOLORS\fP section +for more information. +.RE +.RE + +.B "POPUP STYLE" +.RS +The \fBPopupStyle\fP tag controls the look of popup windows such as those +shown when the mouse sits over a task list item. +This tag supports the following attributes: +.P +\fBdelay\fP \fIint\fP +.RS +The delay in milliseconds before popups activate. +The default is 600. +.RE +.P +\fBenabled\fP \fIbool\fP +.RS +Determine if popups are shown. Default is true. +.RE +.P +Within this tag the following tags are supported: +.P +.B Font +.RS +The font to use. See the \fBFONTS\fP section for more information. +.RE +.P +.B Outline +.RS +The color of the window outline. See the \fBCOLORS\fP section for more +information. +.RE +.P +.B Foreground +.RS +The text color. See the \fBCOLORS\fP section for more information. +.RE +.P +.B Background +.RS +The background color. See the \fBCOLORS\fP section for more information. +.RE +.RE + +.B FONTS +.RS +Fonts for various parts of JWM are specified within a \fBFont\fP tag. The +text of this tag determines the font to use. +This can be either a standard X font string or, if compiled with XFT +support, an XFT font string. +.RE + +.B COLORS +.RS +Colors for various parts of JWM are specified within specific tags +(discribed above). Colors may either be hex triplets in RGB format +(for example, #FF0000 is red) or by a name recognized by the X server. +.RE + +.B ICONS +.RS +Icons for windows that don't supply an icon via the _NET_WM_ICON hint are +located by searching the icon search path(s) for an icon whose name +(minus the ".xpm" or ".png" extension) matches the instance name of the +window as returned in the WM_CLASS hint. If this lookup fails, a default +icon is supplied. This icon will be displayed for the window on it's title +bar and on the task list. Icons that are not an appropriate size will be +scaled. Square icons work best. +.P +For menu items, the icon path is searched for a match. the icon specified for +a menu item must be the exact name of the icon file with the extension. +If no match is found, a blank area will appear where the icon should appear. +If an icon is not specified for any menu item in a menu, no space will be +allocated for icons. +.P +Zero or more \fBIconPath\fP tags may be specified. The text of this tag is +assumed to be an absolute directory path to a directory containing XPM +and/or PNG icons. +When searching for icons, if multiple paths are provided, they will be +searched in order until a match is made. +Note that icon, PNG, and XPM support are compile-time options. +.RE + +.B "KEY BINDINGS" +.RS +Keyboard bindings in JWM are specified in \fBKey\fP tags. +Either the \fBkey\fP or \fBkeycode\fP attributes must be specified +to determine which key will cause an action. The optional +attribute, \fBmask\fP, specifies what key mask, if any, must be in effect +for the binding to match. Finally, the text of the \fBKey\fP tag is the +action to perform. +.P +One or more of the following key masks may be specified for \fBmask\fP: +.RS +.IP \fBA\fP +The "Alt" key. +.IP \fBC\fP +Control +.IP \fBS\fP +Shift +.IP \fBH\fP +Hyper +.IP \fBM\fP +Meta +.IP \fBP\fP +Super +.RE +.P +The key specified in the \fBkey\fP attribute must contain a valid key +string for \fBXStringToKeysym\fP(3). These values are usually what one would +expect (for example, the escape key is called "Escape"). +.P +Valid actions for a key binding are: +.RS +.IP \fBup\fP +Move up. Not grabbed. +.IP \fBdown\fP +Move down. Not grabbed. +.IP \fBright\fP +Move right. Not grabbed. +.IP \fBleft\fP +Move left. Not grabbed. +.IP \fBescape\fP +Stop a move/resize or exit a menu. Not grabbed. +.IP \fBselect\fP +Make a menu selection. Not grabbed. +.IP \fBnext\fP +Move to the next window in the task list. Grabbed. +.IP \fBnextstacked\fP +Move to the next window in the stacking order. Grabbed. +.IP \fBclose\fP +Close the active window. Grabbed. +.IP \fBminimize\fP +Minimize the active window. Grabbed. +.IP \fBmaximize\fP +Maximize the active window. Grabbed. +.IP \fBshade\fP +Shade the active window. Grabbed. +.IP \fBmove\fP +Move the active window. Grabbed. +.IP \fBresize\fP +Resize the active window. Grabbed. +.IP \fBroot:\fP\fIn\fP +Show root menu \fIn\fP. Grabbed. +.IP \fBwindow\fP +Show the window menu for the active window. Grabbed. +.IP \fBdesktop\fP +Switch to the next desktop. Grabbed. +.IP \fBdesktop#\fP +Switch to a specific desktop. To use this, "#" must be specified in +the key section. The number 1 to the number of desktops configured +are then substituted for "#". Grabbed. +.IP \fBexec:\fP\fIcommand\fP +Execute \fIcommand\fP. Grabbed. +.IP \fBrestart\fP +Restart JWM. Grabbed. +.RE +.P +Note that keys that are grabbed will not be available to applications other +than JWM since JWM will interpret these. Also note that there are no +default key bindings. Finally, it is possible to bind multiple key +combinations to the same action. +.RE + +.B "MOUSE BINDINGS" +.RS +Any button (other than the scroll wheel) on the root window will bring up +the root menu unless otherwise specified via the \fBonroot\fP attribute of +\fBRootMenu\fP. Scrolling up on the root window switches to the previous +desktop and scrolling down switches to the next desktop. +.RE +.P +.RS +The right button will show the window menu on the frame. +.RE +.P +.RS +The left button will resize if on the border or move if in the title bar. +.RE +.P +.RS +The middle button will move anywhere on the frame. +.RE +.P +.RS +A double click on the title bar of a window will toggle the maximized state +of the window. Scrolling up over the title bar will shade the window and +scrolling down will unshade the window. +When a menu is open, the scroll wheel will move through menus. +When over the pager, the scroll wheel will switch desktops. +.RE + +.B DESKTOPS +.RS +Virtual desktops are controlled with the \fBDesktops\fP tag. +Within this tag the following attribute is supported: +.P +\fBcount\fP \fIint\fP +.RS +The number of virtual desktops. The default is 4. Valid values are between +1 and 8 inclusive. +.RE +.P +Desktop names may be assigned via the \fBName\fP tag within the +\fBDesktops\fP tag. +.RE + +.B "OTHER SETTINGS" +.P +.RS +The following tags may also be supplied: +.P +.B DoubleClickDelta +.RS +The number of pixels the mouse can move during a double click. +The default is 2. Valid values are between 0 and 32 inclusive. +.RE +.P +.B DoubleClickSpeed +.RS +The maximum number of milliseconds between clicks for a double click. +The default is 400. Valid values are between 1 and 2000 inclusive. +.RE +.P +.B FocusModel +.RS +The focus model to be used. The default is "sloppy". Valid values +are "click" (click to focus) and "sloppy" (focus follows mouse). +.RE +.P +.B MoveMode +.RS +The move mode. The default is "opaque". Valid values are +"opaque" and "outline". The optional \fBcoordinates\fP attribute +determines the location of the move status window. Possible values are: +.RS +.P +.B off +.RS +Disable the status window. +.RE +.P +.B corner +.RS +Place the status window in the corner of the screen. +.RE +.P +.B window +.RS +Center the status window on the window being moved. +.RE +.P +.B screen +.RS +Center the status window on the screen. +.RE +.RE +.RE +.P +.B ResizeMode +.RS +The resize mode. The default is "opaque". Valid values are +"opaque" and "outline". The optional \fBcoordinates\fP attribute +determines the location of the move status window. Possible values are: +.RS +.P +.B off +.RS +Disable the status window. +.RE +.P +.B corner +.RS +Place the status window in the corner of the screen. +.RE +.P +.B window +.RS +Center the status window on the window being resized. +.RE +.P +.B screen +.RS +Center the status window on the screen. +.RE +.RE +.RE +.P +.B SnapMode +.RS +The snap mode. The default is "border". Valid values are +"none" (for no snapping), "screen" (for snapping to the edge of the screen), +and "border" (for snapping to the borders of windows and the screen). +An optional attribute, \fBdistance\fP, +specifies the distance for snapping. The default is 5. Valid values +are between 1 and 32 inclusive. +.RE +.P +.B StartupCommand +.RS +A command to run when JWM starts. +.RE +.P +.B ShutdownCommand +.RS +A command to run when JWM exits. +.RE +.P +.B RestartCommand +.RS +A command to run when JWM restarts. +.RE +.RE +.P + +.SH AUTHOR +Joe Wingbermuehle <joewing@joewing.net> + +.SH "SEE ALSO" +.BR X (1) + diff --git a/package/irix/Makefile.in b/package/irix/Makefile.in new file mode 100644 index 0000000..4750fa7 --- /dev/null +++ b/package/irix/Makefile.in @@ -0,0 +1,18 @@ + +VERSION = @VERSION@ + +PACKAGE_FILE = "jwm-$(VERSION).tardist" + +all: + mkdir -p dist + cp jwm.idb jwm.spec dist + gendist -rbase / -sbase . -idb jwm.idb -spec jwm.spec -dist dist -all + tar -cf $(PACKAGE_FILE) dist/* + +clean: + rm -fr dist + rm -f $(PACKAGE_FILE) + +distclean: + rm Makefile jwm.spec jwm.idb + diff --git a/package/irix/jwm.idb.in b/package/irix/jwm.idb.in new file mode 100644 index 0000000..05f960b --- /dev/null +++ b/package/irix/jwm.idb.in @@ -0,0 +1,3 @@ +f 0755 root sys .@BINDIR@/jwm ../../src/jwm jwm.sw.base +f 0644 root sys .@SYSCONF@/system.jwmrc ../../example.jwmrc jwm.sw.base +f 0644 root sys .@MANDIR@/man1/jwm.1 ../../jwm.1 jwm.man.manpages diff --git a/package/irix/jwm.spec.in b/package/irix/jwm.spec.in new file mode 100644 index 0000000..3b7934c --- /dev/null +++ b/package/irix/jwm.spec.in @@ -0,0 +1,23 @@ +product jwm + id "JWM v@VERSION@ - Joe's Window Manager" + image sw + id "Software" + version @INSTVERSION@ + order 9999 + subsys base default + id "Base Software" + replaces self + exp jwm.sw.base + endsubsys + endimage + image man + id "Man Pages" + version @INSTVERSION@ + order 9999 + subsys manpages default + id "Man Pages" + replaces self + exp jwm.man.manpages + endsubsys + endimage +endproduct diff --git a/package/slackware/Makefile.in b/package/slackware/Makefile.in new file mode 100644 index 0000000..f4c698c --- /dev/null +++ b/package/slackware/Makefile.in @@ -0,0 +1,27 @@ + +BINDIR = @BINDIR@ +MANDIR = @MANDIR@ +SYSCONF = @SYSCONF@ +VERSION = @VERSION@ +ARCH = @ARCH@ + +PACKAGE_FILE = "jwm-$(VERSION)-slackware-$(ARCH).tgz" + +all: + mkdir -p "pkg/install" + mkdir -p "pkg/$(BINDIR)" + mkdir -p "pkg/$(MANDIR)/man1" + mkdir -p "pkg/$(SYSCONF)" + cp ../../src/jwm "pkg/$(BINDIR)" + cp ../../jwm.1 "pkg/$(MANDIR)/man1" + cp slackware.jwmrc "pkg/$(SYSCONF)/system.jwmrc" + cp slack-desc pkg/install + cd pkg ; tar -cf - * | gzip > ../$(PACKAGE_FILE) ; cd .. + +clean: + rm -f $(PACKAGE_FILE) + rm -rf pkg + +distclean: + rm -f slack-desc Makefile + diff --git a/package/slackware/slack-desc.in b/package/slackware/slack-desc.in new file mode 100644 index 0000000..62a9828 --- /dev/null +++ b/package/slackware/slack-desc.in @@ -0,0 +1,20 @@ +# HOW TO EDIT THIS FILE: +# The "handy ruler" below makes it easier to edit a package description. Line +# up the first '|' above the ':' following the base package name, and the '|' +# on the right side marks the last column you can put a character in. You must +# make exactly 11 lines for the formatting to be correct. It's also +# customary to leave one space after the ':'. + + |-----handy-ruler------------------------------------------------------| +jwm: JWM v@VERSION@ by Joe Wingbermuehle +jwm: +jwm: JWM is a window manager for the X11 Window System. JWM is written +jwm: in C and uses only Xlib and (optionally) the shape extension. It +jwm: can support some MWM, GNOME, and WM Spec hints. +jwm: +jwm: +jwm: +jwm: +jwm: +jwm: + diff --git a/package/slackware/slackware.jwmrc b/package/slackware/slackware.jwmrc new file mode 100644 index 0000000..260c8ca --- /dev/null +++ b/package/slackware/slackware.jwmrc @@ -0,0 +1,111 @@ +<?xml version="1.0"?> + +<JWM> + + <!-- The root menu, if this is undefined you will not get a menu. --> + <RootMenu> + <Program label="Terminal">xterm</Program> + <Menu label="Applications"> + <Program label="Mozilla">mozilla</Program> + <Program label="File Manager">$HOME/bin/fm</Program> + <Program label="Gaim">gaim</Program> + <Program label="Gimp">gimp</Program> + <Program label="XMMS">xmms</Program> + </Menu> + <Menu label="Utilities"> + <Program>xfontsel</Program> + <Program>xmag</Program> + </Menu> + <Menu label="Games"> + <Program label="Doom">/usr/games/doom/doom</Program> + <Program label="Doom II">/usr/games/doom2/doom2</Program> + <Program label="Hacknoid">/usr/games/hacknoid/run</Program> + <Program label="Mines">/usr/games/mines/run</Program> + <Program label="Quake II">/usr/games/quake2/quake2</Program> + </Menu> + <Separator/> + <Restart/> + <Exit/> + </RootMenu> + + <Border> + <Font antialias="true">-sgi-rock-*-*-*-*-18-*-*-*-*-*-*-*</Font> + <Width>5</Width> + <Height>20</Height> + <Foreground>black</Foreground> + <Background>#DCDAD5</Background> + <ActiveForeground>white</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </Border> + + <Tray autohide="false"> + <Font antialias="true">-sgi-rock-*-*-*-*-18-*-*-*-*-*-*-*</Font> + <Height>28</Height> + <Foreground>black</Foreground> + <Background>#DCDAD5</Background> + <ActiveForeground>black</ActiveForeground> + <ActiveBackground>#8899AA</ActiveBackground> + </Tray> + + <Pager> + <Outline>black</Outline> + <Foreground>#DCDAD5</Foreground> + <Background>#888888</Background> + <ActiveForeground>#8899AA</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </Pager> + + <Load> + <Program>xload</Program> + <Outline>black</Outline> + <Foreground>red</Foreground> + <Background>#DCDAD5</Background> + </Load> + + <Clock> + <Program>xclock</Program> + </Clock> + + <Popup enabled="true"> + <Font antialias="true">-sgi-screen-*-r-*-*-15-*-*-*-*-*-*-*</Font> + <Outline>black</Outline> + <Foreground>black</Foreground> + <Background>yellow</Background> + </Popup> + + <Menu> + <Font antialias="true">-sgi-screen-*-r-*-*-15-*-*-*-*-*-*-*</Font> + <Foreground>black</Foreground> + <Background>#DCDAD5</Background> + <ActiveForeground>white</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </Menu> + + <!-- Number of virtual desktops --> + <DesktopCount>4</DesktopCount> + + <!-- Double click speed (in milliseconds) --> + <DoubleClickSpeed>400</DoubleClickSpeed> + + <!-- Double click delta (in pixels) --> + <DoubleClickDelta>2</DoubleClickDelta> + + <!-- The focus model (sloppy or click) --> + <FocusModel>sloppy</FocusModel> + + <!-- Key bindings --> + <Key key="Up">up</Key> + <Key key="Down">down</Key> + <Key key="Right">right</Key> + <Key key="Left">left</Key> + <Key key="Return">select</Key> + <Key key="Escape">escape</Key> + + <Key mask="A" key="Tab">next</Key> + <Key mask="A" key="F4">close</Key> + <Key mask="A" key="#">desktop#</Key> + <Key mask="A" key="F1">root</Key> + <Key mask="A" key="F2">window</Key> + +</JWM> + diff --git a/package/solaris/Makefile.in b/package/solaris/Makefile.in new file mode 100644 index 0000000..07b3202 --- /dev/null +++ b/package/solaris/Makefile.in @@ -0,0 +1,28 @@ + +ARCH = @ARCH@ +BINDIR = @BINDIR@ +MANDIR = @MANDIR@ +SYSCONF = @SYSCONF@ +VERSION = @VERSION@ + +PACKAGE_NAME = "jwm-$(VERSION)-solaris-$(ARCH).pkg.tgz" + +all: + mkdir -p "pkg/$(BINDIR)" + mkdir -p "pkg/$(MANDIR)/man1" + mkdir -p "pkg/$(SYSCONF)" + cp ../../src/jwm "pkg/$(BINDIR)" + cp ../../jwm.1 "pkg/$(MANDIR)/man1" + cp solaris.jwmrc "pkg/$(SYSCONF)/system.jwmrc" + cp prototype pkg + cp pkginfo pkg + pkgmk -o -r `pwd`/pkg -d `pwd` -f prototype + tar -cf - JWM | gzip > $(PACKAGE_NAME) + +clean: + rm -rf pkg JWM + +distclean: + rm -f pkginfo prototype Makefile + rm -f $(PACKAGE_NAME) + diff --git a/package/solaris/pkginfo.in b/package/solaris/pkginfo.in new file mode 100644 index 0000000..00c974d --- /dev/null +++ b/package/solaris/pkginfo.in @@ -0,0 +1,9 @@ + +PKG="JWM" +NAME="JWM v@VERSION@" +VERSION="@INSTVERSION@" +BASEDIR="/" +ARCH="@ARCH@" +CATEGORY="utility" +EMAIL="joewing@joewing.net" + diff --git a/package/solaris/prototype.in b/package/solaris/prototype.in new file mode 100644 index 0000000..0a42064 --- /dev/null +++ b/package/solaris/prototype.in @@ -0,0 +1,4 @@ +i pkginfo +f none .@BINDIR@/jwm 0755 root root +f none .@SYSCONF@/system.jwmrc 0644 root root +f none .@MANDIR@/man1/jwm.1 0644 root root diff --git a/package/solaris/solaris.jwmrc b/package/solaris/solaris.jwmrc new file mode 100644 index 0000000..260c8ca --- /dev/null +++ b/package/solaris/solaris.jwmrc @@ -0,0 +1,111 @@ +<?xml version="1.0"?> + +<JWM> + + <!-- The root menu, if this is undefined you will not get a menu. --> + <RootMenu> + <Program label="Terminal">xterm</Program> + <Menu label="Applications"> + <Program label="Mozilla">mozilla</Program> + <Program label="File Manager">$HOME/bin/fm</Program> + <Program label="Gaim">gaim</Program> + <Program label="Gimp">gimp</Program> + <Program label="XMMS">xmms</Program> + </Menu> + <Menu label="Utilities"> + <Program>xfontsel</Program> + <Program>xmag</Program> + </Menu> + <Menu label="Games"> + <Program label="Doom">/usr/games/doom/doom</Program> + <Program label="Doom II">/usr/games/doom2/doom2</Program> + <Program label="Hacknoid">/usr/games/hacknoid/run</Program> + <Program label="Mines">/usr/games/mines/run</Program> + <Program label="Quake II">/usr/games/quake2/quake2</Program> + </Menu> + <Separator/> + <Restart/> + <Exit/> + </RootMenu> + + <Border> + <Font antialias="true">-sgi-rock-*-*-*-*-18-*-*-*-*-*-*-*</Font> + <Width>5</Width> + <Height>20</Height> + <Foreground>black</Foreground> + <Background>#DCDAD5</Background> + <ActiveForeground>white</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </Border> + + <Tray autohide="false"> + <Font antialias="true">-sgi-rock-*-*-*-*-18-*-*-*-*-*-*-*</Font> + <Height>28</Height> + <Foreground>black</Foreground> + <Background>#DCDAD5</Background> + <ActiveForeground>black</ActiveForeground> + <ActiveBackground>#8899AA</ActiveBackground> + </Tray> + + <Pager> + <Outline>black</Outline> + <Foreground>#DCDAD5</Foreground> + <Background>#888888</Background> + <ActiveForeground>#8899AA</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </Pager> + + <Load> + <Program>xload</Program> + <Outline>black</Outline> + <Foreground>red</Foreground> + <Background>#DCDAD5</Background> + </Load> + + <Clock> + <Program>xclock</Program> + </Clock> + + <Popup enabled="true"> + <Font antialias="true">-sgi-screen-*-r-*-*-15-*-*-*-*-*-*-*</Font> + <Outline>black</Outline> + <Foreground>black</Foreground> + <Background>yellow</Background> + </Popup> + + <Menu> + <Font antialias="true">-sgi-screen-*-r-*-*-15-*-*-*-*-*-*-*</Font> + <Foreground>black</Foreground> + <Background>#DCDAD5</Background> + <ActiveForeground>white</ActiveForeground> + <ActiveBackground>#3A4956</ActiveBackground> + </Menu> + + <!-- Number of virtual desktops --> + <DesktopCount>4</DesktopCount> + + <!-- Double click speed (in milliseconds) --> + <DoubleClickSpeed>400</DoubleClickSpeed> + + <!-- Double click delta (in pixels) --> + <DoubleClickDelta>2</DoubleClickDelta> + + <!-- The focus model (sloppy or click) --> + <FocusModel>sloppy</FocusModel> + + <!-- Key bindings --> + <Key key="Up">up</Key> + <Key key="Down">down</Key> + <Key key="Right">right</Key> + <Key key="Left">left</Key> + <Key key="Return">select</Key> + <Key key="Escape">escape</Key> + + <Key mask="A" key="Tab">next</Key> + <Key mask="A" key="F4">close</Key> + <Key mask="A" key="#">desktop#</Key> + <Key mask="A" key="F1">root</Key> + <Key mask="A" key="F2">window</Key> + +</JWM> + diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..8b73c8d --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,38 @@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ +BINDIR = @BINDIR@ + +VPATH=.:os + +OBJECTS = border.o button.o client.o clock.o color.o command.o confirm.o \ + cursor.o debug.o desktop.o dock.o event.o error.o font.o group.o help.o \ + hint.o icon.o image.o key.o lex.o main.o match.o menu.o misc.o move.o \ + outline.o pager.o parse.o place.o popup.o render.o resize.o root.o \ + screen.o status.o swallow.o taskbar.o theme.o timing.o tray.o \ + traybutton.o winmenu.o + +EXE = jwm + +.SUFFIXES: .o .h .c + +all: $(EXE) + +install: all + strip $(EXE) + install -d $(BINDIR) + install $(EXE) $(BINDIR)/$(EXE) + +depend: + makedepend -m -- $(CFLAGS) -- *.c + +$(EXE): $(OBJECTS) + $(CC) -o $(EXE) $(OBJECTS) $(LDFLAGS) + +.c.o: + $(CC) -c $(CFLAGS) $< + +clean: + rm -f $(OBJECTS) $(EXE) core + diff --git a/src/border.c b/src/border.c new file mode 100644 index 0000000..8baf7b7 --- /dev/null +++ b/src/border.c @@ -0,0 +1,753 @@ +/**************************************************************************** + * Functions for dealing with window borders. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "border.h" +#include "client.h" +#include "color.h" +#include "main.h" +#include "icon.h" +#include "font.h" +#include "error.h" + +typedef enum { + BP_CLOSE, + BP_ACTIVE_CLOSE, + BP_MINIMIZE, + BP_ACTIVE_MINIMIZE, + BP_MAXIMIZE, + BP_ACTIVE_MAXIMIZE, + BP_MAXIMIZE_ACTIVE, + BP_ACTIVE_MAXIMIZE_ACTIVE, + BP_COUNT +} BorderPixmapType; + +typedef unsigned char BorderPixmapDataType[32]; + +static BorderPixmapDataType bitmaps[BP_COUNT >> 1] = { + + /* Close */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x38, 0x38, 0x70, 0x1C, + 0xE0, 0x0E, 0xC0, 0x07, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x0E, 0x70, 0x1C, + 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 }, + + /* Minimize */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, + 0xF8, 0x07, 0xF8, 0x07, 0x00, 0x00, 0x00, 0x00 }, + + /* Maximize */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3F, 0xF8, 0x3F, 0xF8, 0x3F, + 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, + 0x08, 0x20, 0xF8, 0x3F, 0x00, 0x00, 0x00, 0x00 }, + + /* Maximize Active */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0xC0, 0x1F, + 0x00, 0x10, 0xF8, 0x13, 0xF8, 0x13, 0x08, 0x12, 0x08, 0x1A, 0x08, 0x02, + 0x08, 0x02, 0xF8, 0x03, 0x00, 0x00, 0x00, 0x00 } + +}; + +static Pixmap pixmaps[BP_COUNT]; + +static Region borderRegion = NULL; +static GC borderGC; + +static void DrawBorderHelper(const ClientNode *np, + unsigned int width, unsigned int height, int drawIcon); +static void DrawButtonBorder(const ClientNode *np, int offset, + Pixmap canvas, GC gc); +static int DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc); + +/**************************************************************************** + ****************************************************************************/ +void InitializeBorders() { +} + +/**************************************************************************** + ****************************************************************************/ +void StartupBorders() { + + XGCValues gcValues; + unsigned long gcMask; + long fg, bg; + int x; + + for(x = 0; x < BP_COUNT; x++) { + + if(x & 1) { + fg = colors[COLOR_BORDER_ACTIVE_FG]; + bg = colors[COLOR_BORDER_ACTIVE_BG]; + } else { + fg = colors[COLOR_BORDER_FG]; + bg = colors[COLOR_BORDER_BG]; + } + + pixmaps[x] = JXCreatePixmapFromBitmapData(display, rootWindow, + (char*)bitmaps[x >> 1], 16, 16, fg, bg, rootDepth); + + } + + gcMask = GCGraphicsExposures; + gcValues.graphics_exposures = False; + borderGC = JXCreateGC(display, rootWindow, gcMask, &gcValues); + +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownBorders() { + + int x; + + JXFreeGC(display, borderGC); + + for(x = 0; x < BP_COUNT; x++) { + JXFreePixmap(display, pixmaps[x]); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyBorders() { +} + +/**************************************************************************** + ****************************************************************************/ +int GetBorderIconSize() { + return titleHeight - 4; +} + +/**************************************************************************** + ****************************************************************************/ +BorderActionType GetBorderActionType(const ClientNode *np, int x, int y) { + + int north; + int offset; + int height, width; + int bsize; + + Assert(np); + + if(np->state.border & BORDER_OUTLINE) { + bsize = borderWidth; + } else { + bsize = 0; + } + + if(np->state.border & BORDER_TITLE) { + + if(y > bsize && y <= bsize + titleHeight) { + if(np->icon && np->width >= titleHeight) { + if(x > bsize && x < bsize + titleHeight) { + return BA_MENU; + } + } + offset = np->width + bsize - titleHeight; + if((np->state.border & BORDER_CLOSE) + && offset > bsize + titleHeight) { + if(x > offset && x < offset + titleHeight) { + return BA_CLOSE; + } + offset -= titleHeight; + } + if((np->state.border & BORDER_MAX) + && offset > bsize + titleHeight) { + if(x > offset && x < offset + titleHeight) { + return BA_MAXIMIZE; + } + offset -= titleHeight; + } + if((np->state.border & BORDER_MIN) && offset > bsize + titleHeight) { + if(x > offset && x < offset + titleHeight) { + return BA_MINIMIZE; + } + } + } + + if(y >= bsize && y <= bsize + titleHeight) { + if(x >= bsize && x <= np->width + bsize) { + if(np->state.border & BORDER_MOVE) { + return BA_MOVE; + } else { + return BA_NONE; + } + } + } + + north = bsize + titleHeight; + } else { + north = bsize; + } + + if(!(np->state.border & BORDER_RESIZE)) { + return BA_NONE; + } + + width = np->width; + + if(np->state.status & STAT_SHADED) { + if(x < bsize) { + return BA_RESIZE_W | BA_RESIZE; + } else if(x >= width + bsize) { + return BA_RESIZE_E | BA_RESIZE; + } else { + return BA_NONE; + } + } + + height = np->height; + + if(width >= titleHeight * 2 && height >= titleHeight * 2) { + if(x < bsize + titleHeight && y < titleHeight + bsize) { + return BA_RESIZE_N | BA_RESIZE_W | BA_RESIZE; + } else if(x < titleHeight + bsize + && y - north >= height - titleHeight) { + return BA_RESIZE_S | BA_RESIZE_W | BA_RESIZE; + } else if(x - bsize >= width - titleHeight + && y < titleHeight + bsize) { + return BA_RESIZE_N | BA_RESIZE_E | BA_RESIZE; + } else if(x - bsize >= width - titleHeight + && y - north >= height - titleHeight) { + return BA_RESIZE_S | BA_RESIZE_E | BA_RESIZE; + } + } + if(x < bsize) { + return BA_RESIZE_W | BA_RESIZE; + } else if(x >= width + bsize) { + return BA_RESIZE_E | BA_RESIZE; + } else if(y < bsize) { + return BA_RESIZE_N | BA_RESIZE; + } else if(y >= height) { + return BA_RESIZE_S | BA_RESIZE; + } else { + return BA_NONE; + } +} + +/**************************************************************************** + ****************************************************************************/ +void DrawBorder(const ClientNode *np, const XExposeEvent *expose) { + + XRectangle rect; + unsigned int width; + unsigned int height; + int bsize; + int drawIcon; + int temp; + + Assert(np); + + if(shouldExit) { + return; + } + + if(!(np->state.status & (STAT_MAPPED | STAT_SHADED))) { + return; + } + if(np->state.status & (STAT_HIDDEN | STAT_FULLSCREEN)) { + return; + } + + if(np->state.border & BORDER_OUTLINE) { + bsize = borderWidth; + } else { + bsize = 0; + } + + if(bsize == 0 && !(np->state.border & BORDER_TITLE)) { + return; + } + + if(expose) { + + if(!borderRegion) { + borderRegion = XCreateRegion(); + } + + rect.x = (short)expose->x; + rect.y = (short)expose->y; + rect.width = (unsigned short)expose->width; + rect.height = (unsigned short)expose->height; + XUnionRectWithRegion(&rect, borderRegion, borderRegion); + + if(expose->count) { + return; + } + + /* Determine if the icon should be redrawn. This is needed + * since icons need a separate GC for applying shape masks. + * Note that if the icon were naively redrawn, icons with + * alpha channels would acquire artifacts since the area under + * them would not be cleared. So if any part of the icon needs + * to be redrawn, we clear the area and redraw the whole icon. + */ + drawIcon = 0; + temp = GetBorderIconSize(); + rect.x = (short)bsize + 2; + rect.y = (short)(bsize + titleHeight / 2 - temp / 2); + rect.width = (unsigned short)temp; + rect.height = (unsigned short)temp; + if(XRectInRegion(borderRegion, rect.x, rect.y, rect.width, rect.height) + != RectangleOut) { + + drawIcon = 1; + XUnionRectWithRegion(&rect, borderRegion, borderRegion); + + } else { + + drawIcon = 0; + + } + + XSetRegion(display, borderGC, borderRegion); + + } else { + + drawIcon = 1; + XSetClipMask(display, borderGC, None); + + } + + if(np->state.status & STAT_SHADED) { + height = titleHeight + bsize * 2; + } else if(np->state.border & BORDER_TITLE) { + height = np->height + titleHeight + bsize * 2; + } else { + height = np->height + 2 * bsize; + } + width = np->width + bsize * 2; + + DrawBorderHelper(np, width, height, drawIcon); + + if(expose) { + XDestroyRegion(borderRegion); + borderRegion = NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void DrawBorderHelper(const ClientNode *np, + unsigned int width, unsigned int height, int drawIcon) { + + ColorType borderTextColor; + long borderPixel, borderTextPixel; + long pixelUp, pixelDown; + int buttonCount, titleWidth; + Pixmap canvas; + GC gc; + int iconSize; + int bsize; + + Assert(np); + + iconSize = GetBorderIconSize(); + + if(np->state.status & STAT_ACTIVE) { + borderTextColor = COLOR_BORDER_ACTIVE_FG; + borderPixel = colors[COLOR_BORDER_ACTIVE_BG]; + borderTextPixel = colors[COLOR_BORDER_ACTIVE_FG]; + pixelUp = colors[COLOR_BORDER_ACTIVE_UP]; + pixelDown = colors[COLOR_BORDER_ACTIVE_DOWN]; + } else { + borderTextColor = COLOR_BORDER_FG; + borderPixel = colors[COLOR_BORDER_BG]; + borderTextPixel = colors[COLOR_BORDER_FG]; + pixelUp = colors[COLOR_BORDER_UP]; + pixelDown = colors[COLOR_BORDER_DOWN]; + } + + if(np->state.border & BORDER_OUTLINE) { + bsize = borderWidth; + } else { + bsize = 0; + } + + canvas = np->parent; + gc = borderGC; + + /* Set the window background to reduce perceived flicker on the + * parts of the window that will need to be redrawn. */ + JXSetWindowBackground(display, canvas, borderPixel); + + JXSetForeground(display, gc, borderPixel); + JXFillRectangle(display, canvas, gc, 0, 0, width + 1, height + 1); + + buttonCount = DrawBorderButtons(np, canvas, gc); + titleWidth = width - (titleHeight + 2) * buttonCount - bsize + - (titleHeight + bsize + 4) - 2; + + if(np->state.border & BORDER_TITLE) { + + if(np->icon && np->width >= titleHeight && drawIcon) { + PutIcon(np->icon, canvas, bsize + 2, + bsize + titleHeight / 2 - iconSize / 2, + iconSize, iconSize); + } + + if(np->name && np->name[0] && titleWidth > 0) { + RenderString(canvas, FONT_BORDER, borderTextColor, + titleHeight + bsize + 4, bsize + titleHeight / 2 + - GetStringHeight(FONT_BORDER) / 2, + titleWidth, borderRegion, np->name); + } + + } + + if(np->state.border & BORDER_OUTLINE) { + + /* Draw title outline */ + JXSetForeground(display, gc, pixelUp); + JXDrawLine(display, canvas, gc, borderWidth, borderWidth, + width - borderWidth - 1, borderWidth); + JXDrawLine(display, canvas, gc, borderWidth, borderWidth + 1, + borderWidth, titleHeight + borderWidth - 1); + + JXSetForeground(display, gc, pixelDown); + JXDrawLine(display, canvas, gc, borderWidth + 1, + titleHeight + borderWidth - 1, width - borderWidth, + titleHeight + borderWidth - 1); + JXDrawLine(display, canvas, gc, width - borderWidth - 1, + borderWidth + 1, width - borderWidth - 1, titleHeight + borderWidth); + + /* Draw outline */ + JXSetForeground(display, gc, pixelUp); + JXDrawLine(display, canvas, gc, width - borderWidth, + borderWidth, width - borderWidth, height - borderWidth); + JXDrawLine(display, canvas, gc, borderWidth, + height - borderWidth, width - borderWidth, height - borderWidth); + + JXSetForeground(display, gc, pixelDown); + JXDrawLine(display, canvas, gc, borderWidth - 1, + borderWidth - 1, width - borderWidth, borderWidth - 1); + JXDrawLine(display, canvas, gc, borderWidth - 1, borderWidth, + borderWidth - 1, height - borderWidth); + + JXFillRectangle(display, canvas, gc, width - 2, 0, 2, height); + JXFillRectangle(display, canvas, gc, 0, height - 2, width, 2); + JXSetForeground(display, gc, pixelUp); + JXDrawLine(display, canvas, gc, 0, 0, 0, height - 1); + JXDrawLine(display, canvas, gc, 1, 1, 1, height - 2); + JXDrawLine(display, canvas, gc, 1, 0, width - 1, 0); + JXDrawLine(display, canvas, gc, 1, 1, width - 2, 1); + + if((np->state.border & BORDER_RESIZE) + && !(np->state.status & STAT_SHADED) + && np->width >= 2 * titleHeight * 2 + && np->height >= titleHeight * 2) { + + /* Draw marks */ + JXSetForeground(display, gc, pixelDown); + + /* Upper left */ + JXDrawLine(display, canvas, gc, + titleHeight + borderWidth - 1, 2, titleHeight + borderWidth - 1, + borderWidth - 2); + JXDrawLine(display, canvas, gc, 2, + titleHeight + borderWidth - 1, borderWidth - 2, + titleHeight + borderWidth - 1); + + /* Upper right */ + JXDrawLine(display, canvas, gc, + width - titleHeight - borderWidth - 1, + 2, width - titleHeight - borderWidth - 1, borderWidth - 2); + JXDrawLine(display, canvas, gc, width - 3, + titleHeight + borderWidth - 1, width - borderWidth + 1, + titleHeight + borderWidth - 1); + + /* Lower left */ + JXDrawLine(display, canvas, gc, 2, + height - titleHeight - borderWidth - 1, borderWidth - 2, + height - titleHeight - borderWidth - 1); + JXDrawLine(display, canvas, gc, + titleHeight + borderWidth - 1, height - 3, + titleHeight + borderWidth - 1, height - borderWidth + 1); + + /* Lower right */ + JXDrawLine(display, canvas, gc, width - 3, + height - titleHeight - borderWidth - 1, width - borderWidth + 1, + height - titleHeight - borderWidth - 1); + JXDrawLine(display, canvas, gc, + width - titleHeight - borderWidth - 1, + height - 3, width - titleHeight - borderWidth - 1, + height - borderWidth + 1); + + JXSetForeground(display, gc, pixelUp); + + /* Upper left */ + JXDrawLine(display, canvas, gc, titleHeight + borderWidth, + 2, titleHeight + borderWidth, borderWidth - 2); + JXDrawLine(display, canvas, gc, 2, + titleHeight + borderWidth, borderWidth - 2, + titleHeight + borderWidth); + + /* Upper right */ + JXDrawLine(display, canvas, gc, + width - titleHeight - borderWidth, 2, + width - titleHeight - borderWidth, borderWidth - 2); + JXDrawLine(display, canvas, gc, width - 3, + titleHeight + borderWidth, width - borderWidth + 1, + titleHeight + borderWidth); + + /* Lower left */ + JXDrawLine(display, canvas, gc, 2, + height - titleHeight - borderWidth, + borderWidth - 2, height - titleHeight - borderWidth); + JXDrawLine(display, canvas, gc, titleHeight + borderWidth, + height - 3, titleHeight + borderWidth, height - borderWidth + 1); + + /* Lower right */ + JXDrawLine(display, canvas, gc, width - 3, + height - titleHeight - borderWidth, width - borderWidth + 1, + height - titleHeight - borderWidth); + JXDrawLine(display, canvas, gc, + width - titleHeight - borderWidth, height - 3, + width - titleHeight - borderWidth, height - borderWidth + 1); + + } + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void DrawButtonBorder(const ClientNode *np, int offset, + Pixmap canvas, GC gc) { + + long up, down; + long bsize; + + Assert(np); + + if(np->state.status & STAT_ACTIVE) { + up = colors[COLOR_BORDER_ACTIVE_UP]; + down = colors[COLOR_BORDER_ACTIVE_DOWN]; + } else { + up = colors[COLOR_BORDER_UP]; + down = colors[COLOR_BORDER_DOWN]; + } + + if(np->state.border & BORDER_OUTLINE) { + bsize = borderWidth; + } else { + bsize = 0; + } + + JXSetForeground(display, gc, up); + JXDrawLine(display, canvas, gc, offset, bsize + 1, + offset, titleHeight + bsize - 2); + + JXSetForeground(display, gc, down); + JXDrawLine(display, canvas, gc, offset - 1, + bsize + 1, offset - 1, titleHeight + bsize - 2); + +} + +/**************************************************************************** + ****************************************************************************/ +int DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc) { + + Pixmap pixmap; + int count = 0; + int offset; + int bsize; + + Assert(np); + + if(!(np->state.border & BORDER_TITLE)) { + return count; + } + if(np->state.border & BORDER_OUTLINE) { + bsize = borderWidth; + } else { + bsize = 0; + } + + offset = np->width + bsize - titleHeight; + + if(offset <= bsize + titleHeight) { + return count; + } + + if(np->state.border & BORDER_CLOSE) { + + DrawButtonBorder(np, offset, canvas, gc); + + if(np->state.status & STAT_ACTIVE) { + pixmap = pixmaps[BP_ACTIVE_CLOSE]; + } else { + pixmap = pixmaps[BP_CLOSE]; + } + + JXCopyArea(display, pixmap, canvas, gc, 0, 0, 16, 16, + offset + titleHeight / 2 - 8, bsize + titleHeight / 2 - 8); + + offset -= titleHeight; + ++count; + + if(offset <= bsize + titleHeight) { + return count; + } + + } + + if(np->state.border & BORDER_MAX) { + + if(np->state.status & STAT_MAXIMIZED) { + if(np->state.status & STAT_ACTIVE) { + pixmap = pixmaps[BP_ACTIVE_MAXIMIZE_ACTIVE]; + } else { + pixmap = pixmaps[BP_MAXIMIZE_ACTIVE]; + } + } else { + if(np->state.status & STAT_ACTIVE) { + pixmap = pixmaps[BP_ACTIVE_MAXIMIZE]; + } else { + pixmap = pixmaps[BP_MAXIMIZE]; + } + } + JXCopyArea(display, pixmap, canvas, gc, 0, 0, 16, 16, + offset + titleHeight / 2 - 8, bsize + titleHeight / 2 - 8); + + DrawButtonBorder(np, offset, canvas, gc); + + offset -= titleHeight; + ++count; + + if(offset <= bsize + titleHeight) { + return count; + } + + } + + if(np->state.border & BORDER_MIN) { + + DrawButtonBorder(np, offset, canvas, gc); + + if(np->state.status & STAT_ACTIVE) { + pixmap = pixmaps[BP_ACTIVE_MINIMIZE]; + } else { + pixmap = pixmaps[BP_MINIMIZE]; + } + + JXCopyArea(display, pixmap, canvas, gc, 0, 0, 16, 16, + offset + titleHeight / 2 - 8, bsize + titleHeight / 2 - 8); + + ++count; + + } + + return count; + +} + +/**************************************************************************** + * Redraw the borders on the current desktop. + * This should be done after loading clients since the stacking order + * may cause borders on the current desktop to become visible after moving + * clients to their assigned desktops. + ****************************************************************************/ +void ExposeCurrentDesktop() { + + ClientNode *np; + int layer; + + for(layer = 0; layer < LAYER_COUNT; layer++) { + for(np = nodes[layer]; np; np = np->next) { + if(!(np->state.status & (STAT_HIDDEN | STAT_MINIMIZED))) { + DrawBorder(np, NULL); + } + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void GetBorderSize(const ClientNode *np, + int *north, int *south, int *east, int *west) { + + Assert(np); + Assert(north); + Assert(south); + Assert(east); + Assert(west); + + /* Full screen is a special case. */ + if(np->state.status & STAT_FULLSCREEN) { + *north = 0; + *south = 0; + *east = 0; + *west = 0; + return; + } + + if(np->state.border & BORDER_OUTLINE) { + + *north = borderWidth; + *south = borderWidth; + *east = borderWidth; + *west = borderWidth; + + } else { + + *north = 0; + *south = 0; + *east = 0; + *west = 0; + + } + + if(np->state.border & BORDER_TITLE) { + *north += titleHeight; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void SetBorderWidth(const char *str) { + + int width; + + if(str) { + + width = atoi(str); + if(width < MIN_BORDER_WIDTH || width > MAX_BORDER_WIDTH) { + borderWidth = DEFAULT_BORDER_WIDTH; + Warning("invalid border width specified: %d", width); + } else { + borderWidth = width; + } + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void SetTitleHeight(const char *str) { + + int height; + + if(str) { + + height = atoi(str); + if(height < MIN_TITLE_HEIGHT || height > MAX_TITLE_HEIGHT) { + titleHeight = DEFAULT_TITLE_HEIGHT; + Warning("invalid title height specified: %d", height); + } else { + titleHeight = height; + } + + } + +} + + diff --git a/src/border.h b/src/border.h new file mode 100644 index 0000000..55458db --- /dev/null +++ b/src/border.h @@ -0,0 +1,80 @@ +/** + * @file border.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header file for border functions. + * + */ + +#ifndef BORDER_H +#define BORDER_H + +struct ClientNode; + +/** Flags to determine what action to take on the border. */ +typedef enum { + BA_NONE = 0, /**< Do nothing. */ + BA_RESIZE = 1, /**< Resize the window. */ + BA_MOVE = 2, /**< Move the window. */ + BA_CLOSE = 3, /**< Close the window. */ + BA_MAXIMIZE = 4, /**< Maximize the window. */ + BA_MINIMIZE = 5, /**< Minimize the window. */ + BA_MENU = 6, /**< Show the window menu. */ + BA_RESIZE_N = 0x10, /**< Resize north. */ + BA_RESIZE_S = 0x20, /**< Resize south. */ + BA_RESIZE_E = 0x40, /**< Resize east. */ + BA_RESIZE_W = 0x80 /**< Resize west. */ +} BorderActionType; + +/*@{*/ +void InitializeBorders(); +void StartupBorders(); +void ShutdownBorders(); +void DestroyBorders(); +/*@}*/ + +/** Determine the action to take for a client. + * @param np The client. + * @param x The x-coordinate of the mouse (frame relative). + * @param y The y-coordinate of the mouse (frame relative). + * @return The action to take. + */ +BorderActionType GetBorderActionType(const struct ClientNode *np, int x, int y); + +/** Draw a window border. + * @param np The client whose frame to draw. + * @param expose The expose event causing the redraw (or NULL). + */ +void DrawBorder(const struct ClientNode *np, const XExposeEvent *expose); + +/** Get the size of a border icon. + * @return The size in pixels (note that icons are square). + */ +int GetBorderIconSize(); + +/** Get the size of a window border. + * @param np The client. + * @param north Pointer to the value to contain the north border size. + * @param south Pointer to the value to contain the south border size. + * @param east Pointer to the value to contain the east border size. + * @param west Pointer to the value to contain the west border size. + */ +void GetBorderSize(const struct ClientNode *np, + int *north, int *south, int *east, int *west); + +/** Set the size of window borders. + * @param str The size to use in string form. + */ +void SetBorderWidth(const char *str); + +/** Set the size of window title bars. + * @param str The size to use in string form. + */ +void SetTitleHeight(const char *str); + +/** Redraw all borders on the current desktop. */ +void ExposeCurrentDesktop(); + +#endif + diff --git a/src/button.c b/src/button.c new file mode 100644 index 0000000..0d62742 --- /dev/null +++ b/src/button.c @@ -0,0 +1,212 @@ +/*************************************************************************** + * Functions to handle drawing buttons. + * Copyright (C) 2004 Joe Wingbermuehle + ***************************************************************************/ + +#include "jwm.h" +#include "button.h" +#include "font.h" +#include "color.h" +#include "main.h" +#include "icon.h" +#include "image.h" + +static void GetScaledIconSize(IconNode *ip, int maxsize, + int *width, int *height); + +/*************************************************************************** + ***************************************************************************/ +void DrawButton(ButtonNode *bp) { + + long outlinePixel; + long topPixel, bottomPixel; + ColorType fg, bg; + + Drawable drawable; + GC gc; + int x, y; + int width, height; + int xoffset, yoffset; + + int iconWidth, iconHeight; + int textWidth, textHeight; + + Assert(bp); + + drawable = bp->drawable; + gc = bp->gc; + x = bp->x; + y = bp->y; + width = bp->width; + height = bp->height; + + switch(bp->type) { + case BUTTON_LABEL: + fg = COLOR_MENU_FG; + bg = COLOR_MENU_BG; + outlinePixel = colors[COLOR_MENU_BG]; + topPixel = colors[COLOR_MENU_BG]; + bottomPixel = colors[COLOR_MENU_BG]; + break; + case BUTTON_MENU_ACTIVE: + fg = COLOR_MENU_ACTIVE_FG; + bg = COLOR_MENU_ACTIVE_BG; + outlinePixel = colors[COLOR_MENU_ACTIVE_DOWN]; + topPixel = colors[COLOR_MENU_ACTIVE_UP]; + bottomPixel = colors[COLOR_MENU_ACTIVE_DOWN]; + break; + case BUTTON_TASK: + fg = COLOR_TASK_FG; + bg = COLOR_TASK_BG; + outlinePixel = colors[COLOR_TASK_DOWN]; + topPixel = colors[COLOR_TASK_UP]; + bottomPixel = colors[COLOR_TASK_DOWN]; + break; + case BUTTON_TASK_ACTIVE: + fg = COLOR_TASK_ACTIVE_FG; + bg = COLOR_TASK_ACTIVE_BG; + outlinePixel = colors[COLOR_TASK_ACTIVE_DOWN]; + topPixel = colors[COLOR_TASK_ACTIVE_DOWN]; + bottomPixel = colors[COLOR_TASK_ACTIVE_UP]; + break; + case BUTTON_MENU: + default: + fg = COLOR_MENU_FG; + bg = COLOR_MENU_BG; + outlinePixel = colors[COLOR_MENU_DOWN]; + topPixel = colors[COLOR_MENU_UP]; + bottomPixel = colors[COLOR_MENU_DOWN]; + break; + } + + JXSetForeground(display, gc, colors[bg]); + JXFillRectangle(display, drawable, gc, x + 2, y + 2, width - 3, height - 3); + + JXSetForeground(display, gc, outlinePixel); + JXDrawLine(display, drawable, gc, x + 1, y, x + width - 1, y); + JXDrawLine(display, drawable, gc, x + 1, y + height, x + width - 1, + y + height); + JXDrawLine(display, drawable, gc, x, y + 1, x, y + height - 1); + JXDrawLine(display, drawable, gc, x + width, y + 1, x + width, + y + height - 1); + + JXSetForeground(display, gc, topPixel); + JXDrawLine(display, drawable, gc, x + 1, y + 1, x + width - 2, y + 1); + JXDrawLine(display, drawable, gc, x + 1, y + 2, x + 1, y + height - 2); + + JXSetForeground(display, gc, bottomPixel); + JXDrawLine(display, drawable, gc, x + 1, y + height - 1, x + width - 1, + y + height - 1); + JXDrawLine(display, drawable, gc, x + width - 1, y + 1, x + width - 1, + y + height - 2); + + iconWidth = 0; + iconHeight = 0; + if(bp->icon) { + + if(width < height) { + GetScaledIconSize(bp->icon, width - 4, &iconWidth, &iconHeight); + } else { + GetScaledIconSize(bp->icon, height - 4, &iconWidth, &iconHeight); + } + + } + + textWidth = 0; + textHeight = 0; + if(bp->text) { + textWidth = GetStringWidth(bp->font, bp->text); + textHeight = GetStringHeight(bp->font); + if(textWidth + iconWidth + 10 > width) { + textWidth = width - iconWidth - 10; + if(textWidth < 0) { + textWidth = 0; + } + } + } + + switch(bp->alignment) { + case ALIGN_RIGHT: + xoffset = width - iconWidth - textWidth + 4; + if(xoffset < 4) { + xoffset = 4; + } + break; + case ALIGN_CENTER: + xoffset = width / 2 - (iconWidth + textWidth) / 2; + if(xoffset < 0) { + xoffset = 0; + } + break; + case ALIGN_LEFT: + default: + xoffset = 4; + break; + } + + if(bp->icon) { + yoffset = height / 2 - iconHeight / 2; + PutIcon(bp->icon, drawable, x + xoffset, y + yoffset, + iconWidth, iconHeight); + xoffset += iconWidth + 2; + } + + if(bp->text) { + yoffset = height / 2 - textHeight / 2; + RenderString(drawable, bp->font, fg, x + xoffset, y + yoffset, + textWidth, NULL, bp->text); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ResetButton(ButtonNode *bp, Drawable d, GC g) { + + Assert(bp); + + bp->type = BUTTON_MENU; + bp->drawable = d; + bp->gc = g; + bp->font = FONT_TRAY; + bp->alignment = ALIGN_LEFT; + bp->x = 0; + bp->y = 0; + bp->width = 1; + bp->height = 1; + bp->icon = NULL; + bp->text = NULL; + +} + +/*************************************************************************** + ***************************************************************************/ +void GetScaledIconSize(IconNode *ip, int maxsize, + int *width, int *height) { + + double ratio; + + Assert(ip); + Assert(width); + Assert(height); + + /* width to height */ + Assert(ip->image->height > 0); + ratio = (double)ip->image->width / ip->image->height; + + if(ip->image->width > ip->image->height) { + + /* Compute size wrt width */ + *width = maxsize * ratio; + *height = *width / ratio; + + } else { + + /* Compute size wrt height */ + *height = maxsize / ratio; + *width = *height * ratio; + + } + +} + diff --git a/src/button.h b/src/button.h new file mode 100644 index 0000000..9303ea5 --- /dev/null +++ b/src/button.h @@ -0,0 +1,63 @@ +/** + * @file button.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header file for button functions. + * + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include "font.h" + +struct IconNode; + +/** Button types. */ +typedef enum { + BUTTON_LABEL, /**< Label. */ + BUTTON_MENU, /**< Menu item. */ + BUTTON_MENU_ACTIVE, /**< Active menu item. */ + BUTTON_TASK, /**< Item in the task list. */ + BUTTON_TASK_ACTIVE /**< Active item in the task list. */ +} ButtonType; + +/** Alignment of content in a button. */ +typedef enum { + ALIGN_LEFT, /**< Left align. */ + ALIGN_CENTER, /**< Center align. */ + ALIGN_RIGHT /**< Right align. */ +} AlignmentType; + +/** Data used for drawing a button. */ +typedef struct { + + ButtonType type; /**< The type of button to draw. */ + Drawable drawable; /**< The place to put the button. */ + GC gc; /**< Graphics context used for drawing. */ + FontType font; /**< The font for button text. */ + AlignmentType alignment; /**< Alignment of the button content. */ + + int x, y; /**< The coordinates to render the button. */ + int width, height; /**< The size of the button. */ + + struct IconNode *icon; /**< Icon used in the button. */ + const char *text; /**< Text used in the button. */ + +} ButtonNode; + +/** Draw a button. + * @param bp The button to draw. + */ +void DrawButton(ButtonNode *bp); + +/** Reset the contents of a ButtonNode structure. + * @param bp The structure to reset. + * @param d The drawable to use. + * @param g The graphics context to use. + */ +void ResetButton(ButtonNode *bp, Drawable d, GC g); + +#endif + diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..6c1976e --- /dev/null +++ b/src/client.c @@ -0,0 +1,1423 @@ +/** + * @file client.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Functions to handle client windows. + * + */ + +#include "jwm.h" +#include "client.h" +#include "main.h" +#include "icon.h" +#include "hint.h" +#include "group.h" +#include "tray.h" +#include "confirm.h" +#include "key.h" +#include "cursor.h" +#include "taskbar.h" +#include "screen.h" +#include "pager.h" +#include "color.h" +#include "error.h" +#include "place.h" + +static const int STACK_BLOCK_SIZE = 8; + +ClientNode *nodes[LAYER_COUNT]; +ClientNode *nodeTail[LAYER_COUNT]; + +static ClientNode *activeClient; + +static int clientCount; + +static void LoadFocus(); +static void ReparentClient(ClientNode *np, int notOwner); + +static void MinimizeTransients(ClientNode *np); +static void CheckShape(ClientNode *np); + +static void RestoreTransients(ClientNode *np, int raise); + +static void KillClientHandler(ClientNode *np); + +/** Initialize client data. */ +void InitializeClients() { +} + +/** Load windows that are already mapped. */ +void StartupClients() { + + XWindowAttributes attr; + Window rootReturn, parentReturn, *childrenReturn; + unsigned int childrenCount; + unsigned int x; + + clientCount = 0; + activeClient = NULL; + currentDesktop = 0; + + /* Clear out the client lists. */ + for(x = 0; x < LAYER_COUNT; x++) { + nodes[x] = NULL; + nodeTail[x] = NULL; + } + + /* Query client windows. */ + JXQueryTree(display, rootWindow, &rootReturn, &parentReturn, + &childrenReturn, &childrenCount); + + /* Add each client. */ + for(x = 0; x < childrenCount; x++) { + if(JXGetWindowAttributes(display, childrenReturn[x], &attr)) { + if(attr.override_redirect == False + && attr.map_state == IsViewable) { + AddClientWindow(childrenReturn[x], 1, 1); + } + } + } + + JXFree(childrenReturn); + + LoadFocus(); + + UpdateTaskBar(); + UpdatePager(); + +} + +/** Release client windows. */ +void ShutdownClients() { + + int x; + + for(x = 0; x < LAYER_COUNT; x++) { + while(nodeTail[x]) { + RemoveClient(nodeTail[x]); + } + } + +} + +/** Destroy client data. */ +void DestroyClients() { +} + +/** Set the focus to the window currently under the mouse pointer. */ +void LoadFocus() { + + ClientNode *np; + Window rootReturn, childReturn; + int rootx, rooty; + int winx, winy; + unsigned int mask; + + JXQueryPointer(display, rootWindow, &rootReturn, &childReturn, + &rootx, &rooty, &winx, &winy, &mask); + + np = FindClientByWindow(childReturn); + if(np) { + FocusClient(np); + } + +} + +/** Add a window to management. */ +ClientNode *AddClientWindow(Window w, int alreadyMapped, int notOwner) { + + XWindowAttributes attr; + ClientNode *np; + + Assert(w != None); + + /* Get window attributes. */ + if(JXGetWindowAttributes(display, w, &attr) == 0) { + return NULL; + } + + /* Determine if we should care about this window. */ + if(attr.override_redirect == True) { + return NULL; + } + if(attr.class == InputOnly) { + return NULL; + } + + /* Prepare a client node for this window. */ + np = Allocate(sizeof(ClientNode)); + memset(np, 0, sizeof(ClientNode)); + + np->window = w; + np->owner = None; + np->state.desktop = currentDesktop; + np->controller = NULL; + np->name = NULL; + np->colormaps = NULL; + + np->x = attr.x; + np->y = attr.y; + np->width = attr.width; + np->height = attr.height; + np->cmap = attr.colormap; + np->colormaps = NULL; + np->state.status = STAT_NONE; + np->state.layer = LAYER_NORMAL; + + np->state.border = BORDER_DEFAULT; + np->borderAction = BA_NONE; + + ReadClientProtocols(np); + + if(!notOwner) { + np->state.border = BORDER_OUTLINE | BORDER_TITLE | BORDER_MOVE; + np->state.status |= STAT_WMDIALOG | STAT_STICKY; + } + + /* We now know the layer, so insert */ + np->prev = NULL; + np->next = nodes[np->state.layer]; + if(np->next) { + np->next->prev = np; + } else { + nodeTail[np->state.layer] = np; + } + nodes[np->state.layer] = np; + + LoadIcon(np); + + ApplyGroups(np); + + SetDefaultCursor(np->window); + ReparentClient(np, notOwner); + PlaceClient(np, alreadyMapped); + + /* If one of these fails we are SOL, so who cares. */ + XSaveContext(display, np->window, clientContext, (void*)np); + XSaveContext(display, np->parent, frameContext, (void*)np); + + if(np->state.status & STAT_MAPPED) { + JXMapWindow(display, np->window); + JXMapWindow(display, np->parent); + } + + DrawBorder(np, NULL); + + AddClientToTaskBar(np); + + if(!alreadyMapped) { + RaiseClient(np); + } + + ++clientCount; + + if(np->state.status & STAT_STICKY) { + SetCardinalAtom(np->window, ATOM_NET_WM_DESKTOP, ~0UL); + } else { + SetCardinalAtom(np->window, ATOM_NET_WM_DESKTOP, np->state.desktop); + } + + /* Shade the client if requested. */ + if(np->state.status & STAT_SHADED) { + ShadeClient(np); + } + + /* Minimize the client if requested. */ + if(np->state.status & STAT_MINIMIZED) { + np->state.status &= ~STAT_MINIMIZED; + MinimizeClient(np); + } + + /* Maximize the client if requested. */ + if(np->state.status & STAT_MAXIMIZED) { + np->state.status &= ~STAT_MAXIMIZED; + MaximizeClient(np); + } + + /* Make sure we're still in sync */ + WriteState(np); + SendConfigureEvent(np); + + /* Hide the client if we're not on the right desktop. */ + if(np->state.desktop != currentDesktop + && !(np->state.status & STAT_STICKY)) { + HideClient(np); + } + + ReadClientStrut(np); + + /* Focus transients if their parent has focus. */ + if(np->owner != None) { + + if(activeClient && np->owner == activeClient->window) { + FocusClient(np); + } + + } + + return np; + +} + +/** Minimize a client window and all of its transients. */ +void MinimizeClient(ClientNode *np) { + + Assert(np); + + if(focusModel == FOCUS_CLICK && np == activeClient) { + FocusNextStacked(np); + } + + MinimizeTransients(np); + + UpdateTaskBar(); + UpdatePager(); + +} + +/** Minimize all transients as well as the specified client. */ +void MinimizeTransients(ClientNode *np) { + + ClientNode *tp; + int x; + + Assert(np); + + /* A minimized client can't be active. */ + if(activeClient == np) { + activeClient = NULL; + np->state.status &= ~STAT_ACTIVE; + } + + /* Unmap the window and update its state. */ + if(np->state.status & (STAT_MAPPED | STAT_SHADED)) { + JXUnmapWindow(display, np->window); + JXUnmapWindow(display, np->parent); + } + np->state.status |= STAT_MINIMIZED; + np->state.status &= ~STAT_MAPPED; + WriteState(np); + + /* Minimize transient windows. */ + for(x = 0; x < LAYER_COUNT; x++) { + for(tp = nodes[x]; tp; tp = tp->next) { + if(tp->owner == np->window + && (tp->state.status & (STAT_MAPPED | STAT_SHADED)) + && !(tp->state.status & STAT_MINIMIZED)) { + MinimizeTransients(tp); + } + } + } + +} + +/** Shade a client. */ +void ShadeClient(ClientNode *np) { + + int north, south, east, west; + + Assert(np); + + if(!(np->state.border & BORDER_TITLE)) { + return; + } + + GetBorderSize(np, &north, &south, &east, &west); + + if(np->state.status & STAT_MAPPED) { + JXUnmapWindow(display, np->window); + } + np->state.status |= STAT_SHADED; + np->state.status &= ~STAT_MINIMIZED; + np->state.status &= ~STAT_SDESKTOP; + np->state.status &= ~STAT_MAPPED; + + JXResizeWindow(display, np->parent, np->width + east + west, + north + south); + + WriteState(np); + +#ifdef USE_SHAPE + if(np->state.status & STAT_SHAPE) { + SetShape(np); + } +#endif + +} + +/** Unshade a client. */ +void UnshadeClient(ClientNode *np) { + + int bsize; + + Assert(np); + + if(!(np->state.border & BORDER_TITLE)) { + return; + } + if(np->state.border & BORDER_OUTLINE) { + bsize = borderWidth; + } else { + bsize = 0; + } + + if(np->state.status & STAT_SHADED) { + JXMapWindow(display, np->window); + np->state.status |= STAT_MAPPED; + np->state.status &= ~STAT_SHADED; + } + + JXResizeWindow(display, np->parent, np->width + 2 * bsize, + np->height + titleHeight + 2 * bsize); + + WriteState(np); + +#ifdef USE_SHAPE + if(np->state.status & STAT_SHAPE) { + SetShape(np); + } +#endif + + RefocusClient(); + RestackClients(); + +} + +/** Set a client's state to withdrawn. */ +void SetClientWithdrawn(ClientNode *np) { + + Assert(np); + + if(activeClient == np) { + activeClient = NULL; + np->state.status &= ~STAT_ACTIVE; + FocusNextStacked(np); + } + + if(np->state.status & STAT_MAPPED) { + JXUnmapWindow(display, np->window); + JXUnmapWindow(display, np->parent); + WriteState(np); + } else if(np->state.status & STAT_SHADED) { + JXUnmapWindow(display, np->parent); + WriteState(np); + } + + np->state.status &= ~STAT_SHADED; + np->state.status &= ~STAT_MAPPED; + np->state.status &= ~STAT_MINIMIZED; + np->state.status &= ~STAT_SDESKTOP; + + UpdateTaskBar(); + UpdatePager(); + +} + +/** Restore a window with its transients (helper method). */ +void RestoreTransients(ClientNode *np, int raise) { + + ClientNode *tp; + int x; + + Assert(np); + + /* Restore this window. */ + if(!(np->state.status & STAT_MAPPED)) { + if(np->state.status & STAT_SHADED) { + JXMapWindow(display, np->parent); + } else { + JXMapWindow(display, np->window); + JXMapWindow(display, np->parent); + np->state.status |= STAT_MAPPED; + } + } + np->state.status &= ~STAT_MINIMIZED; + np->state.status &= ~STAT_SDESKTOP; + + WriteState(np); + + /* Restore transient windows. */ + for(x = 0; x < LAYER_COUNT; x++) { + for(tp = nodes[x]; tp; tp = tp->next) { + if(tp->owner == np->window + && !(tp->state.status & (STAT_MAPPED | STAT_SHADED)) + && (tp->state.status & STAT_MINIMIZED)) { + RestoreTransients(tp, raise); + } + } + } + + if(raise) { + RaiseClient(np); + } + +} + +/** Restore a client window and its transients. */ +void RestoreClient(ClientNode *np, int raise) { + + Assert(np); + + RestoreTransients(np, raise); + + RestackClients(); + UpdateTaskBar(); + UpdatePager(); + +} + +/** Set the client layer. This will affect transients. */ +void SetClientLayer(ClientNode *np, unsigned int layer) { + + ClientNode *tp, *next; + int x; + + Assert(np); + + if(layer > LAYER_TOP) { + Warning("Client %s requested an invalid layer: %d", np->name, layer); + return; + } + + if(np->state.layer != layer) { + + /* Loop through all clients so we get transients. */ + for(x = 0; x < LAYER_COUNT; x++) { + tp = nodes[x]; + while(tp) { + if(tp == np || tp->owner == np->window) { + + next = tp->next; + + /* Remove from the old node list */ + if(next) { + next->prev = tp->prev; + } else { + nodeTail[tp->state.layer] = tp->prev; + } + if(tp->prev) { + tp->prev->next = next; + } else { + nodes[tp->state.layer] = next; + } + + /* Insert into the new node list */ + tp->prev = NULL; + tp->next = nodes[layer]; + if(nodes[layer]) { + nodes[layer]->prev = tp; + } else { + nodeTail[layer] = tp; + } + nodes[layer] = tp; + + /* Set the new layer */ + tp->state.layer = layer; + SetCardinalAtom(tp->window, ATOM_WIN_LAYER, layer); + + /* Make sure we continue on the correct layer list. */ + tp = next; + + } else { + tp = tp->next; + } + } + } + + RestackClients(); + + } + +} + +/** Set a client's sticky status. This will update transients. */ +void SetClientSticky(ClientNode *np, int isSticky) { + + ClientNode *tp; + int old; + int x; + + Assert(np); + + /* Get the old sticky status. */ + if(np->state.status & STAT_STICKY) { + old = 1; + } else { + old = 0; + } + + if(isSticky && !old) { + + /* Change from non-sticky to sticky. */ + + for(x = 0; x < LAYER_COUNT; x++) { + for(tp = nodes[x]; tp; tp = tp->next) { + if(tp == np || tp->owner == np->window) { + tp->state.status |= STAT_STICKY; + SetCardinalAtom(tp->window, ATOM_NET_WM_DESKTOP, ~0UL); + WriteState(tp); + } + } + } + + } else if(!isSticky && old) { + + /* Change from sticky to non-sticky. */ + + for(x = 0; x < LAYER_COUNT; x++) { + for(tp = nodes[x]; tp; tp = tp->next) { + if(tp == np || tp->owner == np->window) { + tp->state.status &= ~STAT_STICKY; + WriteState(tp); + } + } + } + + /* Since this client is no longer sticky, we need to assign + * a desktop. Here we use the current desktop. + * Note that SetClientDesktop updates transients (which is good). + */ + SetClientDesktop(np, currentDesktop); + + } + +} + +/** Set a client's desktop. This will update transients. */ +void SetClientDesktop(ClientNode *np, unsigned int desktop) { + + ClientNode *tp; + int x; + + Assert(np); + + if(desktop >= desktopCount) { + return; + } + + if(!(np->state.status & STAT_STICKY)) { + for(x = 0; x < LAYER_COUNT; x++) { + for(tp = nodes[x]; tp; tp = tp->next) { + if(tp == np || tp->owner == np->window) { + + tp->state.desktop = desktop; + + if(desktop == currentDesktop) { + ShowClient(tp); + } else { + HideClient(tp); + } + + SetCardinalAtom(tp->window, ATOM_NET_WM_DESKTOP, + tp->state.desktop); + } + } + } + UpdatePager(); + UpdateTaskBar(); + } + +} + +/** Hide a client without unmapping. This will NOT update transients. */ +void HideClient(ClientNode *np) { + + Assert(np); + + if(activeClient == np) { + activeClient = NULL; + } + np->state.status |= STAT_HIDDEN; + if(np->state.status & (STAT_MAPPED | STAT_SHADED)) { + JXUnmapWindow(display, np->parent); + } + +} + +/** Show a hidden client. This will NOT update transients. */ +void ShowClient(ClientNode *np) { + + Assert(np); + + if(np->state.status & STAT_HIDDEN) { + np->state.status &= ~STAT_HIDDEN; + if(np->state.status & (STAT_MAPPED | STAT_SHADED)) { + JXMapWindow(display, np->parent); + if(np->state.status & STAT_ACTIVE) { + FocusClient(np); + } + } + } + +} + +/** Maximize a client window. */ +void MaximizeClient(ClientNode *np) { + + int north, south, east, west; + + Assert(np); + + /* We don't want to mess with full screen clients. */ + if(np->state.status & STAT_FULLSCREEN) { + SetClientFullScreen(np, 0); + } + + if(np->state.status & STAT_SHADED) { + UnshadeClient(np); + } + + GetBorderSize(np, &north, &south, &east, &west); + + if(np->state.status & STAT_MAXIMIZED) { + np->x = np->oldx; + np->y = np->oldy; + np->width = np->oldWidth; + np->height = np->oldHeight; + np->state.status &= ~STAT_MAXIMIZED; + } else { + PlaceMaximizedClient(np); + } + + JXMoveResizeWindow(display, np->parent, + np->x - west, np->y - north, + np->width + east + west, + np->height + north + south); + JXMoveResizeWindow(display, np->window, west, + north, np->width, np->height); + + WriteState(np); + SendConfigureEvent(np); + +} + +/** Set a client's full screen state. */ +void SetClientFullScreen(ClientNode *np, int fullScreen) { + + XEvent event; + int north, south, east, west; + const ScreenType *sp; + + Assert(np); + + /* Make sure there's something to do. */ + if(fullScreen && (np->state.status & STAT_FULLSCREEN)) { + return; + } else if (!fullScreen && !(np->state.status & STAT_FULLSCREEN)) { + return; + } + + if(np->state.status & STAT_SHADED) { + UnshadeClient(np); + } + + if(fullScreen) { + + np->state.status |= STAT_FULLSCREEN; + + sp = GetCurrentScreen(np->x, np->y); + + JXReparentWindow(display, np->window, rootWindow, 0, 0); + JXMoveResizeWindow(display, np->window, 0, 0, + sp->width, sp->height); + + SetClientLayer(np, LAYER_TOP); + + } else { + + np->state.status &= ~STAT_FULLSCREEN; + + GetBorderSize(np, &north, &south, &east, &west); + + JXReparentWindow(display, np->window, np->parent, west, north); + JXMoveResizeWindow(display, np->window, west, + north, np->width, np->height); + + event.type = MapRequest; + event.xmaprequest.send_event = True; + event.xmaprequest.display = display; + event.xmaprequest.parent = np->parent; + event.xmaprequest.window = np->window; + JXSendEvent(display, rootWindow, False, + SubstructureRedirectMask, &event); + + SetClientLayer(np, LAYER_NORMAL); + + } + + WriteState(np); + SendConfigureEvent(np); + +} + +/** Set the active client. */ +void FocusClient(ClientNode *np) { + + Assert(np); + + if(!(np->state.status & (STAT_MAPPED | STAT_SHADED))) { + return; + } + if(np->state.status & STAT_HIDDEN) { + return; + } + + if(activeClient != np) { + if(activeClient) { + activeClient->state.status &= ~STAT_ACTIVE; + DrawBorder(activeClient, NULL); + } + np->state.status |= STAT_ACTIVE; + activeClient = np; + + if(!(np->state.status & STAT_SHADED)) { + UpdateClientColormap(np); + SetWindowAtom(rootWindow, ATOM_NET_ACTIVE_WINDOW, np->window); + } + + DrawBorder(np, NULL); + UpdatePager(); + UpdateTaskBar(); + + } + + if(np->state.status & STAT_MAPPED && !(np->state.status & STAT_HIDDEN)) { + JXSetInputFocus(display, np->window, RevertToPointerRoot, CurrentTime); + } else { + JXSetInputFocus(display, rootWindow, RevertToPointerRoot, CurrentTime); + } + +} + +/** Focus the next client in the stacking order. */ +void FocusNextStacked(ClientNode *np) { + + int x; + ClientNode *tp; + + Assert(np); + + for(tp = np->next; tp; tp = tp->next) { + if((tp->state.status & (STAT_MAPPED | STAT_SHADED)) + && !(tp->state.status & STAT_HIDDEN)) { + FocusClient(tp); + return; + } + } + for(x = np->state.layer - 1; x >= LAYER_BOTTOM; x--) { + for(tp = nodes[x]; tp; tp = tp->next) { + if((tp->state.status & (STAT_MAPPED | STAT_SHADED)) + && !(tp->state.status & STAT_HIDDEN)) { + FocusClient(tp); + return; + } + } + } + +} + +/** Refocus the active client (if there is one). */ +void RefocusClient() { + + if(activeClient) { + FocusClient(activeClient); + } + +} + +/** Send a delete message to a client. */ +void DeleteClient(ClientNode *np) { + + ClientProtocolType protocols; + + Assert(np); + + protocols = ReadWMProtocols(np->window); + if(protocols & PROT_DELETE) { + SendClientMessage(np->window, ATOM_WM_PROTOCOLS, + ATOM_WM_DELETE_WINDOW); + } else { + KillClient(np); + } + +} + +/** Callback to kill a client after a confirm dialog. */ +void KillClientHandler(ClientNode *np) { + + Assert(np); + + if(np == activeClient) { + FocusNextStacked(np); + } + + JXGrabServer(display); + JXSync(display, False); + + JXKillClient(display, np->window); + + JXSync(display, True); + JXUngrabServer(display); + + RemoveClient(np); + +} + +/** Kill a client window. */ +void KillClient(ClientNode *np) { + + Assert(np); + + ShowConfirmDialog(np, KillClientHandler, + "Kill this window?", + "This may cause data to be lost!", + NULL); +} + +/** Raise the client. This will affect transients. */ +void RaiseClient(ClientNode *np) { + + ClientNode *tp, *next; + int x; + + Assert(np); + + if(nodes[np->state.layer] != np) { + + /* Raise the window */ + Assert(np->prev); + np->prev->next = np->next; + if(np->next) { + np->next->prev = np->prev; + } else { + nodeTail[np->state.layer] = np->prev; + } + np->next = nodes[np->state.layer]; + nodes[np->state.layer]->prev = np; + np->prev = NULL; + nodes[np->state.layer] = np; + + /* Place any transient windows on top of the owner */ + for(x = 0; x < LAYER_COUNT; x++) { + for(tp = nodes[x]; tp; tp = tp->next) { + if(tp->owner == np->window && tp->prev) { + + next = tp->next; + + tp->prev->next = tp->next; + if(tp->next) { + tp->next->prev = tp->prev; + } else { + nodeTail[tp->state.layer] = tp->prev; + } + tp->next = nodes[tp->state.layer]; + nodes[tp->state.layer]->prev = tp; + tp->prev = NULL; + nodes[tp->state.layer] = tp; + + tp = next; + + } + + /* tp will be tp->next if the above code is executed. */ + /* Thus, if it is NULL, we are done with this layer. */ + if(!tp) { + break; + } + } + } + + RestackClients(); + } + +} + +/** Lower the client. This will not affect transients. */ +void LowerClient(ClientNode *np) { + + ClientNode *tp; + + Assert(np); + + if(nodeTail[np->state.layer] != np) { + + Assert(np->next); + + /* Take the client out of the list. */ + if(np->prev) { + np->prev->next = np->next; + } else { + nodes[np->state.layer] = np->next; + } + np->next->prev = np->prev; + + /* Place the client at the end of the list. */ + tp = nodeTail[np->state.layer]; + nodeTail[np->state.layer] = np; + tp->next = np; + np->prev = tp; + np->next = NULL; + + RestackClients(); + + } + +} + +/** Restack the clients according the way we want them. */ +void RestackClients() { + + TrayType *tp; + ClientNode *np; + unsigned int layer, index; + int trayCount; + Window *stack; + + /* Determine how many tray windows exist. */ + trayCount = 0; + for(tp = GetTrays(); tp; tp = tp->next) { + ++trayCount; + } + + /** Allocate memory for restacking. */ + stack = AllocateStack((clientCount + trayCount) * sizeof(Window)); + + /* Prepare the stacking array. */ + index = 0; + layer = LAYER_TOP; + for(;;) { + + for(np = nodes[layer]; np; np = np->next) { + if((np->state.status & (STAT_MAPPED | STAT_SHADED)) + && !(np->state.status & STAT_HIDDEN)) { + stack[index++] = np->parent; + } + } + + for(tp = GetTrays(); tp; tp = tp->next) { + if(layer == tp->layer) { + stack[index++] = tp->window; + } + } + + if(layer == 0) { + break; + } + --layer; + + } + + JXRestackWindows(display, stack, index); + + ReleaseStack(stack); + + UpdateNetClientList(); + +} + +/** Send a client message to a window. */ +void SendClientMessage(Window w, AtomType type, AtomType message) { + + XEvent event; + int status; + + memset(&event, 0, sizeof(event)); + event.xclient.type = ClientMessage; + event.xclient.window = w; + event.xclient.message_type = atoms[type]; + event.xclient.format = 32; + event.xclient.data.l[0] = atoms[message]; + event.xclient.data.l[1] = CurrentTime; + + status = JXSendEvent(display, w, False, 0, &event); + if(status == False) { + Debug("SendClientMessage failed"); + } + +} + +/** Set the border shape for windows using the shape extension. */ +#ifdef USE_SHAPE +void SetShape(ClientNode *np) { + + XRectangle rect[4]; + int north, south, east, west; + + Assert(np); + + np->state.status |= STAT_SHAPE; + + GetBorderSize(np, &north, &south, &east, &west); + + /* Shaded windows are a special case. */ + if(np->state.status & STAT_SHADED) { + + rect[0].x = 0; + rect[0].y = 0; + rect[0].width = np->width + east + west; + rect[0].height = north + south; + + JXShapeCombineRectangles(display, np->parent, ShapeBounding, + 0, 0, rect, 1, ShapeSet, Unsorted); + + return; + } + + /* Add the shape of window. */ + JXShapeCombineShape(display, np->parent, ShapeBounding, west, north, + np->window, ShapeBounding, ShapeSet); + + /* Add the shape of the border. */ + if(north > 0) { + + /* Top */ + rect[0].x = 0; + rect[0].y = 0; + rect[0].width = np->width + east + west; + rect[0].height = north; + + /* Left */ + rect[1].x = 0; + rect[1].y = 0; + rect[1].width = west; + rect[1].height = np->height + north + south; + + /* Right */ + rect[2].x = np->width + east; + rect[2].y = 0; + rect[2].width = west; + rect[2].height = np->height + north + south; + + /* Bottom */ + rect[3].x = 0; + rect[3].y = np->height + north; + rect[3].width = np->width + east + west; + rect[3].height = south; + + JXShapeCombineRectangles(display, np->parent, ShapeBounding, + 0, 0, rect, 4, ShapeUnion, Unsorted); + + } + +} +#endif /* USE_SHAPE */ + +/** Remove a client window from management. */ +void RemoveClient(ClientNode *np) { + + ColormapNode *cp; + + Assert(np); + Assert(np->window != None); + Assert(np->parent != None); + + JXGrabServer(display); + + /* Remove this client from the client list */ + if(np->next) { + np->next->prev = np->prev; + } else { + nodeTail[np->state.layer] = np->prev; + } + if(np->prev) { + np->prev->next = np->next; + } else { + nodes[np->state.layer] = np->next; + } + --clientCount; + XDeleteContext(display, np->window, clientContext); + XDeleteContext(display, np->parent, frameContext); + + /* Make sure this client isn't active */ + if(activeClient == np && !shouldExit) { + FocusNextStacked(np); + } + if(activeClient == np) { + SetWindowAtom(rootWindow, ATOM_NET_ACTIVE_WINDOW, None); + activeClient = NULL; + } + + /* If the window manager is exiting (ie, not the client), then + * reparent etc. */ + if(shouldExit && !(np->state.status & STAT_WMDIALOG)) { + if(np->state.status & STAT_MAXIMIZED) { + np->x = np->oldx; + np->y = np->oldy; + np->width = np->oldWidth; + np->height = np->oldHeight; + JXMoveResizeWindow(display, np->window, + np->x, np->y, np->width, np->height); + } + GravitateClient(np, 1); + if(!(np->state.status & STAT_MAPPED) + && (np->state.status & (STAT_MINIMIZED | STAT_SHADED))) { + JXMapWindow(display, np->window); + } + JXUngrabButton(display, AnyButton, AnyModifier, np->window); + JXReparentWindow(display, np->window, rootWindow, np->x, np->y); + JXRemoveFromSaveSet(display, np->window); + } + + /* Destroy the parent */ + if(np->parent) { + JXDestroyWindow(display, np->parent); + } + + if(np->name) { + JXFree(np->name); + } + if(np->instanceName) { + JXFree(np->instanceName); + } + if(np->className) { + JXFree(np->className); + } + + RemoveClientFromTaskBar(np); + RemoveClientStrut(np); + UpdatePager(); + + while(np->colormaps) { + cp = np->colormaps->next; + Release(np->colormaps); + np->colormaps = cp; + } + + DestroyIcon(np->icon); + + Release(np); + + JXUngrabServer(display); + +} + +/** Get the active client (possibly NULL). */ +ClientNode *GetActiveClient() { + + return activeClient; + +} + +/** Find a client given a window (searches frame windows too). */ +ClientNode *FindClientByWindow(Window w) { + + ClientNode *np; + + if(!XFindContext(display, w, clientContext, (void*)&np)) { + return np; + } else { + return FindClientByParent(w); + } + +} + +/** Find a client by its frame window. */ +ClientNode *FindClientByParent(Window p) { + + ClientNode *np; + + if(!XFindContext(display, p, frameContext, (void*)&np)) { + return np; + } else { + return NULL; + } + +} + +/** Reparent a client window. */ +void ReparentClient(ClientNode *np, int notOwner) { + + XSetWindowAttributes attr; + int attrMask; + int x, y, width, height; + + Assert(np); + + if(notOwner) { + JXAddToSaveSet(display, np->window); + + attr.event_mask = EnterWindowMask | ColormapChangeMask + | PropertyChangeMask | StructureNotifyMask; + attr.do_not_propagate_mask = NoEventMask; + XChangeWindowAttributes(display, np->window, + CWEventMask | CWDontPropagate, &attr); + + } + JXGrabButton(display, AnyButton, AnyModifier, np->window, + True, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); + GrabKeys(np); + + attrMask = 0; + + attrMask |= CWOverrideRedirect; + attr.override_redirect = True; + + /* We can't use PointerMotionHint mask here since the exact location + * of the mouse on the frame is important. */ + attrMask |= CWEventMask; + attr.event_mask + = ButtonPressMask + | ButtonReleaseMask + | ExposureMask + | PointerMotionMask + | SubstructureRedirectMask + | SubstructureNotifyMask + | EnterWindowMask + | LeaveWindowMask + | KeyPressMask; + + attrMask |= CWDontPropagate; + attr.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask; + + attrMask |= CWBackPixel; + attr.background_pixel = colors[COLOR_BORDER_BG]; + + x = np->x; + y = np->y; + width = np->width; + height = np->height; + if(np->state.border & BORDER_OUTLINE) { + x -= borderWidth; + y -= borderWidth; + width += borderWidth * 2; + height += borderWidth * 2; + } + if(np->state.border & BORDER_TITLE) { + y -= titleHeight; + height += titleHeight; + } + + /* Create the frame window. */ + np->parent = JXCreateWindow(display, rootWindow, + x, y, width, height, 0, rootDepth, InputOutput, + rootVisual, attrMask, &attr); + + /* Update the window to get only the events we want. */ + attrMask = CWDontPropagate; + attr.do_not_propagate_mask + = ButtonPressMask + | ButtonReleaseMask + | PointerMotionMask + | ButtonMotionMask + | KeyPressMask; + JXChangeWindowAttributes(display, np->window, attrMask, &attr); + JXSetWindowBorderWidth(display, np->window, 0); + + /* Reparent the client window. */ + if((np->state.border & BORDER_OUTLINE) + && (np->state.border & BORDER_TITLE)) { + JXReparentWindow(display, np->window, np->parent, + borderWidth, borderWidth + titleHeight); + } else if(np->state.border & BORDER_OUTLINE) { + JXReparentWindow(display, np->window, np->parent, + borderWidth, borderWidth); + } else if(np->state.border & BORDER_TITLE) { + JXReparentWindow(display, np->window, np->parent, + 0, titleHeight); + } else { + JXReparentWindow(display, np->window, np->parent, 0, 0); + } + +#ifdef USE_SHAPE + if(haveShape) { + JXShapeSelectInput(display, np->window, ShapeNotifyMask); + CheckShape(np); + } +#endif + +} + +/** Determine if a window uses the shape extension. */ +#ifdef USE_SHAPE +void CheckShape(ClientNode *np) { + + int xb, yb; + int xc, yc; + unsigned int wb, hb; + unsigned int wc, hc; + Bool boundingShaped, clipShaped; + + JXShapeQueryExtents(display, np->window, &boundingShaped, + &xb, &yb, &wb, &hb, &clipShaped, &xc, &yc, &wc, &hc); + + if(boundingShaped == True) { + SetShape(np); + } + +} +#endif + +/** Send a configure event to a client window. */ +void SendConfigureEvent(ClientNode *np) { + + XConfigureEvent event; + const ScreenType *sp; + + Assert(np); + + event.type = ConfigureNotify; + event.event = np->window; + event.window = np->window; + + if(np->state.status & STAT_FULLSCREEN) { + sp = GetCurrentScreen(np->x, np->y); + event.x = sp->x; + event.y = sp->y; + event.width = sp->width; + event.height = sp->height; + } else { + event.x = np->x; + event.y = np->y; + event.width = np->width; + event.height = np->height; + } + + event.border_width = 0; + event.above = None; + event.override_redirect = False; + + JXSendEvent(display, np->window, False, StructureNotifyMask, + (XEvent*)&event); + +} + +/** Update a window's colormap. + * A call to this function indicates that the colormap(s) for the given + * client changed. This will change the active colormap(s) if the given + * client is active. + */ +void UpdateClientColormap(ClientNode *np) { + + XWindowAttributes attr; + ColormapNode *cp; + int wasInstalled; + + Assert(np); + + cp = np->colormaps; + + if(np == activeClient) { + + wasInstalled = 0; + cp = np->colormaps; + while(cp) { + if(JXGetWindowAttributes(display, cp->window, &attr)) { + if(attr.colormap != None) { + if(attr.colormap == np->cmap) { + wasInstalled = 1; + } + JXInstallColormap(display, attr.colormap); + } + } + cp = cp->next; + } + + if(!wasInstalled && np->cmap != None) { + JXInstallColormap(display, np->cmap); + } + + } + +} + diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..7fc98db --- /dev/null +++ b/src/client.h @@ -0,0 +1,285 @@ +/** + * @file client.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header file client window functions. + * + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "border.h" +#include "hint.h" + +/** Window border flags. */ +typedef enum { + BORDER_NONE = 0, + BORDER_OUTLINE = 1, /**< Window has a border. */ + BORDER_TITLE = 2, /**< Window has a title bar. */ + BORDER_MIN = 4, /**< Window supports minimize. */ + BORDER_MAX = 8, /**< Window supports maximize. */ + BORDER_CLOSE = 16, /**< Window supports close. */ + BORDER_RESIZE = 32, /**< Window supports resizing. */ + BORDER_MOVE = 64 /**< Window supports moving. */ +} BorderFlags; + +/** The default border flags. */ +#define BORDER_DEFAULT ( \ + BORDER_OUTLINE \ + | BORDER_TITLE \ + | BORDER_MIN \ + | BORDER_MAX \ + | BORDER_CLOSE \ + | BORDER_RESIZE \ + | BORDER_MOVE ) + +/** Window status flags. */ +typedef enum { + STAT_NONE = 0, + STAT_ACTIVE = 1 << 0, /**< This client has focus. */ + STAT_MAPPED = 1 << 1, /**< This client is shown (on some desktop). */ + STAT_MAXIMIZED = 1 << 2, /**< This client is maximized. */ + STAT_HIDDEN = 1 << 3, /**< This client is not on the current desktop. */ + STAT_STICKY = 1 << 4, /**< This client is on all desktops. */ + STAT_NOLIST = 1 << 5, /**< Skip this client in the task list. */ + STAT_MINIMIZED = 1 << 6, /**< This client is minimized. */ + STAT_SHADED = 1 << 7, /**< This client is shaded. */ + STAT_WMDIALOG = 1 << 8, /**< This is a JWM dialog window. */ + STAT_PIGNORE = 1 << 9, /**< Ignore the program-specified position. */ + STAT_SHAPE = 1 << 10, /**< This client uses the shape extension. */ + STAT_SDESKTOP = 1 << 11, /**< This client was minimized to show desktop. */ + STAT_FULLSCREEN = 1 << 12 /**< This client wants to be full screen. */ +} StatusFlags; + +/** Colormap window linked list. */ +typedef struct ColormapNode { + Window window; /**< A window containing a colormap. */ + struct ColormapNode *next; /**< Next value in the linked list. */ +} ColormapNode; + +/** The aspect ratio of a window. */ +typedef struct AspectRatio { + int minx, miny; /**< The minimum aspect ratio. */ + int maxx, maxy; /**< The maximum aspect ratio. */ +} AspectRatio; + +/** Struture to store information about a client window. */ +typedef struct ClientNode { + + Window window; /**< The client window. */ + Window parent; /**< The frame window. */ + + Window owner; /**< The owner window (for transients). */ + + int x, y; /**< The location of the window. */ + int width, height; /**< The size of the window. */ + int oldx, oldy; /**< The old location (for maximize). */ + int oldWidth, oldHeight; /**< The old size (for maximize). */ + + long sizeFlags; + int baseWidth, baseHeight; + int minWidth, minHeight; + int maxWidth, maxHeight; + int xinc, yinc; + AspectRatio aspect; + int gravity; + + Colormap cmap; + ColormapNode *colormaps; + + char *name; + char *instanceName; + char *className; + + ClientState state; + + BorderActionType borderAction; + + struct IconNode *icon; + + void (*controller)(int wasDestroyed); + + struct ClientNode *prev; /**< The previous client in this layer. */ + struct ClientNode *next; /**< The next client in this layer. */ + +} ClientNode; + +/** Client windows in linked lists for each layer. */ +extern ClientNode *nodes[LAYER_COUNT]; + +/** Client windows in linked lists for each layer (pointer to the tail). */ +extern ClientNode *nodeTail[LAYER_COUNT]; + +/** Find a client by window or parent window. + * @param w The window. + * @return The client (NULL if not found). + */ +ClientNode *FindClientByWindow(Window w); + +/** Find a client by its parent window. + * @param p The parent window. + * @return The client (NULL if not found). + */ +ClientNode *FindClientByParent(Window p); + +/** Get the active client. + * @return The active client (NULL if no client is active). + */ +ClientNode *GetActiveClient(); + +void InitializeClients(); +void StartupClients(); +void ShutdownClients(); +void DestroyClients(); + +/** Add a window to management. + * @param w The client window. + * @param alreadyMapped 1 if the window is mapped, 0 if not. + * @param notOwner 1 if JWM doesn't own this window, 0 if JWM is the owner. + * @return The client window data. + */ +ClientNode *AddClientWindow(Window w, int alreadyMapped, int notOwner); + +/** Remove a client from management. + * @param np The client to remove. + */ +void RemoveClient(ClientNode *np); + +/** Minimize a client. + * @param np The client to minimize. + */ +void MinimizeClient(ClientNode *np); + +/** Shade a client. + * @param np The client to shade. + */ +void ShadeClient(ClientNode *np); + +/** Unshade a client. + * @param np The client to unshade. + */ +void UnshadeClient(ClientNode *np); + +/** Set a client's status to withdrawn. + * A withdrawn client is a client that is not visible in any way to the + * user. This may be a window that an application keeps around so that + * it can be reused at a later time. + * @param np The client whose status to change. + */ +void SetClientWithdrawn(ClientNode *np); + +/** Restore a client from minimized state. + * @param np The client to restore. + * @param raise 1 to raise the client, 0 to leave stacking unchanged. + */ +void RestoreClient(ClientNode *np, int raise); + +/** Maximize a client. + * @param np The client to maximize. + */ +void MaximizeClient(ClientNode *np); + +/** Set the full screen status of a client. + * @param np The client. + * @param fullScreen 1 to make full screen, 0 to make not full screen. + */ +void SetClientFullScreen(ClientNode *np, int fullScreen); + +/** Set the keyboard focus to a client. + * @param np The client to focus. + */ +void FocusClient(ClientNode *np); + +/** Set the keyboard focus to the next client. + * This is used to focus the next client in the stacking order. + * @param np The client before the client to focus. + */ +void FocusNextStacked(ClientNode *np); + +/** Set the keyboard focus back to the active client. */ +void RefocusClient(); + +/** Tell a client to exit. + * @param np The client to delete. + */ +void DeleteClient(ClientNode *np); + +/** Force a client to exit. + * @param np The client to kill. + */ +void KillClient(ClientNode *np); + +/** Raise a client to the top of its layer. + * @param np The client to raise. + */ +void RaiseClient(ClientNode *np); + +/** Lower a client to the bottom of its layer. + * @param np The client to lower. + */ +void LowerClient(ClientNode *np); + +/** Restack the clients. + * This is used when a client is mapped so that the stacking order + * remains consistent. + */ +void RestackClients(); + +/** Set the layer of a client. + * @param np The client whose layer to set. + * @param layer the layer to assign to the client. + */ +void SetClientLayer(ClientNode *np, unsigned int layer); + +/** Set the desktop for a client. + * @param np The client. + * @parma desktop The desktop to be assigned to the client. + */ +void SetClientDesktop(ClientNode *np, unsigned int desktop); + +/** Set the sticky status of a client. + * A sticky client will appear on all desktops. + * @param np The client. + * @param isSticky 1 to make the client sticky, 0 to make it not sticky. + */ +void SetClientSticky(ClientNode *np, int isSticky); + +/** Hide a client. + * This is used for changing desktops. + * @param np The client to hide. + */ +void HideClient(ClientNode *np); + +/** Show a client. + * This is used for changing desktops. + * @param np The client to show. + */ +void ShowClient(ClientNode *np); + +/** Update a client's colormap. + * @param np The client. + */ +void UpdateClientColormap(ClientNode *np); + +/** Update the shape of a client using the shape extension. + * @param np The client to update. + */ +void SetShape(ClientNode *np); + +/** Send a configure event to a client. + * This will send updated location and size information to a client. + * @param np The client to get the event. + */ +void SendConfigureEvent(ClientNode *np); + +/** Send a message to a client. + * @param w The client window. + * @param type The type of message to send. + * @param message The message to send. + */ +void SendClientMessage(Window w, AtomType type, AtomType message); + +#endif + diff --git a/src/clock.c b/src/clock.c new file mode 100644 index 0000000..147c1f6 --- /dev/null +++ b/src/clock.c @@ -0,0 +1,332 @@ +/** + * @file clock.c + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Clock tray component. + * + */ + +#include "jwm.h" +#include "clock.h" +#include "tray.h" +#include "color.h" +#include "font.h" +#include "timing.h" +#include "main.h" +#include "root.h" +#include "cursor.h" +#include "popup.h" +#include "misc.h" + +/** Structure to respresent a clock tray component. */ +typedef struct ClockType { + + TrayComponentType *cp; /**< Common component data. */ + + char *format; /**< The time format to use. */ + char *command; /**< A command to run when clicked. */ + char shortTime[80]; /**< Currently displayed time. */ + + /* The following are used to control popups. */ + int mousex; /**< Last mouse x-coordinate. */ + int mousey; /**< Last mouse y-coordinate. */ + TimeType mouseTime; /**< Time of the last mouse motion. */ + + int userWidth; /**< User-specified clock width (or 0). */ + + struct ClockType *next; /**< Next clock in the list. */ + +} ClockType; + +/** The default time format to use. */ +static const char *DEFAULT_FORMAT = "%I:%M %p"; + +static ClockType *clocks; +static TimeType lastUpdate = ZERO_TIME; + +static void Create(TrayComponentType *cp); +static void Resize(TrayComponentType *cp); +static void Destroy(TrayComponentType *cp); +static void ProcessClockButtonEvent(TrayComponentType *cp, + int x, int y, int mask); +static void ProcessClockMotionEvent(TrayComponentType *cp, + int x, int y, int mask); + +static void DrawClock(ClockType *clk, const TimeType *now, int x, int y); + +/** Initialize clocks. */ +void InitializeClock() { + clocks = NULL; +} + +/** Start clock(s). */ +void StartupClock() { + + ClockType *clk; + + for(clk = clocks; clk; clk = clk->next) { + if(clk->cp->requestedWidth == 0) { + clk->cp->requestedWidth = GetStringWidth(FONT_CLOCK, clk->format) + 4; + } + if(clk->cp->requestedHeight == 0) { + clk->cp->requestedHeight = GetStringHeight(FONT_CLOCK) + 4; + } + } + +} + +/** Stop clock(s). */ +void ShutdownClock() { +} + +/** Destroy clock(s). */ +void DestroyClock() { + + ClockType *cp; + + while(clocks) { + cp = clocks->next; + + if(clocks->format) { + Release(clocks->format); + } + if(clocks->command) { + Release(clocks->command); + } + + Release(clocks); + clocks = cp; + } + +} + +/** Create a clock tray component. */ +TrayComponentType *CreateClock(const char *format, const char *command, + int width, int height) { + + TrayComponentType *cp; + ClockType *clk; + + clk = Allocate(sizeof(ClockType)); + clk->next = clocks; + clocks = clk; + + clk->mousex = 0; + clk->mousey = 0; + clk->mouseTime.seconds = 0; + clk->mouseTime.ms = 0; + clk->userWidth = 0; + + if(!format) { + format = DEFAULT_FORMAT; + } + clk->format = CopyString(format); + + clk->command = CopyString(command); + + clk->shortTime[0] = 0; + + cp = CreateTrayComponent(); + cp->object = clk; + clk->cp = cp; + if(width > 0) { + cp->requestedWidth = width; + clk->userWidth = 1; + } else { + cp->requestedWidth = 0; + clk->userWidth = 0; + } + cp->requestedHeight = height; + + cp->Create = Create; + cp->Resize = Resize; + cp->Destroy = Destroy; + cp->ProcessButtonEvent = ProcessClockButtonEvent; + cp->ProcessMotionEvent = ProcessClockMotionEvent; + + return cp; + +} + +/** Initialize a clock tray component. */ +void Create(TrayComponentType *cp) { + + ClockType *clk; + + Assert(cp); + + clk = (ClockType*)cp->object; + + Assert(clk); + + cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height, + rootDepth); + + JXSetForeground(display, rootGC, colors[COLOR_CLOCK_BG]); + JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, cp->width, cp->height); + +} + +/** Resize a clock tray component. */ +void Resize(TrayComponentType *cp) { + + ClockType *clk; + TimeType now; + int x, y; + + Assert(cp); + + clk = (ClockType*)cp->object; + + Assert(clk); + + if(cp->pixmap != None) { + JXFreePixmap(display, cp->pixmap); + } + + cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height, + rootDepth); + + clk->shortTime[0] = 0; + + GetCurrentTime(&now); + GetMousePosition(&x, &y); + DrawClock(clk, &now, x, y); + +} + +/** Destroy a clock tray component. */ +void Destroy(TrayComponentType *cp) { + + ClockType *clk; + + Assert(cp); + + clk = (ClockType*)cp->object; + + Assert(clk); + + if(cp->pixmap != None) { + JXFreePixmap(display, cp->pixmap); + } +} + +/** Process a click event on a clock tray component. */ +void ProcessClockButtonEvent(TrayComponentType *cp, int x, int y, int mask) { + + ClockType *clk; + + Assert(cp); + + clk = (ClockType*)cp->object; + + Assert(clk); + + if(clk->command) { + RunCommand(clk->command); + } + +} + +/** Process a motion event on a clock tray component. */ +void ProcessClockMotionEvent(TrayComponentType *cp, + int x, int y, int mask) { + + Assert(cp); + + ClockType *clk = (ClockType*)cp->object; + clk->mousex = cp->screenx + x; + clk->mousey = cp->screeny + y; + GetCurrentTime(&clk->mouseTime); + +} + +/** Update a clock tray component. */ +void SignalClock(const TimeType *now, int x, int y) { + + ClockType *cp; + int shouldDraw; + char *longTime; + time_t t; + + Assert(now); + + /* Determine if we should update the clock(s). */ + if(GetTimeDifference(&lastUpdate, now) > 900) { + shouldDraw = 1; + lastUpdate = *now; + } else { + shouldDraw = 0; + } + + /* Update each clock. */ + for(cp = clocks; cp; cp = cp->next) { + + if(shouldDraw) { + DrawClock(cp, now, x, y); + } + + if(abs(cp->mousex - x) < POPUP_DELTA + && abs(cp->mousey - y) < POPUP_DELTA) { + if(GetTimeDifference(now, &cp->mouseTime) >= popupDelay) { + time(&t); + longTime = asctime(localtime(&t)); + Trim(longTime); + ShowPopup(x, y, longTime); + } + } + + } + +} + +/** Draw a clock tray component. */ +void DrawClock(ClockType *clk, const TimeType *now, int x, int y) { + + TrayComponentType *cp; + const char *shortTime; + int width; + int rwidth; + + Assert(clk); + Assert(now); + + /* Only draw if the label changed. */ + shortTime = GetTimeString(clk->format); + if(!strcmp(clk->shortTime, shortTime)) { + return; + } + strcpy(clk->shortTime, shortTime); + + cp = clk->cp; + + /* Clear the area. */ + JXSetForeground(display, rootGC, colors[COLOR_CLOCK_BG]); + JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, + cp->width, cp->height); + + /* Determine if the clock is the right size. */ + width = GetStringWidth(FONT_CLOCK, shortTime); + rwidth = width + 4; + if(rwidth == clk->cp->requestedWidth || clk->userWidth) { + + /* Draw the clock. */ + RenderString(cp->pixmap, FONT_CLOCK, COLOR_CLOCK_FG, + cp->width / 2 - width / 2, + cp->height / 2 - GetStringHeight(FONT_CLOCK) / 2, + cp->width, NULL, shortTime); + + UpdateSpecificTray(clk->cp->tray, clk->cp); + + } else { + + /* Wrong size. Resize. */ + clk->cp->requestedWidth = rwidth; + ResizeTray(clk->cp->tray); + + } + +} + + diff --git a/src/clock.h b/src/clock.h new file mode 100644 index 0000000..25bd74d --- /dev/null +++ b/src/clock.h @@ -0,0 +1,41 @@ +/** + * @file clock.h + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Clock tray component. + * + */ + +#ifndef CLOCK_H +#define CLOCK_H + +struct TrayComponentType; +struct TimeType; + +/*@{*/ +void InitializeClock(); +void StartupClock(); +void ShutdownClock(); +void DestroyClock(); +/*@}*/ + +/** Create a clock component for the tray. + * @param format The format of the clock. + * @param command The command to execute when the clock is clicked. + * @param width The width of the clock (0 for auto). + * @param height The height of the clock (0 for auto). + */ +struct TrayComponentType *CreateClock(const char *format, + const char *command, int width, int height); + +/** Update clocks. + * This is called on a regular basis to update the time. + * @param now The current time. + * @param x The x-coordinate of the mouse. + * @param y The y-coordinate of the mouse. + */ +void SignalClock(const struct TimeType *now, int x, int y); + +#endif + diff --git a/src/color.c b/src/color.c new file mode 100644 index 0000000..edf5f7a --- /dev/null +++ b/src/color.c @@ -0,0 +1,606 @@ +/**************************************************************************** + * Functions to handle loading colors. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "main.h" +#include "color.h" +#include "error.h" +#include "misc.h" + +typedef struct { + ColorType type; + const char *value; +} DefaultColorNode; + +static const float COLOR_DELTA = 0.45; + +unsigned long colors[COLOR_COUNT]; +static unsigned long rgbColors[COLOR_COUNT]; + +static unsigned long *map; + +#ifdef USE_XFT +static XftColor *xftColors[COLOR_COUNT] = { NULL }; +#endif + +static DefaultColorNode DEFAULT_COLORS[] = { + { COLOR_BORDER_BG, "gray" }, + { COLOR_BORDER_FG, "black" }, + { COLOR_BORDER_ACTIVE_BG, "red" }, + { COLOR_BORDER_ACTIVE_FG, "white" }, + { COLOR_TRAY_BG, "gray" }, + { COLOR_TRAY_FG, "black" }, + { COLOR_TASK_BG, "gray" }, + { COLOR_TASK_FG, "black" }, + { COLOR_TASK_ACTIVE_BG, "red" }, + { COLOR_TASK_ACTIVE_FG, "white" }, + { COLOR_PAGER_BG, "black" }, + { COLOR_PAGER_FG, "gray" }, + { COLOR_PAGER_ACTIVE_BG, "red" }, + { COLOR_PAGER_ACTIVE_FG, "red" }, + { COLOR_PAGER_OUTLINE, "black" }, + { COLOR_MENU_BG, "gray" }, + { COLOR_MENU_FG, "black" }, + { COLOR_MENU_ACTIVE_BG, "red" }, + { COLOR_MENU_ACTIVE_FG, "white" }, + { COLOR_POPUP_BG, "yellow" }, + { COLOR_POPUP_FG, "black" }, + { COLOR_POPUP_OUTLINE, "black" }, + { COLOR_TRAYBUTTON_FG, "black" }, + { COLOR_TRAYBUTTON_BG, "gray" }, + { COLOR_CLOCK_FG, "black" }, + { COLOR_CLOCK_BG, "gray" }, + { COLOR_COUNT, NULL } +}; + +static char **names = NULL; + +static unsigned long redShift; +static unsigned long greenShift; +static unsigned long blueShift; +static unsigned long redMask; +static unsigned long greenMask; +static unsigned long blueMask; + +static void ComputeShiftMask(unsigned long maskIn, + unsigned long *shiftOut, unsigned long *maskOut); + +static void GetDirectPixel(XColor *c); +static void GetMappedPixel(XColor *c); + +static int ParseColor(ColorType type, const char *value); +static void SetDefaultColor(ColorType type); + +static unsigned long ReadHex(const char *hex); + +static unsigned long GetRGBFromXColor(const XColor *c); +static XColor GetXColorFromRGB(unsigned long rgb); + +static int GetColorByName(const char *str, XColor *c); +static void InitializeNames(); + +static void LightenColor(ColorType oldColor, ColorType newColor); +static void DarkenColor(ColorType oldColor, ColorType newColor); + +/**************************************************************************** + ****************************************************************************/ +void InitializeColors() { + +} + +/**************************************************************************** + ****************************************************************************/ +void StartupColors() { + + int x; + int red, green, blue; + XColor c; + + /* Determine how to convert between RGB triples and pixels. */ + Assert(rootVisual); + switch(rootVisual->class) { + case DirectColor: + case TrueColor: + ComputeShiftMask(rootVisual->red_mask, &redShift, &redMask); + ComputeShiftMask(rootVisual->green_mask, &greenShift, &greenMask); + ComputeShiftMask(rootVisual->blue_mask, &blueShift, &blueMask); + map = NULL; + break; + default: + + /* Attempt to get 256 colors, pretend it worked. */ + redMask = 0xE0; + greenMask = 0x1C; + blueMask = 0x03; + ComputeShiftMask(redMask, &redShift, &redMask); + ComputeShiftMask(greenMask, &greenShift, &greenMask); + ComputeShiftMask(blueMask, &blueShift, &blueMask); + map = Allocate(sizeof(unsigned long) * 256); + + /* RGB: 3, 3, 2 */ + x = 0; + for(red = 0; red < 8; red++) { + for(green = 0; green < 8; green++) { + for(blue = 0; blue < 4; blue++) { + c.red = 74898 * red / 8; + c.green = 74898 * green / 8; + c.blue = 87381 * blue / 4; + c.flags = DoRed | DoGreen | DoBlue; + JXAllocColor(display, rootColormap, &c); + map[x] = c.pixel; + ++x; + } + } + } + + break; + } + + /* Inherit unset colors from the tray for tray items. */ + if(names) { + if(!names[COLOR_TASK_BG]) { + names[COLOR_TASK_BG] = CopyString(names[COLOR_TRAY_BG]); + } + if(!names[COLOR_TRAYBUTTON_BG]) { + names[COLOR_TRAYBUTTON_BG] = CopyString(names[COLOR_TRAY_BG]); + } + if(!names[COLOR_CLOCK_BG]) { + names[COLOR_CLOCK_BG] = CopyString(names[COLOR_TRAY_BG]); + } + if(!names[COLOR_TASK_FG]) { + names[COLOR_TASK_FG] = CopyString(names[COLOR_TRAY_FG]); + } + if(!names[COLOR_TRAYBUTTON_FG]) { + names[COLOR_TRAYBUTTON_FG] = CopyString(names[COLOR_TRAY_FG]); + } + if(!names[COLOR_CLOCK_FG]) { + names[COLOR_CLOCK_FG] = CopyString(names[COLOR_TRAY_FG]); + } + } + + /* Get color information used for JWM stuff. */ + for(x = 0; x < COLOR_COUNT; x++) { + if(names && names[x]) { + if(!ParseColor(x, names[x])) { + SetDefaultColor(x); + } + } else { + SetDefaultColor(x); + } + } + + if(names) { + for(x = 0; x < COLOR_COUNT; x++) { + if(names[x]) { + Release(names[x]); + } + } + Release(names); + names = NULL; + } + + LightenColor(COLOR_BORDER_BG, COLOR_BORDER_UP); + DarkenColor(COLOR_BORDER_BG, COLOR_BORDER_DOWN); + + LightenColor(COLOR_BORDER_ACTIVE_BG, COLOR_BORDER_ACTIVE_UP); + DarkenColor(COLOR_BORDER_ACTIVE_BG, COLOR_BORDER_ACTIVE_DOWN); + + LightenColor(COLOR_TRAY_BG, COLOR_TRAY_UP); + DarkenColor(COLOR_TRAY_BG, COLOR_TRAY_DOWN); + + LightenColor(COLOR_TASK_BG, COLOR_TASK_UP); + DarkenColor(COLOR_TASK_BG, COLOR_TASK_DOWN); + + LightenColor(COLOR_TASK_ACTIVE_BG, COLOR_TASK_ACTIVE_UP); + DarkenColor(COLOR_TASK_ACTIVE_BG, COLOR_TASK_ACTIVE_DOWN); + + LightenColor(COLOR_MENU_BG, COLOR_MENU_UP); + DarkenColor(COLOR_MENU_BG, COLOR_MENU_DOWN); + + LightenColor(COLOR_MENU_ACTIVE_BG, COLOR_MENU_ACTIVE_UP); + DarkenColor(COLOR_MENU_ACTIVE_BG, COLOR_MENU_ACTIVE_DOWN); + +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownColors() { + +#ifdef USE_XFT + + int x; + + for(x = 0; x < COLOR_COUNT; x++) { + if(xftColors[x]) { + JXftColorFree(display, rootVisual, rootColormap, xftColors[x]); + Release(xftColors[x]); + xftColors[x] = NULL; + } + } + +#endif + + if(map != NULL) { + JXFreeColors(display, rootColormap, map, 256, 0); + Release(map); + map = NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyColors() { + + int x; + + if(names) { + for(x = 0; x < COLOR_COUNT; x++) { + if(names[x]) { + Release(names[x]); + } + } + Release(names); + names = NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ComputeShiftMask(unsigned long maskIn, + unsigned long *shiftOut, unsigned long *maskOut) { + + int shift; + + Assert(shiftOut); + Assert(maskOut); + + /* Components are stored in 16 bits. + * When computing pixels, we'll first shift left 16 bits + * so to the shift will be an offset from that 32 bit entity. + * shift = 16 - <shift-to-ones> + <shift-to-zeros> + */ + + shift = 0; + *maskOut = maskIn; + while(maskIn && (maskIn & (1 << 31)) == 0) { + ++shift; + maskIn <<= 1; + } + *shiftOut = shift; + +} + +/**************************************************************************** + ****************************************************************************/ +unsigned long GetRGBFromXColor(const XColor *c) { + + float red, green, blue; + unsigned long rgb; + + Assert(c); + + red = (float)c->red / 65535.0; + green = (float)c->green / 65535.0; + blue = (float)c->blue / 65535.0; + + rgb = (unsigned long)(red * 255.0) << 16; + rgb |= (unsigned long)(green * 255.0) << 8; + rgb |= (unsigned long)(blue * 255.0); + + return rgb; + +} + +/**************************************************************************** + ****************************************************************************/ +XColor GetXColorFromRGB(unsigned long rgb) { + + XColor ret = { 0 }; + + ret.flags = DoRed | DoGreen | DoBlue; + ret.red = (unsigned short)(((rgb >> 16) & 0xFF) * 257); + ret.green = (unsigned short)(((rgb >> 8) & 0xFF) * 257); + ret.blue = (unsigned short)((rgb & 0xFF) * 257); + + return ret; + +} + +/**************************************************************************** + ****************************************************************************/ +void SetColor(ColorType c, const char *value) { + + if(!value) { + Warning("empty color tag"); + return; + } + + InitializeNames(); + + if(names[c]) { + Release(names[c]); + } + + names[c] = CopyString(value); + +} + +/**************************************************************************** + ****************************************************************************/ +int ParseColor(ColorType type, const char *value) { + + XColor temp; + unsigned long rgb; + + if(!value) { + return 0; + } + + if(value[0] == '#' && strlen(value) == 7) { + rgb = ReadHex(value + 1); + temp.red = ((rgb >> 16) & 0xFF) * 257; + temp.green = ((rgb >> 8) & 0xFF) * 257; + temp.blue = (rgb & 0xFF) * 257; + temp.flags = DoRed | DoGreen | DoBlue; + GetColor(&temp); + } else { + if(!GetColorByName(value, &temp)) { + Warning("bad color: \"%s\"", value); + return 0; + } + } + colors[type] = temp.pixel; + rgbColors[type] = GetRGBFromXColor(&temp); + + return 1; + +} + +/**************************************************************************** + ****************************************************************************/ +void SetDefaultColor(ColorType type) { + + int x; + + for(x = 0; DEFAULT_COLORS[x].value; x++) { + if(DEFAULT_COLORS[x].type == type) { + ParseColor(type, DEFAULT_COLORS[x].value); + return; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void InitializeNames() { + + int x; + + if(names == NULL) { + names = Allocate(sizeof(char*) * COLOR_COUNT); + for(x = 0; x < COLOR_COUNT; x++) { + names[x] = NULL; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +unsigned long ReadHex(const char *hex) { + + unsigned long value = 0; + int x; + + Assert(hex); + + for(x = 0; hex[x]; x++) { + value *= 16; + if(hex[x] >= '0' && hex[x] <= '9') { + value += hex[x] - '0'; + } else if(hex[x] >= 'A' && hex[x] <= 'F') { + value += hex[x] - 'A' + 10; + } else if(hex[x] >= 'a' && hex[x] <= 'f') { + value += hex[x] - 'a' + 10; + } + } + + return value; + +} + +/**************************************************************************** + ****************************************************************************/ +void LightenColor(ColorType oldColor, ColorType newColor) { + + XColor temp; + float red, green, blue; + float delta = 1.0 + COLOR_DELTA; + + temp = GetXColorFromRGB(rgbColors[oldColor]); + + red = (float)temp.red / 65535.0; + green = (float)temp.green / 65535.0; + blue = (float)temp.blue / 65535.0; + + red = Min(delta * red, 1.0); + green = Min(delta * green, 1.0); + blue = Min(delta * blue, 1.0); + + temp.red = red * 65535.0; + temp.green = green * 65535.0; + temp.blue = blue * 65535.0; + + GetColor(&temp); + colors[newColor] = temp.pixel; + rgbColors[newColor] = GetRGBFromXColor(&temp); + +} + +/**************************************************************************** + ****************************************************************************/ +void DarkenColor(ColorType oldColor, ColorType newColor) { + + XColor temp; + float red, green, blue; + float delta = 1.0 - COLOR_DELTA; + + temp = GetXColorFromRGB(rgbColors[oldColor]); + + red = (float)temp.red / 65535.0; + green = (float)temp.green / 65535.0; + blue = (float)temp.blue / 65535.0; + + red = delta * red; + green = delta * green; + blue = delta * blue; + + temp.red = red * 65535.0; + temp.green = green * 65535.0; + temp.blue = blue * 65535.0; + + GetColor(&temp); + colors[newColor] = temp.pixel; + rgbColors[newColor] = GetRGBFromXColor(&temp); + +} + +/*************************************************************************** + ***************************************************************************/ +int GetColorByName(const char *str, XColor *c) { + + Assert(str); + Assert(c); + + if(!JXParseColor(display, rootColormap, str, c)) { + return 0; + } + + GetColor(c); + + return 1; + +} + +/*************************************************************************** + * Compute the RGB components from an index into our RGB colormap. + ***************************************************************************/ +void GetColorFromIndex(XColor *c) { + + unsigned long red; + unsigned long green; + unsigned long blue; + + Assert(c); + + red = (c->pixel & redMask) << redShift; + green = (c->pixel & greenMask) << greenShift; + blue = (c->pixel & blueMask) << blueShift; + + c->red = red >> 16; + c->green = green >> 16; + c->blue = blue >> 16; + +} + +/*************************************************************************** + * Compute the pixel value from RGB components. + ***************************************************************************/ +void GetDirectPixel(XColor *c) { + + unsigned long red; + unsigned long green; + unsigned long blue; + + Assert(c); + + /* Normalize. */ + red = c->red << 16; + green = c->green << 16; + blue = c->blue << 16; + + /* Shift to the correct offsets and mask. */ + red = (red >> redShift) & redMask; + green = (green >> greenShift) & greenMask; + blue = (blue >> blueShift) & blueMask; + + /* Combine. */ + c->pixel = red | green | blue; + +} + +/*************************************************************************** + * Compute the pixel value from RGB components. + ***************************************************************************/ +void GetMappedPixel(XColor *c) { + + Assert(c); + + GetDirectPixel(c); + c->pixel = map[c->pixel]; + +} + +/*************************************************************************** + * Compute the pixel value from RGB components. + ***************************************************************************/ +void GetColor(XColor *c) { + + Assert(c); + Assert(rootVisual); + + switch(rootVisual->class) { + case DirectColor: + case TrueColor: + GetDirectPixel(c); + return; + default: + GetMappedPixel(c); + return; + } + +} + +/*************************************************************************** + * When loading images from external sources, we need to know the color + * components even if running with a colormap. So here we pretend + * we have a linear RGB colormap even if we don't. + * This prevents calls to XQueryColor later. + ***************************************************************************/ +void GetColorIndex(XColor *c) { + + Assert(c); + + GetDirectPixel(c); + +} + +/**************************************************************************** + ****************************************************************************/ +#ifdef USE_XFT +XftColor *GetXftColor(ColorType type) { + + unsigned long rgb; + XRenderColor rcolor; + + if(!xftColors[type]) { + rgb = rgbColors[type]; + xftColors[type] = Allocate(sizeof(XftColor)); + rcolor.alpha = 65535; + rcolor.red = ((rgb >> 16) & 0xFF) * 257; + rcolor.green = ((rgb >> 8) & 0xFF) * 257; + rcolor.blue = (rgb & 0xFF) * 257; + JXftColorAllocValue(display, rootVisual, rootColormap, &rcolor, + xftColors[type]); + } + + return xftColors[type]; + +} +#endif + diff --git a/src/color.h b/src/color.h new file mode 100644 index 0000000..6e9ba60 --- /dev/null +++ b/src/color.h @@ -0,0 +1,91 @@ +/** + * @file color.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the color functions. + * + */ + +#ifndef COLOR_H +#define COLOR_H + +typedef enum { + + COLOR_BORDER_BG, + COLOR_BORDER_FG, + COLOR_BORDER_ACTIVE_BG, + COLOR_BORDER_ACTIVE_FG, + + COLOR_TRAY_BG, + COLOR_TRAY_FG, + + COLOR_TASK_BG, + COLOR_TASK_FG, + COLOR_TASK_ACTIVE_BG, + COLOR_TASK_ACTIVE_FG, + + COLOR_PAGER_BG, + COLOR_PAGER_FG, + COLOR_PAGER_ACTIVE_BG, + COLOR_PAGER_ACTIVE_FG, + COLOR_PAGER_OUTLINE, + + COLOR_MENU_BG, + COLOR_MENU_FG, + COLOR_MENU_ACTIVE_BG, + COLOR_MENU_ACTIVE_FG, + + COLOR_BORDER_UP, + COLOR_BORDER_DOWN, + COLOR_BORDER_ACTIVE_UP, + COLOR_BORDER_ACTIVE_DOWN, + + COLOR_TRAY_UP, + COLOR_TRAY_DOWN, + + COLOR_TASK_UP, + COLOR_TASK_DOWN, + COLOR_TASK_ACTIVE_UP, + COLOR_TASK_ACTIVE_DOWN, + + COLOR_MENU_UP, + COLOR_MENU_DOWN, + COLOR_MENU_ACTIVE_UP, + COLOR_MENU_ACTIVE_DOWN, + + COLOR_POPUP_BG, + COLOR_POPUP_FG, + COLOR_POPUP_OUTLINE, + + COLOR_TRAYBUTTON_BG, + COLOR_TRAYBUTTON_FG, + + COLOR_CLOCK_BG, + COLOR_CLOCK_FG, + + COLOR_COUNT + +} ColorType; + +extern unsigned long colors[COLOR_COUNT]; + +/*@{*/ +void InitializeColors(); +void StartupColors(); +void ShutdownColors(); +void DestroyColors(); +/*@}*/ + +void SetColor(ColorType c, const char *value); + +void GetColor(XColor *c); +void GetColorIndex(XColor *c); +void GetColorFromIndex(XColor *c); + +#ifdef USE_XFT +XftColor *GetXftColor(ColorType type); +#endif + +#endif + diff --git a/src/command.c b/src/command.c new file mode 100644 index 0000000..b82498d --- /dev/null +++ b/src/command.c @@ -0,0 +1,124 @@ +/** + * @file command.c + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Handle running startup, shutdown, and restart commands. + * + */ + +#include "jwm.h" +#include "command.h" +#include "root.h" +#include "misc.h" +#include "main.h" + +/** Structure to represent a list of commands. */ +typedef struct CommandNode { + char *command; /**< The command. */ + struct CommandNode *next; /**< The next command in the list. */ +} CommandNode; + +static CommandNode *startupCommands; +static CommandNode *shutdownCommands; +static CommandNode *restartCommands; + +static void RunCommands(CommandNode *commands); +static void ReleaseCommands(CommandNode **commands); +static void AddCommand(CommandNode **commands, const char *command); + +/** Initialize the command lists. */ +void InitializeCommands() { + startupCommands = NULL; + shutdownCommands = NULL; + restartCommands = NULL; +} + +/** Process startup/restart commands. */ +void StartupCommands() { + + if(isRestarting) { + RunCommands(restartCommands); + } else { + RunCommands(startupCommands); + } + +} + +/** Process shutdown commands. */ +void ShutdownCommands() { + + if(!shouldRestart) { + RunCommands(shutdownCommands); + } + +} + +/** Destroy the command lists. */ +void DestroyCommands() { + ReleaseCommands(&startupCommands); + ReleaseCommands(&shutdownCommands); + ReleaseCommands(&restartCommands); +} + +/** Run the commands in a command list. */ +void RunCommands(CommandNode *commands) { + + CommandNode *cp; + + for(cp = commands; cp; cp = cp->next) { + RunCommand(cp->command); + } + +} + +/** Release a command list. */ +void ReleaseCommands(CommandNode **commands) { + + CommandNode *cp; + + Assert(commands); + + while(*commands) { + cp = (*commands)->next; + Release((*commands)->command); + Release(*commands); + *commands = cp; + } + +} + +/** Add a command to a command list. */ +void AddCommand(CommandNode **commands, const char *command) { + + CommandNode *cp; + + Assert(commands); + + if(!command) { + return; + } + + cp = Allocate(sizeof(CommandNode)); + cp->next = *commands; + *commands = cp; + + cp->command = CopyString(command); + +} + +/** Add a startup command. */ +void AddStartupCommand(const char *command) { + AddCommand(&startupCommands, command); +} + +/** Add a shutdown command. */ +void AddShutdownCommand(const char *command) { + AddCommand(&shutdownCommands, command); +} + +/** Add a restart command. */ +void AddRestartCommand(const char *command) { + AddCommand(&restartCommands, command); +} + diff --git a/src/command.h b/src/command.h new file mode 100644 index 0000000..7c05e05 --- /dev/null +++ b/src/command.h @@ -0,0 +1,36 @@ +/** + * @file command.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Handle running startup, shutdown, and restart commands. + * + */ + +#ifndef COMMAND_H +#define COMMAND_H + +/*@{*/ +void InitializeCommands(); +void StartupCommands(); +void ShutdownCommands(); +void DestroyCommands(); +/*@}*/ + +/** Add a command to be executed at startup. + * @param command The command to execute. + */ +void AddStartupCommand(const char *command); + +/** Add a command to be executed at shutdown. + * @param command The command to execute. + */ +void AddShutdownCommand(const char *command); + +/** Add a command to be executed after a restart. + * @param command The command to execute. + */ +void AddRestartCommand(const char *command); + +#endif + diff --git a/src/confirm.c b/src/confirm.c new file mode 100644 index 0000000..77aa48e --- /dev/null +++ b/src/confirm.c @@ -0,0 +1,412 @@ +/** + * @file confirm.c + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Confirm dialog functions. + * + */ + +#include "jwm.h" +#include "confirm.h" +#include "client.h" +#include "main.h" +#include "font.h" +#include "button.h" +#include "screen.h" +#include "color.h" +#include "misc.h" + +#ifndef DISABLE_CONFIRM + +typedef struct DialogType { + + int x, y; + int width, height; + int lineHeight; + + int okx; + int cancelx; + int buttony; + int buttonWidth, buttonHeight; + + int lineCount; + char **message; + + ClientNode *node; + + void (*action)(ClientNode*); + ClientNode *client; + + struct DialogType *prev; + struct DialogType *next; + +} DialogType; + +static const char *OK_STRING = "Ok"; +static const char *CANCEL_STRING = "Cancel"; + +static DialogType *dialogList = NULL; + +static int minWidth = 0; + +static void DrawConfirmDialog(DialogType *d); +static void DestroyConfirmDialog(DialogType *d); +static void ComputeDimensions(DialogType *d); +static void DrawMessage(DialogType *d); +static void DrawButtons(DialogType *d); +static DialogType *FindDialogByWindow(Window w); +static int HandleDialogExpose(const XExposeEvent *event); +static int HandleDialogButtonRelease(const XButtonEvent *event); + +/** Initialize the dialog processing data. */ +void InitializeDialogs() { +} + +/** Startup dialog processing. */ +void StartupDialogs() { +} + +/** Stop dialog processing. */ +void ShutdownDialogs() { + + while(dialogList) { + DestroyConfirmDialog(dialogList); + } + +} + +/** Destroy dialog processing data. */ +void DestroyDialogs() { +} + +/** Handle an event on a dialog window. */ +int ProcessDialogEvent(const XEvent *event) { + + int handled = 0; + + Assert(event); + + switch(event->type) { + case Expose: + return HandleDialogExpose(&event->xexpose); + case ButtonRelease: + return HandleDialogButtonRelease(&event->xbutton); + default: + break; + } + + return handled; + +} + +/** Handle an expose event. */ +int HandleDialogExpose(const XExposeEvent *event) { + + DialogType *dp; + + Assert(event); + + dp = FindDialogByWindow(event->window); + if(dp) { + DrawConfirmDialog(dp); + return 1; + } else { + return 0; + } +} + +/** Handle a mouse button release event. */ +int HandleDialogButtonRelease(const XButtonEvent *event) { + + DialogType *dp; + int x, y; + int cancelPressed, okPressed; + + Assert(event); + + dp = FindDialogByWindow(event->window); + if(dp) { + cancelPressed = 0; + okPressed = 0; + y = event->y; + if(y >= dp->buttony && y < dp->buttony + dp->buttonHeight) { + x = event->x; + if(x >= dp->okx && x < dp->okx + dp->buttonWidth) { + okPressed = 1; + } else if(x >= dp->cancelx && x < dp->cancelx + dp->buttonWidth) { + cancelPressed = 1; + } + } + + if(okPressed) { + (dp->action)(dp->client); + } + + if(cancelPressed || okPressed) { + DestroyConfirmDialog(dp); + } + + return 1; + } else { + return 0; + } + +} + +/** Find a dialog by window or frame. */ +DialogType *FindDialogByWindow(Window w) { + + DialogType *dp; + + for(dp = dialogList; dp; dp = dp->next) { + if(dp->node->window == w || dp->node->parent == w) { + return dp; + } + } + + return NULL; + +} + +/** Show a confirm dialog. */ +void ShowConfirmDialog(ClientNode *np, void (*action)(ClientNode*), ...) { + + va_list ap; + DialogType *dp; + XSetWindowAttributes attrs; + XSizeHints shints; + Window window; + char *str; + int x; + + Assert(action); + + dp = Allocate(sizeof(DialogType)); + dp->client = np; + dp->action = action; + + dp->prev = NULL; + dp->next = dialogList; + if(dialogList) { + dialogList->prev = dp; + } + dialogList = dp; + + /* Get the number of lines. */ + va_start(ap, action); + for(dp->lineCount = 0; va_arg(ap, char*); dp->lineCount++); + va_end(ap); + + dp->message = Allocate(dp->lineCount * sizeof(char*)); + va_start(ap, action); + for(x = 0; x < dp->lineCount; x++) { + str = va_arg(ap, char*); + dp->message[x] = CopyString(str); + } + va_end(ap); + + ComputeDimensions(dp); + + attrs.background_pixel = colors[COLOR_MENU_BG]; + attrs.event_mask = ButtonReleaseMask | ExposureMask; + + window = JXCreateWindow(display, rootWindow, + dp->x, dp->y, dp->width, dp->height, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWBackPixel | CWEventMask, &attrs); + + shints.x = dp->x; + shints.y = dp->y; + shints.flags = PPosition; + JXSetWMNormalHints(display, window, &shints); + + JXStoreName(display, window, "Confirm"); + + dp->node = AddClientWindow(window, 0, 0); + Assert(dp->node); + if(np) { + dp->node->owner = np->window; + } + dp->node->state.status |= STAT_WMDIALOG; + FocusClient(dp->node); + + DrawConfirmDialog(dp); + + JXGrabButton(display, AnyButton, AnyModifier, window, + True, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None); + +} + +/** Draw a confirm dialog. */ +void DrawConfirmDialog(DialogType *dp) { + + Assert(dp); + + DrawMessage(dp); + DrawButtons(dp); + +} + +/** Destroy a confirm dialog. */ +void DestroyConfirmDialog(DialogType *dp) { + + int x; + + Assert(dp); + + /* This will take care of destroying the dialog window since + * its parent will be destroyed. */ + RemoveClient(dp->node); + + for(x = 0; x < dp->lineCount; x++) { + Release(dp->message[x]); + } + Release(dp->message); + + if(dp->next) { + dp->next->prev = dp->prev; + } + if(dp->prev) { + dp->prev->next = dp->next; + } else { + dialogList = dp->next; + } + Release(dp); + +} + +/** Compute the size of a dialog window. */ +void ComputeDimensions(DialogType *dp) { + + const ScreenType *sp; + int width; + int x; + + Assert(dp); + + if(!minWidth) { + minWidth = GetStringWidth(FONT_MENU, CANCEL_STRING) * 3; + width = GetStringWidth(FONT_MENU, OK_STRING) * 3; + if(width > minWidth) { + minWidth = width; + } + minWidth += 30; + } + dp->width = minWidth; + + for(x = 0; x < dp->lineCount; x++) { + width = GetStringWidth(FONT_MENU, dp->message[x]); + if(width > dp->width) { + dp->width = width; + } + } + dp->lineHeight = GetStringHeight(FONT_MENU); + dp->width += 8; + dp->height = (dp->lineCount + 2) * dp->lineHeight; + + if(dp->client) { + + dp->x = dp->client->x + dp->client->width / 2 - dp->width / 2; + dp->y = dp->client->y + dp->client->height / 2 - dp->height / 2; + + if(dp->x < 0) { + dp->x = 0; + } + if(dp->y < 0) { + dp->y = 0; + } + if(dp->x + dp->width >= rootWidth) { + dp->x = rootWidth - dp->width - (borderWidth * 2); + } + if(dp->y + dp->height >= rootHeight) { + dp->y = rootHeight - dp->height - (borderWidth * 2 + titleHeight); + } + + } else { + + sp = GetMouseScreen(); + + dp->x = sp->width / 2 - dp->width / 2 + sp->x; + dp->y = sp->height / 2 - dp->height / 2 + sp->y; + + } + +} + +/** Display the message on the dialog window. */ +void DrawMessage(DialogType *dp) { + + int yoffset; + int x; + + Assert(dp); + + yoffset = 4; + for(x = 0; x < dp->lineCount; x++) { + RenderString(dp->node->window, FONT_MENU, COLOR_MENU_FG, + 4, yoffset, dp->width, NULL, dp->message[x]); + yoffset += dp->lineHeight; + } + +} + +/** Draw the buttons on the dialog window. */ +void DrawButtons(DialogType *dp) { + + ButtonNode button; + int temp; + + Assert(dp); + + dp->buttonWidth = GetStringWidth(FONT_MENU, CANCEL_STRING); + temp = GetStringWidth(FONT_MENU, OK_STRING); + if(temp > dp->buttonWidth) { + dp->buttonWidth = temp; + } + dp->buttonWidth += 8; + dp->buttonHeight = dp->lineHeight + 4; + + ResetButton(&button, dp->node->window, rootGC); + button.font = FONT_MENU; + button.width = dp->buttonWidth; + button.height = dp->buttonHeight; + button.alignment = ALIGN_CENTER; + + dp->okx = dp->width / 3 - dp->buttonWidth / 2; + dp->cancelx = 2 * dp->width / 3 - dp->buttonWidth / 2; + dp->buttony = dp->height - dp->lineHeight - dp->lineHeight / 2; + + button.type = BUTTON_MENU; + button.text = OK_STRING; + button.x = dp->okx; + button.y = dp->buttony; + DrawButton(&button); + + button.text = CANCEL_STRING; + button.x = dp->cancelx; + button.y = dp->buttony; + DrawButton(&button); + +} + +#else /* DISABLE_CONFIRM */ + +/** Process an event on a dialog window. */ +int ProcessDialogEvent(const XEvent *event) { + return 0; +} + +/** Show a confirm dialog. */ +void ShowConfirmDialog(ClientNode *np, void (*action)(ClientNode*), ...) { + + Assert(action); + + (action)(np); + +} + +#endif /* DISABLE_CONFIRM */ + + + diff --git a/src/confirm.h b/src/confirm.h new file mode 100644 index 0000000..21161f2 --- /dev/null +++ b/src/confirm.h @@ -0,0 +1,36 @@ +/** + * @file confirm.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the confirm dialog functions. + * + */ + +#ifndef CONFIRM_H +#define CONFIRM_H + +struct ClientNode; + +/*@{*/ +void InitializeDialogs(); +void StartupDialogs(); +void ShutdownDialogs(); +void DestroyDialogs(); +/*@}*/ + +/** Handle an event on a dialog window. + * @param event The event. + * @return 1 if handled, 0 if not handled. + */ +int ProcessDialogEvent(const XEvent *event); + +/** Show a confirm dialog. + * @param np A client window associated with the dialog. + * @param action A callback to run if "OK" is clicked. + */ +void ShowConfirmDialog(struct ClientNode *np, + void (*action)(struct ClientNode*), ...); + +#endif + diff --git a/src/cursor.c b/src/cursor.c new file mode 100644 index 0000000..b4a6ca7 --- /dev/null +++ b/src/cursor.c @@ -0,0 +1,313 @@ +/**************************************************************************** + * Functions to handle the mouse cursor. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "cursor.h" +#include "main.h" +#include "error.h" + +static Cursor defaultCursor; +static Cursor moveCursor; +static Cursor northCursor; +static Cursor southCursor; +static Cursor eastCursor; +static Cursor westCursor; +static Cursor northEastCursor; +static Cursor northWestCursor; +static Cursor southEastCursor; +static Cursor southWestCursor; +static Cursor chooseCursor; + +static Cursor GetResizeCursor(BorderActionType action); +static Cursor CreateCursor(unsigned int shape); + +static int mousex; +static int mousey; + +/**************************************************************************** + ****************************************************************************/ +void InitializeCursors() { +} + +/**************************************************************************** + ****************************************************************************/ +void StartupCursors() { + + Window win1, win2; + int winx, winy; + unsigned int mask; + + defaultCursor = CreateCursor(XC_left_ptr); + moveCursor = CreateCursor(XC_fleur); + northCursor = CreateCursor(XC_top_side); + southCursor = CreateCursor(XC_bottom_side); + eastCursor = CreateCursor(XC_right_side); + westCursor = CreateCursor(XC_left_side); + northEastCursor = CreateCursor(XC_ur_angle); + northWestCursor = CreateCursor(XC_ul_angle); + southEastCursor = CreateCursor(XC_lr_angle); + southWestCursor = CreateCursor(XC_ll_angle); + chooseCursor = CreateCursor(XC_tcross); + + JXQueryPointer(display, rootWindow, &win1, &win2, + &mousex, &mousey, &winx, &winy, &mask); + +} + +/**************************************************************************** + ****************************************************************************/ +Cursor CreateCursor(unsigned int shape) { + return JXCreateFontCursor(display, shape); +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownCursors() { + + JXFreeCursor(display, defaultCursor); + JXFreeCursor(display, moveCursor); + JXFreeCursor(display, northCursor); + JXFreeCursor(display, southCursor); + JXFreeCursor(display, eastCursor); + JXFreeCursor(display, westCursor); + JXFreeCursor(display, northEastCursor); + JXFreeCursor(display, northWestCursor); + JXFreeCursor(display, southEastCursor); + JXFreeCursor(display, southWestCursor); + JXFreeCursor(display, chooseCursor); + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyCursors() { +} + +/**************************************************************************** + ****************************************************************************/ +Cursor GetFrameCursor(BorderActionType action) { + + switch(action & 0x0F) { + case BA_RESIZE: + return GetResizeCursor(action); + case BA_CLOSE: + break; + case BA_MAXIMIZE: + break; + case BA_MINIMIZE: + break; + case BA_MOVE: + break; + default: + break; + } + return defaultCursor; + +} + +/**************************************************************************** + ****************************************************************************/ +Cursor GetResizeCursor(BorderActionType action) { + + if(action & BA_RESIZE_N) { + if(action & BA_RESIZE_E) { + return northEastCursor; + } else if(action & BA_RESIZE_W) { + return northWestCursor; + } else { + return northCursor; + } + } else if(action & BA_RESIZE_S) { + if(action & BA_RESIZE_E) { + return southEastCursor; + } else if(action & BA_RESIZE_W) { + return southWestCursor; + } else { + return southCursor; + } + } else { + if(action & BA_RESIZE_E) { + return eastCursor; + } else { + return westCursor; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +int GrabMouseForResize(BorderActionType action) { + + Cursor cur; + int result; + + cur = GetFrameCursor(action); + + result = JXGrabPointer(display, rootWindow, False, ButtonPressMask + | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, + GrabModeAsync, None, cur, CurrentTime); + + if(result == GrabSuccess) { + return 1; + } else { + return 0; + } + +} + +/**************************************************************************** + ****************************************************************************/ +int GrabMouseForMove() { + + int result; + + result = JXGrabPointer(display, rootWindow, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, moveCursor, CurrentTime); + + if(result == GrabSuccess) { + + return 1; + + } else { + + return 0; + + } + +} + +/**************************************************************************** + ****************************************************************************/ +int GrabMouseForMenu() { + + int result; + + result = JXGrabPointer(display, rootWindow, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, defaultCursor, CurrentTime); + + if(result == GrabSuccess) { + return 1; + } else { + return 0; + } + +} + +/**************************************************************************** + ****************************************************************************/ +int GrabMouseForChoose() { + + int result; + + result = JXGrabPointer(display, rootWindow, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, None, chooseCursor, CurrentTime); + + if(result == GrabSuccess) { + return 1; + } else { + return 0; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void SetDefaultCursor(Window w) { + + JXDefineCursor(display, w, defaultCursor); + +} + +/**************************************************************************** + ****************************************************************************/ +void MoveMouse(Window win, int x, int y) { + + Window win1, win2; + int winx, winy; + unsigned int mask; + + JXWarpPointer(display, None, win, 0, 0, 0, 0, x, y); + + JXQueryPointer(display, rootWindow, &win1, &win2, + &mousex, &mousey, &winx, &winy, &mask); + +} + +/**************************************************************************** + ****************************************************************************/ +void SetMousePosition(int x, int y) { + + mousex = x; + mousey = y; + +} + +/**************************************************************************** + ****************************************************************************/ +void GetMousePosition(int *x, int *y) { + + Assert(x); + Assert(y); + + *x = mousex; + *y = mousey; + +} + +/**************************************************************************** + ****************************************************************************/ +unsigned int GetMouseMask() { + + Window win1, win2; + int winx, winy; + unsigned int mask; + + JXQueryPointer(display, rootWindow, &win1, &win2, + &mousex, &mousey, &winx, &winy, &mask); + + return mask; + +} + +/**************************************************************************** + ****************************************************************************/ +void SetDoubleClickSpeed(const char *str) { + + int speed; + + if(str) { + speed = atoi(str); + if(speed < MIN_DOUBLE_CLICK_SPEED || speed > MAX_DOUBLE_CLICK_SPEED) { + Warning("invalid DoubleClickSpeed: %d", speed); + doubleClickSpeed = DEFAULT_DOUBLE_CLICK_SPEED; + } else { + doubleClickSpeed = speed; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void SetDoubleClickDelta(const char *str) { + + int delta; + + if(str) { + delta = atoi(str); + if(delta < MIN_DOUBLE_CLICK_DELTA || delta > MAX_DOUBLE_CLICK_DELTA) { + Warning("invalid DoubleClickDelta: %d", delta); + doubleClickDelta = DEFAULT_DOUBLE_CLICK_DELTA; + } else { + doubleClickDelta = delta; + } + } + +} + diff --git a/src/cursor.h b/src/cursor.h new file mode 100644 index 0000000..b8b6868 --- /dev/null +++ b/src/cursor.h @@ -0,0 +1,44 @@ +/** + * @file confirm.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the cursor functions. + * + */ + +#ifndef CURSOR_H +#define CURSOR_H + +#include "border.h" + +/*@{*/ +void InitializeCursors(); +void StartupCursors(); +void ShutdownCursors(); +void DestroyCursors(); +/*@}*/ + +int GrabMouseForResize(BorderActionType action); +int GrabMouseForMove(); + +int GrabMouseForMenu(); +int GrabMouseForChoose(); + +Cursor GetFrameCursor(BorderActionType action); + +void MoveMouse(Window win, int x, int y); + +void SetMousePosition(int x, int y); +void GetMousePosition(int *x, int *y); + +unsigned int GetMouseMask(); + +void SetDefaultCursor(Window w); + +void SetDoubleClickSpeed(const char *str); +void SetDoubleClickDelta(const char *str); + +#endif + + diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..6527abd --- /dev/null +++ b/src/debug.c @@ -0,0 +1,396 @@ +/*************************************************************************** + * Debug functions. + * Copyright (C) 2003 Joe Wingbermuehle + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ***************************************************************************/ + +#include "debug.h" + +/*************************************************************************** + * Emit a message. + ***************************************************************************/ +void Debug(const char *str, ...) { +#ifdef DEBUG + va_list ap; + va_start(ap, str); + + Assert(str); + + fprintf(stderr, "DEBUG: "); + vfprintf(stderr, str, ap); + fprintf(stderr, "\n"); + + va_end(ap); +#endif +} + +#ifdef DEBUG + +#define CHECKPOINT_LIST_SIZE 8 + +typedef struct MemoryType { + const char *file; + unsigned int line; + size_t size; + void *pointer; + struct MemoryType *next; +} MemoryType; + +static MemoryType *allocations = NULL; + +typedef struct ResourceType { + int resource; + const char *allocationFiles[CHECKPOINT_LIST_SIZE]; + unsigned int allocationLines[CHECKPOINT_LIST_SIZE]; + const char *releaseFiles[CHECKPOINT_LIST_SIZE]; + unsigned int releaseLines[CHECKPOINT_LIST_SIZE]; + unsigned int allocationOffset; + unsigned int releaseOffset; + size_t count; + struct ResourceType *next; +} ResourceType; + +static ResourceType *resources = NULL; + +static const char *checkpointFile[CHECKPOINT_LIST_SIZE]; +static unsigned int checkpointLine[CHECKPOINT_LIST_SIZE]; +static int checkpointOffset; + +static void DEBUG_PrintResourceStack(ResourceType *rp); + +/*************************************************************************** + * Start the debugger. + ***************************************************************************/ +void DEBUG_StartDebug(const char *file, unsigned int line) { + int x; + + Debug("%s[%u]: debug mode started", file, line); + + checkpointOffset = 0; + for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) { + checkpointFile[x] = NULL; + checkpointLine[x] = 0; + } + +} + +/*************************************************************************** + * Stop the debugger. + ***************************************************************************/ +void DEBUG_StopDebug(const char *file, unsigned int line) { + MemoryType *mp; + ResourceType *rp; + unsigned int count = 0; + + Debug("%s[%u]: debug mode stopped", file, line); + + if(allocations) { + Debug("MEMORY: memory leaks follow"); + for(mp = allocations; mp; mp = mp->next) { + Debug(" %u bytes in %s at line %u", + mp->size, mp->file, mp->line); + ++count; + } + if(count == 1) { + Debug("MEMORY: 1 memory leak"); + } else { + Debug("MEMORY: %u memory leaks", count); + } + } else { + Debug("MEMORY: no memory leaks"); + } + + if(resources) { + for(rp = resources; rp; rp = rp->next) { + if(rp->count > 0) { + Debug("RESOURCE: resource %d has reference count %u", + rp->resource, rp->count); + DEBUG_PrintResourceStack(rp); + } + } + } + +} + +/*************************************************************************** + * Print the resource allocation/release stacks for a resource. + ***************************************************************************/ +void DEBUG_PrintResourceStack(ResourceType *rp) { + unsigned int x, offset; + + Debug(" Allocation stack: (oldest)"); + offset = rp->allocationOffset; + for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) { + if(rp->allocationFiles[offset]) { + Debug(" %s line %u", rp->allocationFiles[offset], + rp->allocationLines[offset]); + } + offset = (offset + 1) % CHECKPOINT_LIST_SIZE; + } + Debug(" Release stack: (oldest)"); + offset = rp->releaseOffset; + for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) { + if(rp->releaseFiles[offset]) { + Debug(" %s line %u", rp->releaseFiles[offset], + rp->releaseLines[offset]); + } + offset = (offset + 1) % CHECKPOINT_LIST_SIZE; + } +} + +/*************************************************************************** + * Set a checkpoint. + ***************************************************************************/ +void DEBUG_SetCheckpoint(const char *file, unsigned int line) { + + checkpointFile[checkpointOffset] = file; + checkpointLine[checkpointOffset] = line; + + checkpointOffset = (checkpointOffset + 1) % CHECKPOINT_LIST_SIZE; + +} + +/*************************************************************************** + * Display the location of the last checkpoint. + ***************************************************************************/ +void DEBUG_ShowCheckpoint() { + int x, offset; + + Debug("CHECKPOINT LIST (oldest)"); + offset = checkpointOffset; + for(x = 0; x < CHECKPOINT_LIST_SIZE; x++) { + if(checkpointFile[offset]) { + Debug(" %s[%u]", checkpointFile[offset], checkpointLine[offset]); + } + offset = (offset + 1) % CHECKPOINT_LIST_SIZE; + } + Debug("END OF CHECKPOINT LIST (most recent)"); + +} + +/*************************************************************************** + * Allocate memory and log. + ***************************************************************************/ +void *DEBUG_Allocate(size_t size, const char *file, unsigned int line) { + MemoryType *mp; + + if(size <= 0) { + Debug("MEMORY: %s[%u]: Attempt to allocate %d bytes of memory", + file, line, size); + } + + mp = (MemoryType*)malloc(sizeof(MemoryType)); + Assert(mp); + + mp->file = file; + mp->line = line; + mp->size = size; + + mp->pointer = malloc(size + sizeof(char)); + if(!mp->pointer) { + Debug("MEMORY: %s[%u]: Memory allocation failed (%d bytes)", + file, line, size); + Assert(0); + } + + /* Make uninitialized accesses easy to find. */ + memset(mp->pointer, 85, size); + + /* Canary value for buffer overflow checking. */ + ((char*)mp->pointer)[size] = 42; + + mp->next = allocations; + allocations = mp; + + return mp->pointer; +} + +/*************************************************************************** + * Reallocate memory and log. + ***************************************************************************/ +void *DEBUG_Reallocate(void *ptr, size_t size, const char *file, + unsigned int line) { + + MemoryType *mp; + + if(size <= 0) { + Debug("MEMORY: %s[%u]: Attempt to reallocate %d bytes of memory", + file, line, size); + } + if(!ptr) { + Debug("MEMORY: %s[%u]: Attempt to reallocate NULL pointer. " + "Calling Allocate...", file, line); + return DEBUG_Allocate(size, file, line); + } else { + + for(mp = allocations; mp; mp = mp->next) { + if(mp->pointer == ptr) { + + if(((char*)ptr)[mp->size] != 42) { + Debug("MEMORY: %s[%u]: The canary is dead.", file, line); + } + + mp->file = file; + mp->line = line; + mp->size = size; + mp->pointer = realloc(ptr, size + sizeof(char)); + if(!mp->pointer) { + Debug("MEMORY: %s[%u]: Failed to reallocate %d bytes.", + file, line, size); + Assert(0); + } + ((char*)mp->pointer)[size] = 42; + return mp->pointer; + } + } + + Debug("MEMORY: %s[%u]: Attempt to reallocate unallocated pointer", + file, line); + mp = malloc(sizeof(MemoryType)); + Assert(mp); + mp->file = file; + mp->line = line; + mp->size = size; + mp->pointer = malloc(size + sizeof(char)); + if(!mp->pointer) { + Debug("MEMORY: %s[%u]: Failed to reallocate %d bytes.", + file, line, size); + Assert(0); + } + memset(mp->pointer, 85, size); + ((char*)mp->pointer)[size] = 42; + + mp->next = allocations; + allocations = mp; + + return mp->pointer; + } + +} + +/*************************************************************************** + * Release memory and log. + ***************************************************************************/ +void DEBUG_Release(void **ptr, const char *file, unsigned int line) { + MemoryType *mp, *last; + + if(!ptr) { + Debug("MEMORY: %s[%u]: Invalid attempt to release", file, line); + } else if(!*ptr) { + Debug("MEMORY: %s[%u]: Attempt to delete NULL pointer", + file, line); + } else { + last = NULL; + for(mp = allocations; mp; mp = mp->next) { + if(mp->pointer == *ptr) { + if(last) { + last->next = mp->next; + } else { + allocations = mp->next; + } + + if(((char*)*ptr)[mp->size] != 42) { + Debug("MEMORY: %s[%u]: The canary is dead.", file, line); + } + + memset(*ptr, 0xFF, mp->size); + free(mp); + free(*ptr); + *ptr = NULL; + return; + } + last = mp; + } + Debug("MEMORY: %s[%u]: Attempt to delete unallocated pointer", + file, line); + memset(*ptr, 0xFF, mp->size); + free(*ptr); + + /* This address should cause a segfault or bus error. */ + *ptr = (void*)1; + + } +} + +/*************************************************************************** + * Add a resource. + ***************************************************************************/ +void DEBUG_AllocateResource(int resource, const char *file, + unsigned int line) { + + ResourceType *rp; + + for(rp = resources; rp; rp = rp->next) { + if(rp->resource == resource) { + + rp->allocationFiles[rp->allocationOffset] = file; + rp->allocationLines[rp->allocationOffset] = line; + rp->allocationOffset + = (rp->allocationOffset + 1) % CHECKPOINT_LIST_SIZE; + + ++rp->count; + return; + } + } + + rp = malloc(sizeof(ResourceType)); + memset(rp, 0, sizeof(ResourceType)); + rp->resource = resource; + rp->allocationFiles[0] = file; + rp->allocationLines[0] = line; + rp->allocationOffset = 1; + rp->releaseOffset = 0; + rp->count = 1; + + rp->next = resources; + resources = rp; + +} + +/*************************************************************************** + * Remove a resource. + ***************************************************************************/ +void DEBUG_ReleaseResource(int resource, const char *file, + unsigned int line) { + + ResourceType *rp; + + for(rp = resources; rp; rp = rp->next) { + if(rp->resource == resource) { + rp->releaseFiles[rp->releaseOffset] = file; + rp->releaseLines[rp->releaseOffset] = line; + rp->releaseOffset = (rp->releaseOffset + 1) % CHECKPOINT_LIST_SIZE; + if(rp->count <= 0) { + Debug("RESOURCE: Multiple attempts to release resource %d", + resource); + DEBUG_PrintResourceStack(rp); + } else { + --rp->count; + } + return; + } + } + + Debug("RESOURCE: Attempt to release unallocated resource %d", + resource); + Debug(" in %s at line %u", file, line); + +} + +#undef CHECKPOINT_LIST_SIZE + +#endif + diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..c74f1a8 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,104 @@ +/** + * @file debug.h + * @author Joe Wingbermuehle + * @date 2003-2006 + * + * @brief Header for the debug functions. + * + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef DEBUG_H +#define DEBUG_H + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_ALLOCA_H +# include <alloca.h> +#endif + +void Debug(const char *str, ...); + +#ifdef HAVE_ALLOCA_H + +# define AllocateStack( x ) alloca( x ) +# define ReleaseStack( x ) ((void)0) + +#else + +# define AllocateStack( x ) Allocate( x ) +# define ReleaseStack( x ) Release( x ) + +#endif + +#ifdef DEBUG + +# define Assert( x ) \ + if(!( x )) { \ + Debug("ASSERT FAILED: %s[%u]", __FILE__, __LINE__ ); \ + abort(); \ + } + +# define SetCheckpoint() \ + DEBUG_SetCheckpoint( __FILE__, __LINE__ ) +# define ShowCheckpoint() \ + DEBUG_ShowCheckpoint() + +# define StartDebug() \ + DEBUG_StartDebug( __FILE__, __LINE__ ) +# define StopDebug() \ + DEBUG_StopDebug( __FILE__, __LINE__ ) + +# define Allocate( x ) \ + DEBUG_Allocate( (x), __FILE__, __LINE__ ) +# define Reallocate( x, y ) \ + DEBUG_Reallocate( (x), (y), __FILE__, __LINE__ ) +# define Release( x ) \ + DEBUG_Release( (void*)(& x), __FILE__, __LINE__ ) + + void DEBUG_SetCheckpoint(const char*, unsigned int); + void DEBUG_ShowCheckpoint(); + + void DEBUG_StartDebug(const char*, unsigned int); + void DEBUG_StopDebug(const char*, unsigned int); + + void *DEBUG_Allocate(size_t, const char*, unsigned int); + void *DEBUG_Reallocate(void*, size_t, const char*, unsigned int); + void DEBUG_Release(void**, const char*, unsigned int); + +#else + +# define Assert( x ) ((void)0) + +# define SetCheckpoint() ((void)0) +# define ShowCheckpoint() ((void)0) + +# define StartDebug() ((void)0) +# define StopDebug() ((void)0) + +# define Allocate( x ) malloc( (x) ) +# define Reallocate( x, y ) realloc( (x), (y) ) +# define Release( x ) free( (x) ) + +#endif + +#endif + diff --git a/src/desktop.c b/src/desktop.c new file mode 100644 index 0000000..1d09050 --- /dev/null +++ b/src/desktop.c @@ -0,0 +1,239 @@ +/*************************************************************************** + ***************************************************************************/ + +#include "jwm.h" +#include "desktop.h" +#include "main.h" +#include "client.h" +#include "hint.h" +#include "pager.h" +#include "taskbar.h" +#include "error.h" +#include "menu.h" +#include "misc.h" + +char **desktopNames = NULL; + +static int showingDesktop; + +/*************************************************************************** + ***************************************************************************/ +void InitializeDesktops() { +} + +/*************************************************************************** + ***************************************************************************/ +void StartupDesktops() { + + unsigned int x; + + if(desktopNames == NULL) { + desktopNames = Allocate(desktopCount * sizeof(char*)); + for(x = 0; x < desktopCount; x++) { + desktopNames[x] = NULL; + } + } + for(x = 0; x < desktopCount; x++) { + if(desktopNames[x] == NULL) { + desktopNames[x] = Allocate(4 * sizeof(char)); + snprintf(desktopNames[x], 4, "%d", x + 1); + } + } + + showingDesktop = 0; + +} + +/*************************************************************************** + ***************************************************************************/ +void ShutdownDesktops() { +} + +/*************************************************************************** + ***************************************************************************/ +void DestroyDesktops() { + + unsigned int x; + + if(desktopNames) { + for(x = 0; x < desktopCount; x++) { + Release(desktopNames[x]); + } + Release(desktopNames); + desktopNames = NULL; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void NextDesktop() { + ChangeDesktop((currentDesktop + 1) % desktopCount); +} + +/*************************************************************************** + ***************************************************************************/ +void PreviousDesktop() { + if(currentDesktop > 0) { + ChangeDesktop(currentDesktop - 1); + } else { + ChangeDesktop(desktopCount - 1); + } +} + +/*************************************************************************** + ***************************************************************************/ +void ChangeDesktop(unsigned int desktop) { + + ClientNode *np; + unsigned int x; + + if(desktop >= desktopCount) { + return; + } + + if(currentDesktop == desktop && !initializing) { + return; + } + + for(x = 0; x < LAYER_COUNT; x++) { + for(np = nodes[x]; np; np = np->next) { + if(np->state.status & STAT_STICKY) { + continue; + } + if(np->state.desktop == desktop) { + ShowClient(np); + } else if(np->state.desktop == currentDesktop) { + HideClient(np); + } + } + } + + currentDesktop = desktop; + + SetCardinalAtom(rootWindow, ATOM_NET_CURRENT_DESKTOP, currentDesktop); + SetCardinalAtom(rootWindow, ATOM_WIN_WORKSPACE, currentDesktop); + + RestackClients(); + + UpdatePager(); + UpdateTaskBar(); + +} + +/*************************************************************************** + ***************************************************************************/ +Menu *CreateDesktopMenu(unsigned int mask) { + + Menu *menu; + MenuItem *item; + int x; + + menu = Allocate(sizeof(Menu)); + menu->itemHeight = 0; + menu->items = NULL; + menu->label = NULL; + + for(x = desktopCount - 1; x >= 0; x--) { + + item = Allocate(sizeof(MenuItem)); + item->type = MENU_ITEM_NORMAL; + item->iconName = NULL; + item->submenu = NULL; + item->next = menu->items; + menu->items = item; + + item->action.type = MA_DESKTOP; + item->action.data.i = x; + + item->name = Allocate(strlen(desktopNames[x]) + 3); + if(mask & (1 << x)) { + strcpy(item->name, "["); + strcat(item->name, desktopNames[x]); + strcat(item->name, "]"); + } else { + strcpy(item->name, " "); + strcat(item->name, desktopNames[x]); + strcat(item->name, " "); + } + + } + + return menu; + +} + +/*************************************************************************** + ***************************************************************************/ +void ShowDesktop() { + + ClientNode *np; + int layer; + + for(layer = 0; layer < LAYER_COUNT; layer++) { + for(np = nodes[layer]; np; np = np->next) { + if(showingDesktop) { + if(np->state.status & STAT_SDESKTOP) { + RestoreClient(np, 0); + } + } else if(np->state.desktop == currentDesktop + || (np->state.status & STAT_STICKY)) { + if(np->state.status & (STAT_MAPPED | STAT_SHADED)) { + MinimizeClient(np); + np->state.status |= STAT_SDESKTOP; + } + } + } + } + + showingDesktop = !showingDesktop; + + RestackClients(); + +} + +/*************************************************************************** + ***************************************************************************/ +void SetDesktopCount(const char *str) { + + if(!str) { + Warning("invalid desktop count"); + return; + } + + desktopCount = atoi(str); + if(desktopCount <= 0 || desktopCount > MAX_DESKTOP_COUNT) { + Warning("invalid desktop count: \"%s\"", str); + desktopCount = DEFAULT_DESKTOP_COUNT; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetDesktopName(unsigned int desktop, const char *str) { + + unsigned int x; + + if(!str) { + Warning("empty Desktops Name tag"); + return; + } + + Assert(desktop >= 0); + Assert(desktop < desktopCount); + + if(!desktopNames) { + desktopNames = Allocate(desktopCount * sizeof(char*)); + for(x = 0; x < desktopCount; x++) { + desktopNames[x] = NULL; + } + } + + Assert(desktopNames[desktop] == NULL); + + desktopNames[desktop] = CopyString(str); + +} + + diff --git a/src/desktop.h b/src/desktop.h new file mode 100644 index 0000000..b8e3312 --- /dev/null +++ b/src/desktop.h @@ -0,0 +1,60 @@ +/** + * @file desktop.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the desktop management functions. + * + */ + +#ifndef DESKTOP_H +#define DESKTOP_H + +struct MenuType; + +extern char **desktopNames; + +/*@{*/ +void InitializeDesktops(); +void StartupDesktops(); +void ShutdownDesktops(); +void DestroyDesktops(); +/*@}*/ + +/** Switch to the next desktop. */ +void NextDesktop(); + +/** Switch to the previous desktop. */ +void PreviousDesktop(); + +/** Switch to a specific desktop. + * @param desktop The desktop to show (0 based). + */ +void ChangeDesktop(unsigned int desktop); + +/** Toggle the "show desktop" state. + * This will either minimize or restore all items on the current desktop. + */ +void ShowDesktop(); + +/** Create a menu containing a list of desktops. + * @param mask A bit mask of desktops to highlight. + * @return A menu containing all the desktops. + */ +struct Menu *CreateDesktopMenu(unsigned int mask); + +/** Set the number of desktops. + * This is called before startup. + * @param str ASCII representation of the number of desktops. + */ +void SetDesktopCount(const char *str); + +/** Set the name of a desktop. + * This is called before startup. + * @param desktop The desktop to name (0 based). + * @param str The name to assign. + */ +void SetDesktopName(unsigned int desktop, const char *str); + +#endif + diff --git a/src/dock.c b/src/dock.c new file mode 100644 index 0000000..289e85f --- /dev/null +++ b/src/dock.c @@ -0,0 +1,602 @@ +/** + * @file dock.c + * @author Joe Wingbermuehle + * @date 2006 + * + * @brief Dock functions. + * + */ + +#include "jwm.h" +#include "dock.h" +#include "tray.h" +#include "main.h" +#include "error.h" +#include "color.h" + +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +#define SYSTEM_TRAY_ORIENTATION_HORZ 0 +#define SYSTEM_TRAY_ORIENTATION_VERT 1 + +/** Structure to represent a docked window. */ +typedef struct DockNode { + + Window window; + int needs_reparent; + + struct DockNode *next; + +} DockNode; + +/** Structure to represent a dock tray component. */ +typedef struct DockType { + + TrayComponentType *cp; + + Window window; + + DockNode *nodes; + +} DockType; + +static const char *BASE_SELECTION_NAME = "_NET_SYSTEM_TRAY_S%d"; +static const char *ORIENTATION_ATOM = "_NET_SYSTEM_TRAY_ORIENTATION"; + +static DockType *dock = NULL; +static int owner = 0; +static Atom dockAtom; +static unsigned long orientation; + +static void SetSize(TrayComponentType *cp, int width, int height); +static void Create(TrayComponentType *cp); +static void Resize(TrayComponentType *cp); + +static void DockWindow(Window win); +static int UndockWindow(Window win); + +static void UpdateDock(); + +/** Initialize dock data. */ +void InitializeDock() { +} + +/** Startup the dock. */ +void StartupDock() { + + char *selectionName; + + if(!dock) { + /* No dock has been requested. */ + return; + } + + if(!dock->cp) { + /* The Dock item has been removed from the configuration. */ + JXDestroyWindow(display, dock->window); + Release(dock); + dock = NULL; + return; + } + + if(dock->window == None) { + + /* No dock yet. */ + + /* Get the selection atom. */ + selectionName = AllocateStack(strlen(BASE_SELECTION_NAME) + 1); + sprintf(selectionName, BASE_SELECTION_NAME, rootScreen); + dockAtom = JXInternAtom(display, selectionName, False); + ReleaseStack(selectionName); + + /* The location and size of the window doesn't matter here. */ + dock->window = JXCreateSimpleWindow(display, rootWindow, + /* x, y, width, height */ 0, 0, 1, 1, + /* border_size, border_color */ 0, 0, + /* background */ colors[COLOR_TRAY_BG]); + JXSelectInput(display, dock->window, + SubstructureNotifyMask + | SubstructureRedirectMask + | PointerMotionMask | PointerMotionHintMask); + + } + dock->cp->window = dock->window; + +} + +/** Shutdown the dock. */ +void ShutdownDock() { + + DockNode *np; + + if(dock) { + + if(shouldRestart) { + + /* If restarting we just reparent the dock window to the root + * window. We need to keep the dock around and visible so that + * we don't cause problems with the docked windows. + * It seems every application handles docking differently... + */ + JXReparentWindow(display, dock->window, rootWindow, 0, 0); + + } else { + + /* JWM is exiting. */ + + /* Release memory used by the dock list. */ + while(dock->nodes) { + np = dock->nodes->next; + JXReparentWindow(display, dock->nodes->window, rootWindow, 0, 0); + Release(dock->nodes); + dock->nodes = np; + } + + /* Release the selection. */ + if(owner) { + JXSetSelectionOwner(display, dockAtom, None, CurrentTime); + } + + /* Destroy the dock window. */ + JXDestroyWindow(display, dock->window); + + } + + } + +} + +/** Destroy dock data. */ +void DestroyDock() { + + if(dock) { + if(shouldRestart) { + dock->cp = NULL; + } else { + Release(dock); + dock = NULL; + } + } + +} + +/** Create a dock component. */ +TrayComponentType *CreateDock() { + + TrayComponentType *cp; + + if(dock != NULL && dock->cp != NULL) { + Warning("only one Dock allowed"); + return NULL; + } else if(dock == NULL) { + dock = Allocate(sizeof(DockType)); + dock->nodes = NULL; + dock->window = None; + } + + cp = CreateTrayComponent(); + cp->object = dock; + dock->cp = cp; + cp->requestedWidth = 1; + cp->requestedHeight = 1; + + cp->SetSize = SetSize; + cp->Create = Create; + cp->Resize = Resize; + + return cp; + +} + +/** Set the size of a dock component. */ +void SetSize(TrayComponentType *cp, int width, int height) { + + int count; + DockNode *np; + + Assert(cp); + Assert(dock); + + count = 0; + for(np = dock->nodes; np; np = np->next) { + ++count; + } + + if(width == 0) { + if(count > 0) { + cp->width = count * height; + cp->requestedWidth = cp->width; + } else { + cp->width = 1; + cp->requestedWidth = 1; + } + } else if(height == 0) { + if(count > 0) { + cp->height = count * width; + cp->requestedHeight = cp->height; + } else { + cp->height = 1; + cp->requestedHeight = 1; + } + } + +} + +/** Initialize a dock component. */ +void Create(TrayComponentType *cp) { + + XEvent event; + Atom orientationAtom; + + Assert(cp); + + /* Map the dock window. */ + if(cp->window != None) { + JXResizeWindow(display, cp->window, cp->width, cp->height); + JXMapRaised(display, cp->window); + } + + /* Set the orientation. */ + orientationAtom = JXInternAtom(display, ORIENTATION_ATOM, False); + if(cp->height == 1) { + orientation = SYSTEM_TRAY_ORIENTATION_VERT; + } else { + orientation = SYSTEM_TRAY_ORIENTATION_HORZ; + } + JXChangeProperty(display, dock->cp->window, orientationAtom, + XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&orientation, 1); + + /* Get the selection if we don't already own it. + * If we did already own it, getting it again would cause problems + * with some clients due to the way restarts are handled. + */ + if(!owner) { + + owner = 1; + JXSetSelectionOwner(display, dockAtom, dock->cp->window, CurrentTime); + if(JXGetSelectionOwner(display, dockAtom) != dock->cp->window) { + + owner = 0; + Warning("could not acquire system tray selection"); + + } else { + + memset(&event, 0, sizeof(event)); + event.xclient.type = ClientMessage; + event.xclient.window = rootWindow; + event.xclient.message_type = JXInternAtom(display, "MANAGER", False); + event.xclient.format = 32; + event.xclient.data.l[0] = CurrentTime; + event.xclient.data.l[1] = dockAtom; + event.xclient.data.l[2] = dock->cp->window; + event.xclient.data.l[3] = 0; + event.xclient.data.l[4] = 0; + + JXSendEvent(display, rootWindow, False, StructureNotifyMask, &event); + + } + + } + +} + +/** Resize a dock component. */ +void Resize(TrayComponentType *cp) { + + Assert(cp); + + JXResizeWindow(display, cp->window, cp->width, cp->height); + UpdateDock(); + +} + +/** Handle a dock event. */ +void HandleDockEvent(const XClientMessageEvent *event) { + + Assert(event); + + switch(event->data.l[1]) { + case SYSTEM_TRAY_REQUEST_DOCK: + DockWindow(event->data.l[2]); + break; + case SYSTEM_TRAY_BEGIN_MESSAGE: + break; + case SYSTEM_TRAY_CANCEL_MESSAGE: + break; + default: + Debug("invalid opcode in dock event"); + break; + } + +} + +/** Handle a resize request event. */ +int HandleDockResizeRequest(const XResizeRequestEvent *event) { + + DockNode *np; + + Assert(event); + + if(!dock) { + return 0; + } + + for(np = dock->nodes; np; np = np->next) { + if(np->window == event->window) { + + JXResizeWindow(display, np->window, event->width, event->height); + UpdateDock(); + + return 1; + } + } + + return 0; +} + +/** Handle a configure request event. */ +int HandleDockConfigureRequest(const XConfigureRequestEvent *event) { + + XWindowChanges wc; + DockNode *np; + + Assert(event); + + if(!dock) { + return 0; + } + + for(np = dock->nodes; np; np = np->next) { + if(np->window == event->window) { + wc.stack_mode = event->detail; + wc.sibling = event->above; + wc.border_width = event->border_width; + wc.x = event->x; + wc.y = event->y; + wc.width = event->width; + wc.height = event->height; + JXConfigureWindow(display, np->window, event->value_mask, &wc); + UpdateDock(); + return 1; + } + } + + return 0; + +} + +/** Handle a reparent notify event. */ +int HandleDockReparentNotify(const XReparentEvent *event) { + + DockNode *np; + int handled; + + Assert(event); + + /* Just return if there is no dock. */ + if(!dock) { + return 0; + } + + /* Check each docked window. */ + handled = 0; + for(np = dock->nodes; np; np = np->next) { + if(np->window == event->window) { + if(event->parent != dock->cp->window) { + /* For some reason the application reparented the window. + * We make note of this condition and reparent every time + * the dock is updated. Unfortunately we can't do this for + * all applications because some won't deal with it. + */ + np->needs_reparent = 1; + handled = 1; + } + } + } + + /* Layout the stuff on the dock again if something happened. */ + if(handled) { + UpdateDock(); + } + + return handled; + +} + +/** Handle a destroy event. */ +int HandleDockDestroy(Window win) { + + if(dock) { + return UndockWindow(win); + } else { + return 0; + } + +} + +/** Handle a selection clear event. */ +int HandleDockSelectionClear(const XSelectionClearEvent *event) { + + if(event->selection == dockAtom) { + Debug("lost _NET_SYSTEM_TRAY selection"); + owner = 0; + } + + return 0; + +} + +/** Add a window to the dock. */ +void DockWindow(Window win) { + + DockNode *np; + + Assert(dock); + + /* Make sure we have a valid window to add. */ + if(win == None) { + return; + } + + /* If this window is already docked ignore it. */ + for(np = dock->nodes; np; np = np->next) { + if(np->window == win) { + return; + } + } + + /* Add the window to our list. */ + np = Allocate(sizeof(DockNode)); + np->window = win; + np->needs_reparent = 0; + np->next = dock->nodes; + dock->nodes = np; + + /* Update the requested size. */ + if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { + if(dock->cp->requestedWidth > 1) { + dock->cp->requestedWidth += dock->cp->height; + } else { + dock->cp->requestedWidth = dock->cp->height; + } + } else { + if(dock->cp->requestedHeight > 1) { + dock->cp->requestedHeight += dock->cp->width; + } else { + dock->cp->requestedHeight = dock->cp->width; + } + } + + /* It's safe to reparent at (0, 0) since we call + * ResizeTray which will invoke the Resize callback. + */ + JXAddToSaveSet(display, win); + JXSelectInput(display, win, + StructureNotifyMask + | ResizeRedirectMask + | PointerMotionMask | PointerMotionHintMask); + JXReparentWindow(display, win, dock->cp->window, 0, 0); + JXMapRaised(display, win); + + /* Resize the tray containing the dock. */ + ResizeTray(dock->cp->tray); + +} + +/** Remove a window from the dock. */ +int UndockWindow(Window win) { + + DockNode *np; + DockNode *last; + + Assert(dock); + + last = NULL; + for(np = dock->nodes; np; np = np->next) { + if(np->window == win) { + + /* Remove the window from our list. */ + if(last) { + last->next = np->next; + } else { + dock->nodes = np->next; + } + Release(np); + + /* Update the requested size. */ + if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { + dock->cp->requestedWidth -= dock->cp->height; + if(dock->cp->requestedWidth <= 0) { + dock->cp->requestedWidth = 1; + } + } else { + dock->cp->requestedHeight -= dock->cp->width; + if(dock->cp->requestedHeight <= 0) { + dock->cp->requestedHeight = 1; + } + } + + /* Resize the tray. */ + ResizeTray(dock->cp->tray); + + return 1; + + } + last = np; + } + + return 0; +} + +/** Layout items on the dock. */ +void UpdateDock() { + + XWindowAttributes attr; + DockNode *np; + int x, y; + int width, height; + int xoffset, yoffset; + int itemWidth, itemHeight; + double ratio; + + Assert(dock); + + /* Determine the size of items in the dock. */ + if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { + itemWidth = dock->cp->height; + itemHeight = dock->cp->height; + } else { + itemHeight = dock->cp->width; + itemWidth = dock->cp->width; + } + + x = 0; + y = 0; + for(np = dock->nodes; np; np = np->next) { + + xoffset = 0; + yoffset = 0; + width = itemWidth; + height = itemHeight; + + if(JXGetWindowAttributes(display, np->window, &attr)) { + + ratio = (double)attr.width / attr.height; + + if(ratio > 1.0) { + if(width > attr.width) { + width = attr.width; + } + height = width / ratio; + } else { + if(height > attr.height) { + height = attr.height; + } + width = height * ratio; + } + + xoffset = (itemWidth - width) / 2; + yoffset = (itemHeight - height) / 2; + + } + + JXMoveResizeWindow(display, np->window, x + xoffset, y + yoffset, + width, height); + + /* Reparent if this window likes to go other places. */ + if(np->needs_reparent) { + JXReparentWindow(display, np->window, dock->cp->window, + x + xoffset, y + yoffset); + } + + if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { + x += itemWidth; + } else { + y += itemHeight; + } + } + +} + diff --git a/src/dock.h b/src/dock.h new file mode 100644 index 0000000..307f348 --- /dev/null +++ b/src/dock.h @@ -0,0 +1,43 @@ +/** + * @file dock.h + * @author Joe Wingbermuehle + * @date 2006 + * + * @brief Header for the dock functions. + * + */ + +#ifndef DOCK_H +#define DOCK_H + +struct TrayComponentType; + +/*@{*/ +void InitializeDock(); +void StartupDock(); +void ShutdownDock(); +void DestroyDock(); +/*@}*/ + +/** Create a dock to be used for notifications. + * Note that only one dock can be created. + */ +struct TrayComponentType *CreateDock(); + +/** Handle a client message sent to the dock window. + * @param event The event. + */ +void HandleDockEvent(const XClientMessageEvent *event); + +int HandleDockDestroy(Window win); + +int HandleDockSelectionClear(const XSelectionClearEvent *event); + +int HandleDockResizeRequest(const XResizeRequestEvent *event); + +int HandleDockConfigureRequest(const XConfigureRequestEvent *event); + +int HandleDockReparentNotify(const XReparentEvent *event); + +#endif + diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..a4b1277 --- /dev/null +++ b/src/error.c @@ -0,0 +1,108 @@ +/** + * @file error.c + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Error handling functions. + * + */ + +#include "jwm.h" +#include "error.h" +#include "main.h" + +/** Log a fatal error and exit. */ +void FatalError(const char *str, ...) { + + va_list ap; + va_start(ap, str); + + Assert(str); + + fprintf(stderr, "JWM: error: "); + vfprintf(stderr, str, ap); + fprintf(stderr, "\n"); + + va_end(ap); + + exit(1); + +} + +/** Log a warning. */ +void Warning(const char *str, ...) { + + va_list ap; + va_start(ap, str); + + Assert(str); + + WarningVA(NULL, str, ap); + + va_end(ap); + +} + +/** Log a warning. */ +void WarningVA(const char *part, const char *str, va_list ap) { + + Assert(str); + + fprintf(stderr, "JWM: warning: "); + if(part) { + fprintf(stderr, "%s: ", part); + } + vfprintf(stderr, str, ap); + fprintf(stderr, "\n"); + +} + +/** Callback to handle errors from Xlib. + * Note that if debug output is directed to an X terminal, emitting too + * much output can cause a dead lock (this happens on HP-UX). Therefore + * ShowCheckpoint isn't used by default. + */ +int ErrorHandler(Display *d, XErrorEvent *e) { + +#ifdef DEBUG + + char buffer[64]; + char code[32]; + +#endif + + if(initializing) { + if(e->request_code == X_ChangeWindowAttributes + && e->error_code == BadAccess) { + FatalError("display is already managed"); + } + } + +#ifdef DEBUG + + if(!e) { + fprintf(stderr, "XError: [no information]\n"); + return 0; + } + + XGetErrorText(display, e->error_code, buffer, sizeof(buffer)); + Debug("XError: %s", buffer); + + snprintf(code, sizeof(code), "%d", e->request_code); + XGetErrorDatabaseText(display, "XRequest", code, "?", + buffer, sizeof(buffer)); + Debug(" Request Code: %d (%s)", e->request_code, buffer); + Debug(" Minor Code: %d", e->minor_code); + Debug(" Resource ID: 0x%lx", (unsigned long)e->resourceid); + Debug(" Error Serial: %lu", (unsigned long)e->serial); + +#if 0 + ShowCheckpoint(); +#endif + +#endif + + return 0; + +} + diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..5028db4 --- /dev/null +++ b/src/error.h @@ -0,0 +1,38 @@ +/** + * @file error.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the error functions. + * + */ + +#ifndef ERROR_H +#define ERROR_H + +/** Display and error message and terminate the program. + * @param str The format of the message to display. + */ +void FatalError(const char *str, ...); + +/** Display a warning message. + * @param str The format of the message to display. + */ +void Warning(const char *str, ...); + +/** Display a warning message. + * @param part A section identifier for the message. + * @param str The format string of the message to display. + * @param ap The argument list. + */ +void WarningVA(const char *part, const char *str, va_list ap); + +/** Handle an XError event. + * @param d The display on which the event occurred. + * @param e The error event. + * @return 0 + */ +int ErrorHandler(Display *d, XErrorEvent *e); + +#endif + diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..3c4f0bb --- /dev/null +++ b/src/event.c @@ -0,0 +1,1138 @@ +/**************************************************************************** + * Functions to handle XServer events. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "event.h" +#include "client.h" +#include "main.h" +#include "hint.h" +#include "tray.h" +#include "pager.h" +#include "desktop.h" +#include "cursor.h" +#include "icon.h" +#include "taskbar.h" +#include "confirm.h" +#include "swallow.h" +#include "popup.h" +#include "winmenu.h" +#include "root.h" +#include "move.h" +#include "resize.h" +#include "key.h" +#include "clock.h" +#include "place.h" +#include "dock.h" +#include "timing.h" +#include "traybutton.h" + +#define MIN_TIME_DELTA 50 + +static void Signal(); +static void DispatchBorderButtonEvent(const XButtonEvent *event, + ClientNode *np); + +static void HandleConfigureRequest(const XConfigureRequestEvent *event); +static int HandleExpose(const XExposeEvent *event); +static int HandlePropertyNotify(const XPropertyEvent *event); +static void HandleClientMessage(const XClientMessageEvent *event); +static void HandleColormapChange(const XColormapEvent *event); +static int HandleDestroyNotify(const XDestroyWindowEvent *event); +static void HandleMapRequest(const XMapEvent *event); +static void HandleUnmapNotify(const XUnmapEvent *event); +static void HandleButtonEvent(const XButtonEvent *event); +static void HandleKeyPress(const XKeyEvent *event); +static void HandleEnterNotify(const XCrossingEvent *event); +static void HandleLeaveNotify(const XCrossingEvent *event); +static void HandleMotionNotify(const XMotionEvent *event); +static int HandleSelectionClear(const XSelectionClearEvent *event); + +static void HandleNetMoveResize(const XClientMessageEvent *event, + ClientNode *np); +static void HandleNetWMState(const XClientMessageEvent *event, + ClientNode *np); + +#ifdef USE_SHAPE +static void HandleShapeEvent(const XShapeEvent *event); +#endif + +/**************************************************************************** + ****************************************************************************/ +void WaitForEvent(XEvent *event) { + + struct timeval timeout; + fd_set fds; + int fd; + int handled; + + fd = JXConnectionNumber(display); + + do { + + while(JXPending(display) == 0) { + FD_ZERO(&fds); + FD_SET(fd, &fds); + timeout.tv_usec = 0; + timeout.tv_sec = 1; + if(select(fd + 1, &fds, NULL, NULL, &timeout) <= 0) { + Signal(); + } + } + + Signal(); + + JXNextEvent(display, event); + + switch(event->type) { + case ConfigureRequest: + HandleConfigureRequest(&event->xconfigurerequest); + handled = 1; + break; + case MapRequest: + HandleMapRequest(&event->xmap); + handled = 1; + break; + case PropertyNotify: + handled = HandlePropertyNotify(&event->xproperty); + break; + case ClientMessage: + HandleClientMessage(&event->xclient); + handled = 1; + break; + case UnmapNotify: + HandleUnmapNotify(&event->xunmap); + handled = 1; + break; + case Expose: + handled = HandleExpose(&event->xexpose); + break; + case ColormapNotify: + HandleColormapChange(&event->xcolormap); + handled = 1; + break; + case DestroyNotify: + handled = HandleDestroyNotify(&event->xdestroywindow); + break; + case SelectionClear: + handled = HandleSelectionClear(&event->xselectionclear); + break; + case ResizeRequest: + handled = HandleDockResizeRequest(&event->xresizerequest); + break; + case MotionNotify: + SetMousePosition(event->xmotion.x_root, event->xmotion.y_root); + handled = 0; + break; + case ReparentNotify: + HandleDockReparentNotify(&event->xreparent); + handled = 1; + break; + case ConfigureNotify: + handled = 0; + break; + case CreateNotify: + case MapNotify: + case GraphicsExpose: + case NoExpose: + handled = 1; + break; + default: +#ifdef USE_SHAPE + if(haveShape && event->type == shapeEvent) { + HandleShapeEvent((XShapeEvent*)event); + handled = 1; + } else { + handled = 0; + } +#else + handled = 0; +#endif + break; + } + + if(!handled) { + handled = ProcessTrayEvent(event); + } + if(!handled) { + handled = ProcessDialogEvent(event); + } + if(!handled) { + handled = ProcessSwallowEvent(event); + } + if(!handled) { + handled = ProcessPopupEvent(event); + } + + } while(handled && !shouldExit); + +} + +/**************************************************************************** + ****************************************************************************/ +void Signal() { + + static TimeType last = ZERO_TIME; + + TimeType now; + int x, y; + + GetCurrentTime(&now); + + if(GetTimeDifference(&now, &last) < MIN_TIME_DELTA) { + return; + } + last = now; + + GetMousePosition(&x, &y); + + SignalTaskbar(&now, x, y); + SignalTrayButton(&now, x, y); + SignalClock(&now, x, y); + SignalTray(&now, x, y); + SignalPopup(&now, x, y); + +} + +/**************************************************************************** + ****************************************************************************/ +void ProcessEvent(XEvent *event) { + + switch(event->type) { + case ButtonPress: + case ButtonRelease: + HandleButtonEvent(&event->xbutton); + break; + case KeyPress: + HandleKeyPress(&event->xkey); + break; + case EnterNotify: + HandleEnterNotify(&event->xcrossing); + break; + case LeaveNotify: + HandleLeaveNotify(&event->xcrossing); + break; + case MotionNotify: + while(JXCheckTypedEvent(display, MotionNotify, event)); + HandleMotionNotify(&event->xmotion); + break; + case DestroyNotify: + case Expose: + case KeyRelease: + case ConfigureNotify: + break; + default: + Debug("Unknown event type: %d", event->type); + break; + } +} + +/**************************************************************************** + ****************************************************************************/ +void DiscardMotionEvents(XEvent *event, Window w) { + + XEvent temp; + + while(JXCheckTypedEvent(display, MotionNotify, &temp)) { + SetMousePosition(temp.xmotion.x_root, temp.xmotion.y_root); + if(temp.xmotion.window == w) { + *event = temp; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +int HandleSelectionClear(const XSelectionClearEvent *event) { + + return HandleDockSelectionClear(event); + +} + +/**************************************************************************** + ****************************************************************************/ +void HandleButtonEvent(const XButtonEvent *event) { + + int x, y; + ClientNode *np; + int north, south, east, west; + int allowMode; + + np = FindClientByParent(event->window); + if(np) { + RaiseClient(np); + if(focusModel == FOCUS_CLICK) { + FocusClient(np); + } + switch(event->button) { + case Button1: + DispatchBorderButtonEvent(event, np); + break; + case Button2: + MoveClient(np, event->x, event->y); + break; + case Button3: + GetBorderSize(np, &north, &south, &east, &west); + x = event->x + np->x - west; + y = event->y + np->y - north; + ShowWindowMenu(np, x, y); + break; + case Button4: + ShadeClient(np); + break; + case Button5: + UnshadeClient(np); + break; + default: + break; + } + } else if(event->window == rootWindow && event->type == ButtonPress) { + if(!ShowRootMenu(event->button, event->x, event->y)) { + if(event->button == 4) { + PreviousDesktop(); + } else if(event->button == 5) { + NextDesktop(); + } + } + } else { + np = FindClientByWindow(event->window); + if(np) { + allowMode = ReplayPointer; + switch(event->button) { + case Button1: + case Button2: + RaiseClient(np); + if(focusModel == FOCUS_CLICK) { + FocusClient(np); + } + if(event->state & Mod1Mask) { + GetBorderSize(np, &north, &south, &east, &west); + MoveClient(np, event->x + west, event->y + north); + } + break; + case Button3: + if(event->state & Mod1Mask) { + LowerClient(np); + allowMode = SyncPointer; + } else { + RaiseClient(np); + if(focusModel == FOCUS_CLICK) { + FocusClient(np); + } + } + break; + default: + break; + } + JXAllowEvents(display, allowMode, CurrentTime); + } + } + + UpdatePager(); +} + +/**************************************************************************** + ****************************************************************************/ +void HandleKeyPress(const XKeyEvent *event) { + ClientNode *np; + KeyType key; + + key = GetKey(event); + + np = GetActiveClient(); + + switch(key & 0xFF) { + case KEY_EXEC: + RunKeyCommand(event); + break; + case KEY_DESKTOP: + if(key >> 8) { + ChangeDesktop((key >> 8) - 1); + } else { + NextDesktop(); + } + break; + case KEY_NEXT: + FocusNext(); + break; + case KEY_NEXT_STACKED: + FocusNextStackedCircular(); + break; + case KEY_CLOSE: + if(np) { + DeleteClient(np); + } + break; + case KEY_SHADE: + if(np) { + if(np->state.status & STAT_SHADED) { + UnshadeClient(np); + } else { + ShadeClient(np); + } + } + break; + case KEY_MOVE: + if(np) { + MoveClientKeyboard(np); + } + break; + case KEY_RESIZE: + if(np) { + ResizeClientKeyboard(np); + } + break; + case KEY_MIN: + if(np) { + MinimizeClient(np); + } + break; + case KEY_MAX: + if(np) { + MaximizeClient(np); + } + break; + case KEY_ROOT: + ShowKeyMenu(event); + break; + case KEY_WIN: + if(np) { + ShowWindowMenu(np, np->x, np->y); + } + break; + case KEY_RESTART: + Restart(); + break; + case KEY_EXIT: + Exit(); + break; + default: + break; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void HandleConfigureRequest(const XConfigureRequestEvent *event) { + + XWindowChanges wc; + ClientNode *np; + int north, south, east, west; + int changed; + int handled; + + handled = HandleDockConfigureRequest(event); + if(handled) { + return; + } + + np = FindClientByWindow(event->window); + if(np && np->window == event->window) { + + changed = 0; + if((event->value_mask & CWWidth) && (event->width != np->width)) { + np->width = event->width; + changed = 1; + } + if((event->value_mask & CWHeight) && (event->height != np->height)) { + np->height = event->height; + changed = 1; + } + if((event->value_mask & CWX) && (event->x != np->x)) { + np->x = event->x; + changed = 1; + } + if((event->value_mask & CWY) && (event->y != np->y)) { + np->y = event->y; + changed = 1; + } + + if(!changed) { + return; + } + + if(np->controller) { + (np->controller)(0); + } + + GetBorderSize(np, &north, &south, &east, &west); + + wc.stack_mode = Above; + wc.sibling = np->parent; + wc.border_width = 0; + + ConstrainSize(np); + + if(np->state.status & STAT_MAXIMIZED) { + np->state.status &= ~STAT_MAXIMIZED; + } + + wc.x = np->x; + wc.y = np->y; + wc.width = np->width + east + west; + wc.height = np->height + north + south; + JXConfigureWindow(display, np->parent, event->value_mask, &wc); + + wc.x = west; + wc.y = north; + wc.width = np->width; + wc.height = np->height; + JXConfigureWindow(display, np->window, event->value_mask, &wc); + + } else { + + wc.stack_mode = event->detail; + wc.sibling = event->above; + wc.border_width = event->border_width; + wc.x = event->x; + wc.y = event->y; + wc.width = event->width > rootWidth ? rootWidth : event->width; + wc.height = event->height > rootHeight ? rootHeight : event->height; + JXConfigureWindow(display, event->window, event->value_mask, &wc); + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void HandleEnterNotify(const XCrossingEvent *event) { + + ClientNode *np; + Cursor cur; + + SetMousePosition(event->x_root, event->y_root); + + np = FindClientByWindow(event->window); + if(np) { + if(!(np->state.status & STAT_ACTIVE) && (focusModel == FOCUS_SLOPPY)) { + FocusClient(np); + } + if(np->parent == event->window) { + np->borderAction = GetBorderActionType(np, event->x, event->y); + cur = GetFrameCursor(np->borderAction); + JXDefineCursor(display, np->parent, cur); + } else if(np->borderAction != BA_NONE) { + SetDefaultCursor(np->parent); + np->borderAction = BA_NONE; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void HandleLeaveNotify(const XCrossingEvent *event) { + + ClientNode *np; + + SetMousePosition(event->x_root, event->y_root); + + np = FindClientByParent(event->window); + if(np) { + SetDefaultCursor(np->parent); + } + +} + +/**************************************************************************** + ****************************************************************************/ +int HandleExpose(const XExposeEvent *event) { + + ClientNode *np; + + np = FindClientByWindow(event->window); + if(np) { + if(event->window == np->parent) { + DrawBorder(np, event); + return 1; + } else if(event->window == np->window + && np->state.status & STAT_WMDIALOG) { + return 0; + } else { + return 1; + } + } else { + return event->count ? 1 : 0; + } + +} + +/**************************************************************************** + ****************************************************************************/ +int HandlePropertyNotify(const XPropertyEvent *event) { + + ClientNode *np; + int changed; + + np = FindClientByWindow(event->window); + if(np) { + changed = 0; + switch(event->atom) { + case XA_WM_NAME: + ReadWMName(np); + changed = 1; + break; + case XA_WM_NORMAL_HINTS: + ReadWMNormalHints(np); + changed = 1; + break; + case XA_WM_HINTS: + case XA_WM_ICON_NAME: + case XA_WM_CLIENT_MACHINE: + break; + default: + if(event->atom == atoms[ATOM_WM_COLORMAP_WINDOWS]) { + ReadWMColormaps(np); + UpdateClientColormap(np); + } else if(event->atom == atoms[ATOM_NET_WM_ICON]) { + LoadIcon(np); + changed = 1; + } else if(event->atom == atoms[ATOM_NET_WM_NAME]) { + ReadWMName(np); + changed = 1; + } else if(event->atom == atoms[ATOM_NET_WM_STRUT_PARTIAL]) { + ReadClientStrut(np); + } else if(event->atom == atoms[ATOM_NET_WM_STRUT]) { + ReadClientStrut(np); + } + break; + } + + if(changed) { + DrawBorder(np, NULL); + UpdateTaskBar(); + UpdatePager(); + } + if(np->state.status & STAT_WMDIALOG) { + return 0; + } else { + return 1; + } + } + + return 1; +} + +/**************************************************************************** + ****************************************************************************/ +void HandleClientMessage(const XClientMessageEvent *event) { + + ClientNode *np; + long mask, flags; +#ifdef DEBUG + char *atomName; +#endif + + np = FindClientByWindow(event->window); + if(np) { + if(event->message_type == atoms[ATOM_WIN_STATE]) { + + mask = event->data.l[0]; + flags = event->data.l[1]; + + if(mask & WIN_STATE_STICKY) { + if(flags & WIN_STATE_STICKY) { + SetClientSticky(np, 1); + } else { + SetClientSticky(np, 0); + } + } + + if(mask & WIN_STATE_HIDDEN) { + if(flags & WIN_STATE_HIDDEN) { + np->state.status |= STAT_NOLIST; + } else { + np->state.status &= ~STAT_NOLIST; + } + UpdateTaskBar(); + UpdatePager(); + } + + } else if(event->message_type == atoms[ATOM_WIN_LAYER]) { + + SetClientLayer(np, event->data.l[0]); + + } else if(event->message_type == atoms[ATOM_WM_CHANGE_STATE]) { + + if(np->controller) { + (np->controller)(0); + } + + switch(event->data.l[0]) { + case WithdrawnState: + SetClientWithdrawn(np); + break; + case IconicState: + MinimizeClient(np); + break; + case NormalState: + RestoreClient(np, 1); + break; + default: + break; + } + + } else if(event->message_type == atoms[ATOM_NET_ACTIVE_WINDOW]) { + + RestoreClient(np, 1); + FocusClient(np); + + } else if(event->message_type == atoms[ATOM_NET_WM_DESKTOP]) { + + if(event->data.l[0] == ~0L) { + SetClientSticky(np, 1); + } else { + + if(np->controller) { + (np->controller)(0); + } + + if(event->data.l[0] >= 0 && event->data.l[0] < (long)desktopCount) { + np->state.status &= ~STAT_STICKY; + SetClientDesktop(np, event->data.l[0]); + } + } + + } else if(event->message_type == atoms[ATOM_NET_CLOSE_WINDOW]) { + + DeleteClient(np); + + } else if(event->message_type == atoms[ATOM_NET_MOVERESIZE_WINDOW]) { + + HandleNetMoveResize(event, np); + + } else if(event->message_type == atoms[ATOM_NET_WM_STATE]) { + + HandleNetWMState(event, np); + + } else { + +#ifdef DEBUG + atomName = JXGetAtomName(display, event->message_type); + Debug("Uknown ClientMessage to client: %s", atomName); + JXFree(atomName); +#endif + + } + + } else if(event->window == rootWindow) { + + if(event->message_type == atoms[ATOM_JWM_RESTART]) { + Restart(); + } else if(event->message_type == atoms[ATOM_JWM_EXIT]) { + Exit(); + } else if(event->message_type == atoms[ATOM_NET_CURRENT_DESKTOP]) { + ChangeDesktop(event->data.l[0]); + } else { +#ifdef DEBUG + atomName = JXGetAtomName(display, event->message_type); + Debug("Uknown ClientMessage to root: %s", atomName); + JXFree(atomName); +#endif + } + + } else if(event->message_type == atoms[ATOM_NET_SYSTEM_TRAY_OPCODE]) { + + HandleDockEvent(event); + + } + +} + +/**************************************************************************** + * Handle a _NET_MOVERESIZE_WINDOW request. + ****************************************************************************/ +void HandleNetMoveResize(const XClientMessageEvent *event, ClientNode *np) { + + long flags, gravity; + long x, y; + long width, height; + int deltax, deltay; + int north, south, east, west; + + Assert(event); + Assert(np); + + gravity = event->data.l[0] & 0xFF; + flags = event->data.l[0] >> 8; + + x = np->x; + y = np->y; + width = np->width; + height = np->height; + + if(flags & (1 << 0)) { + x = event->data.l[1]; + } + if(flags & (1 << 1)) { + y = event->data.l[2]; + } + if(flags & (1 << 2)) { + width = event->data.l[3]; + } + if(flags & (1 << 3)) { + height = event->data.l[4]; + } + + if(gravity == 0) { + gravity = np->gravity; + } + + GetBorderSize(np, &north, &south, &east, &west); + GetGravityDelta(np, &deltax, &deltay); + + x -= deltax; + y -= deltay; + + np->x = x; + np->y = y; + np->width = width; + np->height = height; + + JXMoveResizeWindow(display, np->parent, + np->x - west, np->y - north, + np->width + east + west, + np->height + north + south); + JXMoveResizeWindow(display, np->window, west, north, + np->width, np->height); + + WriteState(np); + SendConfigureEvent(np); + +} + +/**************************************************************************** + * Handle a _NET_WM_STATE request. + ****************************************************************************/ +void HandleNetWMState(const XClientMessageEvent *event, ClientNode *np) { + + int actionMaximize; + int actionStick; + int actionShade; + int actionFullScreen; + int x; + + /* Up to two actions to be applied together, figure it out. */ + actionMaximize = 0; + actionStick = 0; + actionShade = 0; + actionFullScreen = 0; + + for(x = 1; x <= 2; x++) { + if(event->data.l[x] + == (long)atoms[ATOM_NET_WM_STATE_STICKY]) { + actionStick = 1; + } else if(event->data.l[x] + == (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT]) { + actionMaximize = 1; + } else if(event->data.l[x] + == (long)atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) { + actionMaximize = 1; + } else if(event->data.l[x] + == (long)atoms[ATOM_NET_WM_STATE_SHADED]) { + actionShade = 1; + } else if(event->data.l[x] + == (long)atoms[ATOM_NET_WM_STATE_FULLSCREEN]) { + actionFullScreen = 1; + } + } + + switch(event->data.l[0]) { + case 0: /* Remove */ + if(actionStick) { + SetClientSticky(np, 0); + } + if(actionMaximize && (np->state.status & STAT_MAXIMIZED)) { + MaximizeClient(np); + } + if(actionShade) { + UnshadeClient(np); + } + if(actionFullScreen) { + SetClientFullScreen(np, 0); + } + break; + case 1: /* Add */ + if(actionStick) { + SetClientSticky(np, 1); + } + if(actionMaximize && !(np->state.status & STAT_MAXIMIZED)) { + MaximizeClient(np); + } + if(actionShade) { + ShadeClient(np); + } + if(actionFullScreen) { + SetClientFullScreen(np, 1); + } + break; + case 2: /* Toggle */ + if(actionStick) { + if(np->state.status & STAT_STICKY) { + SetClientSticky(np, 0); + } else { + SetClientSticky(np, 1); + } + } + if(actionMaximize) { + MaximizeClient(np); + } + if(actionShade) { + if(np->state.status & STAT_SHADED) { + UnshadeClient(np); + } else { + ShadeClient(np); + } + } + if(actionFullScreen) { + if(np->state.status & STAT_FULLSCREEN) { + SetClientFullScreen(np, 0); + } else { + SetClientFullScreen(np, 1); + } + } + break; + default: + Debug("bad _NET_WM_STATE action: %ld", event->data.l[0]); + break; + } +} + +/**************************************************************************** + ****************************************************************************/ +void HandleMotionNotify(const XMotionEvent *event) { + + ClientNode *np; + Cursor cur; + BorderActionType action; + + if(event->is_hint) { + return; + } + + SetMousePosition(event->x_root, event->y_root); + + np = FindClientByParent(event->window); + if(np && (np->state.border & BORDER_OUTLINE)) { + action = GetBorderActionType(np, event->x, event->y); + if(np->borderAction != action) { + np->borderAction = action; + cur = GetFrameCursor(action); + JXDefineCursor(display, np->parent, cur); + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +#ifdef USE_SHAPE +void HandleShapeEvent(const XShapeEvent *event) { + + ClientNode *np; + + np = FindClientByWindow(event->window); + if(np) { + SetShape(np); + } + +} +#endif /* USE_SHAPE */ + +/**************************************************************************** + ****************************************************************************/ +void HandleColormapChange(const XColormapEvent *event) { + ClientNode *np; + + if(event->new == True) { + np = FindClientByWindow(event->window); + if(np) { + np->cmap = event->colormap; + UpdateClientColormap(np); + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void HandleMapRequest(const XMapEvent *event) { + + ClientNode *np; + + Assert(event); + + if(CheckSwallowMap(event)) { + return; + } + + np = FindClientByWindow(event->window); + if(!np) { + JXSync(display, False); + JXGrabServer(display); + np = AddClientWindow(event->window, 0, 1); + if(np) { + if(focusModel == FOCUS_CLICK) { + FocusClient(np); + } + } else { + JXMapWindow(display, event->window); + } + JXSync(display, False); + JXUngrabServer(display); + } else { + if(!(np->state.status & STAT_MAPPED)) { + np->state.status |= STAT_MAPPED; + np->state.status &= ~STAT_MINIMIZED; + np->state.status &= ~STAT_SDESKTOP; + JXMapWindow(display, np->window); + JXMapWindow(display, np->parent); + RaiseClient(np); + if(focusModel == FOCUS_CLICK) { + FocusClient(np); + } + UpdateTaskBar(); + UpdatePager(); + } + } + RestackClients(); +} + +/**************************************************************************** + ****************************************************************************/ +void HandleUnmapNotify(const XUnmapEvent *event) { + + ClientNode *np; + XEvent e; + + Assert(event); + + np = FindClientByWindow(event->window); + if(np && np->window == event->window) { + + if(JXCheckTypedWindowEvent(display, np->window, DestroyNotify, &e)) { + HandleDestroyNotify(&e.xdestroywindow); + return; + } + + if(np->controller) { + (np->controller)(1); + } + + if(np->state.status & STAT_MAPPED) { + + np->state.status &= ~STAT_MAPPED; + JXUnmapWindow(display, np->parent); + + WriteState(np); + UpdateTaskBar(); + UpdatePager(); + + } + + } + +} + +/**************************************************************************** + ****************************************************************************/ +int HandleDestroyNotify(const XDestroyWindowEvent *event) { + + ClientNode *np; + + np = FindClientByWindow(event->window); + if(np && np->window == event->window) { + + if(np->controller) { + (np->controller)(1); + } + + RemoveClient(np); + + return 1; + + } else if(!np) { + + return HandleDockDestroy(event->window); + + } + + return 0; + +} + +/**************************************************************************** + ****************************************************************************/ +void DispatchBorderButtonEvent(const XButtonEvent *event, ClientNode *np) { + + static Time lastClickTime = 0; + static int lastX = 0, lastY = 0; + static int doubleClickActive = 0; + BorderActionType action; + int bsize; + + action = GetBorderActionType(np, event->x, event->y); + + switch(action & 0x0F) { + case BA_RESIZE: + if(event->type == ButtonPress) { + ResizeClient(np, action, event->x, event->y); + } + break; + case BA_MOVE: + if(event->type == ButtonPress) { + if(doubleClickActive + && abs(event->time - lastClickTime) > 0 + && abs(event->time - lastClickTime) <= doubleClickSpeed + && abs(event->x - lastX) <= doubleClickDelta + && abs(event->y - lastY) <= doubleClickDelta) { + MaximizeClient(np); + doubleClickActive = 0; + } else { + if(MoveClient(np, event->x, event->y)) { + doubleClickActive = 0; + } else { + doubleClickActive = 1; + lastClickTime = event->time; + lastX = event->x; + lastY = event->y; + } + } + } + break; + case BA_MENU: + if(event->type == ButtonPress) { + if(np->state.border & BORDER_OUTLINE) { + bsize = borderWidth; + } else { + bsize = 0; + } + ShowWindowMenu(np, np->x + event->x - bsize, + np->y + event->y - titleHeight - bsize); + } + break; + case BA_CLOSE: + if(event->type == ButtonRelease) { + DeleteClient(np); + } + break; + case BA_MAXIMIZE: + if(event->type == ButtonRelease) { + MaximizeClient(np); + } + break; + case BA_MINIMIZE: + if(event->type == ButtonRelease) { + MinimizeClient(np); + } + break; + default: + break; + } + +} + diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..627442b --- /dev/null +++ b/src/event.h @@ -0,0 +1,28 @@ +/** + * @file event.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the event functions. + * + */ + +#ifndef EVENT_H +#define EVENT_H + +/** Wait for an event and process it. */ +void WaitForEvent(); + +/** Process an event. + * @param event The event to process. + */ +void ProcessEvent(XEvent *event); + +/** Discard excess motion events. + * @param event The event to return. + * @param w The window whose events to discard. + */ +void DiscardMotionEvents(XEvent *event, Window w); + +#endif + diff --git a/src/font.c b/src/font.c new file mode 100644 index 0000000..8ebf19a --- /dev/null +++ b/src/font.c @@ -0,0 +1,295 @@ +/**************************************************************************** + * Functions to load fonts. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "font.h" +#include "main.h" +#include "error.h" +#include "color.h" +#include "misc.h" + +static const char *DEFAULT_FONT = "-*-courier-*-r-*-*-14-*-*-*-*-*-*-*"; + +static char *fontNames[FONT_COUNT]; + +#ifdef USE_XFT +static XftFont *fonts[FONT_COUNT]; +#else +static XFontStruct *fonts[FONT_COUNT]; +static GC fontGC; +#endif + +/**************************************************************************** + ****************************************************************************/ +void InitializeFonts() { + + int x; + + for(x = 0; x < FONT_COUNT; x++) { + fonts[x] = NULL; + fontNames[x] = NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void StartupFonts() { + +#ifndef USE_XFT + XGCValues gcValues; + unsigned long gcMask; +#endif + int x; + + /* Inherit unset fonts from the tray for tray items. */ + if(!fontNames[FONT_TASK]) { + fontNames[FONT_TASK] = CopyString(fontNames[FONT_TRAY]); + } + if(!fontNames[FONT_TRAYBUTTON]) { + fontNames[FONT_TRAYBUTTON] = CopyString(fontNames[FONT_TRAY]); + } + if(!fontNames[FONT_CLOCK]) { + fontNames[FONT_CLOCK] = CopyString(fontNames[FONT_TRAY]); + } + +#ifdef USE_XFT + + for(x = 0; x < FONT_COUNT; x++) { + if(fontNames[x]) { + fonts[x] = JXftFontOpenName(display, rootScreen, fontNames[x]); + if(!fonts[x]) { + fonts[x] = JXftFontOpenXlfd(display, rootScreen, fontNames[x]); + } + if(!fonts[x]) { + Warning("could not load font: %s", fontNames[x]); + } + } + if(!fonts[x]) { + fonts[x] = JXftFontOpenXlfd(display, rootScreen, DEFAULT_FONT); + } + if(!fonts[x]) { + FatalError("could not load the default font: %s", DEFAULT_FONT); + } + } + +#else + + for(x = 0; x < FONT_COUNT; x++) { + if(fontNames[x]) { + fonts[x] = JXLoadQueryFont(display, fontNames[x]); + if(!fonts[x] && fontNames[x]) { + Warning("could not load font: %s", fontNames[x]); + } + } + if(!fonts[x]) { + fonts[x] = JXLoadQueryFont(display, DEFAULT_FONT); + } + if(!fonts[x]) { + FatalError("could not load the default font: %s", DEFAULT_FONT); + } + } + + gcMask = GCGraphicsExposures; + gcValues.graphics_exposures = False; + fontGC = JXCreateGC(display, rootWindow, gcMask, &gcValues); + +#endif + +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownFonts() { + + int x; + + for(x = 0; x < FONT_COUNT; x++) { + if(fonts[x]) { +#ifdef USE_XFT + JXftFontClose(display, fonts[x]); +#else + JXFreeFont(display, fonts[x]); +#endif + fonts[x] = NULL; + } + } + +#ifndef USE_XFT + + JXFreeGC(display, fontGC); + +#endif + + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyFonts() { + + int x; + + for(x = 0; x < FONT_COUNT; x++) { + if(fontNames[x]) { + Release(fontNames[x]); + fontNames[x] = NULL; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +int GetStringWidth(FontType type, const char *str) { +#ifdef USE_XFT + + XGlyphInfo extents; + unsigned int length; + + Assert(str); + Assert(fonts[type]); + + length = strlen(str); + + JXftTextExtentsUtf8(display, fonts[type], (const unsigned char*)str, + length, &extents); + + return extents.width; + +#else + + Assert(str); + Assert(fonts[type]); + + return XTextWidth(fonts[type], str, strlen(str)); + +#endif +} + +/**************************************************************************** + ****************************************************************************/ +int GetStringHeight(FontType type) { + + Assert(fonts[type]); + + return fonts[type]->ascent + fonts[type]->descent; + +} + +/**************************************************************************** + ****************************************************************************/ +void SetFont(FontType type, const char *value) { + + if(!value) { + Warning("empty Font tag"); + return; + } + + if(fontNames[type]) { + Release(fontNames[type]); + } + + fontNames[type] = CopyString(value); + +} + +/**************************************************************************** + ****************************************************************************/ +void RenderString(Drawable d, FontType font, ColorType color, + int x, int y, int width, Region region, const char *str) { + +#ifdef USE_XFT + XftDraw *xd; +#endif + + XRectangle rect; + Region renderRegion; + int len; + char *output; + +#ifdef USE_FRIBIDI + + FriBidiChar *temp; + FriBidiCharType type = FRIBIDI_TYPE_ON; + int unicodeLength; + +#endif + + if(!str) { + return; + } + + len = strlen(str); + if(len == 0) { + return; + } + + /* Get the bounds for the string based on the specified width. */ + rect.x = x; + rect.y = y; + rect.width = Min(GetStringWidth(font, str), width) + 2; + rect.height = GetStringHeight(font); + + /* Create a region to use. */ + renderRegion = XCreateRegion(); + + /* Combine the width bounds with the region to use. */ + XUnionRectWithRegion(&rect, renderRegion, renderRegion); + + /* Combine the provided region with the region to use. */ + if(region) { + XIntersectRegion(region, renderRegion, renderRegion); + } + + /* Apply the bidi algorithm if requested. */ + +#ifdef USE_FRIBIDI + + temp = AllocateStack((len + 1) * sizeof(FriBidiChar)); + unicodeLength = fribidi_utf8_to_unicode((char*)str, len, temp); + + fribidi_log2vis(temp, unicodeLength, &type, temp, NULL, NULL, NULL); + + fribidi_unicode_to_utf8(temp, len, (char*)temp); + output = (char*)temp; + +#else + + output = (char*)str; + +#endif + + /* Display the string. */ + +#ifdef USE_XFT + + xd = XftDrawCreate(display, d, rootVisual, rootColormap); + XftDrawSetClip(xd, renderRegion); + JXftDrawStringUtf8(xd, GetXftColor(color), fonts[font], + x, y + fonts[font]->ascent, (const unsigned char*)output, len); + XftDrawDestroy(xd); + +#else + + JXSetForeground(display, fontGC, colors[color]); + XSetRegion(display, fontGC, renderRegion); + JXSetFont(display, fontGC, fonts[font]->fid); + JXDrawString(display, d, fontGC, x, y + fonts[font]->ascent, output, len); + +#endif + + /* Free any memory used for UTF conversion. */ + +#ifdef USE_FRIBIDI + + ReleaseStack(output); + +#endif + + XDestroyRegion(renderRegion); + +} + diff --git a/src/font.h b/src/font.h new file mode 100644 index 0000000..fe1c0c5 --- /dev/null +++ b/src/font.h @@ -0,0 +1,43 @@ +/** + * @file font.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the font functions. + * + */ + +#ifndef FONT_H +#define FONT_H + +#include "color.h" + +typedef enum { + + FONT_BORDER, + FONT_MENU, + FONT_TASK, + FONT_POPUP, + FONT_CLOCK, + FONT_TRAY, + FONT_TRAYBUTTON, + + FONT_COUNT + +} FontType; + +void InitializeFonts(); +void StartupFonts(); +void ShutdownFonts(); +void DestroyFonts(); + +void SetFont(FontType type, const char *value); + +void RenderString(Drawable d, FontType font, ColorType color, + int x, int y, int width, Region region, const char *str); + +int GetStringWidth(FontType type, const char *str); +int GetStringHeight(FontType type); + +#endif + diff --git a/src/group.c b/src/group.c new file mode 100644 index 0000000..cd3aba4 --- /dev/null +++ b/src/group.c @@ -0,0 +1,299 @@ +/**************************************************************************** + * Functions for handling window groups. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "group.h" +#include "client.h" +#include "icon.h" +#include "error.h" +#include "match.h" +#include "desktop.h" +#include "main.h" +#include "misc.h" + +typedef enum { + MATCH_NAME, + MATCH_CLASS +} MatchType; + +typedef struct PatternListType { + char *pattern; + MatchType match; + struct PatternListType *next; +} PatternListType; + +typedef struct OptionListType { + OptionType option; + char *value; + struct OptionListType *next; +} OptionListType; + +typedef struct GroupType { + PatternListType *patterns; + OptionListType *options; + struct GroupType *next; +} GroupType; + +static GroupType *groups = NULL; + +static void ReleasePatternList(PatternListType *lp); +static void ReleaseOptionList(OptionListType *lp); +static void AddPattern(PatternListType **lp, const char *pattern, + MatchType match); +static void ApplyGroup(const GroupType *gp, ClientNode *np); + +/**************************************************************************** + ****************************************************************************/ +void InitializeGroups() { +} + +/**************************************************************************** + ****************************************************************************/ +void StartupGroups() { +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownGroups() { +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyGroups() { + + GroupType *gp; + + while(groups) { + gp = groups->next; + ReleasePatternList(groups->patterns); + ReleaseOptionList(groups->options); + Release(groups); + groups = gp; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ReleasePatternList(PatternListType *lp) { + + PatternListType *tp; + + while(lp) { + tp = lp->next; + Release(lp->pattern); + Release(lp); + lp = tp; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ReleaseOptionList(OptionListType *lp) { + + OptionListType *tp; + + while(lp) { + tp = lp->next; + if(lp->value) { + Release(lp->value); + } + Release(lp); + lp = tp; + } + +} + +/**************************************************************************** + ****************************************************************************/ +GroupType *CreateGroup() { + GroupType *tp; + + tp = Allocate(sizeof(GroupType)); + tp->patterns = NULL; + tp->options = NULL; + tp->next = groups; + groups = tp; + + return tp; +} + +/**************************************************************************** + ****************************************************************************/ +void AddGroupClass(GroupType *gp, const char *pattern) { + + Assert(gp); + + if(pattern) { + AddPattern(&gp->patterns, pattern, MATCH_CLASS); + } else { + Warning("invalid group class"); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void AddGroupName(GroupType *gp, const char *pattern) { + + Assert(gp); + + if(pattern) { + AddPattern(&gp->patterns, pattern, MATCH_NAME); + } else { + Warning("invalid group name"); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void AddPattern(PatternListType **lp, const char *pattern, MatchType match) { + + PatternListType *tp; + + Assert(lp); + Assert(pattern); + + tp = Allocate(sizeof(PatternListType)); + tp->next = *lp; + *lp = tp; + + tp->pattern = CopyString(pattern); + tp->match = match; + +} + +/**************************************************************************** + ****************************************************************************/ +void AddGroupOption(GroupType *gp, OptionType option) { + + OptionListType *lp; + + lp = Allocate(sizeof(OptionListType)); + lp->option = option; + lp->value = NULL; + lp->next = gp->options; + gp->options = lp; + +} + +/**************************************************************************** + ****************************************************************************/ +void AddGroupOptionValue(GroupType *gp, OptionType option, + const char *value) { + + OptionListType *lp; + + Assert(value); + + lp = Allocate(sizeof(OptionListType)); + lp->option = option; + lp->value = CopyString(value); + lp->next = gp->options; + gp->options = lp; + +} + +/**************************************************************************** + ****************************************************************************/ +void ApplyGroups(ClientNode *np) { + + PatternListType *lp; + GroupType *gp; + + Assert(np); + + for(gp = groups; gp; gp = gp->next) { + for(lp = gp->patterns; lp; lp = lp->next) { + if(lp->match == MATCH_CLASS) { + if(Match(lp->pattern, np->className)) { + ApplyGroup(gp, np); + break; + } + } else if(lp->match == MATCH_NAME) { + if(Match(lp->pattern, np->name)) { + ApplyGroup(gp, np); + break; + } + } else { + Debug("invalid match in ApplyGroups: %d", lp->match); + } + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ApplyGroup(const GroupType *gp, ClientNode *np) { + + OptionListType *lp; + unsigned int temp; + + Assert(gp); + Assert(np); + + for(lp = gp->options; lp; lp = lp->next) { + switch(lp->option) { + case OPTION_STICKY: + np->state.status |= STAT_STICKY; + break; + case OPTION_NOLIST: + np->state.status |= STAT_NOLIST; + break; + case OPTION_BORDER: + np->state.border |= BORDER_OUTLINE; + break; + case OPTION_NOBORDER: + np->state.border &= ~BORDER_OUTLINE; + break; + case OPTION_TITLE: + np->state.border |= BORDER_TITLE; + break; + case OPTION_NOTITLE: + np->state.border &= ~BORDER_TITLE; + break; + case OPTION_LAYER: + temp = atoi(lp->value); + if(temp <= LAYER_COUNT) { + SetClientLayer(np, temp); + } else { + Warning("invalid group layer: %s", lp->value); + } + break; + case OPTION_DESKTOP: + temp = atoi(lp->value); + if(temp >= 1 && temp <= desktopCount) { + np->state.desktop = temp - 1; + } else { + Warning("invalid group desktop: %s", lp->value); + } + break; + case OPTION_ICON: + DestroyIcon(np->icon); + np->icon = LoadNamedIcon(lp->value); + break; + case OPTION_PIGNORE: + np->state.status |= STAT_PIGNORE; + break; + case OPTION_MAXIMIZED: + np->state.status |= STAT_MAXIMIZED; + break; + case OPTION_MINIMIZED: + np->state.status |= STAT_MINIMIZED; + break; + case OPTION_SHADED: + np->state.status |= STAT_SHADED; + break; + default: + Debug("invalid option: %d", lp->option); + break; + } + } + +} + diff --git a/src/group.h b/src/group.h new file mode 100644 index 0000000..ccbf62f --- /dev/null +++ b/src/group.h @@ -0,0 +1,48 @@ +/** + * @file font.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Functions for handling window groups. + * + */ + +#ifndef GROUP_H +#define GROUP_H + +struct ClientNode; +struct GroupType; + +typedef enum { + OPTION_INVALID = 0, + OPTION_STICKY = 1, + OPTION_LAYER = 2, + OPTION_DESKTOP = 3, + OPTION_ICON = 4, + OPTION_NOLIST = 5, + OPTION_BORDER = 6, + OPTION_NOBORDER = 7, + OPTION_TITLE = 8, + OPTION_NOTITLE = 9, + OPTION_PIGNORE = 10, + OPTION_MAXIMIZED = 11, + OPTION_MINIMIZED = 12, + OPTION_SHADED = 13 +} OptionType; + +void InitializeGroups(); +void StartupGroups(); +void ShutdownGroups(); +void DestroyGroups(); + +struct GroupType *CreateGroup(); +void AddGroupClass(struct GroupType *gp, const char *pattern); +void AddGroupName(struct GroupType *gp, const char *pattern); +void AddGroupOption(struct GroupType *gp, OptionType option); +void AddGroupOptionValue(struct GroupType *gp, OptionType option, + const char *value); + +void ApplyGroups(struct ClientNode *np); + +#endif + diff --git a/src/help.c b/src/help.c new file mode 100644 index 0000000..cdcd377 --- /dev/null +++ b/src/help.c @@ -0,0 +1,84 @@ +/** + * @file help.c + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Functions for displaying information about JWM. + * + */ + +#include "jwm.h" +#include "help.h" + +/** Display program name, version, and compiled options . */ +void DisplayAbout() { + printf("JWM v%s by Joe Wingbermuehle\n", PACKAGE_VERSION); + DisplayCompileOptions(); +} + +/** Display compiled options. */ +void DisplayCompileOptions() { + + printf("compiled options: "); + +#ifndef DISABLE_CONFIRM + printf("confirm "); +#endif + +#ifdef DEBUG + printf("debug "); +#endif + +#ifdef USE_FRIBIDI + printf("fribidi "); +#endif + +#ifdef USE_ICONS + printf("icons "); +#endif + +#ifdef USE_PNG + printf("png "); +#endif + +#ifdef USE_SHAPE + printf("shape "); +#endif + +#ifdef USE_XFT + printf("xft "); +#endif + +#ifdef USE_XINERAMA + printf("xinerama "); +#endif + +#ifdef USE_XPM + printf("xpm "); +#endif + +#ifdef USE_XRENDER + printf("xrender "); +#endif + + printf("\nsystem configuration: %s\n", SYSTEM_CONFIG); + +} + +/** Display all help. */ +void DisplayHelp() { + DisplayUsage(); + printf(" -display X Set the X display to use\n"); + printf(" -exit Exit JWM (send _JWM_EXIT to the root)\n"); + printf(" -h Display this help message\n"); + printf(" -p Parse the configuration file and exit\n"); + printf(" -restart Restart JWM (send _JWM_RESTART to the root)\n"); + printf(" -v Display version information\n"); +} + +/** Display program usage information. */ +void DisplayUsage() { + DisplayAbout(); + printf("usage: jwm [ options ]\n"); +} + diff --git a/src/help.h b/src/help.h new file mode 100644 index 0000000..dca2f75 --- /dev/null +++ b/src/help.h @@ -0,0 +1,26 @@ +/** + * @file help.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the help functions. + * + */ + +#ifndef HELP_H +#define HELP_H + +/** Display program name, version, and compiled options . */ +void DisplayAbout(); + +/** Display compiled options. */ +void DisplayCompileOptions(); + +/** Display all help. */ +void DisplayHelp(); + +/** Display program usage information. */ +void DisplayUsage(); + +#endif + diff --git a/src/hint.c b/src/hint.c new file mode 100644 index 0000000..6ab096b --- /dev/null +++ b/src/hint.c @@ -0,0 +1,970 @@ +/**************************************************************************** + * Functions to handle hints. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "hint.h" +#include "client.h" +#include "main.h" +#include "tray.h" +#include "desktop.h" +#include "misc.h" + +/* MWM Defines */ +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + +#define MWM_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 + +#define MWM_TEAROFF_WINDOW (1L << 0) + +typedef struct { + + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; + +} PropMwmHints; + +typedef struct { + Atom *atom; + const char *name; +} ProtocolNode; + +typedef struct { + Atom *atom; + const char *name; +} AtomNode; + +Atom atoms[ATOM_COUNT]; + +static const AtomNode atomList[] = { + + { &atoms[ATOM_COMPOUND_TEXT], "COMPOUND_TEXT" }, + { &atoms[ATOM_UTF8_STRING], "UTF8_STRING" }, + + { &atoms[ATOM_WM_STATE], "WM_STATE" }, + { &atoms[ATOM_WM_PROTOCOLS], "WM_PROTOCOLS" }, + { &atoms[ATOM_WM_DELETE_WINDOW], "WM_DELETE_WINDOW" }, + { &atoms[ATOM_WM_TAKE_FOCUS], "WM_TAKE_FOCUS" }, + { &atoms[ATOM_WM_LOCALE_NAME], "WM_LOCALE_NAME" }, + { &atoms[ATOM_WM_CHANGE_STATE], "WM_CHANGE_STATE" }, + { &atoms[ATOM_WM_COLORMAP_WINDOWS], "WM_COLORMAP_WINDOWS" }, + + { &atoms[ATOM_NET_SUPPORTED], "_NET_SUPPORTED" }, + { &atoms[ATOM_NET_NUMBER_OF_DESKTOPS], "_NET_NUMBER_OF_DESKTOPS" }, + { &atoms[ATOM_NET_DESKTOP_NAMES], "_NET_DESKTOP_NAMES" }, + { &atoms[ATOM_NET_DESKTOP_GEOMETRY], "_NET_DESKTOP_GEOMETRY" }, + { &atoms[ATOM_NET_DESKTOP_VIEWPORT], "_NET_DESKTOP_VIEWPORT" }, + { &atoms[ATOM_NET_CURRENT_DESKTOP], "_NET_CURRENT_DESKTOP" }, + { &atoms[ATOM_NET_ACTIVE_WINDOW], "_NET_ACTIVE_WINDOW" }, + { &atoms[ATOM_NET_WORKAREA], "_NET_WORKAREA" }, + { &atoms[ATOM_NET_SUPPORTING_WM_CHECK], "_NET_SUPPORTING_WM_CHECK" }, + { &atoms[ATOM_NET_FRAME_EXTENTS], "_NET_FRAME_EXTENTS" }, + { &atoms[ATOM_NET_WM_DESKTOP], "_NET_WM_DESKTOP" }, + { &atoms[ATOM_NET_WM_STATE], "_NET_WM_STATE" }, + { &atoms[ATOM_NET_WM_STATE_STICKY], "_NET_WM_STATE_STICKY" }, + { &atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT], "_NET_WM_STATE_MAXIMIZED_VERT"}, + { &atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ], "_NET_WM_STATE_MAXIMIZED_HORZ"}, + { &atoms[ATOM_NET_WM_STATE_SHADED], "_NET_WM_STATE_SHADED" }, + { &atoms[ATOM_NET_WM_STATE_FULLSCREEN], "_NET_WM_STATE_FULLSCREEN" }, + { &atoms[ATOM_NET_WM_ALLOWED_ACTIONS], "_NET_WM_ALLOWED_ACTIONS" }, + { &atoms[ATOM_NET_WM_ACTION_MOVE], "_NET_WM_ACTION_MOVE" }, + { &atoms[ATOM_NET_WM_ACTION_RESIZE], "_NET_WM_ACTION_RESIZE" }, + { &atoms[ATOM_NET_WM_ACTION_MINIMIZE], "_NET_WM_ACTION_MINIMIZE" }, + { &atoms[ATOM_NET_WM_ACTION_SHADE], "_NET_WM_ACTION_SHADE" }, + { &atoms[ATOM_NET_WM_ACTION_STICK], "_NET_WM_ACTION_STICK" }, + { &atoms[ATOM_NET_WM_ACTION_MAXIMIZE_HORZ], "_NET_WM_ACTION_MAXIMIZE_HORZ"}, + { &atoms[ATOM_NET_WM_ACTION_MAXIMIZE_VERT], "_NET_WM_ACTION_MAXIMIZE_VERT"}, + { &atoms[ATOM_NET_WM_ACTION_CHANGE_DESKTOP], + "_NET_WM_ACTION_CHANGE_DESKTOP"}, + { &atoms[ATOM_NET_WM_ACTION_CLOSE], "_NET_WM_ACTION_CLOSE" }, + { &atoms[ATOM_NET_CLOSE_WINDOW], "_NET_CLOSE_WINDOW" }, + { &atoms[ATOM_NET_MOVERESIZE_WINDOW], "_NET_MOVERESIZE_WINDOW" }, + { &atoms[ATOM_NET_WM_NAME], "_NET_WM_NAME" }, + { &atoms[ATOM_NET_WM_ICON], "_NET_WM_ICON" }, + { &atoms[ATOM_NET_WM_WINDOW_TYPE], "_NET_WM_WINDOW_TYPE" }, + { &atoms[ATOM_NET_WM_WINDOW_TYPE_DESKTOP],"_NET_WM_WINDOW_TYPE_DESKTOP" }, + { &atoms[ATOM_NET_WM_WINDOW_TYPE_DOCK], "_NET_WM_WINDOW_TYPE_DOCK" }, + { &atoms[ATOM_NET_CLIENT_LIST], "_NET_CLIENT_LIST" }, + { &atoms[ATOM_NET_CLIENT_LIST_STACKING], "_NET_CLIENT_LIST_STACKING" }, + { &atoms[ATOM_NET_WM_STRUT_PARTIAL], "_NET_WM_STRUT_PARTIAL" }, + { &atoms[ATOM_NET_WM_STRUT], "_NET_WM_STRUT" }, + { &atoms[ATOM_NET_SYSTEM_TRAY_OPCODE], "_NET_SYSTEM_TRAY_OPCODE" }, + + { &atoms[ATOM_WIN_LAYER], "_WIN_LAYER" }, + { &atoms[ATOM_WIN_STATE], "_WIN_STATE" }, + { &atoms[ATOM_WIN_WORKSPACE], "_WIN_WORKSPACE" }, + { &atoms[ATOM_WIN_WORKSPACE_COUNT], "_WIN_WORKSPACE_COUNT" }, + { &atoms[ATOM_WIN_SUPPORTING_WM_CHECK], "_WIN_SUPPORTING_WM_CHECK" }, + { &atoms[ATOM_WIN_PROTOCOLS], "_WIN_PROTOCOLS" }, + + { &atoms[ATOM_MOTIF_WM_HINTS], "_MOTIF_WM_HINTS" }, + + { &atoms[ATOM_JWM_RESTART], "_JWM_RESTART" }, + { &atoms[ATOM_JWM_EXIT], "_JWM_EXIT" } + +}; + +static void WriteNetState(ClientNode *np); +static void WriteNetAllowed(ClientNode *np); +static void WriteWinState(ClientNode *np); +static void ReadWMHints(Window win, ClientState *state); +static void ReadMotifHints(Window win, ClientState *state); + +/**************************************************************************** + ****************************************************************************/ +void InitializeHints() { +} + +/**************************************************************************** + ****************************************************************************/ +void StartupHints() { + + unsigned long array[128]; + Atom supported[ATOM_COUNT]; + Window win; + char *data; + unsigned int x; + unsigned int count; + + /* Intern the atoms */ + for(x = 0; x < ATOM_COUNT; x++) { + *atomList[x].atom = JXInternAtom(display, atomList[x].name, False); + } + + /* _NET_SUPPORTED */ + for(x = FIRST_NET_ATOM; x <= LAST_NET_ATOM; x++) { + supported[x - FIRST_NET_ATOM] = atoms[x]; + } + JXChangeProperty(display, rootWindow, atoms[ATOM_NET_SUPPORTED], + XA_ATOM, 32, PropModeReplace, (unsigned char*)supported, + LAST_NET_ATOM - FIRST_NET_ATOM + 1); + + /* _WIN_PROTOCOLS */ + for(x = FIRST_WIN_ATOM; x <= LAST_WIN_ATOM; x++) { + supported[x - FIRST_WIN_ATOM] = atoms[x]; + } + JXChangeProperty(display, rootWindow, atoms[ATOM_WIN_PROTOCOLS], + XA_ATOM, 32, PropModeReplace, (unsigned char*)supported, + LAST_WIN_ATOM - FIRST_WIN_ATOM + 1); + + /* _NET_NUMBER_OF_DESKTOPS */ + SetCardinalAtom(rootWindow, ATOM_NET_NUMBER_OF_DESKTOPS, desktopCount); + + /* _NET_DESKTOP_NAMES */ + count = 0; + for(x = 0; x < desktopCount; x++) { + count += strlen(desktopNames[x]) + 1; + } + data = AllocateStack(count); + count = 0; + for(x = 0; x < desktopCount; x++) { + strcpy(data + count, desktopNames[x]); + count += strlen(desktopNames[x]) + 1; + } + JXChangeProperty(display, rootWindow, atoms[ATOM_NET_DESKTOP_NAMES], + atoms[ATOM_UTF8_STRING], 8, PropModeReplace, + (unsigned char*)data, count); + ReleaseStack(data); + + /* _NET_DESKTOP_GEOMETRY */ + array[0] = rootWidth; + array[1] = rootHeight; + JXChangeProperty(display, rootWindow, atoms[ATOM_NET_DESKTOP_GEOMETRY], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)array, 2); + + /* _NET_DESKTOP_VIEWPORT */ + array[0] = 0; + array[1] = 0; + JXChangeProperty(display, rootWindow, atoms[ATOM_NET_DESKTOP_VIEWPORT], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)array, 2); + + /* _NET_WORKAREA */ + for(x = 0; x < desktopCount; x++) { + array[x * 4 + 0] = 0; + array[x * 4 + 1] = 0; + array[x * 4 + 2] = rootWidth; + array[x * 4 + 3] = rootHeight; + } + JXChangeProperty(display, rootWindow, atoms[ATOM_NET_WORKAREA], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)array, desktopCount * 4); + + win = GetSupportingWindow(); + JXChangeProperty(display, win, atoms[ATOM_NET_WM_NAME], + atoms[ATOM_UTF8_STRING], 8, PropModeReplace, + (unsigned char*)"JWM", 3); + + SetWindowAtom(rootWindow, ATOM_NET_SUPPORTING_WM_CHECK, win); + SetWindowAtom(win, ATOM_NET_SUPPORTING_WM_CHECK, win); + + SetWindowAtom(rootWindow, ATOM_WIN_SUPPORTING_WM_CHECK, win); + SetWindowAtom(win, ATOM_WIN_SUPPORTING_WM_CHECK, win); + + SetCardinalAtom(rootWindow, ATOM_WIN_WORKSPACE_COUNT, desktopCount); + +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownHints() { +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyHints() { +} + +/**************************************************************************** + ****************************************************************************/ +void ReadCurrentDesktop() { + + unsigned long temp; + + currentDesktop = 0; + + if(GetCardinalAtom(rootWindow, ATOM_NET_CURRENT_DESKTOP, &temp)) { + ChangeDesktop(temp); + } else if(GetCardinalAtom(rootWindow, ATOM_WIN_WORKSPACE, &temp)) { + ChangeDesktop(temp); + } else { + ChangeDesktop(0); + } + +} + +/**************************************************************************** + * Read client protocls/hints. + * This is called while the client is being added to management. + ****************************************************************************/ +void ReadClientProtocols(ClientNode *np) { + + Status status; + ClientNode *pp; + + Assert(np); + + ReadWMName(np); + ReadWMClass(np); + ReadWMNormalHints(np); + ReadWMColormaps(np); + + status = JXGetTransientForHint(display, np->window, &np->owner); + if(!status) { + np->owner = None; + } + + np->state = ReadWindowState(np->window); + if(np->minWidth == np->maxWidth && np->minHeight == np->maxHeight) { + np->state.border &= ~BORDER_RESIZE; + } + + /* Set the client to the same layer as its owner. */ + if(np->owner != None) { + pp = FindClientByWindow(np->owner); + if(pp) { + np->state.layer = pp->state.layer; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void WriteState(ClientNode *np) { + + unsigned long data[2]; + + Assert(np); + + if(np->state.status & STAT_MAPPED) { + data[0] = NormalState; + } else if(np->state.status & STAT_MINIMIZED) { + data[0] = IconicState; + } else { + data[0] = WithdrawnState; + } + data[1] = None; + + JXChangeProperty(display, np->window, atoms[ATOM_WM_STATE], + atoms[ATOM_WM_STATE], 32, PropModeReplace, + (unsigned char*)data, 2); + + WriteNetState(np); + WriteNetAllowed(np); + WriteWinState(np); + +} + +/**************************************************************************** + ****************************************************************************/ +void WriteNetState(ClientNode *np) { + + unsigned long values[5]; + int north, south, east, west; + int index; + + Assert(np); + + index = 0; + + if(np->state.status & STAT_MAXIMIZED) { + values[index++] = atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT]; + values[index++] = atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]; + } + + if(np->state.status & STAT_SHADED) { + values[index++] = atoms[ATOM_NET_WM_STATE_SHADED]; + } + + if(np->state.status & STAT_STICKY) { + values[index++] = atoms[ATOM_NET_WM_STATE_STICKY]; + } + + if(np->state.status & STAT_FULLSCREEN) { + values[index++] = atoms[ATOM_NET_WM_STATE_FULLSCREEN]; + } + + JXChangeProperty(display, np->window, atoms[ATOM_NET_WM_STATE], + XA_ATOM, 32, PropModeReplace, (unsigned char*)values, index); + + GetBorderSize(np, &north, &south, &east, &west); + + /* left, right, top, bottom */ + values[0] = west; + values[1] = east; + values[2] = north; + values[3] = south; + + JXChangeProperty(display, np->window, atoms[ATOM_NET_FRAME_EXTENTS], + XA_CARDINAL, 32, PropModeReplace, (unsigned char*)values, 4); + +} + +/**************************************************************************** + ****************************************************************************/ +void WriteNetAllowed(ClientNode *np) { + + unsigned long values[10]; + int index; + + Assert(np); + + index = 0; + + if(np->state.border & BORDER_TITLE) { + values[index++] = atoms[ATOM_NET_WM_ACTION_SHADE]; + } + + if(np->state.border & BORDER_MIN) { + values[index++] = atoms[ATOM_NET_WM_ACTION_MINIMIZE]; + } + + if(np->state.border & BORDER_MAX) { + values[index++] = atoms[ATOM_NET_WM_ACTION_MAXIMIZE_HORZ]; + values[index++] = atoms[ATOM_NET_WM_ACTION_MAXIMIZE_VERT]; + } + + if(np->state.border & BORDER_CLOSE) { + values[index++] = atoms[ATOM_NET_WM_ACTION_CLOSE]; + } + + if(np->state.border & BORDER_RESIZE) { + values[index++] = atoms[ATOM_NET_WM_ACTION_RESIZE]; + } + + if(np->state.border & BORDER_MOVE) { + values[index++] = atoms[ATOM_NET_WM_ACTION_MOVE]; + } + + if(!(np->state.status & STAT_STICKY)) { + values[index++] = atoms[ATOM_NET_WM_ACTION_CHANGE_DESKTOP]; + } + + values[index++] = atoms[ATOM_NET_WM_ACTION_STICK]; + + JXChangeProperty(display, np->window, atoms[ATOM_NET_WM_ALLOWED_ACTIONS], + XA_ATOM, 32, PropModeReplace, (unsigned char*)values, index); + +} + +/**************************************************************************** + ****************************************************************************/ +void WriteWinState(ClientNode *np) { + + unsigned long flags; + + Assert(np); + + flags = 0; + if(np->state.status & STAT_STICKY) { + flags |= WIN_STATE_STICKY; + } + if(np->state.status & STAT_MINIMIZED) { + flags |= WIN_STATE_MINIMIZED; + } + if(np->state.status & STAT_MAXIMIZED) { + flags |= WIN_STATE_MAXIMIZED_VERT; + flags |= WIN_STATE_MAXIMIZED_HORIZ; + } + if(np->state.status & STAT_NOLIST) { + flags |= WIN_STATE_HIDDEN; + } + if(np->state.status & STAT_SHADED) { + flags |= WIN_STATE_SHADED; + } + + SetCardinalAtom(np->window, ATOM_WIN_STATE, flags); + +} + +/**************************************************************************** + * Read all hints needed to determine the current window state. + ****************************************************************************/ +ClientState ReadWindowState(Window win) { + + ClientState result; + Status status; + unsigned long count, x; + unsigned long extra; + Atom realType; + int realFormat; + unsigned char *temp; + Atom *state; + unsigned long card; + int maxVert, maxHorz; + int fullScreen; + + Assert(win != None); + + result.status = STAT_NONE; + result.border = BORDER_DEFAULT; + result.layer = LAYER_NORMAL; + result.desktop = currentDesktop; + + ReadWMHints(win, &result); + ReadMotifHints(win, &result); + + /* _NET_WM_DESKTOP */ + if(GetCardinalAtom(win, ATOM_NET_WM_DESKTOP, &card)) { + if(card == ~0UL) { + result.status |= STAT_STICKY; + } else if(card < desktopCount) { + result.desktop = card; + } else { + result.desktop = desktopCount - 1; + } + } + + /* _NET_WM_STATE */ + status = JXGetWindowProperty(display, win, + atoms[ATOM_NET_WM_STATE], 0, 32, False, XA_ATOM, &realType, + &realFormat, &count, &extra, &temp); + if(status == Success) { + if(count > 0) { + maxVert = 0; + maxHorz = 0; + fullScreen = 0; + state = (unsigned long*)temp; + for(x = 0; x < count; x++) { + if(state[x] == atoms[ATOM_NET_WM_STATE_STICKY]) { + result.status |= STAT_STICKY; + } else if(state[x] == atoms[ATOM_NET_WM_STATE_SHADED]) { + result.status |= STAT_SHADED; + } else if(state[x] == atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT]) { + maxVert = 1; + } else if(state[x] == atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) { + maxHorz = 1; + } else if(state[x] == atoms[ATOM_NET_WM_STATE_FULLSCREEN]) { + fullScreen = 1; + } + } + if(maxVert && maxHorz) { + result.status |= STAT_MAXIMIZED; + } + if(fullScreen) { + result.status |= STAT_FULLSCREEN; + } + } + if(temp) { + JXFree(temp); + } + } + + /* _NET_WM_WINDOW_TYPE */ + status = JXGetWindowProperty(display, win, + atoms[ATOM_NET_WM_WINDOW_TYPE], 0, 32, False, XA_ATOM, &realType, + &realFormat, &count, &extra, &temp); + if(status == Success) { + if(count > 0) { + state = (unsigned long*)temp; + for(x = 0; x < count; x++) { + if(state[x] == atoms[ATOM_NET_WM_WINDOW_TYPE_DESKTOP]) { + result.status |= STAT_STICKY | STAT_NOLIST; + result.layer = 0; + result.border = BORDER_NONE; + } else if(state[x] == atoms[ATOM_NET_WM_WINDOW_TYPE_DOCK]) { + result.status |= STAT_STICKY | STAT_NOLIST; + result.layer = 0; + result.border = BORDER_NONE; + } + } + } + if(temp) { + JXFree(temp); + } + } + + /* _WIN_STATE */ + if(GetCardinalAtom(win, ATOM_WIN_STATE, &card)) { + if(card & WIN_STATE_STICKY) { + result.status |= STAT_STICKY; + } + if(card & WIN_STATE_MINIMIZED) { + result.status |= STAT_MINIMIZED; + } + if(card & WIN_STATE_HIDDEN) { + result.status |= STAT_NOLIST; + } + if(card & WIN_STATE_SHADED) { + result.status |= STAT_SHADED; + } + if((card & WIN_STATE_MAXIMIZED_VERT) + && (card & WIN_STATE_MAXIMIZED_HORIZ)) { + result.status |= STAT_MAXIMIZED; + } + } + + /* _WIN_LAYER */ + if(GetCardinalAtom(win, ATOM_WIN_LAYER, &card)) { + result.layer = card; + } + + return result; + +} + +/**************************************************************************** + ****************************************************************************/ +void ReadWMName(ClientNode *np) { + + unsigned long count; + int status; + unsigned long extra; + Atom realType; + int realFormat; + unsigned char *name; + + Assert(np); + + if(np->name) { + JXFree(np->name); + } + + status = JXGetWindowProperty(display, np->window, + atoms[ATOM_NET_WM_NAME], 0, 1024, False, + atoms[ATOM_UTF8_STRING], &realType, &realFormat, &count, &extra, &name); + if(status != Success) { + np->name = NULL; + } else { + np->name = (char*)name; + } + + if(!np->name) { + if(JXFetchName(display, np->window, &np->name) == 0) { + np->name = NULL; + } + } + + if(!np->name) { + status = JXGetWindowProperty(display, np->window, XA_WM_NAME, + 0, 1024, False, atoms[ATOM_COMPOUND_TEXT], &realType, + &realFormat, &count, &extra, &name); + if(status != Success) { + np->name = NULL; + } else { + np->name = (char*)name; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ReadWMClass(ClientNode *np) { + + XClassHint hint; + + Assert(np); + + if(JXGetClassHint(display, np->window, &hint)) { + np->instanceName = hint.res_name; + np->className = hint.res_class; + } + +} + +/**************************************************************************** + ****************************************************************************/ +ClientProtocolType ReadWMProtocols(Window w) { + + ClientProtocolType result; + unsigned long count, x; + int status; + unsigned long extra; + Atom realType; + int realFormat; + unsigned char *temp; + Atom *p; + + Assert(w != None); + + result = PROT_NONE; + status = JXGetWindowProperty(display, w, atoms[ATOM_WM_PROTOCOLS], + 0, 32, False, XA_ATOM, &realType, &realFormat, &count, &extra, &temp); + p = (Atom*)temp; + + if(status != Success || !p) { + return result; + } + + for(x = 0; x < count; x++) { + if(p[x] == atoms[ATOM_WM_DELETE_WINDOW]) { + result |= PROT_DELETE; + } else if(p[x] == atoms[ATOM_WM_TAKE_FOCUS]) { + result |= PROT_TAKE_FOCUS; + } + } + + JXFree(p); + + return result; + +} + +/**************************************************************************** + ****************************************************************************/ +void ReadWMNormalHints(ClientNode *np) { + + XSizeHints hints; + long temp; + + Assert(np); + + if(!JXGetWMNormalHints(display, np->window, &hints, &temp)) { + np->sizeFlags = 0; + } else { + np->sizeFlags = hints.flags; + } + + if(np->sizeFlags & PResizeInc) { + np->xinc = Max(1, hints.width_inc); + np->yinc = Max(1, hints.height_inc); + } else { + np->xinc = 1; + np->yinc = 1; + } + + if(np->sizeFlags & PMinSize) { + np->minWidth = Max(0, hints.min_width); + np->minHeight = Max(0, hints.min_height); + } else { + np->minWidth = 1; + np->minHeight = 1; + } + + if(np->sizeFlags & PMaxSize) { + np->maxWidth = hints.max_width; + np->maxHeight = hints.max_height; + if(np->maxWidth <= 0) { + np->maxWidth = rootWidth; + } + if(np->maxHeight <= 0) { + np->maxHeight = rootHeight; + } + } else { + np->maxWidth = rootWidth; + np->maxHeight = rootHeight; + } + + if(np->sizeFlags & PBaseSize) { + np->baseWidth = hints.base_width; + np->baseHeight = hints.base_height; + } else if(np->sizeFlags & PMinSize) { + np->baseWidth = np->minWidth; + np->baseHeight = np->minHeight; + } else { + np->baseWidth = 0; + np->baseHeight = 0; + } + + if(np->sizeFlags & PAspect) { + np->aspect.minx = hints.min_aspect.x; + np->aspect.miny = hints.min_aspect.y; + np->aspect.maxx = hints.max_aspect.x; + np->aspect.maxy = hints.max_aspect.y; + if(np->aspect.minx < 1) { + np->aspect.minx = 1; + } + if(np->aspect.miny < 1) { + np->aspect.miny = 1; + } + if(np->aspect.maxx < 1) { + np->aspect.maxx = 1; + } + if(np->aspect.maxy < 1) { + np->aspect.maxy = 1; + } + } + + if(np->sizeFlags & PWinGravity) { + np->gravity = hints.win_gravity; + } else { + np->gravity = 1; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ReadWMColormaps(ClientNode *np) { + + Window *windows; + ColormapNode *cp; + int count; + int x; + + Assert(np); + + if(JXGetWMColormapWindows(display, np->window, &windows, &count)) { + if(count > 0) { + + /* Free old colormaps. */ + while(np->colormaps) { + cp = np->colormaps->next; + Release(np->colormaps); + np->colormaps = cp; + } + + /* Put the maps in the list in order so they will come out in + * reverse order. This way they will be installed with the + * most important last. + * Keep track of at most colormapCount colormaps for each + * window to avoid doing extra work. */ + count = Min(colormapCount, count); + for(x = 0; x < count; x++) { + cp = Allocate(sizeof(ColormapNode)); + cp->window = windows[x]; + cp->next = np->colormaps; + np->colormaps = cp; + } + + JXFree(windows); + + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ReadWMHints(Window win, ClientState *state) { + + XWMHints *wmhints; + + Assert(win != None); + Assert(state); + + wmhints = JXGetWMHints(display, win); + if(wmhints) { + switch(wmhints->flags & StateHint) { + case IconicState: + state->status |= STAT_MINIMIZED; + break; + default: + if(!(state->status & STAT_MINIMIZED)) { + state->status |= STAT_MAPPED; + } + break; + } + JXFree(wmhints); + } else { + state->status |= STAT_MAPPED; + } + +} + +/**************************************************************************** + * Read _MOTIF_WM_HINTS + ****************************************************************************/ +void ReadMotifHints(Window win, ClientState *state) { + + PropMwmHints *mhints; + Atom type; + unsigned long itemCount, bytesLeft; + unsigned char *data; + int format; + + Assert(win != None); + Assert(state); + + if(JXGetWindowProperty(display, win, atoms[ATOM_MOTIF_WM_HINTS], + 0L, 20L, False, atoms[ATOM_MOTIF_WM_HINTS], &type, &format, + &itemCount, &bytesLeft, &data) != Success) { + return; + } + + mhints = (PropMwmHints*)data; + if(mhints) { + + if((mhints->flags & MWM_HINTS_FUNCTIONS) + && !(mhints->functions & MWM_FUNC_ALL)) { + + if(!(mhints->functions & MWM_FUNC_RESIZE)) { + state->border &= ~BORDER_RESIZE; + } + if(!(mhints->functions & MWM_FUNC_MOVE)) { + state->border &= ~BORDER_MOVE; + } + if(!(mhints->functions & MWM_FUNC_MINIMIZE)) { + state->border &= ~BORDER_MIN; + } + if(!(mhints->functions & MWM_FUNC_MAXIMIZE)) { + state->border &= ~BORDER_MAX; + } + if(!(mhints->functions & MWM_FUNC_CLOSE)) { + state->border &= ~BORDER_CLOSE; + } + } + + if((mhints->flags & MWM_HINTS_DECORATIONS) + && !(mhints->decorations & MWM_DECOR_ALL)) { + + if(!(mhints->decorations & MWM_DECOR_BORDER)) { + state->border &= ~BORDER_OUTLINE; + } + if(!(mhints->decorations & MWM_DECOR_TITLE)) { + state->border &= ~BORDER_TITLE; + } + if(!(mhints->decorations & MWM_DECOR_MINIMIZE)) { + state->border &= ~BORDER_MIN; + } + if(!(mhints->decorations & MWM_DECOR_MAXIMIZE)) { + state->border &= ~BORDER_MAX; + } + } + + JXFree(mhints); + } +} + +/**************************************************************************** + ****************************************************************************/ +int GetCardinalAtom(Window window, AtomType atom, unsigned long *value) { + + unsigned long count; + int status; + unsigned long extra; + Atom realType; + int realFormat; + unsigned char *data; + int ret; + + Assert(window != None); + Assert(value); + + status = JXGetWindowProperty(display, window, atoms[atom], 0, 1, False, + XA_CARDINAL, &realType, &realFormat, &count, &extra, &data); + + ret = 0; + if(status == Success && data) { + if(count == 1) { + *value = *(unsigned long*)data; + ret = 1; + } + JXFree(data); + } + + return ret; + +} + +/**************************************************************************** + ****************************************************************************/ +int GetWindowAtom(Window window, AtomType atom, Window *value) { + unsigned long count; + int status; + unsigned long extra; + Atom realType; + int realFormat; + unsigned char *data; + int ret; + + Assert(window != None); + Assert(value); + + status = JXGetWindowProperty(display, window, atoms[atom], 0, 1, False, + XA_WINDOW, &realType, &realFormat, &count, &extra, &data); + + ret = 0; + if(status == Success && data) { + if(count == 1) { + *value = *(Window*)data; + ret = 1; + } + JXFree(data); + } + + return ret; + +} + +/**************************************************************************** + ****************************************************************************/ +void SetCardinalAtom(Window window, AtomType atom, unsigned long value) { + + Assert(window != None); + + JXChangeProperty(display, window, atoms[atom], XA_CARDINAL, 32, + PropModeReplace, (unsigned char*)&value, 1); + +} + +/**************************************************************************** + ****************************************************************************/ +void SetWindowAtom(Window window, AtomType atom, unsigned long value) { + + Assert(window != None); + + JXChangeProperty(display, window, atoms[atom], XA_WINDOW, 32, + PropModeReplace, (unsigned char*)&value, 1); + +} + + diff --git a/src/hint.h b/src/hint.h new file mode 100644 index 0000000..7eae815 --- /dev/null +++ b/src/hint.h @@ -0,0 +1,173 @@ +/** + * @file hint.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for reading and writing X properties. + * + */ + +#ifndef HINT_H +#define HINT_H + +struct ClientNode; + +typedef enum { + + /* Misc */ + ATOM_COMPOUND_TEXT, + ATOM_UTF8_STRING, + + /* Standard atoms */ + ATOM_WM_STATE, + ATOM_WM_PROTOCOLS, + ATOM_WM_DELETE_WINDOW, + ATOM_WM_TAKE_FOCUS, + ATOM_WM_LOCALE_NAME, + ATOM_WM_CHANGE_STATE, + ATOM_WM_COLORMAP_WINDOWS, + + /* WM Spec atoms */ + ATOM_NET_SUPPORTED, + ATOM_NET_NUMBER_OF_DESKTOPS, + ATOM_NET_DESKTOP_NAMES, + ATOM_NET_DESKTOP_GEOMETRY, + ATOM_NET_DESKTOP_VIEWPORT, + ATOM_NET_CURRENT_DESKTOP, + ATOM_NET_ACTIVE_WINDOW, + ATOM_NET_WORKAREA, + ATOM_NET_SUPPORTING_WM_CHECK, + ATOM_NET_FRAME_EXTENTS, + ATOM_NET_WM_DESKTOP, + + ATOM_NET_WM_STATE, + ATOM_NET_WM_STATE_STICKY, + ATOM_NET_WM_STATE_MAXIMIZED_VERT, + ATOM_NET_WM_STATE_MAXIMIZED_HORZ, + ATOM_NET_WM_STATE_SHADED, + ATOM_NET_WM_STATE_FULLSCREEN, + + ATOM_NET_WM_ALLOWED_ACTIONS, + ATOM_NET_WM_ACTION_MOVE, + ATOM_NET_WM_ACTION_RESIZE, + ATOM_NET_WM_ACTION_MINIMIZE, + ATOM_NET_WM_ACTION_SHADE, + ATOM_NET_WM_ACTION_STICK, + ATOM_NET_WM_ACTION_MAXIMIZE_HORZ, + ATOM_NET_WM_ACTION_MAXIMIZE_VERT, + ATOM_NET_WM_ACTION_CHANGE_DESKTOP, + ATOM_NET_WM_ACTION_CLOSE, + + ATOM_NET_CLOSE_WINDOW, + ATOM_NET_MOVERESIZE_WINDOW, + + ATOM_NET_WM_NAME, + ATOM_NET_WM_ICON, + ATOM_NET_WM_WINDOW_TYPE, + ATOM_NET_WM_WINDOW_TYPE_DESKTOP, + ATOM_NET_WM_WINDOW_TYPE_DOCK, + + ATOM_NET_CLIENT_LIST, + ATOM_NET_CLIENT_LIST_STACKING, + + ATOM_NET_WM_STRUT_PARTIAL, + ATOM_NET_WM_STRUT, + + ATOM_NET_SYSTEM_TRAY_OPCODE, + + /* GNOME atoms */ + ATOM_WIN_LAYER, + ATOM_WIN_STATE, + ATOM_WIN_WORKSPACE_COUNT, + ATOM_WIN_WORKSPACE, + ATOM_WIN_SUPPORTING_WM_CHECK, + ATOM_WIN_PROTOCOLS, + + /* MWM atoms */ + ATOM_MOTIF_WM_HINTS, + + /* JWM-specific atoms. */ + ATOM_JWM_RESTART, + ATOM_JWM_EXIT, + + ATOM_COUNT +} AtomType; + +#define FIRST_NET_ATOM ATOM_NET_SUPPORTED +#define LAST_NET_ATOM ATOM_NET_SYSTEM_TRAY_OPCODE + +#define FIRST_WIN_ATOM ATOM_WIN_LAYER +#define LAST_WIN_ATOM ATOM_WIN_PROTOCOLS + +#define FIRST_MWM_ATOM ATOM_MOTIF_WM_HINTS +#define LAST_MWM_ATOM ATOM_MOTIF_WM_HINTS + +#define WIN_STATE_STICKY (1UL << 0) +#define WIN_STATE_MINIMIZED (1UL << 1) +#define WIN_STATE_MAXIMIZED_VERT (1UL << 2) +#define WIN_STATE_MAXIMIZED_HORIZ (1UL << 3) +#define WIN_STATE_HIDDEN (1UL << 4) +#define WIN_STATE_SHADED (1UL << 5) +#define WIN_STATE_HIDE_WORKSPACE (1UL << 6) +#define WIN_STATE_HIDE_TRANSIENT (1UL << 7) +#define WIN_STATE_FIXED_POSITION (1UL << 8) +#define WIN_STATE_ARRANGE_IGNORE (1UL << 9) + +#define WIN_HINT_SKIP_FOCUS (1UL << 0) +#define WIN_HINT_SKIP_WINLIST (1UL << 1) +#define WIN_HINT_SKIP_TASKBAR (1UL << 2) +#define WIN_HINT_GROUP_TRANSIENT (1UL << 3) +#define WIN_HINT_FOCUS_ON_CLICK (1UL << 4) + +typedef enum { + LAYER_BOTTOM = 0, + LAYER_NORMAL = 4, + DEFAULT_TRAY_LAYER = 8, + LAYER_TOP = 12, + LAYER_COUNT = 13 +} WinLayerType; + +typedef struct ClientState { + unsigned int status; + unsigned int border; + unsigned int layer; + unsigned int desktop; +} ClientState; + +typedef enum { + PROT_NONE = 0, + PROT_DELETE = 1, + PROT_TAKE_FOCUS = 2 +} ClientProtocolType; + +extern Atom atoms[ATOM_COUNT]; + +void InitializeHints(); +void StartupHints(); +void ShutdownHints(); +void DestroyHints(); + +void ReadCurrentDesktop(); + +void ReadClientProtocols(struct ClientNode *np); + +void ReadWMName(struct ClientNode *np); +void ReadWMClass(struct ClientNode *np); +void ReadWMNormalHints(struct ClientNode *np); +ClientProtocolType ReadWMProtocols(Window w); +void ReadWMColormaps(struct ClientNode *np); + +void ReadWinLayer(struct ClientNode *np); + +ClientState ReadWindowState(Window win); + +void WriteState(struct ClientNode *np); + +int GetCardinalAtom(Window window, AtomType atom, unsigned long *value); +int GetWindowAtom(Window window, AtomType atom, Window *value); + +void SetCardinalAtom(Window window, AtomType atom, unsigned long value); +void SetWindowAtom(Window window, AtomType atom, unsigned long value); + +#endif + diff --git a/src/icon.c b/src/icon.c new file mode 100644 index 0000000..b6f5ca6 --- /dev/null +++ b/src/icon.c @@ -0,0 +1,726 @@ +/**************************************************************************** + ****************************************************************************/ + +#include "jwm.h" +#include "icon.h" +#include "client.h" +#include "render.h" +#include "main.h" +#include "image.h" +#include "misc.h" +#include "hint.h" +#include "color.h" + +static int iconSize = 0; + +#ifdef USE_ICONS + +#include "x.xpm" + +#define HASH_SIZE 64 + +typedef struct IconPathNode { + char *path; + struct IconPathNode *next; +} IconPathNode; + +static IconNode **iconHash; + +static IconPathNode *iconPaths; +static IconPathNode *iconPathsTail; + +static GC iconGC; + +static void SetIconSize(); + +static void DoDestroyIcon(int index, IconNode *icon); +static void ReadNetWMIcon(ClientNode *np); +static IconNode *GetDefaultIcon(); +static IconNode *CreateIconFromData(const char *name, char **data); +static IconNode *CreateIconFromFile(const char *fileName); +static IconNode *CreateIconFromBinary(const unsigned long *data, + unsigned int length); +static IconNode *LoadNamedIconHelper(const char *name, const char *path); + +static IconNode *LoadSuffixedIcon(const char *path, const char *name, + const char *suffix); + +static ScaledIconNode *GetScaledIcon(IconNode *icon, int width, int height); + +static void InsertIcon(IconNode *icon); +static IconNode *FindIcon(const char *name); +static int GetHash(const char *str); + +/**************************************************************************** + * Must be initialized before parsing the configuration. + ****************************************************************************/ +void InitializeIcons() { + + int x; + + iconPaths = NULL; + iconPathsTail = NULL; + + iconHash = Allocate(sizeof(IconNode*) * HASH_SIZE); + for(x = 0; x < HASH_SIZE; x++) { + iconHash[x] = NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void StartupIcons() { + + XGCValues gcValues; + unsigned long gcMask; + + gcMask = GCGraphicsExposures; + gcValues.graphics_exposures = False; + iconGC = JXCreateGC(display, rootWindow, gcMask, &gcValues); + + QueryRenderExtension(); + +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownIcons() { + + int x; + + for(x = 0; x < HASH_SIZE; x++) { + while(iconHash[x]) { + DoDestroyIcon(x, iconHash[x]); + } + } + + JXFreeGC(display, iconGC); + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyIcons() { + + IconPathNode *pn; + + while(iconPaths) { + pn = iconPaths->next; + Release(iconPaths->path); + Release(iconPaths); + iconPaths = pn; + } + iconPathsTail = NULL; + + if(iconHash) { + Release(iconHash); + iconHash = NULL; + } +} + +/**************************************************************************** + ****************************************************************************/ +void SetIconSize() { + + XIconSize size; + + if(!iconSize) { + + /* FIXME: compute values based on the sizes we can actually use. */ + iconSize = 32; + + size.min_width = iconSize; + size.min_height = iconSize; + size.max_width = iconSize; + size.max_height = iconSize; + size.width_inc = iconSize; + size.height_inc = iconSize; + + JXSetIconSizes(display, rootWindow, &size, 1); + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void AddIconPath(char *path) { + + IconPathNode *ip; + int length; + int addSep; + + if(!path) { + return; + } + + Trim(path); + + length = strlen(path); + if(path[length - 1] != '/') { + addSep = 1; + } else { + addSep = 0; + } + + ip = Allocate(sizeof(IconPathNode)); + ip->path = Allocate(length + addSep + 1); + strcpy(ip->path, path); + if(addSep) { + ip->path[length] = '/'; + ip->path[length + 1] = 0; + } + ExpandPath(&ip->path); + ip->next = NULL; + + if(iconPathsTail) { + iconPathsTail->next = ip; + } else { + iconPaths = ip; + } + iconPathsTail = ip; + +} + +/**************************************************************************** + ****************************************************************************/ +void PutIcon(IconNode *icon, Drawable d, int x, int y, + int width, int height) { + + ScaledIconNode *node; + + Assert(icon); + + node = GetScaledIcon(icon, width, height); + + if(node) { + + if(PutScaledRenderIcon(icon, node, d, x, y)) { + return; + } + + if(node->image != None) { + + if(node->mask != None) { + JXSetClipOrigin(display, iconGC, x, y); + JXSetClipMask(display, iconGC, node->mask); + } + + JXCopyArea(display, node->image, d, iconGC, 0, 0, + node->width, node->height, x, y); + + if(node->mask != None) { + JXSetClipMask(display, iconGC, None); + JXSetClipOrigin(display, iconGC, 0, 0); + } + + } + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void LoadIcon(ClientNode *np) { + + IconPathNode *ip; + + Assert(np); + + SetIconSize(); + + DestroyIcon(np->icon); + np->icon = NULL; + + /* Attempt to read _NET_WM_ICON for an icon */ + ReadNetWMIcon(np); + if(np->icon) { + return; + } + + /* Attempt to find an icon for this program in the icon directory */ + if(np->instanceName) { + for(ip = iconPaths; ip; ip = ip->next) { + +#ifdef USE_PNG + np->icon = LoadSuffixedIcon(ip->path, np->instanceName, ".png"); + if(np->icon) { + return; + } +#endif + +#ifdef USE_XPM + np->icon = LoadSuffixedIcon(ip->path, np->instanceName, ".xpm"); + if(np->icon) { + return; + } +#endif + + } + } + + /* Load the default icon */ + np->icon = GetDefaultIcon(); + +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *LoadSuffixedIcon(const char *path, const char *name, + const char *suffix) { + + IconNode *result; + ImageNode *image; + char *iconName; + + Assert(path); + Assert(name); + Assert(suffix); + + iconName = Allocate(strlen(name) + + strlen(path) + strlen(suffix) + 1); + strcpy(iconName, path); + strcat(iconName, name); + strcat(iconName, suffix); + + result = FindIcon(iconName); + if(result) { + Release(iconName); + return result; + } + + image = LoadImage(iconName); + if(image) { + result = CreateIcon(); + result->name = iconName; + result->image = image; + InsertIcon(result); + return result; + } else { + Release(iconName); + return NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *LoadNamedIcon(const char *name) { + + IconPathNode *ip; + IconNode *icon; + + Assert(name); + + SetIconSize(); + + if(name[0] == '/') { + return CreateIconFromFile(name); + } else { + for(ip = iconPaths; ip; ip = ip->next) { + icon = LoadNamedIconHelper(name, ip->path); + if(icon) { + return icon; + } + } + return NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *LoadNamedIconHelper(const char *name, const char *path) { + + IconNode *result; + char *temp; + + temp = AllocateStack(strlen(name) + strlen(path) + 1); + strcpy(temp, path); + strcat(temp, name); + result = CreateIconFromFile(temp); + ReleaseStack(temp); + + return result; + +} + +/**************************************************************************** + ****************************************************************************/ +void ReadNetWMIcon(ClientNode *np) { + + unsigned long count; + int status; + unsigned long extra; + Atom realType; + int realFormat; + unsigned char *data; + + status = JXGetWindowProperty(display, np->window, atoms[ATOM_NET_WM_ICON], + 0, 256 * 256 * 4, False, XA_CARDINAL, &realType, &realFormat, &count, + &extra, &data); + + if(status == Success && data) { + np->icon = CreateIconFromBinary((unsigned long*)data, count); + JXFree(data); + } + +} + + +/**************************************************************************** + ****************************************************************************/ +IconNode *GetDefaultIcon() { + return CreateIconFromData("default", x_xpm); +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *CreateIconFromData(const char *name, char **data) { + + ImageNode *image; + IconNode *result; + + Assert(name); + Assert(data); + + /* Check if this icon has already been loaded */ + result = FindIcon(name); + if(result) { + return result; + } + + image = LoadImageFromData(data); + if(image) { + result = CreateIcon(); + result->name = CopyString(name); + result->image = image; + InsertIcon(result); + return result; + } else { + return NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *CreateIconFromFile(const char *fileName) { + + ImageNode *image; + IconNode *result; + + if(!fileName) { + return NULL; + } + + /* Check if this icon has already been loaded */ + result = FindIcon(fileName); + if(result) { + return result; + } + + image = LoadImage(fileName); + if(image) { + result = CreateIcon(); + result->name = CopyString(fileName); + result->image = image; + InsertIcon(result); + return result; + } else { + return NULL; + } + +} + +/**************************************************************************** + ****************************************************************************/ +ScaledIconNode *GetScaledIcon(IconNode *icon, int rwidth, int rheight) { + + XColor color; + ScaledIconNode *np; + GC maskGC; + int x, y; + int index; + int alpha; + double scalex, scaley; + double srcx, srcy; + double ratio; + int nwidth, nheight; + + Assert(icon); + Assert(icon->image); + + if(rwidth == 0) { + rwidth = icon->image->width; + } + if(rheight == 0) { + rheight = icon->image->height; + } + + ratio = (double)icon->image->width / icon->image->height; + nwidth = Min(rwidth, rheight * ratio); + nheight = Min(rheight, nwidth / ratio); + nwidth = nheight * ratio; + if(nwidth < 1) { + nwidth = 1; + } + if(nheight < 1) { + nheight = 1; + } + + /* Check if this size already exists. */ + for(np = icon->nodes; np; np = np->next) { + if(np->width == nwidth && np->height == nheight) { + return np; + } + } + + /* See if we can use XRender to create the icon. */ + np = CreateScaledRenderIcon(icon, nwidth, nheight); + if(np) { + return np; + } + + /* Create a new ScaledIconNode the old-fashioned way. */ + np = Allocate(sizeof(ScaledIconNode)); + np->width = nwidth; + np->height = nheight; + np->next = icon->nodes; +#ifdef USE_XRENDER + np->imagePicture = None; + np->maskPicture = None; +#endif + icon->nodes = np; + + np->mask = JXCreatePixmap(display, rootWindow, nwidth, nheight, 1); + maskGC = JXCreateGC(display, np->mask, 0, NULL); + np->image = JXCreatePixmap(display, rootWindow, nwidth, nheight, rootDepth); + + scalex = (double)icon->image->width / nwidth; + scaley = (double)icon->image->height / nheight; + + srcy = 0.0; + for(y = 0; y < nheight; y++) { + srcx = 0.0; + for(x = 0; x < nwidth; x++) { + + index = (int)srcy * icon->image->width + (int)srcx; + + alpha = (icon->image->data[index] >> 24) & 0xFFUL; + if(alpha >= 128) { + + color.red = ((icon->image->data[index] >> 16) & 0xFFUL) * 257; + color.green = ((icon->image->data[index] >> 8) & 0xFFUL) * 257; + color.blue = (icon->image->data[index] & 0xFFUL) * 257; + GetColor(&color); + + JXSetForeground(display, rootGC, color.pixel); + JXDrawPoint(display, np->image, rootGC, x, y); + + JXSetForeground(display, maskGC, 1); + + } else { + JXSetForeground(display, maskGC, 0); + } + JXDrawPoint(display, np->mask, maskGC, x, y); + + srcx += scalex; + + } + + srcy += scaley; + } + + JXFreeGC(display, maskGC); + + return np; + +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *CreateIconFromBinary(const unsigned long *input, + unsigned int length) { + + unsigned long height, width; + IconNode *result; + unsigned long *data; + unsigned int x; + + if(!input) { + return NULL; + } + + width = input[0]; + height = input[1]; + + if(width * height + 2 > length) { + Debug("invalid image size: %d x %d + 2 > %d", width, height, length); + return NULL; + } else if(width == 0 || height == 0) { + Debug("invalid image size: %d x %d", width, height); + return NULL; + } + + result = CreateIcon(); + + result->image = Allocate(sizeof(ImageNode)); + result->image->width = width; + result->image->height = height; + + result->image->data = Allocate(width * height * sizeof(unsigned long)); + data = (unsigned long*)result->image->data; + + /* Note: the data types here might be of different sizes. */ + for(x = 0; x < width * height; x++) { + data[x] = input[x + 2]; + } + + /* Don't insert this icon since it is transient. */ + + return result; + +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *CreateIcon() { + + IconNode *icon; + + icon = Allocate(sizeof(IconNode)); + icon->name = NULL; + icon->image = NULL; + icon->nodes = NULL; + icon->next = NULL; + icon->prev = NULL; + + return icon; + +} + +/**************************************************************************** + ****************************************************************************/ +void DoDestroyIcon(int index, IconNode *icon) { + + ScaledIconNode *np; + + if(icon) { + while(icon->nodes) { + np = icon->nodes->next; + +#ifdef USE_XRENDER + if(icon->nodes->imagePicture != None) { + JXRenderFreePicture(display, icon->nodes->imagePicture); + } + if(icon->nodes->maskPicture != None) { + JXRenderFreePicture(display, icon->nodes->maskPicture); + } +#endif + + if(icon->nodes->image != None) { + JXFreePixmap(display, icon->nodes->image); + } + if(icon->nodes->mask != None) { + JXFreePixmap(display, icon->nodes->mask); + } + + Release(icon->nodes); + icon->nodes = np; + } + + if(icon->name) { + Release(icon->name); + } + DestroyImage(icon->image); + + if(icon->prev) { + icon->prev->next = icon->next; + } else { + iconHash[index] = icon->next; + } + if(icon->next) { + icon->next->prev = icon->prev; + } + Release(icon); + } +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyIcon(IconNode *icon) { + + int index; + + if(icon && !icon->name) { + index = GetHash(icon->name); + DoDestroyIcon(index, icon); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void InsertIcon(IconNode *icon) { + + int index; + + Assert(icon); + Assert(icon->name); + + index = GetHash(icon->name); + + icon->prev = NULL; + if(iconHash[index]) { + iconHash[index]->prev = icon; + } + icon->next = iconHash[index]; + iconHash[index] = icon; + +} + +/**************************************************************************** + ****************************************************************************/ +IconNode *FindIcon(const char *name) { + + IconNode *icon; + int index; + + index = GetHash(name); + + icon = iconHash[index]; + while(icon) { + if(!strcmp(icon->name, name)) { + return icon; + } + icon = icon->next; + } + + return NULL; + +} + +/**************************************************************************** + ****************************************************************************/ +int GetHash(const char *str) { + + int x; + int hash = 0; + + if(!str || !str[0]) { + return hash % HASH_SIZE; + } + + for(x = 0; str[x]; x++) { + hash = (hash + str[x]) % HASH_SIZE; + } + + return hash; + +} + +#endif /* USE_ICONS */ + diff --git a/src/icon.h b/src/icon.h new file mode 100644 index 0000000..ad9645a --- /dev/null +++ b/src/icon.h @@ -0,0 +1,110 @@ +/** + * @file icon.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header file for icon functions. + * + */ + +#ifndef ICON_H +#define ICON_H + +struct ClientNode; + +/** Structure to hold a scaled icon. */ +typedef struct ScaledIconNode { + + int width; /**< The scaled width of the icon. */ + int height; /**< The scaled height of the icon. */ + + Pixmap image; + Pixmap mask; +#ifdef USE_XRENDER + Picture imagePicture; + Picture maskPicture; +#endif + + struct ScaledIconNode *next; + +} ScaledIconNode; + +/** Structure to hold an icon. */ +typedef struct IconNode { + + char *name; /**< The name of the icon. */ + struct ImageNode *image; /**< The image data. */ + struct ScaledIconNode *nodes; /**< Scaled versions of the icon. */ + + struct IconNode *next; /**< The next icon in the list. */ + struct IconNode *prev; /**< The previous icon in the list. */ + +} IconNode; + +#ifdef USE_ICONS + +/*@{*/ +void InitializeIcons(); +void StartupIcons(); +void ShutdownIcons(); +void DestroyIcons(); +/*@}*/ + +/** Add an icon path. + * This adds a path to the list of icon search paths. + * @param path The icon path to add. + */ +void AddIconPath(char *path); + +/** Render an icon. + * This will scale an icon if necessary to fit the requested size. The + * aspect ratio of the icon is preserved. + * @param icon The icon to render. + * @param d The drawable on which to place the icon. + * @param x The x offset on the drawable to render the icon. + * @param y The y offset on the drawable to render the icon. + * @param width The width of the icon to display. + * @param height The height of the icon to display. + */ +void PutIcon(IconNode *icon, Drawable d, int x, int y, + int width, int height); + +/** Load an icon for a client. + * @param np The client. + */ +void LoadIcon(struct ClientNode *np); + +/** Load an icon. + * @param name The name of the icon to load. + * @return A pointer to the icon (NULL if not found). + */ +IconNode *LoadNamedIcon(const char *name); + +/** Destroy an icon. + * @param icon The icon to destroy. + */ +void DestroyIcon(IconNode *icon); + +/** Create and initialize a new icon structure. + * @return The new icon structure. + */ +IconNode *CreateIcon(); + +#else + +#define ICON_DUMMY_FUNCTION 0 + +#define InitializeIcons() ICON_DUMMY_FUNCTION +#define StartupIcons() ICON_DUMMY_FUNCTION +#define ShutdownIcons() ICON_DUMMY_FUNCTION +#define DestroyIcons() ICON_DUMMY_FUNCTION +#define AddIconPath( a ) ICON_DUMMY_FUNCTION +#define PutIcon( a, b, c, d, e, f ) ICON_DUMMY_FUNCTION +#define LoadIcon( a ) ICON_DUMMY_FUNCTION +#define LoadNamedIcon( a ) ICON_DUMMY_FUNCTION +#define DestroyIcon( a ) ICON_DUMMY_FUNCTION + +#endif /* USE_ICONS */ + +#endif /* ICON_H */ + diff --git a/src/image.c b/src/image.c new file mode 100644 index 0000000..af52ae7 --- /dev/null +++ b/src/image.c @@ -0,0 +1,362 @@ +/**************************************************************************** + * Functions to load images. + * Copyright (C) 2005 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "image.h" +#include "main.h" +#include "error.h" +#include "color.h" + +static ImageNode *LoadPNGImage(const char *fileName); +static ImageNode *LoadXPMImage(const char *fileName); +static ImageNode *CreateImageFromXImages(XImage *image, XImage *shape); + +#ifdef USE_XPM +static int AllocateColor(Display *d, Colormap cmap, char *name, + XColor *c, void *closure); +static int FreeColors(Display *d, Colormap cmap, Pixel *pixels, int n, + void *closure); +#endif + +/**************************************************************************** + ****************************************************************************/ +ImageNode *LoadImage(const char *fileName) { + + ImageNode *result; + + if(!fileName) { + return NULL; + } + + /* Attempt to load the file as a PNG image. */ + result = LoadPNGImage(fileName); + if(result) { + return result; + } + + /* Attempt to load the file as an XPM image. */ + result = LoadXPMImage(fileName); + if(result) { + return result; + } + + return NULL; + +} + +/**************************************************************************** + ****************************************************************************/ +ImageNode *LoadImageFromData(char **data) { + + ImageNode *result = NULL; + +#ifdef USE_XPM + + XpmAttributes attr; + XImage *image; + XImage *shape; + int rc; + + Assert(data); + + attr.valuemask = XpmAllocColor | XpmFreeColors | XpmColorClosure; + attr.alloc_color = AllocateColor; + attr.free_colors = FreeColors; + attr.color_closure = NULL; + rc = XpmCreateImageFromData(display, data, &image, &shape, &attr); + if(rc == XpmSuccess) { + result = CreateImageFromXImages(image, shape); + JXDestroyImage(image); + if(shape) { + JXDestroyImage(shape); + } + } + +#endif + + return result; + +} + +/**************************************************************************** + * Load a PNG image from the given file name. Returns NULL on error. + * Since libpng uses longjmp, this function is not reentrant to simplify + * the issues surrounding longjmp and local variables. + ****************************************************************************/ +ImageNode *LoadPNGImage(const char *fileName) { + +#ifdef USE_PNG + + static ImageNode *result; + static FILE *fd; + static unsigned char **rows; + static png_structp pngData; + static png_infop pngInfo; + static png_infop pngEndInfo; + static unsigned char *data; + + unsigned char header[8]; + unsigned long rowBytes; + int bitDepth, colorType; + unsigned int x, y; + unsigned long temp; + + Assert(fileName); + + result = NULL; + fd = NULL; + rows = NULL; + pngData = NULL; + pngInfo = NULL; + pngEndInfo = NULL; + data = NULL; + + fd = fopen(fileName, "rb"); + if(!fd) { + return NULL; + } + + x = fread(header, 1, sizeof(header), fd); + if(x != sizeof(header) || png_sig_cmp(header, 0, sizeof(header))) { + fclose(fd); + return NULL; + } + + pngData = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!pngData) { + fclose(fd); + Warning("could not create read struct for PNG image: %s", fileName); + return NULL; + } + + if(setjmp(png_jmpbuf(pngData))) { + png_destroy_read_struct(&pngData, &pngInfo, &pngEndInfo); + if(fd) { + fclose(fd); + } + if(rows) { + Release(rows); + } + if(result) { + if(result->data) { + Release(result->data); + } + Release(result); + } + Warning("error reading PNG image: %s", fileName); + } + + pngInfo = png_create_info_struct(pngData); + if(!pngInfo) { + png_destroy_read_struct(&pngData, NULL, NULL); + fclose(fd); + Warning("could not create info struct for PNG image: %s", fileName); + return NULL; + } + + pngEndInfo = png_create_info_struct(pngData); + if(!pngEndInfo) { + png_destroy_read_struct(&pngData, &pngInfo, NULL); + fclose(fd); + Warning("could not create end info struct for PNG image: %s", fileName); + return NULL; + } + + png_init_io(pngData, fd); + png_set_sig_bytes(pngData, sizeof(header)); + + png_read_info(pngData, pngInfo); + + result = Allocate(sizeof(ImageNode)); + + png_get_IHDR(pngData, pngInfo, &result->width, &result->height, + &bitDepth, &colorType, NULL, NULL, NULL); + + png_set_expand(pngData); + + if(bitDepth == 16) { + png_set_strip_16(pngData); + } else if(bitDepth < 8) { + png_set_packing(pngData); + } + + png_set_swap_alpha(pngData); + png_set_filler(pngData, 0xFF, PNG_FILLER_BEFORE); + + if(colorType == PNG_COLOR_TYPE_GRAY + || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(pngData); + } + + png_read_update_info(pngData, pngInfo); + + rowBytes = png_get_rowbytes(pngData, pngInfo); + data = Allocate(rowBytes * result->height); + + rows = AllocateStack(result->height * sizeof(result->data)); + + y = 0; + for(x = 0; x < result->height; x++) { + rows[x] = &data[y]; + y += result->width * 4; + } + + png_read_image(pngData, rows); + + png_read_end(pngData, pngInfo); + png_destroy_read_struct(&pngData, &pngInfo, &pngEndInfo); + + fclose(fd); + + /* Convert the row data to ARGB format. */ + /* Source is stored ARGB bytes. */ + /* Destination is stored in unsigned longs with A most significant. */ + result->data = Allocate(sizeof(unsigned long) + * result->width * result->height); + for(y = 0; y < result->height; y++) { + for(x = 0; x < result->width; x++) { + temp = (unsigned long)rows[y][4 * x + 0] << 24; + temp |= (unsigned long)rows[y][4 * x + 1] << 16; + temp |= (unsigned long)rows[y][4 * x + 2] << 8; + temp |= (unsigned long)rows[y][4 * x + 3] << 0; + result->data[y * result->width + x] = temp; + } + } + + ReleaseStack(rows); + Release(data); + + return result; + +#else + + return NULL; + +#endif + +} + +/**************************************************************************** + ****************************************************************************/ +ImageNode *LoadXPMImage(const char *fileName) { + + ImageNode *result = NULL; + +#ifdef USE_XPM + + XpmAttributes attr; + XImage *image; + XImage *shape; + int rc; + + Assert(fileName); + + attr.valuemask = XpmAllocColor | XpmFreeColors | XpmColorClosure; + attr.alloc_color = AllocateColor; + attr.free_colors = FreeColors; + attr.color_closure = NULL; + rc = XpmReadFileToImage(display, (char*)fileName, &image, &shape, &attr); + if(rc == XpmSuccess) { + result = CreateImageFromXImages(image, shape); + + JXDestroyImage(image); + if(shape) { + JXDestroyImage(shape); + } + } + +#endif + + return result; + +} + +/**************************************************************************** + ****************************************************************************/ +ImageNode *CreateImageFromXImages(XImage *image, XImage *shape) { + + ImageNode *result; + XColor color; + unsigned long red, green, blue, alpha; + int index; + int x, y; + + result = Allocate(sizeof(ImageNode)); + result->data = Allocate(sizeof(unsigned long) + * image->width * image->height); + result->width = image->width; + result->height = image->height; + + index = 0; + for(y = 0; y < image->height; y++) { + for(x = 0; x < image->width; x++) { + + color.pixel = XGetPixel(image, x, y); + GetColorFromIndex(&color); + + red = color.red >> 8; + green = color.green >> 8; + blue = color.blue >> 8; + + alpha = 0; + if(!shape || XGetPixel(shape, x, y)) { + alpha = 255; + } + + result->data[index] = alpha << 24; + result->data[index] |= red << 16; + result->data[index] |= green << 8; + result->data[index] |= blue; + ++index; + + } + } + + return result; + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyImage(ImageNode *image) { + if(image) { + Release(image->data); + Release(image); + } +} + +/**************************************************************************** + * Function to allocate a color for libxpm. + ****************************************************************************/ +#ifdef USE_XPM +int AllocateColor(Display *d, Colormap cmap, char *name, + XColor *c, void *closure) +{ + + if(name) { + if(!JXParseColor(d, cmap, name, c)) { + return -1; + } + } + + GetColorIndex(c); + return 1; + +} +#endif + +/**************************************************************************** + * Function to free colors allocated by libxpm. + * We don't need to do anything here as color.c takes care of this. + ****************************************************************************/ +#ifdef USE_XPM +int FreeColors(Display *d, Colormap cmap, Pixel *pixels, int n, + void *closure) { + + return 1; + +} +#endif + diff --git a/src/image.h b/src/image.h new file mode 100644 index 0000000..1291e1b --- /dev/null +++ b/src/image.h @@ -0,0 +1,47 @@ +/** + * @file image.h + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Functions to load images. + * + */ + +#ifndef IMAGE_H +#define IMAGE_H + +/** Structure to represent an image. */ +typedef struct ImageNode { + +#ifdef USE_PNG + png_uint_32 width; /**< Width of the image. */ + png_uint_32 height; /**< Height of the image. */ +#else + int width; /**< Width of the image. */ + int height; /**< Height of the image. */ +#endif + + unsigned long *data; /**< Image data. */ + +} ImageNode; + +/** Load an image from a file. + * @param fileName The file containing the image. + * @return A new image node (NULL if the image could not be loaded). + */ +ImageNode *LoadImage(const char *fileName); + +/** Load an image from data. + * The data must be in the format from the EWMH spec. + * @param data The image data. + * @return A new image node (NULL if there were errors. + */ +ImageNode *LoadImageFromData(char **data); + +/** Destroy an image node. + * @param image The image to destroy. + */ +void DestroyImage(ImageNode *image); + +#endif + diff --git a/src/jwm.h b/src/jwm.h new file mode 100644 index 0000000..131528a --- /dev/null +++ b/src/jwm.h @@ -0,0 +1,128 @@ +/** + * @file jwm.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief The main JWM header file. + * + */ + +#ifndef JWM_H +#define JWM_H + +#include "../config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <limits.h> + +#ifdef HAVE_STDARG_H +# include <stdarg.h> +#endif +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef HAVE_TIME_H +# include <time.h> +#endif +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif + +#include <X11/Xlib.h> +#ifdef HAVE_X11_XUTIL_H +# include <X11/Xutil.h> +#endif +#ifdef HAVE_X11_XRESOURCE_H +# include <X11/Xresource.h> +#endif +#ifdef HAVE_X11_CURSORFONT_H +# include <X11/cursorfont.h> +#endif +#ifdef HAVE_X11_XPROTO_H +# include <X11/Xproto.h> +#endif +#ifdef HAVE_X11_XATOM_H +# include <X11/Xatom.h> +#endif +#ifdef HAVE_X11_KEYSYM_H +# include <X11/keysym.h> +#endif + +#ifdef USE_XPM +# include <X11/xpm.h> +#endif +#ifdef USE_PNG +# include <png.h> +#endif +#ifdef USE_SHAPE +# include <X11/extensions/shape.h> +#endif +#ifdef USE_XINERAMA +# include <X11/extensions/Xinerama.h> +#endif +#ifdef USE_XFT +# ifdef HAVE_FT2BUILD_H +# include <ft2build.h> +# endif +# include <X11/Xft/Xft.h> +#endif +#ifdef USE_XRENDER +# include <X11/extensions/Xrender.h> +#endif +#ifdef USE_FRIBIDI +# include <fribidi/fribidi.h> +# include <fribidi/fribidi_char_sets_utf8.h> +#endif + +#define MAX_DESKTOP_COUNT 8 + +#define MAX_INCLUDE_DEPTH 16 + +#define MAX_BORDER_WIDTH 32 +#define MIN_BORDER_WIDTH 3 +#define DEFAULT_BORDER_WIDTH 5 + +#define MAX_TITLE_HEIGHT 64 +#define MIN_TITLE_HEIGHT 2 +#define DEFAULT_TITLE_HEIGHT 21 + +#define MAX_DOUBLE_CLICK_DELTA 32 +#define MIN_DOUBLE_CLICK_DELTA 0 +#define DEFAULT_DOUBLE_CLICK_DELTA 2 + +#define MAX_DOUBLE_CLICK_SPEED 2000 +#define MIN_DOUBLE_CLICK_SPEED 1 +#define DEFAULT_DOUBLE_CLICK_SPEED 400 + +#define MAX_SNAP_DISTANCE 32 +#define MIN_SNAP_DISTANCE 1 +#define DEFAULT_SNAP_DISTANCE 5 + +#define MAX_TRAY_BORDER 32 +#define MIN_TRAY_BORDER 0 +#define DEFAULT_TRAY_BORDER 1 + +#define MOVE_DELTA 3 + +#define SHELL_NAME "/bin/sh" + +#define DEFAULT_MENU_TITLE "JWM" + +#define DEFAULT_DESKTOP_COUNT 4 + +#include "debug.h" +#include "jxlib.h" + +#endif + diff --git a/src/jxlib.h b/src/jxlib.h new file mode 100644 index 0000000..e306c2d --- /dev/null +++ b/src/jxlib.h @@ -0,0 +1,420 @@ +/** + * @file jxlib.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Macros to wrap X calls for debugging. + * + */ + +#ifndef JXLIB_H +#define JXLIB_H + +#define JXAddToSaveSet( a, b ) \ + ( SetCheckpoint(), XAddToSaveSet( a, b ) ) + +#define JXAllocColor( a, b, c ) \ + ( SetCheckpoint(), XAllocColor( a, b, c ) ) + +#define JXGetRGBColormaps( a, b, c, d, e ) \ + ( SetCheckpoint(), XGetRGBColormaps( a, b, c, d, e ) ) + +#define JXQueryColor( a, b, c ) \ + ( SetCheckpoint(), XQueryColor( a, b, c ) ) + +#define JXAllowEvents( a, b, c ) \ + ( SetCheckpoint(), XAllowEvents( a, b, c ) ) + +#define JXChangeProperty( a, b, c, d, e, f, g, h ) \ + ( SetCheckpoint(), XChangeProperty( a, b, c, d, e, f, g, h ) ) + +#define JXChangeWindowAttributes( a, b, c, d ) \ + ( SetCheckpoint(), XChangeWindowAttributes( a, b, c, d ) ) + +#define JXCheckTypedEvent( a, b, c ) \ + ( SetCheckpoint(), XCheckTypedEvent( a, b, c ) ) + +#define JXCheckTypedWindowEvent( a, b, c, d ) \ + ( SetCheckpoint(), XCheckTypedWindowEvent( a, b, c, d ) ) + +#define JXClearWindow( a, b ) \ + ( SetCheckpoint(), XClearWindow( a, b ) ) + +#define JXCloseDisplay( a ) \ + ( SetCheckpoint(), XCloseDisplay( a ) ) + +#define JXConfigureWindow( a, b, c, d ) \ + ( SetCheckpoint(), XConfigureWindow( a, b, c, d ) ) + +#define JXConnectionNumber( a ) \ + ( SetCheckpoint(), XConnectionNumber( a ) ) + +#define JXCopyArea( a, b, c, d, e, f, g, h, i, j ) \ + ( SetCheckpoint(), XCopyArea( a, b, c, d, e, f, g, h, i, j ) ) + +#define JXCopyPlane( a, b, c, d, e, f, g, h, i, j, k ) \ + ( SetCheckpoint(), XCopyPlane( a, b, c, d, e, f, g, h, i, j, k ) ) + +#define JXCreateFontCursor( a, b ) \ + ( SetCheckpoint(), XCreateFontCursor( a, b ) ) + +#define JXCreateGC( a, b, c, d ) \ + ( SetCheckpoint(), XCreateGC( a, b, c, d ) ) + +#define JXCreateImage( a, b, c, d, e, f, g, h, i, j ) \ + ( \ + SetCheckpoint(), \ + XCreateImage( a, b, c, d, e, f, g, h, i, j ) \ + ) + +#define JXCreatePixmap( a, b, c, d, e ) \ + ( SetCheckpoint(), XCreatePixmap( a, b, c, d, e ) ) + +#define JXCreatePixmapFromBitmapData( a, b, c, d, e, f, g, h ) \ + ( \ + SetCheckpoint(), \ + XCreatePixmapFromBitmapData( a, b, c, d, e, f, g, h ) \ + ) + +#define JXCreateSimpleWindow( a, b, c, d, e, f, g, h, i ) \ + ( \ + SetCheckpoint(), \ + XCreateSimpleWindow( a, b, c, d, e, f, g, h, i ) \ + ) + +#define JXCreateWindow( a, b, c, d, e, f, g, h, i, j, k, l ) \ + ( \ + SetCheckpoint(), \ + XCreateWindow( a, b, c, d, e, f, g, h, i, j, k, l ) \ + ) + +#define JXDefineCursor( a, b, c ) \ + ( SetCheckpoint(), XDefineCursor( a, b, c ) ) + +#define JXDestroyImage( a ) \ + ( SetCheckpoint(), XDestroyImage( a ) ) + +#define JXDestroyWindow( a, b ) \ + ( SetCheckpoint(), XDestroyWindow( a, b ) ) + +#define JXDrawPoint( a, b, c, d, e ) \ + ( SetCheckpoint(), XDrawPoint( a, b, c, d, e ) ) + +#define JXDrawLine( a, b, c, d, e, f, g ) \ + ( SetCheckpoint(), XDrawLine( a, b, c, d, e, f, g ) ) + +#define JXDrawRectangle( a, b, c, d, e, f, g ) \ + ( SetCheckpoint(), XDrawRectangle( a, b, c, d, e, f, g ) ) + +#define JXDrawString( a, b, c, d, e, f, g ) \ + ( SetCheckpoint(), XDrawString( a, b, c, d, e, f, g ) ) + +#define JXFetchName( a, b, c ) \ + ( SetCheckpoint(), XFetchName( a, b, c ) ) + +#define JXFillRectangle( a, b, c, d, e, f, g ) \ + ( SetCheckpoint(), XFillRectangle( a, b, c, d, e, f, g ) ) + +#define JXFlush( a ) \ + ( SetCheckpoint(), XFlush( a ) ) + +#define JXFree( a ) \ + ( SetCheckpoint(), XFree( a ) ) + +#define JXFreeColors( a, b, c, d, e ) \ + ( SetCheckpoint(), XFreeColors( a, b, c, d, e ) ) + +#define JXFreeCursor( a, b ) \ + ( SetCheckpoint(), XFreeCursor( a, b ) ) + +#define JXFreeFont( a, b ) \ + ( SetCheckpoint(), XFreeFont( a, b ) ) + +#define JXFreeGC( a, b ) \ + ( SetCheckpoint(), XFreeGC( a, b ) ) + +#define JXFreeModifiermap( a ) \ + ( SetCheckpoint(), XFreeModifiermap( a ) ) + +#define JXFreePixmap( a, b ) \ + ( SetCheckpoint(), XFreePixmap( a, b ) ) + +#define JXGetAtomName( a, b ) \ + ( SetCheckpoint(), XGetAtomName( a, b ) ) + +#define JXGetModifierMapping( a ) \ + ( SetCheckpoint(), XGetModifierMapping( a ) ) + +#define JXGetSubImage( a, b, c, d, e, f, g, h, i, j, k ) \ + ( SetCheckpoint(), XGetSubImage( a, b, c, d, e, f, g, h, i, j, k ) ) + +#define JXGetTransientForHint( a, b, c ) \ + ( SetCheckpoint(), XGetTransientForHint( a, b, c ) ) + +#define JXGetClassHint( a, b, c ) \ + ( SetCheckpoint(), XGetClassHint( a, b, c ) ) + +#define JXGetWindowAttributes( a, b, c ) \ + ( SetCheckpoint(), XGetWindowAttributes( a, b, c ) ) + +#define JXGetWindowProperty( a, b, c, d, e, f, g, h, i, j, k, l ) \ + ( SetCheckpoint(), \ + XGetWindowProperty( a, b, c, d, e, f, g, h, i, j, k, l ) ) + +#define JXGetWMColormapWindows( a, b, c, d ) \ + ( SetCheckpoint(), XGetWMColormapWindows( a, b, c, d ) ) + +#define JXGetWMNormalHints( a, b, c, d ) \ + ( SetCheckpoint(), XGetWMNormalHints( a, b, c, d ) ) + +#define JXSetIconSizes( a, b, c, d ) \ + ( SetCheckpoint(), XSetIconSizes( a, b, c, d ) ) + +#define JXSetWindowBorder( a, b, c ) \ + ( SetCheckpoint(), XSetWindowBorder( a, b, c ) ) + +#define JXGetWMHints( a, b ) \ + ( SetCheckpoint(), XGetWMHints( a, b ) ) + +#define JXGrabButton( a, b, c, d, e, f, g, h, i, j ) \ + ( SetCheckpoint(), XGrabButton( a, b, c, d, e, f, g, h, i, j ) ) + +#define JXKeycodeToKeysym( a, b, c ) \ + ( SetCheckpoint(), XKeycodeToKeysym( a, b, c ) ) + +#define JXGrabKey( a, b, c, d, e, f, g ) \ + ( SetCheckpoint(), XGrabKey( a, b, c, d, e, f, g ) ) + +#define JXUngrabKey( a, b, c, d ) \ + ( SetCheckpoint(), XUngrabKey( a, b, c, d ) ) + +#define JXGrabKeyboard( a, b, c, d, e, f ) \ + ( SetCheckpoint(), XGrabKeyboard( a, b, c, d, e, f ) ) + +#define JXGrabPointer( a, b, c, d, e, f, g, h, i ) \ + ( SetCheckpoint(), XGrabPointer( a, b, c, d, e, f, g, h, i ) ) + +#define JXGrabServer( a ) \ + ( SetCheckpoint(), XGrabServer( a ) ) + +#define JXInstallColormap( a, b ) \ + ( SetCheckpoint(), XInstallColormap( a, b ) ) + +#define JXInternAtom( a, b, c ) \ + ( SetCheckpoint(), XInternAtom( a, b, c ) ) + +#define JXKeysymToKeycode( a, b ) \ + ( SetCheckpoint(), XKeysymToKeycode( a, b ) ) + +#define JXKillClient( a, b ) \ + ( SetCheckpoint(), XKillClient( a, b ) ) + +#define JXLoadQueryFont( a, b ) \ + ( SetCheckpoint(), XLoadQueryFont( a, b ) ) + +#define JXMapRaised( a, b ) \ + ( SetCheckpoint(), XMapRaised( a, b ) ) + +#define JXMapWindow( a, b ) \ + ( SetCheckpoint(), XMapWindow( a, b ) ) + +#define JXMoveResizeWindow( a, b, c, d, e, f ) \ + ( SetCheckpoint(), XMoveResizeWindow( a, b, c, d, e, f ) ) + +#define JXMoveWindow( a, b, c, d ) \ + ( SetCheckpoint(), XMoveWindow( a, b, c, d ) ) + +#define JXNextEvent( a, b ) \ + ( SetCheckpoint(), XNextEvent( a, b ) ) + +#define JXMaskEvent( a, b, c ) \ + ( SetCheckpoint(), XMaskEvent( a, b, c ) ) + +#define JXCheckMaskEvent( a, b, c ) \ + ( SetCheckpoint(), XCheckMaskEvent( a, b, c ) ) + +#define JXOpenDisplay( a ) \ + ( SetCheckpoint(), XOpenDisplay( a ) ) + +#define JXParseColor( a, b, c, d ) \ + ( SetCheckpoint(), XParseColor( a, b, c, d ) ) + +#define JXPending( a ) \ + ( SetCheckpoint(), XPending( a ) ) + +#define JXPutBackEvent( a, b ) \ + ( SetCheckpoint(), XPutBackEvent( a, b ) ) + +#define JXGetImage( a, b, c, d, e, f, g, h ) \ + ( SetCheckpoint(), XGetImage( a, b, c, d, e, f, g, h ) ) + +#define JXPutImage( a, b, c, d, e, f, g, h, i, j ) \ + ( SetCheckpoint(), XPutImage( a, b, c, d, e, f, g, h, i, j ) ) + +#define JXQueryPointer( a, b, c, d, e, f, g, h, i ) \ + ( SetCheckpoint(), XQueryPointer( a, b, c, d, e, f, g, h, i ) ) + +#define JXQueryTree( a, b, c, d, e, f ) \ + ( SetCheckpoint(), XQueryTree( a, b, c, d, e, f ) ) + +#define JXReparentWindow( a, b, c, d, e ) \ + ( SetCheckpoint(), XReparentWindow( a, b, c, d, e ) ) + +#define JXRemoveFromSaveSet( a, b ) \ + ( SetCheckpoint(), XRemoveFromSaveSet( a, b ) ) + +#define JXResizeWindow( a, b, c, d ) \ + ( SetCheckpoint(), XResizeWindow( a, b, c, d ) ) + +#define JXRestackWindows( a, b, c ) \ + ( SetCheckpoint(), XRestackWindows( a, b, c ) ) + +#define JXSelectInput( a, b, c ) \ + ( SetCheckpoint(), XSelectInput( a, b, c ) ) + +#define JXSendEvent( a, b, c, d, e ) \ + ( SetCheckpoint(), XSendEvent( a, b, c, d, e ) ) + +#define JXSetBackground( a, b, c ) \ + ( SetCheckpoint(), XSetBackground( a, b, c ) ) + +#define JXSetClipMask( a, b, c ) \ + ( SetCheckpoint(), XSetClipMask( a, b, c ) ) + +#define JXSetClipOrigin( a, b, c, d ) \ + ( SetCheckpoint(), XSetClipOrigin( a, b, c, d) ) + +#define JXSetClipRectangles( a, b, c, d, e, f, g ) \ + ( SetCheckpoint(), XSetClipRectangles( a, b, c, d, e, f, g ) ) + +#define JXSetErrorHandler( a ) \ + ( SetCheckpoint(), XSetErrorHandler( a ) ) + +#define JXSetFont( a, b, c ) \ + ( SetCheckpoint(), XSetFont( a, b, c ) ) + +#define JXSetForeground( a, b, c ) \ + ( SetCheckpoint(), XSetForeground( a, b, c ) ) + +#define JXSetInputFocus( a, b, c, d ) \ + ( SetCheckpoint(), XSetInputFocus( a, b, c, d ) ) + +#define JXSetWindowBackground( a, b, c ) \ + ( SetCheckpoint(), XSetWindowBackground( a, b, c ) ) + +#define JXSetWindowBorderWidth( a, b, c ) \ + ( SetCheckpoint(), XSetWindowBorderWidth( a, b, c ) ) + +#define JXSetWMNormalHints( a, b, c ) \ + ( SetCheckpoint(), XSetWMNormalHints( a, b, c ) ) + +#define JXShapeCombineRectangles( a, b, c, d, e, f, g, h, i ) \ + ( SetCheckpoint(), XShapeCombineRectangles( a, b, c, d, e, f, g, h, i ) ) + +#define JXShapeCombineShape( a, b, c, d, e, f, g, h ) \ + ( SetCheckpoint(), XShapeCombineShape( a, b, c, d, e, f, g, h ) ) + +#define JXShapeQueryExtension( a, b, c ) \ + ( SetCheckpoint(), XShapeQueryExtension( a, b, c ) ) + +#define JXShapeQueryExtents( a, b, c, d, e, f, g, h, i, j, k, l ) \ + ( SetCheckpoint(), \ + XShapeQueryExtents( a, b, c, d, e, f, g, h, i, j, k, l ) ) + +#define JXShapeSelectInput( a, b, c ) \ + ( SetCheckpoint(), XShapeSelectInput( a, b, c ) ) + +#define JXStoreName( a, b, c ) \ + ( SetCheckpoint(), XStoreName( a, b, c ) ) + +#define JXStringToKeysym( a ) \ + ( SetCheckpoint(), XStringToKeysym( a ) ) + +#define JXSync( a, b ) \ + ( SetCheckpoint(), XSync( a, b ) ) + +#define JXTextWidth( a, b, c ) \ + ( SetCheckpoint(), XTextWidth( a, b, c ) ) + +#define JXUngrabButton( a, b, c, d ) \ + ( SetCheckpoint(), XUngrabButton( a, b, c, d ) ) + +#define JXUngrabKeyboard( a, b ) \ + ( SetCheckpoint(), XUngrabKeyboard( a, b ) ) + +#define JXUngrabPointer( a, b ) \ + ( SetCheckpoint(), XUngrabPointer( a, b ) ) + +#define JXUngrabServer( a ) \ + ( SetCheckpoint(), XUngrabServer( a ) ) + +#define JXUnmapWindow( a, b ) \ + ( SetCheckpoint(), XUnmapWindow( a, b ) ) + +#define JXWarpPointer( a, b, c, d, e, f, g, h, i ) \ + ( SetCheckpoint(), XWarpPointer( a, b, c, d, e, f, g, h, i ) ) + +#define JXSetSelectionOwner( a, b, c, d ) \ + ( SetCheckpoint(), XSetSelectionOwner( a, b, c, d ) ) + +#define JXGetSelectionOwner( a, b ) \ + ( SetCheckpoint(), XGetSelectionOwner( a, b ) ) + +/* XFT */ + +#define JXftFontOpenName( a, b, c ) \ + ( SetCheckpoint(), XftFontOpenName( a, b, c ) ) + +#define JXftFontOpenXlfd( a, b, c ) \ + ( SetCheckpoint(), XftFontOpenXlfd( a, b, c ) ) + +#define JXftDrawCreate( a, b, c, d ) \ + ( SetCheckpoint(), XftDrawCreate( a, b, c, d ) ) + +#define JXftDrawDestroy( a ) \ + ( SetCheckpoint(), XftDrawDestroy( a ) ) + +#define JXftTextExtentsUtf8( a, b, c, d, e ) \ + ( SetCheckpoint(), XftTextExtentsUtf8( a, b, c, d, e ) ) + +#define JXftDrawChange( a, b ) \ + ( SetCheckpoint(), XftDrawChange( a, b ) ) + +#define JXftDrawSetClipRectangles( a, b, c, d, e ) \ + ( SetCheckpoint(), XftDrawSetClipRectangles( a, b, c, d, e ) ) + +#define JXftDrawStringUtf8( a, b, c, d, e, f, g ) \ + ( SetCheckpoint(), XftDrawStringUtf8( a, b, c, d, e, f, g ) ) + +#define JXftColorFree( a, b, c, d ) \ + ( SetCheckpoint(), XftColorFree( a, b, c, d ) ) + +#define JXftColorAllocValue( a, b, c, d, e ) \ + ( SetCheckpoint(), XftColorAllocValue( a, b, c, d, e ) ) + +#define JXftFontClose( a, b ) \ + ( SetCheckpoint(), XftFontClose( a, b ) ) + +/* Xrender */ + +#define JXRenderQueryExtension( a, b, c ) \ + ( SetCheckpoint(), XRenderQueryExtension( a, b, c ) ) + +#define JXRenderFindVisualFormat( a, b ) \ + ( SetCheckpoint(), XRenderFindVisualFormat( a, b ) ) + +#define JXRenderFindFormat( a, b, c, d ) \ + ( SetCheckpoint(), XRenderFindFormat( a, b, c, d ) ) + +#define JXRenderCreatePicture( a, b, c, d, e ) \ + ( SetCheckpoint(), XRenderCreatePicture( a, b, c, d, e ) ) + +#define JXRenderFreePicture( a, b ) \ + ( SetCheckpoint(), XRenderFreePicture( a, b ) ) + +#define JXRenderComposite( a, b, c, d, e, f, g, h, i, j, k, l, m ) \ + ( SetCheckpoint(), \ + XRenderComposite( a, b, c, d, e, f, g, h, i, j, k, l, m) ) + +#endif /* JXLIB_H */ + diff --git a/src/key.c b/src/key.c new file mode 100644 index 0000000..b6e9ee6 --- /dev/null +++ b/src/key.c @@ -0,0 +1,452 @@ +/*************************************************************************** + * Key input functions. + * Copyright (C) 2004 Joe Wingbermuehle + ***************************************************************************/ + +#include "jwm.h" +#include "key.h" +#include "main.h" +#include "client.h" +#include "root.h" +#include "error.h" +#include "tray.h" +#include "misc.h" + +typedef enum { + MASK_NONE = 0, + MASK_ALT = 1, + MASK_CTRL = 2, + MASK_SHIFT = 4, + MASK_HYPER = 8, + MASK_META = 16, + MASK_SUPER = 32 +} MaskType; + +typedef struct KeyNode { + + /* These are filled in when the configuration file is parsed */ + int key; + unsigned int mask; + KeySym symbol; + char *command; + struct KeyNode *next; + + /* This is filled in by StartupKeys if it isn't already set. */ + KeyCode code; + + /* This is filled in by StartupKeys. */ + unsigned int state; + +} KeyNode; + +typedef struct LockNode { + KeySym symbol; + unsigned int mask; +} LockNode; + +static XModifierKeymap *modmap; + +static LockNode mods[] = { + { XK_Caps_Lock, 0 }, + { XK_Num_Lock, 0 } +}; + +static KeyNode *bindings; +static unsigned int modifierMask; + +static unsigned int GetModifierMask(KeySym key); +static unsigned int ParseModifierString(const char *str); +static KeySym ParseKeyString(const char *str); +static int ShouldGrab(KeyType key); +static void GrabKey(KeyNode *np); + +/*************************************************************************** + ***************************************************************************/ +void InitializeKeys() { + + bindings = NULL; + modifierMask = 0; + +} + +/*************************************************************************** + ***************************************************************************/ +void StartupKeys() { + + KeyNode *np; + int x; + + modmap = JXGetModifierMapping(display); + + for(x = 0; x < sizeof(mods) / sizeof(mods[0]); x++) { + mods[x].mask = GetModifierMask(mods[x].symbol); + modifierMask |= mods[x].mask; + } + + for(np = bindings; np; np = np->next) { + + np->state = 0; + if(np->mask & MASK_ALT) { + np->state |= GetModifierMask(XK_Alt_L); + } + if(np->mask & MASK_CTRL) { + np->state |= GetModifierMask(XK_Control_L); + } + if(np->mask & MASK_SHIFT) { + np->state |= GetModifierMask(XK_Shift_L); + } + if(np->mask & MASK_HYPER) { + np->state |= GetModifierMask(XK_Hyper_L); + } + if(np->mask & MASK_META) { + np->state |= GetModifierMask(XK_Meta_L); + } + if(np->mask & MASK_SUPER) { + np->state |= GetModifierMask(XK_Super_L); + } + + if(!np->code) { + np->code = JXKeysymToKeycode(display, np->symbol); + } + + if(ShouldGrab(np->key)) { + GrabKey(np); + } + + } + + JXFreeModifiermap(modmap); + +} + +/*************************************************************************** + ***************************************************************************/ +void ShutdownKeys() { + + ClientNode *np; + int layer; + + for(layer = 0; layer < LAYER_COUNT; layer++) { + for(np = nodes[layer]; np; np = np->next) { + JXUngrabKey(display, AnyKey, AnyModifier, np->window); + } + } + + JXUngrabKey(display, AnyKey, AnyModifier, rootWindow); + +} + +/*************************************************************************** + ***************************************************************************/ +void DestroyKeys() { + + KeyNode *np; + + while(bindings) { + np = bindings->next; + if(bindings->command) { + Release(bindings->command); + } + Release(bindings); + bindings = np; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void GrabKey(KeyNode *np) { + + TrayType *tp; + int x; + int index, maxIndex; + unsigned int mask; + + maxIndex = 1 << (sizeof(mods) / sizeof(mods[0])); + for(index = 0; index < maxIndex; index++) { + + mask = 0; + for(x = 0; x < sizeof(mods) / sizeof(mods[0]); x++) { + if(index & (1 << x)) { + mask |= mods[x].mask; + } + } + + mask |= np->state; + JXGrabKey(display, np->code, mask, + rootWindow, True, GrabModeAsync, GrabModeAsync); + for(tp = GetTrays(); tp; tp = tp->next) { + JXGrabKey(display, np->code, mask, + tp->window, True, GrabModeAsync, GrabModeAsync); + } + + } + +} + +/*************************************************************************** + ***************************************************************************/ +KeyType GetKey(const XKeyEvent *event) { + + KeyNode *np; + unsigned int state; + + state = event->state & ~modifierMask; + for(np = bindings; np; np = np->next) { + if(np->state == state && np->code == event->keycode) { + return np->key; + } + } + + return KEY_NONE; + +} + +/*************************************************************************** + ***************************************************************************/ +void RunKeyCommand(const XKeyEvent *event) { + + KeyNode *np; + + for(np = bindings; np; np = np->next) { + if(np->state == event->state && np->code == event->keycode) { + RunCommand(np->command); + return; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ShowKeyMenu(const XKeyEvent *event) { + + KeyNode *np; + int button; + + for(np = bindings; np; np = np->next) { + if(np->state == event->state && np->code == event->keycode) { + button = atoi(np->command); + if(button >= 0 && button <= 9) { + ShowRootMenu(button, 0, 0); + } + return; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +int ShouldGrab(KeyType key) { + switch(key & 0xFF) { + case KEY_NEXT: + case KEY_NEXT_STACKED: + case KEY_CLOSE: + case KEY_MIN: + case KEY_MAX: + case KEY_SHADE: + case KEY_MOVE: + case KEY_RESIZE: + case KEY_ROOT: + case KEY_WIN: + case KEY_DESKTOP: + case KEY_EXEC: + case KEY_RESTART: + case KEY_EXIT: + return 1; + default: + return 0; + } +} + +/*************************************************************************** + ***************************************************************************/ +void GrabKeys(ClientNode *np) { + + KeyNode *kp; + + for(kp = bindings; kp; kp = kp->next) { + if(ShouldGrab(kp->key)) { + JXGrabKey(display, kp->code, kp->state, + np->window, True, GrabModeAsync, GrabModeAsync); + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +unsigned int GetModifierMask(KeySym key) { + + KeyCode temp; + int x; + + temp = JXKeysymToKeycode(display, key); + for(x = 0; x < 8 * modmap->max_keypermod; x++) { + if(modmap->modifiermap[x] == temp) { + return 1 << (x / modmap->max_keypermod); + } + } + + Warning("modifier not found for keysym 0x%0x", key); + + return 0; + +} + +/*************************************************************************** + ***************************************************************************/ +unsigned int ParseModifierString(const char *str) { + unsigned int mask; + int x; + + if(!str) { + return MASK_NONE; + } + + mask = MASK_NONE; + for(x = 0; str[x]; x++) { + switch(str[x]) { + case 'A': + mask |= MASK_ALT; + break; + case 'C': + mask |= MASK_CTRL; + break; + case 'S': + mask |= MASK_SHIFT; + break; + case 'H': + mask |= MASK_HYPER; + break; + case 'M': + mask |= MASK_META; + break; + case 'P': + mask |= MASK_SUPER; + break; + default: + Warning("invalid modifier: \"%c\"", str[x]); + break; + } + } + + return mask; + +} + +/*************************************************************************** + ***************************************************************************/ +KeySym ParseKeyString(const char *str) { + + KeySym symbol; + + symbol = JXStringToKeysym(str); + if(symbol == NoSymbol) { + Warning("invalid key symbol: \"%s\"", str); + } + + return symbol; + +} + +/*************************************************************************** + ***************************************************************************/ +void InsertBinding(KeyType key, const char *modifiers, + const char *stroke, const char *code, const char *command) { + + KeyNode *np; + unsigned int mask; + char *temp; + int offset; + KeySym sym; + + mask = ParseModifierString(modifiers); + + if(stroke && strlen(stroke) > 0) { + + for(offset = 0; stroke[offset]; offset++) { + if(stroke[offset] == '#') { + + temp = CopyString(stroke); + + for(temp[offset] = '1'; temp[offset] <= '9'; temp[offset]++) { + + sym = ParseKeyString(temp); + if(sym == NoSymbol) { + Release(temp); + return; + } + + np = Allocate(sizeof(KeyNode)); + np->next = bindings; + bindings = np; + + np->key = key | ((temp[offset] - '1' + 1) << 8); + np->mask = mask; + np->symbol = sym; + np->command = NULL; + np->code = 0; + + } + + Release(temp); + + return; + } + } + + sym = ParseKeyString(stroke); + if(sym == NoSymbol) { + return; + } + + np = Allocate(sizeof(KeyNode)); + np->next = bindings; + bindings = np; + + np->key = key; + np->mask = mask; + np->symbol = sym; + np->command = CopyString(command); + np->code = 0; + + } else if(code && strlen(code) > 0) { + + np = Allocate(sizeof(KeyNode)); + np->next = bindings; + bindings = np; + + np->key = key; + np->mask = mask; + np->symbol = NoSymbol; + np->command = CopyString(command); + np->code = atoi(code); + + } else { + + Warning("neither key nor keycode specified for Key"); + + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ValidateKeys() { + + KeyNode *kp; + int bindex; + + for(kp = bindings; kp; kp = kp->next) { + if((kp->key & 0xFF) == KEY_ROOT && kp->command) { + bindex = atoi(kp->command); + if(!IsRootMenuDefined(bindex)) { + Warning("key binding: root menu %d not defined", bindex); + } + } + } + +} + diff --git a/src/key.h b/src/key.h new file mode 100644 index 0000000..a18aa46 --- /dev/null +++ b/src/key.h @@ -0,0 +1,57 @@ +/** + * @file key.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the key binding functions. + * + */ + +#ifndef KEY_H +#define KEY_H + +struct ClientNode; + +typedef enum { + KEY_NONE = 0, + KEY_UP = 1, + KEY_DOWN = 2, + KEY_RIGHT = 3, + KEY_LEFT = 4, + KEY_ESC = 5, + KEY_ENTER = 6, + KEY_NEXT = 7, + KEY_NEXT_STACKED = 8, + KEY_CLOSE = 9, + KEY_MIN = 10, + KEY_MAX = 11, + KEY_SHADE = 12, + KEY_MOVE = 13, + KEY_RESIZE = 14, + KEY_ROOT = 15, + KEY_WIN = 16, + KEY_DESKTOP = 17, + KEY_EXEC = 18, + KEY_RESTART = 19, + KEY_EXIT = 20 +} KeyType; + +void InitializeKeys(); +void StartupKeys(); +void ShutdownKeys(); +void DestroyKeys(); + +KeyType GetKey(const XKeyEvent *event); +void GrabKeys(struct ClientNode *np); + +void InsertBinding(KeyType key, const char *modifiers, + const char *stroke, const char *code, const char *command); + +void RunKeyCommand(const XKeyEvent *event); + +void ShowKeyMenu(const XKeyEvent *event); + +void ValidateKeys(); + +#endif + diff --git a/src/lex.c b/src/lex.c new file mode 100644 index 0000000..0e0f65d --- /dev/null +++ b/src/lex.c @@ -0,0 +1,572 @@ +/***************************************************************************** + * XML lexer functions. + * Copyright (C) 2004 Joe Wingbermuehle + *****************************************************************************/ + +#include "jwm.h" +#include "lex.h" +#include "error.h" +#include "misc.h" + +static const int BLOCK_SIZE = 16; + +/* Order is important! The order must match the order in lex.h */ +static const char *TOKEN_MAP[] = { + "[invalid]", + "ActiveBackground", + "ActiveForeground", + "Background", + "BorderStyle", + "Class", + "Clock", + "ClockStyle", + "Close", + "Desktops", + "Dock", + "DoubleClickSpeed", + "DoubleClickDelta", + "Exit", + "FocusModel", + "Font", + "Foreground", + "Group", + "Height", + "IconPath", + "Include", + "JWM", + "Key", + "Kill", + "Layer", + "Maximize", + "Menu", + "MenuStyle", + "Minimize", + "Mouse", + "Move", + "MoveMode", + "Name", + "Option", + "Outline", + "Pager", + "PagerStyle", + "Popup", + "PopupStyle", + "Program", + "Resize", + "ResizeMode", + "Restart", + "RestartCommand", + "RootMenu", + "SendTo", + "Separator", + "Shade", + "ShutdownCommand", + "SnapMode", + "StartupCommand", + "Stick", + "Swallow", + "TaskListStyle", + "TaskList", + "Theme", + "ThemePath", + "Tray", + "TrayButton", + "TrayButtonStyle", + "TrayStyle", + "Width" +}; + +static TokenNode *head, *current; + +static TokenNode *CreateNode(TokenNode *parent, const char *file, int line); +static AttributeNode *CreateAttribute(TokenNode *np); + +static int IsElementEnd(char ch); +static int IsValueEnd(char ch); +static int IsAttributeEnd(char ch); +static int IsSpace(char ch, int *lineNumber); +static char *ReadElementName(const char *line); +static char *ReadElementValue(const char *line, + const char *file, int *lineNumber); +static char *ReadAttributeValue(const char *line, const char *file, + int *lineNumber); +static int ParseEntity(const char *entity, char *ch, + const char *file, int line); +static TokenType LookupType(const char *name, TokenNode *np); + +/***************************************************************************** + *****************************************************************************/ +TokenNode *Tokenize(const char *line, const char *fileName) { + + TokenNode *np; + AttributeNode *ap; + char *temp; + int inElement; + int x; + int found; + int lineNumber; + + head = NULL; + current = NULL; + inElement = 0; + lineNumber = 1; + + x = 0; + /* Skip any initial white space */ + while(IsSpace(line[x], &lineNumber)) ++x; + + /* Skip any XML stuff */ + if(!strncmp(line + x, "<?", 2)) { + while(line[x]) { + if(line[x] == '\n') { + ++lineNumber; + } + if(!strncmp(line + x, "?>", 2)) { + x += 2; + break; + } + ++x; + } + } + + while(line[x]) { + + do { + + while(IsSpace(line[x], &lineNumber)) ++x; + + /* Skip comments */ + found = 0; + if(!strncmp(line + x, "<!--", 4)) { + while(line[x]) { + if(line[x] == '\n') { + ++lineNumber; + } + if(!strncmp(line + x, "-->", 3)) { + x += 3; + found = 1; + break; + } + ++x; + } + } + } while(found); + + switch(line[x]) { + case '<': + ++x; + if(line[x] == '/') { + ++x; + temp = ReadElementName(line + x); + + if(current) { + + if(temp) { + + if(current->type != LookupType(temp, NULL)) { + Warning("%s[%d]: close tag \"%s\" does not " + "match open tag \"%s\"", + fileName, lineNumber, temp, + GetTokenName(current)); + } + + } else { + Warning("%s[%d]: unexpected and invalid close tag", + fileName, lineNumber); + } + + current = current->parent; + } else { + if(temp) { + Warning("%s[%d]: close tag \"%s\" without open " + "tag", fileName, lineNumber, temp); + } else { + Warning("%s[%d]: invalid close tag", fileName, lineNumber); + } + } + + if(temp) { + x += strlen(temp); + Release(temp); + } + + } else { + np = current; + current = NULL; + np = CreateNode(np, fileName, lineNumber); + temp = ReadElementName(line + x); + if(temp) { + x += strlen(temp); + LookupType(temp, np); + Release(temp); + } else { + Warning("%s[%d]: invalid open tag", fileName, lineNumber); + } + } + inElement = 1; + break; + case '/': + if(inElement) { + ++x; + if(line[x] == '>' && current) { + ++x; + current = current->parent; + inElement = 0; + } else { + Warning("%s[%d]: invalid tag", fileName, lineNumber); + } + } else { + goto ReadDefault; + } + break; + case '>': + ++x; + inElement = 0; + break; + default: +ReadDefault: + if(inElement) { + ap = CreateAttribute(current); + ap->name = ReadElementName(line + x); + if(ap->name) { + x += strlen(ap->name); + if(line[x] == '=') { + ++x; + } + if(line[x] == '\"') { + ++x; + } + ap->value = ReadAttributeValue(line + x, fileName, + &lineNumber); + if(ap->value) { + x += strlen(ap->value); + } + if(line[x] == '\"') { + ++x; + } + } + } else { + temp = ReadElementValue(line + x, fileName, &lineNumber); + if(temp) { + x += strlen(temp); + if(current) { + if(current->value) { + current->value = Reallocate(current->value, + strlen(current->value) + strlen(temp) + 1); + strcat(current->value, temp); + Release(temp); + } else { + current->value = temp; + } + } else { + if(temp[0]) { + Warning("%s[%d]: unexpected text: \"%s\"", + fileName, lineNumber, temp); + } + Release(temp); + } + } + } + break; + } + } + + return head; +} + +/***************************************************************************** + * Parse an entity reference. + * The entity value is returned in ch and the length of the entity + * is returned as the value of the function. + *****************************************************************************/ +int ParseEntity(const char *entity, char *ch, const char *file, int line) { + char *temp; + int x; + + if(!strncmp(""", entity, 6)) { + *ch = '\"'; + return 6; + } else if(!strncmp("<", entity, 4)) { + *ch = '<'; + return 4; + } else if(!strncmp(">", entity, 4)) { + *ch = '>'; + return 4; + } else if(!strncmp("&", entity, 5)) { + *ch = '&'; + return 5; + } else if(!strncmp("'", entity, 6)) { + *ch = '\''; + return 6; + } else { + for(x = 0; entity[x]; x++) { + if(entity[x] == ';') { + break; + } + } + temp = AllocateStack(x + 2); + strncpy(temp, entity, x + 1); + temp[x + 1] = 0; + Warning("%s[%d]: invalid entity: \"%.8s\"", file, line, temp); + ReleaseStack(temp); + *ch = '&'; + return 1; + } +} + +/***************************************************************************** + *****************************************************************************/ +int IsElementEnd(char ch) { + switch(ch) { + case ' ': + case '\t': + case '\n': + case '\r': + case '\"': + case '>': + case '<': + case '/': + case '=': + case 0: + return 1; + default: + return 0; + } +} + +/***************************************************************************** + *****************************************************************************/ +int IsAttributeEnd(char ch) { + switch(ch) { + case 0: + case '\"': + return 1; + default: + return 0; + } +} + +/***************************************************************************** + *****************************************************************************/ +int IsValueEnd(char ch) { + switch(ch) { + case 0: + case '<': + return 1; + default: + return 0; + } +} + +/***************************************************************************** + *****************************************************************************/ +int IsSpace(char ch, int *lineNumber) { + switch(ch) { + case ' ': + case '\t': + case '\r': + return 1; + case '\n': + ++*lineNumber; + return 1; + default: + return 0; + } +} + +/***************************************************************************** + *****************************************************************************/ +char *ReadElementName(const char *line) { + char *buffer; + int len, max; + int x; + + len = 0; + max = BLOCK_SIZE; + buffer = Allocate(max + 1); + + for(x = 0; !IsElementEnd(line[x]); x++) { + buffer[len++] = line[x]; + if(len >= max) { + max += BLOCK_SIZE; + buffer = Reallocate(buffer, max + 1); + } + } + buffer[len] = 0; + + return buffer; +} + +/***************************************************************************** + *****************************************************************************/ +char *ReadElementValue(const char *line, const char *file, int *lineNumber) { + char *buffer; + char ch; + int len, max; + int x; + + len = 0; + max = BLOCK_SIZE; + buffer = Allocate(max + 1); + + for(x = 0; !IsValueEnd(line[x]); x++) { + if(line[x] == '&') { + x += ParseEntity(line + x, &ch, file, *lineNumber) - 1; + if(ch) { + buffer[len] = ch; + } else { + buffer[len] = line[x]; + } + } else { + if(line[x] == '\n') { + ++*lineNumber; + } + buffer[len] = line[x]; + } + ++len; + if(len >= max) { + max += BLOCK_SIZE; + buffer = Reallocate(buffer, max + 1); + } + } + buffer[len] = 0; + Trim(buffer); + + return buffer; +} + +/***************************************************************************** + *****************************************************************************/ +char *ReadAttributeValue(const char *line, const char *file, + int *lineNumber) { + + char *buffer; + char ch; + int len, max; + int x; + + len = 0; + max = BLOCK_SIZE; + buffer = Allocate(max + 1); + + for(x = 0; !IsAttributeEnd(line[x]); x++) { + if(line[x] == '&') { + x += ParseEntity(line + x, &ch, file, *lineNumber) - 1; + if(ch) { + buffer[len] = ch; + } else { + buffer[len] = line[x]; + } + } else { + if(line[x] == '\n') { + ++*lineNumber; + } + buffer[len] = line[x]; + } + ++len; + if(len >= max) { + max += BLOCK_SIZE; + buffer = Reallocate(buffer, max + 1); + } + } + buffer[len] = 0; + + return buffer; +} + +/***************************************************************************** + *****************************************************************************/ +TokenType LookupType(const char *name, TokenNode *np) { + unsigned int x; + + Assert(name); + + for(x = 0; x < sizeof(TOKEN_MAP) / sizeof(char*); x++) { + if(!strcmp(name, TOKEN_MAP[x])) { + if(np) { + np->type = x; + } + return x; + } + } + + if(np) { + np->type = TOK_INVALID; + np->invalidName = CopyString(name); + } + + return TOK_INVALID; + +} + +/***************************************************************************** + *****************************************************************************/ +const char *GetTokenName(const TokenNode *tp) { + if(tp->invalidName) { + return tp->invalidName; + } else if(tp->type >= sizeof(TOKEN_MAP) / sizeof(const char*)) { + return "[invalid]"; + } else { + return TOKEN_MAP[tp->type]; + } +} + +/***************************************************************************** + *****************************************************************************/ +const char *GetTokenTypeName(TokenType type) { + return TOKEN_MAP[type]; +} + +/***************************************************************************** + *****************************************************************************/ +TokenNode *CreateNode(TokenNode *parent, const char *file, int line) { + TokenNode *np; + + np = Allocate(sizeof(TokenNode)); + np->type = TOK_INVALID; + np->value = NULL; + np->attributes = NULL; + np->subnodeHead = NULL; + np->subnodeTail = NULL; + np->parent = parent; + np->next = NULL; + + np->fileName = Allocate(strlen(file) + 1); + strcpy(np->fileName, file); + np->line = line; + np->invalidName = NULL; + + if(!head) { + head = np; + } + if(parent) { + if(parent->subnodeHead) { + parent->subnodeTail->next = np; + } else { + parent->subnodeHead = np; + } + parent->subnodeTail = np; + } else if(current) { + current->next = np; + } + current = np; + + return np; +} + +/***************************************************************************** + *****************************************************************************/ +AttributeNode *CreateAttribute(TokenNode *np) { + AttributeNode *ap; + + ap = Allocate(sizeof(AttributeNode)); + ap->name = NULL; + ap->value = NULL; + + ap->next = np->attributes; + np->attributes = ap; + + return ap; +} + + diff --git a/src/lex.h b/src/lex.h new file mode 100644 index 0000000..2105d0e --- /dev/null +++ b/src/lex.h @@ -0,0 +1,115 @@ +/** + * @file lex.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief XML lexer header file. + * + */ + +#ifndef LEX_H +#define LEX_H + +/** Tokens. + * Note that any change made to this typedef must be reflected in + * TOKEN_MAP in lex.c. + */ +typedef enum { + + TOK_INVALID, + + TOK_ACTIVEBACKGROUND, + TOK_ACTIVEFOREGROUND, + TOK_BACKGROUND, + TOK_BORDERSTYLE, + TOK_CLASS, + TOK_CLOCK, + TOK_CLOCKSTYLE, + TOK_CLOSE, + TOK_DESKTOPS, + TOK_DOCK, + TOK_DOUBLECLICKSPEED, + TOK_DOUBLECLICKDELTA, + TOK_EXIT, + TOK_FOCUSMODEL, + TOK_FONT, + TOK_FOREGROUND, + TOK_GROUP, + TOK_HEIGHT, + TOK_ICONPATH, + TOK_INCLUDE, + TOK_JWM, + TOK_KEY, + TOK_KILL, + TOK_LAYER, + TOK_MAXIMIZE, + TOK_MENU, + TOK_MENUSTYLE, + TOK_MINIMIZE, + TOK_MOUSE, + TOK_MOVE, + TOK_MOVEMODE, + TOK_NAME, + TOK_OPTION, + TOK_OUTLINE, + TOK_PAGER, + TOK_PAGERSTYLE, + TOK_POPUP, + TOK_POPUPSTYLE, + TOK_PROGRAM, + TOK_RESIZE, + TOK_RESIZEMODE, + TOK_RESTART, + TOK_RESTARTCOMMAND, + TOK_ROOTMENU, + TOK_SENDTO, + TOK_SEPARATOR, + TOK_SHADE, + TOK_SHUTDOWNCOMMAND, + TOK_SNAPMODE, + TOK_STARTUPCOMMAND, + TOK_STICK, + TOK_SWALLOW, + TOK_TASKLISTSTYLE, + TOK_TASKLIST, + TOK_THEME, + TOK_THEMEPATH, + TOK_TRAY, + TOK_TRAYBUTTON, + TOK_TRAYBUTTONSTYLE, + TOK_TRAYSTYLE, + TOK_WIDTH + +} TokenType; + +/** Structure to represent an XML attribute. */ +typedef struct AttributeNode { + + char *name; /**< The name of the attribute. */ + char *value; /**< The value for the attribute. */ + struct AttributeNode *next; /**< The next attribute in the list. */ + +} AttributeNode; + +/** Structure to represent an XML tag. */ +typedef struct TokenNode { + + TokenType type; + char *invalidName; + char *value; + char *fileName; + int line; + struct AttributeNode *attributes; + struct TokenNode *parent; + struct TokenNode *subnodeHead, *subnodeTail; + struct TokenNode *next; + +} TokenNode; + +TokenNode *Tokenize(const char *line, const char *fileName); + +const char *GetTokenName(const TokenNode *tp); +const char *GetTokenTypeName(TokenType type); + +#endif + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d9c6e78 --- /dev/null +++ b/src/main.c @@ -0,0 +1,507 @@ +/**************************************************************************** + * The main entry point and related JWM functions. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "main.h" +#include "lex.h" +#include "parse.h" +#include "help.h" +#include "error.h" +#include "event.h" + +#include "border.h" +#include "client.h" +#include "color.h" +#include "command.h" +#include "cursor.h" +#include "confirm.h" +#include "font.h" +#include "hint.h" +#include "group.h" +#include "key.h" +#include "icon.h" +#include "outline.h" +#include "taskbar.h" +#include "tray.h" +#include "traybutton.h" +#include "popup.h" +#include "pager.h" +#include "swallow.h" +#include "screen.h" +#include "root.h" +#include "desktop.h" +#include "place.h" +#include "clock.h" +#include "dock.h" +#include "theme.h" +#include "misc.h" + +Display *display = NULL; +Window rootWindow; +int rootWidth, rootHeight; +int rootDepth; +int rootScreen; +Colormap rootColormap; +Visual *rootVisual; +GC rootGC; +int colormapCount; + +int shouldExit = 0; +int shouldRestart = 0; +int isRestarting = 0; +int initializing = 0; + +unsigned int desktopCount = 4; +unsigned int currentDesktop = 0; + +char *exitCommand = NULL; + +int borderWidth = DEFAULT_BORDER_WIDTH; +int titleHeight = DEFAULT_TITLE_HEIGHT; + +unsigned int doubleClickSpeed; +unsigned int doubleClickDelta; + +FocusModelType focusModel = FOCUS_SLOPPY; + +XContext clientContext; +XContext frameContext; + +#ifdef USE_SHAPE +int haveShape; +int shapeEvent; +#endif + + +static const char *CONFIG_FILE = "/.jwmrc"; + +static void Initialize(); +static void Startup(); +static void Shutdown(); +static void Destroy(); + +static void OpenConnection(); +static void CloseConnection(); +static void StartupConnection(); +static void ShutdownConnection(); +static void EventLoop(); +static void HandleExit(); +static void DoExit(int code); +static void SendRestart(); +static void SendExit(); + +static char *configPath = NULL; +static char *displayString = NULL; + +/**************************************************************************** + ****************************************************************************/ +int main(int argc, char *argv[]) { + char *temp; + int x; + + StartDebug(); + + temp = getenv("HOME"); + if(temp) { + configPath = Allocate(strlen(temp) + strlen(CONFIG_FILE) + 1); + strcpy(configPath, temp); + strcat(configPath, CONFIG_FILE); + } else { + configPath = CopyString(CONFIG_FILE); + } + + for(x = 1; x < argc; x++) { + if(!strcmp(argv[x], "-v")) { + DisplayAbout(); + DoExit(0); + } else if(!strcmp(argv[x], "-h")) { + DisplayHelp(); + DoExit(0); + } else if(!strcmp(argv[x], "-p")) { + Initialize(); + ParseConfig(configPath); + DoExit(0); + } else if(!strcmp(argv[x], "-restart")) { + SendRestart(); + DoExit(0); + } else if(!strcmp(argv[x], "-exit")) { + SendExit(); + DoExit(0); + } else if(!strcmp(argv[x], "-display") && x + 1 < argc) { + displayString = argv[++x]; + } else { + DisplayUsage(); + DoExit(1); + } + } + + StartupConnection(); + do { + + isRestarting = shouldRestart; + shouldExit = 0; + shouldRestart = 0; + + Initialize(); + + ParseConfig(configPath); + + Startup(); + + EventLoop(); + + Shutdown(); + + Destroy(); + + } while(shouldRestart); + ShutdownConnection(); + + if(exitCommand) { + execl(SHELL_NAME, SHELL_NAME, "-c", exitCommand, NULL); + Warning("exec failed: (%s) %s", SHELL_NAME, exitCommand); + DoExit(1); + } else { + DoExit(0); + } + + /* Control shoud never get here. */ + return -1; + +} + +/**************************************************************************** + ****************************************************************************/ +void DoExit(int code) { + Destroy(); + + if(configPath) { + Release(configPath); + configPath = NULL; + } + if(exitCommand) { + Release(exitCommand); + exitCommand = NULL; + } + + StopDebug(); + exit(code); +} + +/**************************************************************************** + ****************************************************************************/ +void EventLoop() { + XEvent event; + + while(!shouldExit) { + WaitForEvent(&event); + ProcessEvent(&event); + } +} + +/**************************************************************************** + ****************************************************************************/ +void OpenConnection() { + + display = JXOpenDisplay(displayString); + if(!display) { + if(displayString) { + printf("error: could not open display %s\n", displayString); + } else { + printf("error: could not open display\n"); + } + DoExit(1); + } + + rootScreen = DefaultScreen(display); + rootWindow = RootWindow(display, rootScreen); + rootWidth = DisplayWidth(display, rootScreen); + rootHeight = DisplayHeight(display, rootScreen); + rootDepth = DefaultDepth(display, rootScreen); + rootColormap = DefaultColormap(display, rootScreen); + rootVisual = DefaultVisual(display, rootScreen); + rootGC = DefaultGC(display, rootScreen); + colormapCount = MaxCmapsOfScreen(ScreenOfDisplay(display, rootScreen)); + + XSetGraphicsExposures(display, rootGC, False); + +} + +/**************************************************************************** + ****************************************************************************/ +void StartupConnection() { + XSetWindowAttributes attr; + int temp; + + initializing = 1; + OpenConnection(); + +#if 0 + XSynchronize(display, True); +#endif + + JXSetErrorHandler(ErrorHandler); + + clientContext = XUniqueContext(); + frameContext = XUniqueContext(); + + attr.event_mask + = SubstructureRedirectMask + | SubstructureNotifyMask + | PropertyChangeMask + | ColormapChangeMask + | ButtonPressMask + | ButtonReleaseMask + | PointerMotionMask | PointerMotionHintMask; + JXChangeWindowAttributes(display, rootWindow, CWEventMask, &attr); + + signal(SIGTERM, HandleExit); + signal(SIGINT, HandleExit); + signal(SIGHUP, HandleExit); + +#ifdef USE_SHAPE + haveShape = JXShapeQueryExtension(display, &shapeEvent, &temp); + if (haveShape) { + Debug("shape extension enabled"); + } else { + Debug("shape extension disabled"); + } +#endif + + initializing = 0; + +} + +/**************************************************************************** + ****************************************************************************/ +void CloseConnection() { + JXFlush(display); + JXCloseDisplay(display); +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownConnection() { + CloseConnection(); +} + +/**************************************************************************** + ****************************************************************************/ +void HandleExit() { + signal(SIGTERM, HandleExit); + signal(SIGINT, HandleExit); + signal(SIGHUP, HandleExit); + shouldExit = 1; +} + +/**************************************************************************** + * This is called before the X connection is opened. + ****************************************************************************/ +void Initialize() { + InitializeBorders(); + InitializeClients(); + InitializeClock(); + InitializeColors(); + InitializeCommands(); + InitializeCursors(); + InitializeDesktops(); + #ifndef DISABLE_CONFIRM + InitializeDialogs(); + #endif + InitializeDock(); + InitializeFonts(); + InitializeGroups(); + InitializeHints(); + InitializeIcons(); + InitializeKeys(); + InitializeOutline(); + InitializePager(); + InitializePlacement(); + InitializePopup(); + InitializeRootMenu(); + InitializeScreens(); + InitializeSwallow(); + InitializeTaskBar(); + InitializeThemes(); + InitializeTray(); + InitializeTrayButtons(); +} + +/**************************************************************************** + * This is called after the X connection is opened. + ****************************************************************************/ +void Startup() { + + /* This order is important. */ + + StartupCommands(); + + /* First we grab the server to prevent clients from changing things + * while we're still loading. */ + JXGrabServer(display); + + StartupScreens(); + + StartupGroups(); + StartupColors(); + StartupIcons(); + StartupFonts(); + StartupCursors(); + StartupOutline(); + + StartupThemes(); + + StartupPager(); + StartupClock(); + StartupTaskBar(); + StartupTrayButtons(); + StartupDock(); + StartupTray(); + StartupKeys(); + StartupDesktops(); + StartupHints(); + StartupBorders(); + StartupPlacement(); + StartupClients(); + + #ifndef DISABLE_CONFIRM + StartupDialogs(); + #endif + StartupPopup(); + + StartupRootMenu(); + + SetDefaultCursor(rootWindow); + ReadCurrentDesktop(); + JXFlush(display); + + RestackClients(); + + /* Allow clients to do their thing. */ + JXSync(display, True); + JXUngrabServer(display); + + StartupSwallow(); + + /* Send expose events. */ + ExposeCurrentDesktop(); + +} + +/**************************************************************************** + * This is called before the X connection is closed. + ****************************************************************************/ +void Shutdown() { + + /* This order is important. */ + + ShutdownSwallow(); + + ShutdownOutline(); + #ifndef DISABLE_CONFIRM + ShutdownDialogs(); + #endif + ShutdownPopup(); + ShutdownKeys(); + ShutdownPager(); + ShutdownRootMenu(); + ShutdownDock(); + ShutdownTray(); + ShutdownTrayButtons(); + ShutdownTaskBar(); + ShutdownClock(); + ShutdownBorders(); + ShutdownClients(); + ShutdownThemes(); + ShutdownIcons(); + ShutdownCursors(); + ShutdownFonts(); + ShutdownColors(); + ShutdownGroups(); + ShutdownDesktops(); + + ShutdownPlacement(); + ShutdownHints(); + ShutdownScreens(); + + ShutdownCommands(); + +} + +/**************************************************************************** + * This is called after the X connection is closed. + * Note that it is possible for this to be called more than once. + ****************************************************************************/ +void Destroy() { + DestroyBorders(); + DestroyClients(); + DestroyClock(); + DestroyColors(); + DestroyCommands(); + DestroyCursors(); + DestroyDesktops(); + #ifndef DISABLE_CONFIRM + DestroyDialogs(); + #endif + DestroyDock(); + DestroyFonts(); + DestroyGroups(); + DestroyHints(); + DestroyIcons(); + DestroyKeys(); + DestroyOutline(); + DestroyPager(); + DestroyPlacement(); + DestroyPopup(); + DestroyRootMenu(); + DestroyScreens(); + DestroySwallow(); + DestroyTaskBar(); + DestroyThemes(); + DestroyTray(); + DestroyTrayButtons(); +} + +/**************************************************************************** + * Send _JWM_RESTART to the root window. + ****************************************************************************/ +void SendRestart() { + + XEvent event; + + OpenConnection(); + + memset(&event, 0, sizeof(event)); + event.xclient.type = ClientMessage; + event.xclient.window = rootWindow; + event.xclient.message_type = JXInternAtom(display, "_JWM_RESTART", False); + event.xclient.format = 32; + + JXSendEvent(display, rootWindow, False, SubstructureRedirectMask, &event); + + CloseConnection(); + +} + +/**************************************************************************** + * Send _JWM_EXIT to the root window. + ****************************************************************************/ +void SendExit() { + + XEvent event; + + OpenConnection(); + + memset(&event, 0, sizeof(event)); + event.xclient.type = ClientMessage; + event.xclient.window = rootWindow; + event.xclient.message_type = JXInternAtom(display, "_JWM_EXIT", False); + event.xclient.format = 32; + + JXSendEvent(display, rootWindow, False, SubstructureRedirectMask, &event); + + CloseConnection(); +} + diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..2216de0 --- /dev/null +++ b/src/main.h @@ -0,0 +1,56 @@ +/** + * @file main.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the main functions. + * + */ + +#ifndef MAIN_H +#define MAIN_H + +typedef enum { + FOCUS_SLOPPY = 0, + FOCUS_CLICK = 1 +} FocusModelType; + +extern Display *display; +extern Window rootWindow; +extern int rootWidth, rootHeight; +extern int rootDepth; +extern int rootScreen; +extern Colormap rootColormap; +extern Visual *rootVisual; +extern GC rootGC; +extern int colormapCount; + +extern char *exitCommand; + +extern unsigned int desktopCount; +extern unsigned int currentDesktop; + +extern int shouldExit; +extern int shouldRestart; +extern int isRestarting; + +extern int initializing; + +extern int borderWidth; +extern int titleHeight; + +extern unsigned int doubleClickSpeed; +extern unsigned int doubleClickDelta; + +extern FocusModelType focusModel; + +extern XContext clientContext; +extern XContext frameContext; + +#ifdef USE_SHAPE +extern int haveShape; +extern int shapeEvent; +#endif + +#endif + diff --git a/src/match.c b/src/match.c new file mode 100644 index 0000000..95edd39 --- /dev/null +++ b/src/match.c @@ -0,0 +1,80 @@ +/**************************************************************************** + * Expression matching. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "match.h" + +typedef struct MatchStateType { + const char *pattern; + const char *expression; + int patternOffset; + int expressionOffset; + int expressionLength; +} MatchStateType; + +static int DoMatch(MatchStateType state); + +/**************************************************************************** + ****************************************************************************/ +int Match(const char *pattern, const char *expression) { + + MatchStateType state; + + if(!pattern && !expression) { + return 1; + } else if(!pattern || !expression) { + return 0; + } + + state.pattern = pattern; + state.expression = expression; + state.patternOffset = 0; + state.expressionOffset = 0; + state.expressionLength = strlen(expression); + + return DoMatch(state); + +} + +/**************************************************************************** + ****************************************************************************/ +int DoMatch(MatchStateType state) { + + char p, e; + + for(;;) { + p = state.pattern[state.patternOffset]; + e = state.expression[state.expressionOffset]; + + if(p == 0 && e == 0) { + return 1; + } else if(p == 0 || e == 0) { + return 0; + } + + switch(p) { + case '*': + ++state.patternOffset; + while(state.expressionOffset < state.expressionLength) { + if(DoMatch(state)) { + return 1; + } + ++state.expressionOffset; + } + return 0; + default: + if(p == e) { + ++state.patternOffset; + ++state.expressionOffset; + break; + } else { + return 0; + } + } + } + +} + + diff --git a/src/match.h b/src/match.h new file mode 100644 index 0000000..2cdb2e2 --- /dev/null +++ b/src/match.h @@ -0,0 +1,21 @@ +/** + * @file match.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Expression matching. + * + */ + +#ifndef MATCH_H +#define MATCH_H + +/** Check if an expression matches a pattern. + * @param pattern The pattern to match against. + * @param expression The expression to check. + * @return 1 if there is a match, 0 otherwise. + */ +int Match(const char *pattern, const char *expression); + +#endif + diff --git a/src/menu.c b/src/menu.c new file mode 100644 index 0000000..9f6b80c --- /dev/null +++ b/src/menu.c @@ -0,0 +1,799 @@ +/*************************************************************************** + * Menu functions display and handling functions. + * Copyright (C) 2004 Joe Wingbermuehle + ***************************************************************************/ + +#include "jwm.h" +#include "menu.h" +#include "font.h" +#include "client.h" +#include "color.h" +#include "icon.h" +#include "image.h" +#include "main.h" +#include "cursor.h" +#include "key.h" +#include "button.h" +#include "event.h" + +#define BASE_ICON_OFFSET 3 + +typedef enum { + MENU_NOSELECTION = 0, + MENU_LEAVE = 1, + MENU_SUBSELECT = 2 +} MenuSelectionType; + +/* Submenu arrow, 4 x 7 pixels */ +static char menu_bitmap[] = { + 0x01, 0x03, 0x07, 0x0F, 0x07, 0x03, 0x01 +}; + +static int ShowSubmenu(Menu *menu, Menu *parent, int x, int y); + +static void CreateMenu(Menu *menu, int x, int y); +static void HideMenu(Menu *menu); +static void DrawMenu(Menu *menu); +static void RedrawMenuTree(Menu *menu); + +static int MenuLoop(Menu *menu); +static MenuSelectionType UpdateMotion(Menu *menu, XEvent *event); + +static void UpdateMenu(Menu *menu); +static void DrawMenuItem(Menu *menu, MenuItem *item, int index); +static MenuItem *GetMenuItem(Menu *menu, int index); +static int GetNextMenuIndex(Menu *menu); +static int GetPreviousMenuIndex(Menu *menu); +static int GetMenuIndex(Menu *menu, int index); +static void SetPosition(Menu *tp, int index); + +static MenuAction *menuAction = NULL; + +int menuShown = 0; + +/*************************************************************************** + ***************************************************************************/ +void InitializeMenu(Menu *menu) { + + MenuItem *np; + int index, temp; + int hasSubmenu; + int userHeight; + + menu->textOffset = 0; + menu->itemCount = 0; + + /* Compute the max size needed */ + userHeight = menu->itemHeight; + if(userHeight < 0) { + userHeight = 0; + } + menu->itemHeight = GetStringHeight(FONT_MENU); + for(np = menu->items; np; np = np->next) { + if(np->iconName) { + np->icon = LoadNamedIcon(np->iconName); + if(np->icon) { + if(userHeight == 0) { + if(menu->itemHeight < (int)np->icon->image->height) { + menu->itemHeight = np->icon->image->height; + } + if(menu->textOffset < (int)np->icon->image->width + 4) { + menu->textOffset = np->icon->image->width + 4; + } + } + } + } else { + np->icon = NULL; + } + ++menu->itemCount; + } + menu->itemHeight += BASE_ICON_OFFSET * 2; + + if(userHeight) { + menu->itemHeight = userHeight + BASE_ICON_OFFSET * 2; + menu->textOffset = menu->itemHeight + BASE_ICON_OFFSET * 2; + } + + menu->width = 5; + menu->parent = NULL; + menu->parentOffset = 0; + + menu->height = 1; + if(menu->label) { + menu->height += menu->itemHeight; + } + + /* Nothing else to do if there is nothing in the menu. */ + if(menu->itemCount == 0) { + return; + } + + menu->offsets = Allocate(sizeof(int) * menu->itemCount); + + hasSubmenu = 0; + index = 0; + for(np = menu->items; np; np = np->next) { + menu->offsets[index++] = menu->height; + if(np->type == MENU_ITEM_SEPARATOR) { + menu->height += 5; + } else { + menu->height += menu->itemHeight; + } + if(np->name) { + temp = GetStringWidth(FONT_MENU, np->name); + if(temp > menu->width) { + menu->width = temp; + } + } + if(np->submenu) { + hasSubmenu = 7; + InitializeMenu(np->submenu); + } + } + menu->height += 2; + menu->width += 15 + hasSubmenu + menu->textOffset; + +} + +/*************************************************************************** + ***************************************************************************/ +void ShowMenu(Menu *menu, RunMenuCommandType runner, int x, int y) { + + int mouseStatus, keyboardStatus; + + mouseStatus = GrabMouseForMenu(); + keyboardStatus = JXGrabKeyboard(display, rootWindow, False, + GrabModeAsync, GrabModeAsync, CurrentTime); + if(!mouseStatus || keyboardStatus != GrabSuccess) { + return; + } + + ShowSubmenu(menu, NULL, x, y); + + JXUngrabKeyboard(display, CurrentTime); + JXUngrabPointer(display, CurrentTime); + RefocusClient(); + + if(menuAction) { + (runner)(menuAction); + menuAction = NULL; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void DestroyMenu(Menu *menu) { + MenuItem *np; + + if(menu) { + while(menu->items) { + np = menu->items->next; + if(menu->items->name) { + Release(menu->items->name); + } + switch(menu->items->action.type) { + case MA_EXECUTE: + case MA_EXIT: + if(menu->items->action.data.str) { + Release(menu->items->action.data.str); + } + break; + default: + break; + } + if(menu->items->iconName) { + Release(menu->items->iconName); + } + if(menu->items->submenu) { + DestroyMenu(menu->items->submenu); + } + Release(menu->items); + menu->items = np; + } + if(menu->label) { + Release(menu->label); + } + if(menu->offsets) { + Release(menu->offsets); + } + Release(menu); + menu = NULL; + } +} + +/*************************************************************************** + ***************************************************************************/ +int ShowSubmenu(Menu *menu, Menu *parent, int x, int y) { + int status; + + menu->parent = parent; + CreateMenu(menu, x, y); + + ++menuShown; + status = MenuLoop(menu); + --menuShown; + + HideMenu(menu); + + return status; +} + +/*************************************************************************** + * Returns 0 if no selection was made or 1 if a selection was made. + ***************************************************************************/ +int MenuLoop(Menu *menu) { + + XEvent event; + MenuItem *ip; + int count; + int hadMotion; + int pressx, pressy; + + hadMotion = 0; + + GetMousePosition(&pressx, &pressy); + + for(;;) { + + WaitForEvent(&event); + + switch(event.type) { + case Expose: + RedrawMenuTree(menu); + break; + + case ButtonPress: + + pressx = -100; + pressy = -100; + + case KeyPress: + case MotionNotify: + hadMotion = 1; + switch(UpdateMotion(menu, &event)) { + case MENU_NOSELECTION: /* no selection */ + break; + case MENU_LEAVE: /* mouse left the menu */ + JXPutBackEvent(display, &event); + return 0; + case MENU_SUBSELECT: /* selection made */ + return 1; + } + break; + + case ButtonRelease: + + if(event.xbutton.button == Button4) { + break; + } + if(event.xbutton.button == Button5) { + break; + } + if(!hadMotion) { + break; + } + if(abs(event.xbutton.x_root - pressx) < doubleClickDelta) { + if(abs(event.xbutton.y_root - pressy) < doubleClickDelta) { + break; + } + } + + if(menu->currentIndex >= 0) { + count = 0; + for(ip = menu->items; ip; ip = ip->next) { + if(count == menu->currentIndex) { + menuAction = &ip->action; + break; + } + ++count; + } + } + return 1; + default: + break; + } + + } +} + +/*************************************************************************** + ***************************************************************************/ +void CreateMenu(Menu *menu, int x, int y) { + + XSetWindowAttributes attr; + unsigned long attrMask; + int temp; + + menu->lastIndex = -1; + menu->currentIndex = -1; + + if(x + menu->width > rootWidth) { + if(menu->parent) { + x = menu->parent->x - menu->width; + } else { + x = rootWidth - menu->width; + } + } + temp = y; + if(y + menu->height > rootHeight) { + y = rootHeight - menu->height; + } + if(y < 0) { + y = 0; + } + + menu->x = x; + menu->y = y; + menu->parentOffset = temp - y; + + attrMask = 0; + + attrMask |= CWEventMask; + attr.event_mask = ExposureMask; + + attrMask |= CWBackPixel; + attr.background_pixel = colors[COLOR_MENU_BG]; + + attrMask |= CWSaveUnder; + attr.save_under = True; + + menu->window = JXCreateWindow(display, rootWindow, x, y, + menu->width, menu->height, 0, CopyFromParent, InputOutput, + CopyFromParent, attrMask, &attr); + + JXMapRaised(display, menu->window); + +} + +/*************************************************************************** + ***************************************************************************/ +void HideMenu(Menu *menu) { + + JXDestroyWindow(display, menu->window); + +} + +/*************************************************************************** + ***************************************************************************/ +void RedrawMenuTree(Menu *menu) { + + if(menu->parent) { + RedrawMenuTree(menu->parent); + } + + DrawMenu(menu); + UpdateMenu(menu); + +} + +/*************************************************************************** + ***************************************************************************/ +void DrawMenu(Menu *menu) { + + MenuItem *np; + int x; + + if(menu->label) { + DrawMenuItem(menu, NULL, -1); + } + + x = 0; + for(np = menu->items; np; np = np->next) { + DrawMenuItem(menu, np, x); + ++x; + } + + JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]); + JXDrawLine(display, menu->window, rootGC, + 0, 0, menu->width - 1, 0); + JXDrawLine(display, menu->window, rootGC, + 0, 1, menu->width - 2, 1); + JXDrawLine(display, menu->window, rootGC, + 0, 2, 0, menu->height - 1); + JXDrawLine(display, menu->window, rootGC, + 1, 2, 1, menu->height - 2); + + JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]); + JXDrawLine(display, menu->window, rootGC, + 1, menu->height - 1, menu->width - 1, menu->height - 1); + JXDrawLine(display, menu->window, rootGC, + 2, menu->height - 2, menu->width - 1, menu->height - 2); + JXDrawLine(display, menu->window, rootGC, + menu->width - 1, 1, menu->width - 1, menu->height - 3); + JXDrawLine(display, menu->window, rootGC, + menu->width - 2, 2, menu->width - 2, menu->height - 3); + +} + +/*************************************************************************** + ***************************************************************************/ +MenuSelectionType UpdateMotion(Menu *menu, XEvent *event) { + + MenuItem *ip; + Menu *tp; + Window subwindow; + int x, y; + + if(event->type == MotionNotify) { + + SetMousePosition(event->xmotion.x_root, event->xmotion.y_root); + DiscardMotionEvents(event, menu->window); + + x = event->xmotion.x_root - menu->x; + y = event->xmotion.y_root - menu->y; + subwindow = event->xmotion.subwindow; + + } else if(event->type == ButtonPress) { + + if(menu->currentIndex >= 0 || !menu->parent) { + tp = menu; + } else { + tp = menu->parent; + } + + y = -1; + if(event->xbutton.button == Button4) { + y = GetPreviousMenuIndex(tp); + } else if(event->xbutton.button == Button5) { + y = GetNextMenuIndex(tp); + } + + if(y >= 0) { + SetPosition(tp, y); + } + + return MENU_NOSELECTION; + + } else if(event->type == KeyPress) { + + if(menu->currentIndex >= 0 || !menu->parent) { + tp = menu; + } else { + tp = menu->parent; + } + + y = -1; + switch(GetKey(&event->xkey) & 0xFF) { + case KEY_UP: + y = GetPreviousMenuIndex(tp); + break; + case KEY_DOWN: + y = GetNextMenuIndex(tp); + break; + case KEY_RIGHT: + tp = menu; + y = 0; + break; + case KEY_LEFT: + if(tp->parent) { + tp = tp->parent; + if(tp->currentIndex >= 0) { + y = tp->currentIndex; + } else { + y = 0; + } + } + break; + case KEY_ESC: + return MENU_SUBSELECT; + case KEY_ENTER: + if(tp->currentIndex >= 0) { + x = 0; + for(ip = tp->items; ip; ip = ip->next) { + if(x == tp->currentIndex) { + menuAction = &ip->action; + break; + } + ++x; + } + } + return MENU_SUBSELECT; + default: + break; + } + + if(y >= 0) { + SetPosition(tp, y); + } + + return MENU_NOSELECTION; + + } else { + Debug("invalid event type in menu.c:UpdateMotion"); + return MENU_SUBSELECT; + } + + /* Update the selection on the current menu */ + if(x > 0 && y > 0 && x < menu->width && y < menu->height) { + menu->currentIndex = GetMenuIndex(menu, y); + } else if(menu->parent && subwindow != menu->parent->window) { + + /* Leave if over a menu window. */ + for(tp = menu->parent->parent; tp; tp = tp->parent) { + if(tp->window == subwindow) { + return MENU_LEAVE; + } + } + menu->currentIndex = -1; + + } else { + + /* Leave if over the parent, but not on this selection. */ + tp = menu->parent; + if(tp && subwindow == tp->window) { + if(y < menu->parentOffset + || y > tp->itemHeight + menu->parentOffset) { + return MENU_LEAVE; + } + } + + menu->currentIndex = -1; + + } + + /* Move the menu if needed. */ + if(menu->height > rootHeight && menu->currentIndex >= 0) { + + /* If near the top, shift down. */ + if(y + menu->y <= 0) { + if(menu->currentIndex > 0) { + --menu->currentIndex; + SetPosition(menu, menu->currentIndex); + } + } + + /* If near the bottom, shift up. */ + if(y + menu->y + menu->itemHeight / 2 >= rootHeight) { + if(menu->currentIndex + 1 < menu->itemCount) { + ++menu->currentIndex; + SetPosition(menu, menu->currentIndex); + } + } + + } + + if(menu->lastIndex != menu->currentIndex) { + UpdateMenu(menu); + menu->lastIndex = menu->currentIndex; + } + + /* If the selected item is a submenu, show it. */ + ip = GetMenuItem(menu, menu->currentIndex); + if(ip && ip->submenu) { + if(ShowSubmenu(ip->submenu, menu, menu->x + menu->width, + menu->y + menu->offsets[menu->currentIndex])) { + + /* Item selected; destroy the menu tree. */ + return MENU_SUBSELECT; + + } else { + + /* No selection made. */ + UpdateMenu(menu); + + } + } + + return MENU_NOSELECTION; + +} + +/*************************************************************************** + ***************************************************************************/ +void UpdateMenu(Menu *menu) { + + ButtonNode button; + Pixmap pixmap; + MenuItem *ip; + + /* Clear the old selection. */ + ip = GetMenuItem(menu, menu->lastIndex); + DrawMenuItem(menu, ip, menu->lastIndex); + + /* Highlight the new selection. */ + ip = GetMenuItem(menu, menu->currentIndex); + if(ip) { + + if(ip->type == MENU_ITEM_SEPARATOR) { + return; + } + + ResetButton(&button, menu->window, rootGC); + button.type = BUTTON_MENU_ACTIVE; + button.font = FONT_MENU; + button.width = menu->width - 5; + button.height = menu->itemHeight - 2; + button.icon = ip->icon; + button.text = ip->name; + button.x = 2; + button.y = menu->offsets[menu->currentIndex] + 1; + DrawButton(&button); + + if(ip->submenu) { + pixmap = JXCreatePixmapFromBitmapData(display, menu->window, + menu_bitmap, 4, 7, colors[COLOR_MENU_ACTIVE_FG], + colors[COLOR_MENU_ACTIVE_BG], rootDepth); + JXCopyArea(display, pixmap, menu->window, rootGC, 0, 0, 4, 7, + menu->width - 9, + menu->offsets[menu->currentIndex] + menu->itemHeight / 2 - 4); + JXFreePixmap(display, pixmap); + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void DrawMenuItem(Menu *menu, MenuItem *item, int index) { + + ButtonNode button; + Pixmap pixmap; + + Assert(menu); + + if(!item) { + if(index == -1 && menu->label) { + ResetButton(&button, menu->window, rootGC); + button.x = 2; + button.y = 2; + button.width = menu->width - 5; + button.height = menu->itemHeight - 2; + button.font = FONT_MENU; + button.type = BUTTON_LABEL; + button.text = menu->label; + button.alignment = ALIGN_CENTER; + DrawButton(&button); + } + return; + } + + if(item->name) { + + ResetButton(&button, menu->window, rootGC); + button.x = 2; + button.y = 1 + menu->offsets[index]; + button.font = FONT_MENU; + button.type = BUTTON_LABEL; + button.width = menu->width - 5; + button.height = menu->itemHeight - 2; + button.text = item->name; + button.icon = item->icon; + DrawButton(&button); + + } else if(item->type == MENU_ITEM_SEPARATOR) { + + JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]); + JXDrawLine(display, menu->window, rootGC, 4, + menu->offsets[index] + 2, menu->width - 6, + menu->offsets[index] + 2); + JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]); + JXDrawLine(display, menu->window, rootGC, 4, + menu->offsets[index] + 3, menu->width - 6, + menu->offsets[index] + 3); + + } + + if(item->submenu) { + + pixmap = JXCreatePixmapFromBitmapData(display, menu->window, + menu_bitmap, 4, 7, colors[COLOR_MENU_FG], + colors[COLOR_MENU_BG], rootDepth); + JXCopyArea(display, pixmap, menu->window, rootGC, 0, 0, 4, 7, + menu->width - 9, menu->offsets[index] + menu->itemHeight / 2 - 4); + JXFreePixmap(display, pixmap); + + } + +} + +/*************************************************************************** + ***************************************************************************/ +int GetNextMenuIndex(Menu *menu) { + + MenuItem *item; + int x; + + for(x = menu->currentIndex + 1; x < menu->itemCount; x++) { + item = GetMenuItem(menu, x); + if(item->type != MENU_ITEM_SEPARATOR) { + return x; + } + } + + return 0; + +} + +/*************************************************************************** + ***************************************************************************/ +int GetPreviousMenuIndex(Menu *menu) { + + MenuItem *item; + int x; + + for(x = menu->currentIndex - 1; x >= 0; x--) { + item = GetMenuItem(menu, x); + if(item->type != MENU_ITEM_SEPARATOR) { + return x; + } + } + + return menu->itemCount - 1; + +} + +/*************************************************************************** + ***************************************************************************/ +int GetMenuIndex(Menu *menu, int y) { + + int x; + + if(y < menu->offsets[0]) { + return -1; + } + for(x = 0; x < menu->itemCount - 1; x++) { + if(y >= menu->offsets[x] && y < menu->offsets[x + 1]) { + return x; + } + } + return x; + +} + +/*************************************************************************** + ***************************************************************************/ +MenuItem *GetMenuItem(Menu *menu, int index) { + + MenuItem *ip; + + if(index >= 0) { + for(ip = menu->items; ip; ip = ip->next) { + if(!index) { + return ip; + } + --index; + } + } else { + ip = NULL; + } + + return ip; + +} + +/*************************************************************************** + ***************************************************************************/ +void SetPosition(Menu *tp, int index) { + + int y; + int updated; + + y = tp->offsets[index] + tp->itemHeight / 2; + + if(tp->height > rootHeight) { + + updated = 0; + while(y + tp->y < tp->itemHeight / 2) { + tp->y += tp->itemHeight; + updated = tp->itemHeight; + } + while(y + tp->y >= rootHeight) { + tp->y -= tp->itemHeight; + updated = -tp->itemHeight; + } + if(updated) { + JXMoveWindow(display, tp->window, tp->x, tp->y); + y += updated; + } + + } + + /* We need to do this twice so the event gets registered + * on the submenu if one exists. */ + MoveMouse(tp->window, 6, y); + MoveMouse(tp->window, 6, y); + +} + + diff --git a/src/menu.h b/src/menu.h new file mode 100644 index 0000000..5d4ad94 --- /dev/null +++ b/src/menu.h @@ -0,0 +1,89 @@ +/** + * @file menu.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the menu functions. + * + */ + +#ifndef MENU_H +#define MENU_H + +typedef enum { + MA_NONE, + MA_EXECUTE, + MA_DESKTOP, + MA_SENDTO, + MA_LAYER, + MA_STICK, + MA_MAXIMIZE, + MA_MINIMIZE, + MA_RESTORE, + MA_SHADE, + MA_MOVE, + MA_RESIZE, + MA_KILL, + MA_CLOSE, + MA_EXIT, + MA_RESTART +} MenuActionType; + +typedef struct MenuAction { + MenuActionType type; + union { + int i; + char *str; + } data; +} MenuAction; + +typedef enum { + MENU_ITEM_NORMAL, + MENU_ITEM_SUBMENU, + MENU_ITEM_SEPARATOR +} MenuItemType; + +typedef struct MenuItem { + + MenuItemType type; + char *name; + MenuAction action; + char *iconName; + struct Menu *submenu; + struct MenuItem *next; + + /* This field is handled by menu.c */ + struct IconNode *icon; + +} MenuItem; + +typedef struct Menu { + + /* These fields must be set before calling ShowMenu */ + struct MenuItem *items; + char *label; + int itemHeight; + + /* These fields are handled by menu.c */ + Window window; + int x, y; + int width, height; + int currentIndex, lastIndex; + unsigned int itemCount; + int parentOffset; + int textOffset; + int *offsets; + struct Menu *parent; + +} Menu; + +typedef void (*RunMenuCommandType)(const MenuAction *action); + +void InitializeMenu(Menu *menu); +void ShowMenu(Menu *menu, RunMenuCommandType runner, int x, int y); +void DestroyMenu(Menu *menu); + +extern int menuShown; + +#endif + diff --git a/src/misc.c b/src/misc.c new file mode 100644 index 0000000..5728323 --- /dev/null +++ b/src/misc.c @@ -0,0 +1,196 @@ +/** + * @file misc.c + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Miscellaneous functions and macros. + * + */ + +#include "jwm.h" +#include "misc.h" + +static int IsSpace(char ch); +static int IsSymbolic(char ch); +static char *GetSymbolName(const char *str); +static void ReplaceSymbol(char **str, const char *name, const char *value); + +/** Determine if a character is a space character. */ +int IsSpace(char ch) { + switch(ch) { + case ' ': + case '\t': + case '\n': + case '\r': + return 1; + default: + return 0; + } +} + +/** Determine if a character is a valid for a shell variable. */ +int IsSymbolic(char ch) { + + if(ch >= 'A' && ch <= 'Z') { + return 1; + } else if(ch >= 'a' && ch <= 'z') { + return 1; + } else if(ch >= '0' && ch <= '9') { + return 1; + } else if(ch == '_') { + return 1; + } else { + return 0; + } + +} + +/** Get the name of a shell variable (returns a copy). */ +char *GetSymbolName(const char *str) { + + char *temp; + int stop; + + if(*str == '$') { + temp = Allocate(2); + temp[0] = '$'; + temp[1] = 0; + } else { + for(stop = 0; IsSymbolic(str[stop]); stop++); + temp = Allocate(stop + 1); + memcpy(temp, str, stop); + temp[stop] = 0; + } + + return temp; + +} + +/** Replace "name" with "value" in str (reallocates if needed). */ +void ReplaceSymbol(char **str, const char *name, const char *value) { + + char *temp; + int strLength; + int nameLength; + int valueLength; + int x; + + Assert(str); + Assert(name); + + strLength = strlen(*str); + nameLength = strlen(name) + 1; + if(value) { + valueLength = strlen(value); + } else { + valueLength = 0; + } + + if(valueLength > nameLength) { + temp = Allocate(strLength - nameLength + valueLength + 1); + strcpy(temp, *str); + Release(*str); + *str = temp; + } + + temp = strstr(*str, name); + Assert(temp); + --temp; /* Account for the "$" */ + + if(nameLength > valueLength) { + + /* Move left */ + for(x = 0; temp[x]; x++) { + temp[x] = temp[x + nameLength - valueLength]; + } + temp[x] = temp[x + nameLength - valueLength]; + + } else if(nameLength < valueLength) { + + /* Move right */ + for(x = strlen(temp); x >= 0; x--) { + temp[x + valueLength - nameLength] = temp[x]; + } + + } + + + if(value) { + memcpy(temp, value, valueLength); + } + +} + +/** Perform shell-like macro path expansion. */ +void ExpandPath(char **path) { + + char *name; + char *value; + int x; + + Assert(path); + + for(x = 0; (*path)[x]; x++) { + + if((*path)[x] == '$') { + name = GetSymbolName(*path + x + 1); + value = getenv(name); + ReplaceSymbol(path, name, value); + Release(name); + if(value) { + x += strlen(value) - 1; + } + } + + } + +} + +/** Trim leading and trailing whitespace from a string. */ +void Trim(char *str) { + + int length; + int start; + int x; + + Assert(str); + + length = strlen(str); + + /* Determine how much to cut off of the left. */ + for(start = 0; IsSpace(str[start]); start++); + + /* Trim the left. */ + if(start > 0) { + length -= start; + for(x = 0; x < length + 1; x++) { + str[x] = str[x + start]; + } + } + + /* Trim the right. */ + while(IsSpace(str[length - 1])) { + --length; + str[length] = 0; + } + +} + +/** Copy a string. */ +char *CopyString(const char *str) { + + char *temp; + int len; + + if(!str) { + return NULL; + } + + len = strlen(str) + 1; + temp = Allocate(len); + memcpy(temp, str, len); + + return temp; + +} + diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 0000000..c97a59e --- /dev/null +++ b/src/misc.h @@ -0,0 +1,37 @@ +/** + * @file misc.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Miscellaneous functions and macros. + * + */ + +#ifndef MISC_H +#define MISC_H + +/** Return the minimum of two values. */ +#define Min( x, y ) ( (x) > (y) ? (y) : (x) ) + +/** Return the maximum of two values. */ +#define Max( x, y ) ( (x) > (y) ? (x) : (y) ) + +/** Perform shell-like macro path expansion. + * @param path The path to expand (possibly reallocated). + */ +void ExpandPath(char **path); + +/** Trim leading and trailing whitespace from a string. + * @param str The string to trim. + */ +void Trim(char *str); + +/** Copy a string. + * Note that NULL is accepted. When provided NULL, NULL will be returned. + * @param str The string to copy. + * @return A copy of the string. + */ +char *CopyString(const char *str); + +#endif + diff --git a/src/move.c b/src/move.c new file mode 100644 index 0000000..882ac90 --- /dev/null +++ b/src/move.c @@ -0,0 +1,729 @@ +/**************************************************************************** + * Functions to handle moving client windows. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "move.h" +#include "client.h" +#include "border.h" +#include "outline.h" +#include "error.h" +#include "screen.h" +#include "main.h" +#include "cursor.h" +#include "event.h" +#include "pager.h" +#include "key.h" +#include "tray.h" +#include "status.h" + +typedef struct { + int valid; + int left, right; + int top, bottom; +} RectangleType; + +static int shouldStopMove; +static SnapModeType snapMode = SNAP_BORDER; +static int snapDistance = DEFAULT_SNAP_DISTANCE; + +static MoveModeType moveMode = MOVE_OPAQUE; + +static void StopMove(ClientNode *np, int doMove, int oldx, int oldy); +static void MoveController(int wasDestroyed); + +static void DoSnap(ClientNode *np, int north, int west); +static void DoSnapScreen(ClientNode *np, int north, int west); +static void DoSnapBorder(ClientNode *np, int north, int west); +static int ShouldSnap(const ClientNode *np); +static void GetClientRectangle(const ClientNode *np, RectangleType *r); + +static int CheckOverlapTopBottom(const RectangleType *a, + const RectangleType *b); +static int CheckOverlapLeftRight(const RectangleType *a, + const RectangleType *b); + +static int CheckLeftValid(const RectangleType *client, + const RectangleType *other, const RectangleType *left); +static int CheckRightValid(const RectangleType *client, + const RectangleType *other, const RectangleType *right); +static int CheckTopValid(const RectangleType *client, + const RectangleType *other, const RectangleType *top); +static int CheckBottomValid(const RectangleType *client, + const RectangleType *other, const RectangleType *bottom); + +/**************************************************************************** + ****************************************************************************/ +void SetSnapMode(SnapModeType mode) { + snapMode = mode; +} + +/**************************************************************************** + ****************************************************************************/ +void SetMoveMode(MoveModeType mode) { + moveMode = mode; +} + +/**************************************************************************** + ****************************************************************************/ +void SetSnapDistance(const char *value) { + int temp; + + Assert(value); + + temp = atoi(value); + if(temp > MAX_SNAP_DISTANCE || temp < MIN_SNAP_DISTANCE) { + snapDistance = DEFAULT_SNAP_DISTANCE; + Warning("invalid snap distance specified: %d", temp); + } else { + snapDistance = temp; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void SetDefaultSnapDistance() { + snapDistance = DEFAULT_SNAP_DISTANCE; +} + +/**************************************************************************** + ****************************************************************************/ +void MoveController(int wasDestroyed) { + + if(moveMode == MOVE_OUTLINE) { + ClearOutline(); + } + + JXUngrabPointer(display, CurrentTime); + JXUngrabKeyboard(display, CurrentTime); + + DestroyMoveWindow(); + shouldStopMove = 1; + +} + +/**************************************************************************** + ****************************************************************************/ +int MoveClient(ClientNode *np, int startx, int starty) { + + XEvent event; + int oldx, oldy; + int doMove; + int north, south, east, west; + int height; + + Assert(np); + + if(!(np->state.border & BORDER_MOVE)) { + return 0; + } + + GrabMouseForMove(); + + np->controller = MoveController; + shouldStopMove = 0; + + oldx = np->x; + oldy = np->y; + + if(!(GetMouseMask() & (Button1Mask | Button2Mask))) { + StopMove(np, 0, oldx, oldy); + return 0; + } + + GetBorderSize(np, &north, &south, &east, &west); + + startx -= west; + starty -= north; + + doMove = 0; + for(;;) { + + WaitForEvent(&event); + + if(shouldStopMove) { + np->controller = NULL; + SetDefaultCursor(np->parent); + return doMove; + } + + switch(event.type) { + case ButtonRelease: + if(event.xbutton.button == Button1 + || event.xbutton.button == Button2) { + StopMove(np, doMove, oldx, oldy); + return doMove; + } + break; + case MotionNotify: + + DiscardMotionEvents(&event, np->window); + + np->x = event.xmotion.x_root - startx; + np->y = event.xmotion.y_root - starty; + + DoSnap(np, north, west); + + if(!doMove && (abs(np->x - oldx) > MOVE_DELTA + || abs(np->y - oldy) > MOVE_DELTA)) { + + if(np->state.status & STAT_MAXIMIZED) { + MaximizeClient(np); + startx = west + np->width / 2; + starty = north / 2; + MoveMouse(np->parent, startx, starty); + } + + CreateMoveWindow(np); + doMove = 1; + } + + if(doMove) { + + if(moveMode == MOVE_OUTLINE) { + ClearOutline(); + height = north + south; + if(!(np->state.status & STAT_SHADED)) { + height += np->height; + } + DrawOutline(np->x - west, np->y - north, + np->width + west + east, height); + } else { + JXMoveWindow(display, np->parent, np->x - west, + np->y - north); + SendConfigureEvent(np); + } + UpdateMoveWindow(np); + UpdatePager(); + } + + break; + default: + break; + } + } +} + +/**************************************************************************** + ****************************************************************************/ +int MoveClientKeyboard(ClientNode *np) { + + XEvent event; + int oldx, oldy; + int moved; + int height; + int north, south, east, west; + + Assert(np); + + if(!(np->state.border & BORDER_MOVE)) { + return 0; + } + + if(np->state.status & STAT_MAXIMIZED) { + MaximizeClient(np); + } + + GrabMouseForMove(); + if(JXGrabKeyboard(display, np->window, True, GrabModeAsync, + GrabModeAsync, CurrentTime) != GrabSuccess) { + Debug("could not grab keyboard for client move"); + return 0; + } + + GetBorderSize(np, &north, &south, &east, &west); + + oldx = np->x; + oldy = np->y; + + np->controller = MoveController; + shouldStopMove = 0; + + CreateMoveWindow(np); + UpdateMoveWindow(np); + + MoveMouse(rootWindow, np->x, np->y); + DiscardMotionEvents(&event, np->window); + + if(np->state.status & STAT_SHADED) { + height = 0; + } else { + height = np->height; + } + + for(;;) { + + WaitForEvent(&event); + + if(shouldStopMove) { + np->controller = NULL; + SetDefaultCursor(np->parent); + return 1; + } + + moved = 0; + + if(event.type == KeyPress) { + + while(JXCheckTypedWindowEvent(display, np->window, KeyPress, &event)); + + switch(GetKey(&event.xkey) & 0xFF) { + case KEY_UP: + if(np->y + height > 0) { + np->y -= 10; + } + break; + case KEY_DOWN: + if(np->y < rootHeight) { + np->y += 10; + } + break; + case KEY_RIGHT: + if(np->x < rootWidth) { + np->x += 10; + } + break; + case KEY_LEFT: + if(np->x + np->width > 0) { + np->x -= 10; + } + break; + default: + StopMove(np, 1, oldx, oldy); + return 1; + } + + MoveMouse(rootWindow, np->x, np->y); + JXCheckTypedWindowEvent(display, np->window, MotionNotify, &event); + + moved = 1; + + } else if(event.type == MotionNotify) { + + while(JXCheckTypedWindowEvent(display, np->window, + MotionNotify, &event)); + + np->x = event.xmotion.x; + np->y = event.xmotion.y; + + moved = 1; + + } else if(event.type == ButtonRelease) { + + StopMove(np, 1, oldx, oldy); + return 1; + + } + + if(moved) { + + if(moveMode == MOVE_OUTLINE) { + ClearOutline(); + DrawOutline(np->x - west, np->y - west, + np->width + west + east, height + north + west); + } else { + JXMoveWindow(display, np->parent, np->x - west, np->y - north); + SendConfigureEvent(np); + } + + UpdateMoveWindow(np); + UpdatePager(); + + } + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void StopMove(ClientNode *np, int doMove, int oldx, int oldy) { + + int north, south, east, west; + + Assert(np); + Assert(np->controller); + + (np->controller)(0); + + np->controller = NULL; + + SetDefaultCursor(np->parent); + + if(!doMove) { + np->x = oldx; + np->y = oldy; + return; + } + + GetBorderSize(np, &north, &south, &east, &west); + + JXMoveWindow(display, np->parent, np->x - west, np->y - north); + SendConfigureEvent(np); + +} + +/**************************************************************************** + ****************************************************************************/ +void DoSnap(ClientNode *np, int north, int west) { + switch(snapMode) { + case SNAP_BORDER: + DoSnapBorder(np, north, west); + DoSnapScreen(np, north, west); + break; + case SNAP_SCREEN: + DoSnapScreen(np, north, west); + break; + default: + break; + } +} + +/**************************************************************************** + ****************************************************************************/ +void DoSnapScreen(ClientNode *np, int north, int west) { + + RectangleType client; + int screen; + const ScreenType *sp; + int screenCount; + + GetClientRectangle(np, &client); + + screenCount = GetScreenCount(); + for(screen = 0; screen < screenCount; screen++) { + + sp = GetScreen(screen); + + if(abs(client.right - sp->width - sp->x) <= snapDistance) { + np->x = sp->x + sp->width - west - np->width; + } + if(abs(client.left - sp->x) <= snapDistance) { + np->x = sp->x + west; + } + if(abs(client.bottom - sp->height - sp->y) <= snapDistance) { + np->y = sp->y + sp->height - west; + if(!(np->state.status & STAT_SHADED)) { + np->y -= np->height; + } + } + if(abs(client.top - sp->y) <= snapDistance) { + np->y = north + sp->y; + } + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void DoSnapBorder(ClientNode *np, int north, int west) { + + const ClientNode *tp; + const TrayType *tray; + RectangleType client, other; + RectangleType left = { 0 }; + RectangleType right = { 0 }; + RectangleType top = { 0 }; + RectangleType bottom = { 0 }; + int layer; + + GetClientRectangle(np, &client); + + other.valid = 1; + + /* Work from the bottom of the window stack to the top. */ + for(layer = 0; layer < LAYER_COUNT; layer++) { + + /* Check tray windows. */ + for(tray = GetTrays(); tray; tray = tray->next) { + + if(tray->hidden) { + continue; + } + + other.left = tray->x; + other.right = tray->x + tray->width; + other.top = tray->y; + other.bottom = tray->y + tray->height; + + left.valid = CheckLeftValid(&client, &other, &left); + right.valid = CheckRightValid(&client, &other, &right); + top.valid = CheckTopValid(&client, &other, &top); + bottom.valid = CheckBottomValid(&client, &other, &bottom); + + if(CheckOverlapTopBottom(&client, &other)) { + if(abs(client.left - other.right) <= snapDistance) { + left = other; + } + if(abs(client.right - other.left) <= snapDistance) { + right = other; + } + } + if(CheckOverlapLeftRight(&client, &other)) { + if(abs(client.top - other.bottom) <= snapDistance) { + top = other; + } + if(abs(client.bottom - other.top) <= snapDistance) { + bottom = other; + } + } + + } + + /* Check client windows. */ + for(tp = nodeTail[layer]; tp; tp = tp->prev) { + + if(tp == np || !ShouldSnap(tp)) { + continue; + } + + GetClientRectangle(tp, &other); + + /* Check if this border invalidates any previous value. */ + left.valid = CheckLeftValid(&client, &other, &left); + right.valid = CheckRightValid(&client, &other, &right); + top.valid = CheckTopValid(&client, &other, &top); + bottom.valid = CheckBottomValid(&client, &other, &bottom); + + /* Compute the new snap values. */ + if(CheckOverlapTopBottom(&client, &other)) { + if(abs(client.left - other.right) <= snapDistance) { + left = other; + } + if(abs(client.right - other.left) <= snapDistance) { + right = other; + } + } + if(CheckOverlapLeftRight(&client, &other)) { + if(abs(client.top - other.bottom) <= snapDistance) { + top = other; + } + if(abs(client.bottom - other.top) <= snapDistance) { + bottom = other; + } + } + + } + + } + + if(right.valid) { + np->x = right.left - np->width - west; + } + if(left.valid) { + np->x = left.right + west; + } + if(bottom.valid) { + np->y = bottom.top - west; + if(!(np->state.status & STAT_SHADED)) { + np->y -= np->height; + } + } + if(top.valid) { + np->y = top.bottom + north; + } + +} + +/**************************************************************************** + ****************************************************************************/ +int ShouldSnap(const ClientNode *np) { + if(np->state.status & STAT_HIDDEN) { + return 0; + } else if(np->state.status & STAT_MINIMIZED) { + return 0; + } else { + return 1; + } +} + +/**************************************************************************** + ****************************************************************************/ +void GetClientRectangle(const ClientNode *np, RectangleType *r) { + + int border; + + r->left = np->x; + r->right = np->x + np->width; + r->top = np->y; + if(np->state.status & STAT_SHADED) { + r->bottom = np->y; + } else { + r->bottom = np->y + np->height; + } + + if(np->state.border & BORDER_OUTLINE) { + border = borderWidth; + r->left -= border; + r->right += border; + r->bottom += border; + } else { + border = 0; + } + + if(np->state.border & BORDER_TITLE) { + r->top -= titleHeight + border; + } else { + r->top -= border; + } + + r->valid = 1; + +} + +/**************************************************************************** + ****************************************************************************/ +int CheckOverlapTopBottom(const RectangleType *a, const RectangleType *b) { + if(a->top >= b->bottom) { + return 0; + } else if(a->bottom <= b->top) { + return 0; + } else { + return 1; + } +} + +/**************************************************************************** + ****************************************************************************/ +int CheckOverlapLeftRight(const RectangleType *a, const RectangleType *b) { + if(a->left >= b->right) { + return 0; + } else if(a->right <= b->left) { + return 0; + } else { + return 1; + } +} + +/**************************************************************************** + * Check if the current left snap position is valid. + * client - The window being moved. + * other - A window higher in stacking order than previously check windows. + * left - The top/bottom of the current left snap window. + * Returns 1 if the current left snap position is still valid, otherwise 0. + ****************************************************************************/ +int CheckLeftValid(const RectangleType *client, + const RectangleType *other, const RectangleType *left) { + + if(!left->valid) { + return 0; + } + + if(left->right > other->right) { + return 1; + } + + /* If left and client go higher than other then still valid. */ + if(left->top < other->top && client->top < other->top) { + return 1; + } + + /* If left and client go lower than other then still valid. */ + if(left->bottom > other->bottom && client->bottom > other->bottom) { + return 1; + } + + if(other->left >= left->right) { + return 1; + } + + return 0; + +} + +/**************************************************************************** + ****************************************************************************/ +int CheckRightValid(const RectangleType *client, + const RectangleType *other, const RectangleType *right) { + + if(!right->valid) { + return 0; + } + + if(right->left < other->left) { + return 1; + } + + /* If right and client go higher than other then still valid. */ + if(right->top < other->top && client->top < other->top) { + return 1; + } + + /* If right and client go lower than other then still valid. */ + if(right->bottom > other->bottom && client->bottom > other->bottom) { + return 1; + } + + if(other->right <= right->left) { + return 1; + } + + return 0; + +} + +/**************************************************************************** + ****************************************************************************/ +int CheckTopValid(const RectangleType *client, + const RectangleType *other, const RectangleType *top) { + + if(!top->valid) { + return 0; + } + + if(top->bottom > other->bottom) { + return 1; + } + + /* If top and client are to the left of other then still valid. */ + if(top->left < other->left && client->left < other->left) { + return 1; + } + + /* If top and client are to the right of other then still valid. */ + if(top->right > other->right && client->right > other->right) { + return 1; + } + + if(other->top >= top->bottom) { + return 1; + } + + return 0; + +} + +/**************************************************************************** + ****************************************************************************/ +int CheckBottomValid(const RectangleType *client, + const RectangleType *other, const RectangleType *bottom) { + + if(!bottom->valid) { + return 0; + } + + if(bottom->top < other->top) { + return 1; + } + + /* If bottom and client are to the left of other then still valid. */ + if(bottom->left < other->left && client->left < other->left) { + return 1; + } + + /* If bottom and client are to the right of other then still valid. */ + if(bottom->right > other->right && client->right > other->right) { + return 1; + } + + if(other->bottom <= bottom->top) { + return 1; + } + + return 0; + +} + diff --git a/src/move.h b/src/move.h new file mode 100644 index 0000000..8a737ff --- /dev/null +++ b/src/move.h @@ -0,0 +1,61 @@ +/** + * @file move.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for client window move functions. + * + */ + +#ifndef MOVE_H +#define MOVE_H + +struct ClientNode; + +/** Window snap modes. */ +typedef enum { + SNAP_NONE = 0, /**< Don't snap. */ + SNAP_SCREEN = 1, /**< Snap to the edges of the screen. */ + SNAP_BORDER = 2 /**< Snap to all borders. */ +} SnapModeType; + +/** Window move modes. */ +typedef enum { + MOVE_OPAQUE, /**< Show window contents while moving. */ + MOVE_OUTLINE /**< Show an outline while moving. */ +} MoveModeType; + +/** Move a client window. + * @param np The client to move. + * @param startx The starting mouse x-coordinate (window relative). + * @param starty The starting mouse y-coordinate (window relative). + * @return 1 if the client moved, 0 otherwise. + */ +int MoveClient(struct ClientNode *np, int startx, int starty); + +/** Move a client window using the keyboard (mouse optional). + * @param np The client to move. + * @return 1 if the client moved, 0 otherwise. + */ +int MoveClientKeyboard(struct ClientNode *np); + +/** Set the snap mode to use. + * @param mode The snap mode to use. + */ +void SetSnapMode(SnapModeType mode); + +/** Set the snap distance to use. + * @param value A string representation of the distance to use. + */ +void SetSnapDistance(const char *value); + +/** Set the snap distance to the default. */ +void SetDefaultSnapDistance(); + +/** Set the move mode to use. + * @param mode The move mode to use. + */ +void SetMoveMode(MoveModeType mode); + +#endif + diff --git a/src/outline.c b/src/outline.c new file mode 100644 index 0000000..b42e70a --- /dev/null +++ b/src/outline.c @@ -0,0 +1,73 @@ +/**************************************************************************** + * Outlines for moving and resizing. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "outline.h" +#include "main.h" + +static GC outlineGC; + +static int lastX, lastY; +static int lastWidth, lastHeight; +static int outlineDrawn; + +/**************************************************************************** + ****************************************************************************/ +void InitializeOutline() { +} + +/**************************************************************************** + ****************************************************************************/ +void StartupOutline() { + + XGCValues gcValues; + + gcValues.function = GXinvert; + gcValues.subwindow_mode = IncludeInferiors; + gcValues.line_width = 2; + outlineGC = JXCreateGC(display, rootWindow, + GCFunction | GCSubwindowMode | GCLineWidth, &gcValues); + outlineDrawn = 0; + +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownOutline() { + JXFreeGC(display, outlineGC); +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyOutline() { +} + +/**************************************************************************** + ****************************************************************************/ +void DrawOutline(int x, int y, int width, int height) { + if(!outlineDrawn) { + JXSync(display, False); + JXGrabServer(display); + JXDrawRectangle(display, rootWindow, outlineGC, x, y, width, height); + lastX = x; + lastY = y; + lastWidth = width; + lastHeight = height; + outlineDrawn = 1; + } +} + +/**************************************************************************** + ****************************************************************************/ +void ClearOutline() { + if(outlineDrawn) { + JXDrawRectangle(display, rootWindow, outlineGC, + lastX, lastY, lastWidth, lastHeight); + outlineDrawn = 0; + JXUngrabServer(display); + JXSync(display, False); + } +} + diff --git a/src/outline.h b/src/outline.h new file mode 100644 index 0000000..26a4a5e --- /dev/null +++ b/src/outline.h @@ -0,0 +1,32 @@ +/** + * @file outline.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Outlines for moving and resizing client windows. + * + */ + +#ifndef OUTLINE_H +#define OUTLINE_H + +/*@{*/ +void InitializeOutline(); +void StartupOutline(); +void ShutdownOutline(); +void DestroyOutline(); +/*@}*/ + +/** Draw an outline. + * @param x The x-coordinate. + * @param y The y-coordinate. + * @param width The width of the outline. + * @param height The height of the outline. + */ +void DrawOutline(int x, int y, int width, int height); + +/** Clear an outline. */ +void ClearOutline(); + +#endif + diff --git a/src/pager.c b/src/pager.c new file mode 100644 index 0000000..6c237bd --- /dev/null +++ b/src/pager.c @@ -0,0 +1,331 @@ +/**************************************************************************** + * Functions for displaying the pager. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "pager.h" +#include "tray.h" +#include "main.h" +#include "desktop.h" +#include "client.h" +#include "color.h" + +typedef struct PagerType { + + TrayComponentType *cp; + + int deskWidth; + int deskHeight; + double scalex, scaley; + LayoutType layout; + + Pixmap buffer; + + struct PagerType *next; + +} PagerType; + +static PagerType *pagers; + +static void Create(TrayComponentType *cp); +static void Destroy(TrayComponentType *cp); + +static void SetSize(TrayComponentType *cp, int width, int height); + +static void ProcessPagerButtonEvent(TrayComponentType *cp, + int x, int y, int mask); + +static void DrawPagerClient(const PagerType *pp, const ClientNode *np); + +/**************************************************************************** + ****************************************************************************/ +void InitializePager() { + pagers = NULL; +} + +/**************************************************************************** + ****************************************************************************/ +void StartupPager() { +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownPager() { + + PagerType *pp; + + for(pp = pagers; pp; pp = pp->next) { + JXFreePixmap(display, pp->buffer); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyPager() { + + PagerType *pp; + + while(pagers) { + pp = pagers->next; + Release(pagers); + pagers = pp; + } + +} + +/**************************************************************************** + ****************************************************************************/ +TrayComponentType *CreatePager() { + + TrayComponentType *cp; + PagerType *pp; + + pp = Allocate(sizeof(PagerType)); + pp->next = pagers; + pagers = pp; + + cp = CreateTrayComponent(); + cp->object = pp; + pp->cp = cp; + cp->Create = Create; + cp->Destroy = Destroy; + cp->SetSize = SetSize; + cp->ProcessButtonEvent = ProcessPagerButtonEvent; + + return cp; +} + +/**************************************************************************** + ****************************************************************************/ +void Create(TrayComponentType *cp) { + + PagerType *pp; + + Assert(cp); + + pp = (PagerType*)cp->object; + + Assert(pp); + + Assert(cp->width > 0); + Assert(cp->height > 0); + + cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, + cp->height, rootDepth); + pp->buffer = cp->pixmap; + +} + +/**************************************************************************** + ****************************************************************************/ +void Destroy(TrayComponentType *cp) { + +} + +/**************************************************************************** + ****************************************************************************/ +void SetSize(TrayComponentType *cp, int width, int height) { + + PagerType *pp; + + Assert(cp); + + pp = (PagerType*)cp->object; + + Assert(pp); + + if(width) { + + /* Vertical pager, compute height from width. */ + cp->width = width; + pp->deskWidth = width; + pp->deskHeight = (cp->width * rootHeight) / rootWidth; + cp->height = (pp->deskHeight + 1) * desktopCount; + pp->layout = LAYOUT_VERTICAL; + + } else if(height) { + + /* Horizontal pager, compute width from height. */ + cp->height = height; + pp->deskHeight = height; + pp->deskWidth = (cp->height * rootWidth) / rootHeight; + cp->width = (pp->deskWidth + 1) * desktopCount; + pp->layout = LAYOUT_HORIZONTAL; + + } else { + Assert(0); + } + + pp->scalex = (double)(pp->deskWidth - 2) / rootWidth; + pp->scaley = (double)(pp->deskHeight - 2) / rootHeight; + +} + +/**************************************************************************** + ****************************************************************************/ +void ProcessPagerButtonEvent(TrayComponentType *cp, int x, int y, int mask) { + + PagerType *pp; + + switch(mask) { + case Button1: + case Button2: + case Button3: + pp = (PagerType*)cp->object; + if(pp->layout == LAYOUT_HORIZONTAL) { + ChangeDesktop(x / (pp->deskWidth + 1)); + } else { + ChangeDesktop(y / (pp->deskHeight + 1)); + } + break; + case Button4: + PreviousDesktop(); + break; + case Button5: + NextDesktop(); + break; + default: + break; + } +} + +/**************************************************************************** + ****************************************************************************/ +void UpdatePager() { + + PagerType *pp; + ClientNode *np; + Pixmap buffer; + int width, height; + int deskWidth, deskHeight; + unsigned int x; + + if(shouldExit) { + return; + } + + for(pp = pagers; pp; pp = pp->next) { + + buffer = pp->cp->pixmap; + width = pp->cp->width; + height = pp->cp->height; + deskWidth = pp->deskWidth; + deskHeight = pp->deskHeight; + + /* Draw the background. */ + JXSetForeground(display, rootGC, colors[COLOR_PAGER_BG]); + JXFillRectangle(display, buffer, rootGC, 0, 0, width, height); + + /* Highlight the current desktop. */ + JXSetForeground(display, rootGC, colors[COLOR_PAGER_ACTIVE_BG]); + if(pp->layout == LAYOUT_HORIZONTAL) { + JXFillRectangle(display, buffer, rootGC, + currentDesktop * (deskWidth + 1), 0, + deskWidth, height); + } else { + JXFillRectangle(display, buffer, rootGC, + 0, currentDesktop * (deskHeight + 1), + width, deskHeight); + } + + /* Draw the clients. */ + for(x = LAYER_BOTTOM; x <= LAYER_TOP; x++) { + for(np = nodeTail[x]; np; np = np->prev) { + DrawPagerClient(pp, np); + } + } + + /* Draw the desktop dividers. */ + JXSetForeground(display, rootGC, colors[COLOR_PAGER_FG]); + for(x = 1; x < desktopCount; x++) { + if(pp->layout == LAYOUT_HORIZONTAL) { + JXDrawLine(display, buffer, rootGC, + (deskWidth + 1) * x - 1, 0, + (deskWidth + 1) * x - 1, height); + } else { + JXDrawLine(display, buffer, rootGC, + 0, (deskHeight + 1) * x - 1, + width, (deskHeight + 1) * x - 1); + } + } + + /* Tell the tray to redraw. */ + UpdateSpecificTray(pp->cp->tray, pp->cp); + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void DrawPagerClient(const PagerType *pp, const ClientNode *np) { + + int x, y; + int width, height; + int deskOffset; + ColorType fillColor; + + if(!(np->state.status & STAT_MAPPED)) { + return; + } + + if(np->state.status & STAT_STICKY) { + deskOffset = currentDesktop; + } else { + deskOffset = np->state.desktop; + } + if(pp->layout == LAYOUT_HORIZONTAL) { + deskOffset *= pp->deskWidth + 1; + } else { + deskOffset *= pp->deskHeight + 1; + } + + x = (int)((double)np->x * pp->scalex + 1.0); + y = (int)((double)np->y * pp->scaley + 1.0); + width = (int)((double)np->width * pp->scalex); + height = (int)((double)np->height * pp->scaley); + + if(x + width > pp->deskWidth) { + width = pp->deskWidth - x; + } + if(y + height > pp->deskHeight) { + height = pp->deskHeight - y; + } + if(x < 0) { + width += x; + x = 0; + } + if(y < 0) { + height += y; + y = 0; + } + if(width <= 0 || height <= 0) { + return; + } + + if(pp->layout == LAYOUT_HORIZONTAL) { + x += deskOffset; + } else { + y += deskOffset; + } + + JXSetForeground(display, rootGC, colors[COLOR_PAGER_OUTLINE]); + JXDrawRectangle(display, pp->cp->pixmap, rootGC, x, y, width, height); + + if(width > 1 && height > 1) { + if((np->state.status & STAT_ACTIVE) + && (np->state.desktop == currentDesktop + || (np->state.status & STAT_STICKY))) { + fillColor = COLOR_PAGER_ACTIVE_FG; + } else { + fillColor = COLOR_PAGER_FG; + } + JXSetForeground(display, rootGC, colors[fillColor]); + JXFillRectangle(display, pp->cp->pixmap, rootGC, x + 1, y + 1, + width - 1, height - 1); + } + +} + diff --git a/src/pager.h b/src/pager.h new file mode 100644 index 0000000..a3a8f01 --- /dev/null +++ b/src/pager.h @@ -0,0 +1,27 @@ +/** + * @file pager.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Pager tray component. + * + */ + +#ifndef PAGER_H +#define PAGER_H + +struct TrayComponentType; + +/*@{*/ +void InitializePager(); +void StartupPager(); +void ShutdownPager(); +void DestroyPager(); +/*@}*/ + +struct TrayComponentType *CreatePager(); + +void UpdatePager(); + +#endif + diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..5dd82af --- /dev/null +++ b/src/parse.c @@ -0,0 +1,1551 @@ +/**************************************************************************** + * Parser for the JWM XML configuration file. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "parse.h" +#include "lex.h" +#include "menu.h" +#include "root.h" +#include "client.h" +#include "tray.h" +#include "group.h" +#include "desktop.h" +#include "move.h" +#include "resize.h" +#include "misc.h" +#include "swallow.h" +#include "pager.h" +#include "error.h" +#include "key.h" +#include "cursor.h" +#include "main.h" +#include "font.h" +#include "color.h" +#include "icon.h" +#include "command.h" +#include "button.h" +#include "event.h" +#include "taskbar.h" +#include "traybutton.h" +#include "clock.h" +#include "dock.h" +#include "popup.h" +#include "status.h" +#include "theme.h" + +typedef struct KeyMapType { + char *name; + KeyType key; +} KeyMapType; + +static const KeyMapType KEY_MAP[] = { + { "up", KEY_UP }, + { "down", KEY_DOWN }, + { "right", KEY_RIGHT }, + { "left", KEY_LEFT }, + { "escape", KEY_ESC }, + { "select", KEY_ENTER }, + { "next", KEY_NEXT }, + { "nextstacked", KEY_NEXT_STACKED }, + { "close", KEY_CLOSE }, + { "minimize", KEY_MIN }, + { "maximize", KEY_MAX }, + { "shade", KEY_SHADE }, + { "move", KEY_MOVE }, + { "resize", KEY_RESIZE }, + { "window", KEY_WIN }, + { "restart", KEY_RESTART }, + { "exit", KEY_EXIT }, + { "desktop", KEY_DESKTOP }, + { "desktop#", KEY_DESKTOP }, + { NULL, KEY_NONE } +}; + +static const char *DEFAULT_TITLE = "JWM"; +static const char *LABEL_ATTRIBUTE = "label"; +static const char *ICON_ATTRIBUTE = "icon"; +static const char *CONFIRM_ATTRIBUTE = "confirm"; +static const char *LABELED_ATTRIBUTE = "labeled"; +static const char *ONROOT_ATTRIBUTE = "onroot"; +static const char *LAYER_ATTRIBUTE = "layer"; +static const char *LAYOUT_ATTRIBUTE = "layout"; +static const char *AUTOHIDE_ATTRIBUTE = "autohide"; +static const char *X_ATTRIBUTE = "x"; +static const char *Y_ATTRIBUTE = "y"; +static const char *WIDTH_ATTRIBUTE = "width"; +static const char *HEIGHT_ATTRIBUTE = "height"; +static const char *NAME_ATTRIBUTE = "name"; +static const char *BORDER_ATTRIBUTE = "border"; +static const char *COUNT_ATTRIBUTE = "count"; +static const char *DISTANCE_ATTRIBUTE = "distance"; +static const char *INSERT_ATTRIBUTE = "insert"; +static const char *MAX_WIDTH_ATTRIBUTE = "maxwidth"; +static const char *FORMAT_ATTRIBUTE = "format"; +static const char *VALIGN_ATTRIBUTE = "valign"; +static const char *HALIGN_ATTRIBUTE = "halign"; +static const char *POPUP_ATTRIBUTE = "popup"; +static const char *DELAY_ATTRIBUTE = "delay"; +static const char *ENABLED_ATTRIBUTE = "enabled"; +static const char *COORDINATES_ATTRIBUTE = "coordinates"; + +static const char *FALSE_VALUE = "false"; +static const char *TRUE_VALUE = "true"; + +static int ParseFile(const char *fileName, int depth); +static char *ReadFile(FILE *fd); + +/* Misc. */ +static void Parse(const TokenNode *start, int depth); +static void ParseInclude(const TokenNode *tp, int depth); +static void ParseDesktops(const TokenNode *tp); + +/* Menus. */ +static void ParseRootMenu(const TokenNode *start); +static MenuItem *ParseMenuItem(const TokenNode *start, Menu *menu, + MenuItem *last); +static MenuItem *ParseMenuInclude(const TokenNode *tp, Menu *menu, + MenuItem *last); +static MenuItem *InsertMenuItem(MenuItem *last); + +/* Tray. */ +static void ParseTray(const TokenNode *tp); +static void ParsePager(const TokenNode *tp, TrayType *tray); +static void ParseTaskList(const TokenNode *tp, TrayType *tray); +static void ParseSwallow(const TokenNode *tp, TrayType *tray); +static void ParseTrayButton(const TokenNode *tp, TrayType *tray); +static void ParseClock(const TokenNode *tp, TrayType *tray); +static void ParseDock(const TokenNode *tp, TrayType *tray); + +/* Groups. */ +static void ParseGroup(const TokenNode *tp); +static void ParseGroupOption(const TokenNode *tp, + struct GroupType *group, const char *option); + +/* Style. */ +static void ParseBorderStyle(const TokenNode *start); +static void ParseTaskListStyle(const TokenNode *start); +static void ParseTrayStyle(const TokenNode *start); +static void ParsePagerStyle(const TokenNode *start); +static void ParseMenuStyle(const TokenNode *start); +static void ParsePopupStyle(const TokenNode *start); +static void ParseClockStyle(const TokenNode *start); +static void ParseTrayButtonStyle(const TokenNode *start); + +/* Feel. */ +static void ParseKey(const TokenNode *tp); +static void ParseMouse(const TokenNode *tp); +static void ParseSnapMode(const TokenNode *tp); +static void ParseMoveMode(const TokenNode *tp); +static void ParseResizeMode(const TokenNode *tp); +static void ParseFocusModel(const TokenNode *tp); + +static char *FindAttribute(AttributeNode *ap, const char *name); +static void ReleaseTokens(TokenNode *np); +static void InvalidTag(const TokenNode *tp, TokenType parent); +static void ParseError(const TokenNode *tp, const char *str, ...); + +/**************************************************************************** + ****************************************************************************/ +void ParseConfig(const char *fileName) { + if(!ParseFile(fileName, 0)) { + if(!ParseFile(SYSTEM_CONFIG, 0)) { + ParseError(NULL, "could not open %s or %s", fileName, SYSTEM_CONFIG); + } + } + ValidateTrayButtons(); + ValidateKeys(); +} + +/**************************************************************************** + * Parse a specific file. + * Returns 1 on success and 0 on failure. + ****************************************************************************/ +int ParseFile(const char *fileName, int depth) { + + TokenNode *tokens; + FILE *fd; + char *buffer; + + ++depth; + if(depth > MAX_INCLUDE_DEPTH) { + ParseError(NULL, "include depth (%d) exceeded", MAX_INCLUDE_DEPTH); + return 0; + } + + fd = fopen(fileName, "r"); + if(!fd) { + return 0; + } + + buffer = ReadFile(fd); + fclose(fd); + + tokens = Tokenize(buffer, fileName); + Release(buffer); + Parse(tokens, depth); + ReleaseTokens(tokens); + + return 1; + +} + +/*************************************************************************** + ***************************************************************************/ +void ReleaseTokens(TokenNode *np) { + + AttributeNode *ap; + TokenNode *tp; + + while(np) { + tp = np->next; + + while(np->attributes) { + ap = np->attributes->next; + if(np->attributes->name) { + Release(np->attributes->name); + } + if(np->attributes->value) { + Release(np->attributes->value); + } + Release(np->attributes); + np->attributes = ap; + } + + if(np->subnodeHead) { + ReleaseTokens(np->subnodeHead); + } + + if(np->value) { + Release(np->value); + } + + if(np->invalidName) { + Release(np->invalidName); + } + + if(np->fileName) { + Release(np->fileName); + } + + Release(np); + np = tp; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void Parse(const TokenNode *start, int depth) { + + TokenNode *tp; + + if(!start) { + return; + } + + if(start->type == TOK_JWM) { + for(tp = start->subnodeHead; tp; tp = tp->next) { + switch(tp->type) { + case TOK_BORDERSTYLE: + ParseBorderStyle(tp); + break; + case TOK_DESKTOPS: + ParseDesktops(tp); + break; + case TOK_DOUBLECLICKSPEED: + SetDoubleClickSpeed(tp->value); + break; + case TOK_DOUBLECLICKDELTA: + SetDoubleClickDelta(tp->value); + break; + case TOK_FOCUSMODEL: + ParseFocusModel(tp); + break; + case TOK_GROUP: + ParseGroup(tp); + break; + case TOK_ICONPATH: + AddIconPath(tp->value); + break; + case TOK_INCLUDE: + ParseInclude(tp, depth); + break; + case TOK_KEY: + ParseKey(tp); + break; + case TOK_MENUSTYLE: + ParseMenuStyle(tp); + break; + case TOK_MOUSE: + ParseMouse(tp); + break; + case TOK_MOVEMODE: + ParseMoveMode(tp); + break; + case TOK_PAGERSTYLE: + ParsePagerStyle(tp); + break; + case TOK_POPUPSTYLE: + ParsePopupStyle(tp); + break; + case TOK_RESIZEMODE: + ParseResizeMode(tp); + break; + case TOK_RESTARTCOMMAND: + AddRestartCommand(tp->value); + break; + case TOK_ROOTMENU: + ParseRootMenu(tp); + break; + case TOK_SHUTDOWNCOMMAND: + AddShutdownCommand(tp->value); + break; + case TOK_SNAPMODE: + ParseSnapMode(tp); + break; + case TOK_STARTUPCOMMAND: + AddStartupCommand(tp->value); + break; + case TOK_TASKLISTSTYLE: + ParseTaskListStyle(tp); + break; + case TOK_TRAY: + ParseTray(tp); + break; + case TOK_TRAYSTYLE: + ParseTrayStyle(tp); + break; + case TOK_TRAYBUTTONSTYLE: + ParseTrayButtonStyle(tp); + break; + case TOK_CLOCKSTYLE: + ParseClockStyle(tp); + break; + case TOK_THEMEPATH: + AddThemePath(tp->value); + break; + case TOK_THEME: + SetTheme(tp->value); + break; + default: + InvalidTag(tp, TOK_JWM); + break; + } + } + } else { + ParseError(start, "invalid start tag: %s", GetTokenName(start)); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseFocusModel(const TokenNode *tp) { + if(tp->value) { + if(!strcmp(tp->value, "sloppy")) { + focusModel = FOCUS_SLOPPY; + } else if(!strcmp(tp->value, "click")) { + focusModel = FOCUS_CLICK; + } else { + ParseError(tp, "invalid focus model: \"%s\"", tp->value); + } + } else { + ParseError(tp, "focus model not specified"); + } +} + +/**************************************************************************** + ****************************************************************************/ +void ParseSnapMode(const TokenNode *tp) { + + const char *distance; + + distance = FindAttribute(tp->attributes, DISTANCE_ATTRIBUTE); + if(distance) { + SetSnapDistance(distance); + } else { + SetDefaultSnapDistance(); + } + + if(tp->value) { + if(!strcmp(tp->value, "none")) { + SetSnapMode(SNAP_NONE); + } else if(!strcmp(tp->value, "screen")) { + SetSnapMode(SNAP_SCREEN); + } else if(!strcmp(tp->value, "border")) { + SetSnapMode(SNAP_BORDER); + } else { + ParseError(tp, "invalid snap mode: %s", tp->value); + } + } else { + ParseError(tp, "snap mode not specified"); + } +} + +/**************************************************************************** + ****************************************************************************/ +void ParseMoveMode(const TokenNode *tp) { + + const char *str; + + str = FindAttribute(tp->attributes, COORDINATES_ATTRIBUTE); + SetMoveStatusType(str); + + if(tp->value) { + if(!strcmp(tp->value, "outline")) { + SetMoveMode(MOVE_OUTLINE); + } else if(!strcmp(tp->value, "opaque")) { + SetMoveMode(MOVE_OPAQUE); + } else { + ParseError(tp, "invalid move mode: %s", tp->value); + } + } else { + ParseError(tp, "move mode not specified"); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseResizeMode(const TokenNode *tp) { + + const char *str; + + str = FindAttribute(tp->attributes, COORDINATES_ATTRIBUTE); + SetResizeStatusType(str); + + if(tp->value) { + if(!strcmp(tp->value, "outline")) { + SetResizeMode(RESIZE_OUTLINE); + } else if(!strcmp(tp->value, "opaque")) { + SetResizeMode(RESIZE_OPAQUE); + } else { + ParseError(tp, "invalid resize mode: %s", tp->value); + } + } else { + ParseError(tp, "resize mode not specified"); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseRootMenu(const TokenNode *start) { + + const char *value; + Menu *menu; + + menu = Allocate(sizeof(Menu)); + + value = FindAttribute(start->attributes, HEIGHT_ATTRIBUTE); + if(value) { + menu->itemHeight = atoi(value); + } else { + menu->itemHeight = 0; + } + + value = FindAttribute(start->attributes, LABELED_ATTRIBUTE); + if(value && !strcmp(value, TRUE_VALUE)) { + value = FindAttribute(start->attributes, LABEL_ATTRIBUTE); + if(!value) { + value = DEFAULT_TITLE; + } + menu->label = CopyString(value); + } else { + menu->label = NULL; + } + + menu->items = NULL; + ParseMenuItem(start->subnodeHead, menu, NULL); + + value = FindAttribute(start->attributes, ONROOT_ATTRIBUTE); + if(!value) { + value = "123"; + } + + SetRootMenu(value, menu); + +} + +/**************************************************************************** + ****************************************************************************/ +MenuItem *InsertMenuItem(MenuItem *last) { + + MenuItem *item; + + item = Allocate(sizeof(MenuItem)); + item->name = NULL; + item->type = MENU_ITEM_NORMAL; + item->iconName = NULL; + item->action.type = MA_NONE; + item->action.data.str = NULL; + item->submenu = NULL; + + item->next = NULL; + if(last) { + last->next = item; + } + + return item; + +} + +/**************************************************************************** + ****************************************************************************/ +MenuItem *ParseMenuItem(const TokenNode *start, Menu *menu, + MenuItem *last) { + + Menu *child; + const char *value; + + Assert(menu); + + menu->offsets = NULL; + while(start) { + switch(start->type) { + case TOK_INCLUDE: + + last = ParseMenuInclude(start, menu, last); + + break; + case TOK_MENU: + + last = InsertMenuItem(last); + last->type = MENU_ITEM_SUBMENU; + if(!menu->items) { + menu->items = last; + } + + value = FindAttribute(start->attributes, LABEL_ATTRIBUTE); + last->name = CopyString(value); + + value = FindAttribute(start->attributes, ICON_ATTRIBUTE); + last->iconName = CopyString(value); + + last->submenu = Allocate(sizeof(Menu)); + child = last->submenu; + + value = FindAttribute(start->attributes, HEIGHT_ATTRIBUTE); + if(value) { + child->itemHeight = atoi(value); + } else { + child->itemHeight = menu->itemHeight; + } + + value = FindAttribute(start->attributes, LABELED_ATTRIBUTE); + if(value && !strcmp(value, TRUE_VALUE)) { + if(last->name) { + child->label = CopyString(last->name); + } else { + child->label = CopyString(DEFAULT_TITLE); + } + } else { + child->label = NULL; + } + + last->submenu->items = NULL; + ParseMenuItem(start->subnodeHead, last->submenu, NULL); + + break; + case TOK_PROGRAM: + + last = InsertMenuItem(last); + if(!menu->items) { + menu->items = last; + } + + value = FindAttribute(start->attributes, LABEL_ATTRIBUTE); + if(value) { + last->name = CopyString(value); + } else if(start->value) { + last->name = CopyString(start->value); + } + + value = FindAttribute(start->attributes, ICON_ATTRIBUTE); + last->iconName = CopyString(value); + + last->action.type = MA_EXECUTE; + last->action.data.str = CopyString(start->value); + + break; + case TOK_SEPARATOR: + + last = InsertMenuItem(last); + last->type = MENU_ITEM_SEPARATOR; + if(!menu->items) { + menu->items = last; + } + + break; + case TOK_DESKTOPS: + case TOK_STICK: + case TOK_MAXIMIZE: + case TOK_MINIMIZE: + case TOK_SHADE: + case TOK_MOVE: + case TOK_RESIZE: + case TOK_KILL: + case TOK_CLOSE: + + last = InsertMenuItem(last); + if(!menu->items) { + menu->items = last; + } + + value = FindAttribute(start->attributes, LABEL_ATTRIBUTE); + if(!value) { + value = GetTokenName(start); + } + last->name = CopyString(value); + + value = FindAttribute(start->attributes, ICON_ATTRIBUTE); + last->iconName = CopyString(value); + + switch(start->type) { + case TOK_DESKTOPS: + last->action.type = MA_DESKTOP; + break; + case TOK_STICK: + last->action.type = MA_STICK; + break; + case TOK_MAXIMIZE: + last->action.type = MA_MAXIMIZE; + break; + case TOK_MINIMIZE: + last->action.type = MA_MINIMIZE; + break; + case TOK_SHADE: + last->action.type = MA_SHADE; + break; + case TOK_MOVE: + last->action.type = MA_MOVE; + break; + case TOK_RESIZE: + last->action.type = MA_RESIZE; + break; + case TOK_KILL: + last->action.type = MA_KILL; + break; + case TOK_CLOSE: + last->action.type = MA_CLOSE; + break; + default: + break; + } + + break; + case TOK_EXIT: + + last = InsertMenuItem(last); + if(!menu->items) { + menu->items = last; + } + + value = FindAttribute(start->attributes, CONFIRM_ATTRIBUTE); + if(value && !strcmp(value, FALSE_VALUE)) { + SetShowExitConfirmation(0); + } else { + SetShowExitConfirmation(1); + } + + value = FindAttribute(start->attributes, LABEL_ATTRIBUTE); + if(!value) { + value = GetTokenName(start); + } + last->name = CopyString(value); + + value = FindAttribute(start->attributes, ICON_ATTRIBUTE); + last->iconName = CopyString(value); + + last->action.type = MA_EXIT; + last->action.data.str = CopyString(start->value); + + break; + case TOK_RESTART: + + last = InsertMenuItem(last); + if(!menu->items) { + menu->items = last; + } + + value = FindAttribute(start->attributes, LABEL_ATTRIBUTE); + if(!value) { + value = GetTokenName(start); + } + last->name = CopyString(value); + + value = FindAttribute(start->attributes, ICON_ATTRIBUTE); + last->iconName = CopyString(value); + + last->action.type = MA_RESTART; + + break; + default: + InvalidTag(start, TOK_MENU); + break; + } + start = start->next; + } + + return last; + +} + +/**************************************************************************** + ****************************************************************************/ +MenuItem *ParseMenuInclude(const TokenNode *tp, Menu *menu, + MenuItem *last) { + + FILE *fd; + char *path; + char *buffer = NULL; + TokenNode *mp; + + Assert(tp); + + if(!strncmp(tp->value, "exec:", 5)) { + + path = Allocate(strlen(tp->value) - 5 + 1); + strcpy(path, tp->value + 5); + ExpandPath(&path); + + fd = popen(path, "r"); + if(fd) { + buffer = ReadFile(fd); + pclose(fd); + } else { + ParseError(tp, "could not execute included program: %s", path); + } + + } else { + + path = CopyString(tp->value); + ExpandPath(&path); + + fd = fopen(path, "r"); + if(fd) { + buffer = ReadFile(fd); + fclose(fd); + } else { + ParseError(tp, "could not open include: %s", path); + } + + } + + if(!buffer) { + Release(path); + return last; + } + + mp = Tokenize(buffer, path); + Release(buffer); + Release(path); + + if(!mp || mp->type != TOK_MENU) { + ParseError(tp, "invalid included menu: %s", tp->value); + } else { + last = ParseMenuItem(mp, menu, last); + } + + if(mp) { + ReleaseTokens(mp); + } + + return last; + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseKey(const TokenNode *tp) { + + const char *key; + const char *code; + const char *mask; + const char *action; + const char *command; + KeyType k; + int x; + + Assert(tp); + + mask = FindAttribute(tp->attributes, "mask"); + key = FindAttribute(tp->attributes, "key"); + code = FindAttribute(tp->attributes, "keycode"); + + action = tp->value; + if(action == NULL) { + ParseError(tp, "no action specified for Key"); + return; + } + + command = NULL; + k = KEY_NONE; + if(!strncmp(action, "exec:", 5)) { + k = KEY_EXEC; + command = action + 5; + } else if(!strncmp(action, "root:", 5)) { + k = KEY_ROOT; + command = action + 5; + } else { + for(x = 0; KEY_MAP[x].name; x++) { + if(!strcmp(action, KEY_MAP[x].name)) { + k = KEY_MAP[x].key; + break; + } + } + } + + if(k == KEY_NONE) { + ParseError(tp, "invalid Key action: \"%s\"", action); + } else { + InsertBinding(k, mask, key, code, command); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseMouse(const TokenNode *tp) { +} + +/*************************************************************************** + ***************************************************************************/ +void ParseBorderStyle(const TokenNode *tp) { + + const TokenNode *np; + + Assert(tp); + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_FONT: + SetFont(FONT_BORDER, np->value); + break; + case TOK_WIDTH: + SetBorderWidth(np->value); + break; + case TOK_HEIGHT: + SetTitleHeight(np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_BORDER_FG, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_BORDER_BG, np->value); + break; + case TOK_ACTIVEFOREGROUND: + SetColor(COLOR_BORDER_ACTIVE_FG, np->value); + break; + case TOK_ACTIVEBACKGROUND: + SetColor(COLOR_BORDER_ACTIVE_BG, np->value); + break; + default: + InvalidTag(np, TOK_BORDERSTYLE); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseInclude(const TokenNode *tp, int depth) { + + char *temp; + + Assert(tp); + + if(!tp->value) { + + ParseError(tp, "no include file specified", temp); + + } else { + + temp = CopyString(tp->value); + + ExpandPath(&temp); + + if(!ParseFile(temp, depth)) { + ParseError(tp, "could not open included file %s", temp); + } + + Release(temp); + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseDesktops(const TokenNode *tp) { + + TokenNode *np; + char *attr; + unsigned int x; + + Assert(tp); + + attr = FindAttribute(tp->attributes, COUNT_ATTRIBUTE); + if(attr) { + SetDesktopCount(attr); + } else { + desktopCount = DEFAULT_DESKTOP_COUNT; + } + + x = 0; + for(x = 0, np = tp->subnodeHead; np; np = np->next, x++) { + if(x >= desktopCount) { + break; + } + switch(np->type) { + case TOK_NAME: + SetDesktopName(x, np->value); + break; + default: + InvalidTag(np, TOK_DESKTOPS); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseTaskListStyle(const TokenNode *tp) { + + const char *temp; + TokenNode *np; + + temp = FindAttribute(tp->attributes, INSERT_ATTRIBUTE); + if(temp) { + SetTaskBarInsertMode(temp); + } + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_FONT: + SetFont(FONT_TASK, np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_TASK_FG, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_TASK_BG, np->value); + break; + case TOK_ACTIVEFOREGROUND: + SetColor(COLOR_TASK_ACTIVE_FG, np->value); + break; + case TOK_ACTIVEBACKGROUND: + SetColor(COLOR_TASK_ACTIVE_BG, np->value); + break; + default: + InvalidTag(np, TOK_TASKLISTSTYLE); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseTrayStyle(const TokenNode *tp) { + + const TokenNode *np; + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_FONT: + SetFont(FONT_TRAY, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_TRAY_BG, np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_TRAY_FG, np->value); + break; + default: + InvalidTag(np, TOK_TRAYSTYLE); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseTray(const TokenNode *tp) { + + const TokenNode *np; + const char *attr; + TrayType *tray; + + Assert(tp); + + tray = CreateTray(); + + attr = FindAttribute(tp->attributes, AUTOHIDE_ATTRIBUTE); + if(attr && !strcmp(attr, TRUE_VALUE)) { + SetAutoHideTray(tray, 1); + } else { + SetAutoHideTray(tray, 0); + } + + attr = FindAttribute(tp->attributes, X_ATTRIBUTE); + if(attr) { + SetTrayX(tray, attr); + } + + attr = FindAttribute(tp->attributes, Y_ATTRIBUTE); + if(attr) { + SetTrayY(tray, attr); + } + + attr = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE); + if(attr) { + SetTrayWidth(tray, attr); + } + + attr = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE); + if(attr) { + SetTrayHeight(tray, attr); + } + + attr = FindAttribute(tp->attributes, VALIGN_ATTRIBUTE); + SetTrayVerticalAlignment(tray, attr); + + attr = FindAttribute(tp->attributes, HALIGN_ATTRIBUTE); + SetTrayHorizontalAlignment(tray, attr); + + attr = FindAttribute(tp->attributes, LAYOUT_ATTRIBUTE); + SetTrayLayout(tray, attr); + + attr = FindAttribute(tp->attributes, LAYER_ATTRIBUTE); + if(attr) { + SetTrayLayer(tray, attr); + } + + attr = FindAttribute(tp->attributes, BORDER_ATTRIBUTE); + if(attr) { + SetTrayBorder(tray, attr); + } + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_PAGER: + ParsePager(np, tray); + break; + case TOK_TASKLIST: + ParseTaskList(np, tray); + break; + case TOK_SWALLOW: + ParseSwallow(np, tray); + break; + case TOK_TRAYBUTTON: + ParseTrayButton(np, tray); + break; + case TOK_CLOCK: + ParseClock(np, tray); + break; + case TOK_DOCK: + ParseDock(np, tray); + break; + default: + InvalidTag(np, TOK_TRAY); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParsePager(const TokenNode *tp, TrayType *tray) { + + TrayComponentType *cp; + + Assert(tp); + Assert(tray); + + cp = CreatePager(); + AddTrayComponent(tray, cp); + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseTaskList(const TokenNode *tp, TrayType *tray) { + + TrayComponentType *cp; + const char *temp; + + Assert(tp); + Assert(tray); + + cp = CreateTaskBar(); + AddTrayComponent(tray, cp); + + temp = FindAttribute(tp->attributes, MAX_WIDTH_ATTRIBUTE); + if(temp) { + SetMaxTaskBarItemWidth(cp, temp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseSwallow(const TokenNode *tp, TrayType *tray) { + + TrayComponentType *cp; + const char *name; + const char *temp; + int width, height; + + Assert(tp); + Assert(tray); + + name = FindAttribute(tp->attributes, NAME_ATTRIBUTE); + if(name == NULL) { + name = tp->value; + } + + temp = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE); + if(temp) { + width = atoi(temp); + } else { + width = 0; + } + + temp = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE); + if(temp) { + height = atoi(temp); + } else { + height = 0; + } + + cp = CreateSwallow(name, tp->value, width, height); + if(cp) { + AddTrayComponent(tray, cp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseTrayButton(const TokenNode *tp, TrayType *tray) { + + TrayComponentType *cp; + const char *icon; + const char *label; + const char *popup; + const char *temp; + int width, height; + + Assert(tp); + Assert(tray); + + icon = FindAttribute(tp->attributes, ICON_ATTRIBUTE); + label = FindAttribute(tp->attributes, LABEL_ATTRIBUTE); + popup = FindAttribute(tp->attributes, POPUP_ATTRIBUTE); + + temp = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE); + if(temp) { + width = atoi(temp); + } else { + width = 0; + } + + temp = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE); + if(temp) { + height = atoi(temp); + } else { + height = 0; + } + + cp = CreateTrayButton(icon, label, tp->value, popup, width, height); + if(cp) { + AddTrayComponent(tray, cp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseClock(const TokenNode *tp, TrayType *tray) { + + TrayComponentType *cp; + const char *format; + const char *command; + const char *temp; + int width, height; + + Assert(tp); + Assert(tray); + + format = FindAttribute(tp->attributes, FORMAT_ATTRIBUTE); + + if(tp->value && strlen(tp->value) > 0) { + command = tp->value; + } else { + command = NULL; + } + + temp = FindAttribute(tp->attributes, WIDTH_ATTRIBUTE); + if(temp) { + width = atoi(temp); + } else { + width = 0; + } + + temp = FindAttribute(tp->attributes, HEIGHT_ATTRIBUTE); + if(temp) { + height = atoi(temp); + } else { + height = 0; + } + + cp = CreateClock(format, command, width, height); + if(cp) { + AddTrayComponent(tray, cp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseDock(const TokenNode *tp, TrayType *tray) { + + TrayComponentType *cp; + + Assert(tp); + Assert(tray); + + cp = CreateDock(); + if(cp) { + AddTrayComponent(tray, cp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParsePagerStyle(const TokenNode *tp) { + + const TokenNode *np; + + Assert(tp); + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_OUTLINE: + SetColor(COLOR_PAGER_OUTLINE, np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_PAGER_FG, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_PAGER_BG, np->value); + break; + case TOK_ACTIVEFOREGROUND: + SetColor(COLOR_PAGER_ACTIVE_FG, np->value); + break; + case TOK_ACTIVEBACKGROUND: + SetColor(COLOR_PAGER_ACTIVE_BG, np->value); + break; + default: + InvalidTag(np, TOK_PAGERSTYLE); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParsePopupStyle(const TokenNode *tp) { + + const TokenNode *np; + const char *str; + + Assert(tp); + + str = FindAttribute(tp->attributes, ENABLED_ATTRIBUTE); + if(str) { + if(!strcmp(str, TRUE_VALUE)) { + SetPopupEnabled(1); + } else if(!strcmp(str, FALSE_VALUE)) { + SetPopupEnabled(0); + } else { + ParseError(tp, "invalid enabled value: \"%s\"", str); + } + } + + str = FindAttribute(tp->attributes, DELAY_ATTRIBUTE); + if(str) { + SetPopupDelay(str); + } + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_FONT: + SetFont(FONT_POPUP, np->value); + break; + case TOK_OUTLINE: + SetColor(COLOR_POPUP_OUTLINE, np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_POPUP_FG, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_POPUP_BG, np->value); + break; + default: + InvalidTag(np, TOK_POPUPSTYLE); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseMenuStyle(const TokenNode *tp) { + + const TokenNode *np; + + Assert(tp); + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_FONT: + SetFont(FONT_MENU, np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_MENU_FG, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_MENU_BG, np->value); + break; + case TOK_ACTIVEFOREGROUND: + SetColor(COLOR_MENU_ACTIVE_FG, np->value); + break; + case TOK_ACTIVEBACKGROUND: + SetColor(COLOR_MENU_ACTIVE_BG, np->value); + break; + default: + InvalidTag(np, TOK_MENUSTYLE); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseClockStyle(const TokenNode *tp) { + + const TokenNode *np; + + Assert(tp); + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_FONT: + SetFont(FONT_CLOCK, np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_CLOCK_FG, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_CLOCK_BG, np->value); + break; + default: + InvalidTag(np, TOK_CLOCKSTYLE); + break; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseTrayButtonStyle(const TokenNode *tp) { + + const TokenNode *np; + + Assert(tp); + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_FONT: + SetFont(FONT_TRAYBUTTON, np->value); + break; + case TOK_FOREGROUND: + SetColor(COLOR_TRAYBUTTON_FG, np->value); + break; + case TOK_BACKGROUND: + SetColor(COLOR_TRAYBUTTON_BG, np->value); + break; + default: + InvalidTag(np, TOK_TRAYBUTTONSTYLE); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseGroup(const TokenNode *tp) { + + const TokenNode *np; + struct GroupType *group; + + Assert(tp); + + group = CreateGroup(); + + for(np = tp->subnodeHead; np; np = np->next) { + switch(np->type) { + case TOK_CLASS: + AddGroupClass(group, np->value); + break; + case TOK_NAME: + AddGroupName(group, np->value); + break; + case TOK_OPTION: + ParseGroupOption(np, group, np->value); + break; + default: + InvalidTag(np, TOK_GROUP); + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ParseGroupOption(const TokenNode *tp, struct GroupType *group, + const char *option) { + + if(!option) { + return; + } + + if(!strcmp(option, "sticky")) { + AddGroupOption(group, OPTION_STICKY); + } else if(!strcmp(option, "nolist")) { + AddGroupOption(group, OPTION_NOLIST); + } else if(!strcmp(option, "border")) { + AddGroupOption(group, OPTION_BORDER); + } else if(!strcmp(option, "noborder")) { + AddGroupOption(group, OPTION_NOBORDER); + } else if(!strcmp(option, "title")) { + AddGroupOption(group, OPTION_TITLE); + } else if(!strcmp(option, "notitle")) { + AddGroupOption(group, OPTION_NOTITLE); + } else if(!strcmp(option, "pignore")) { + AddGroupOption(group, OPTION_PIGNORE); + } else if(!strcmp(option, "maximized")) { + AddGroupOption(group, OPTION_MAXIMIZED); + } else if(!strcmp(option, "minimized")) { + AddGroupOption(group, OPTION_MINIMIZED); + } else if(!strcmp(option, "shaded")) { + AddGroupOption(group, OPTION_SHADED); + } else if(!strncmp(option, "layer:", 6)) { + AddGroupOptionValue(group, OPTION_LAYER, option + 6); + } else if(!strncmp(option, "desktop:", 8)) { + AddGroupOptionValue(group, OPTION_DESKTOP, option + 8); + } else if(!strncmp(option, "icon:", 5)) { + AddGroupOptionValue(group, OPTION_ICON, option + 5); + } else { + ParseError(tp, "invalid Group Option: %s", option); + } + +} + +/*************************************************************************** + ***************************************************************************/ +char *FindAttribute(AttributeNode *ap, const char *name) { + + while(ap) { + if(!strcmp(name, ap->name)) { + return ap->value; + } + ap = ap->next; + } + + return NULL; +} + +/*************************************************************************** + ***************************************************************************/ +char *ReadFile(FILE *fd) { + + const int BLOCK_SIZE = 1024; + + char *buffer; + int len, max; + int ch; + + len = 0; + max = BLOCK_SIZE; + buffer = Allocate(max + 1); + + for(;;) { + ch = fgetc(fd); + if(ch == EOF) { + break; + } + buffer[len++] = ch; + if(len >= max) { + max += BLOCK_SIZE; + buffer = Reallocate(buffer, max + 1); + } + } + buffer[len] = 0; + + return buffer; +} + +/**************************************************************************** + ****************************************************************************/ +void InvalidTag(const TokenNode *tp, TokenType parent) { + + ParseError(tp, "invalid tag in %s: %s", + GetTokenTypeName(parent), GetTokenName(tp)); + +} + +/**************************************************************************** + ****************************************************************************/ +void ParseError(const TokenNode *tp, const char *str, ...) { + + va_list ap; + + static const char *NULL_MESSAGE = "configuration error"; + static const char *FILE_MESSAGE = "%s[%d]"; + + char *msg; + + va_start(ap, str); + + if(tp) { + msg = Allocate(strlen(FILE_MESSAGE) + strlen(tp->fileName) + 1); + sprintf(msg, FILE_MESSAGE, tp->fileName, tp->line); + } else { + msg = CopyString(NULL_MESSAGE); + } + + WarningVA(msg, str, ap); + + Release(msg); + + va_end(ap); + +} + + diff --git a/src/parse.h b/src/parse.h new file mode 100644 index 0000000..4e60f13 --- /dev/null +++ b/src/parse.h @@ -0,0 +1,19 @@ +/** + * @file parse.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header file for the JWM configuration parser. + * + */ + +#ifndef PARSE_H +#define PARSE_H + +/** Parse a configuration file. + * @param fileName The file to parse. + */ +void ParseConfig(const char *fileName); + +#endif + diff --git a/src/place.c b/src/place.c new file mode 100644 index 0000000..f70ce73 --- /dev/null +++ b/src/place.c @@ -0,0 +1,647 @@ +/**************************************************************************** + * Client placement functions. + * Copyright (C) 2005 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "place.h" +#include "client.h" +#include "screen.h" +#include "border.h" +#include "tray.h" +#include "main.h" + +typedef struct BoundingBox { + int x, y; + int width, height; +} BoundingBox; + +typedef struct Strut { + ClientNode *client; + BoundingBox box; + struct Strut *prev; + struct Strut *next; +} Strut; + +static Strut *struts = NULL; +static Strut *strutsTail = NULL; + +/* desktopCount x screenCount */ +/* Note that we assume x and y are 0 based for all screens here. */ +static int *cascadeOffsets = NULL; + +static void GetScreenBounds(const ScreenType *sp, BoundingBox *box); +static void UpdateTrayBounds(BoundingBox *box, unsigned int layer); +static void UpdateStrutBounds(BoundingBox *box); +static void SubtractBounds(const BoundingBox *src, BoundingBox *dest); + +/**************************************************************************** + ****************************************************************************/ +void InitializePlacement() { +} + +/**************************************************************************** + ****************************************************************************/ +void StartupPlacement() { + + int count; + int x; + + count = desktopCount * GetScreenCount(); + cascadeOffsets = Allocate(count * sizeof(int)); + + for(x = 0; x < count; x++) { + cascadeOffsets[x] = borderWidth + titleHeight; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownPlacement() { + + Strut *sp; + + Release(cascadeOffsets); + + while(struts) { + sp = struts->next; + Release(struts); + struts = sp; + } + strutsTail = NULL; + +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyPlacement() { +} + +/**************************************************************************** + ****************************************************************************/ +void RemoveClientStrut(ClientNode *np) { + + Strut *sp; + + for(sp = struts; sp; sp = sp->next) { + if(sp->client == np) { + if(sp->prev) { + sp->prev->next = sp->next; + } else { + struts = sp->next; + } + if(sp->next) { + sp->next->prev = sp->prev; + } else { + strutsTail = sp->prev; + } + Release(sp); + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ReadClientStrut(ClientNode *np) { + + BoundingBox box; + Strut *sp; + int status; + Atom actualType; + int actualFormat; + unsigned long count; + unsigned long bytesLeft; + unsigned char *value; + long *lvalue; + long leftWidth, rightWidth, topHeight, bottomHeight; + long leftStart, leftEnd, rightStart, rightEnd; + long topStart, topEnd, bottomStart, bottomEnd; + + RemoveClientStrut(np); + + box.x = 0; + box.y = 0; + box.width = 0; + box.height = 0; + + /* First try to read _NET_WM_STRUT_PARTIAL */ + /* Format is: + * left_width, right_width, top_width, bottom_width, + * left_start_y, left_end_y, right_start_y, right_end_y, + * top_start_x, top_end_x, bottom_start_x, bottom_end_x + */ + status = JXGetWindowProperty(display, np->window, + atoms[ATOM_NET_WM_STRUT_PARTIAL], 0, 12, False, XA_CARDINAL, + &actualType, &actualFormat, &count, &bytesLeft, &value); + if(status == Success) { + if(count == 12) { + lvalue = (long*)value; + leftWidth = lvalue[0]; + rightWidth = lvalue[1]; + topHeight = lvalue[2]; + bottomHeight = lvalue[3]; + leftStart = lvalue[4]; + leftEnd = lvalue[5]; + rightStart = lvalue[6]; + rightEnd = lvalue[7]; + topStart = lvalue[8]; + topEnd = lvalue[9]; + bottomStart = lvalue[10]; + bottomEnd = lvalue[11]; + + if(leftWidth > 0) { + box.width = leftWidth; + box.x = leftStart; + } + + if(rightWidth > 0) { + box.width = rightWidth; + box.x = rightStart; + } + + if(topHeight > 0) { + box.height = topHeight; + box.y = topStart; + } + + if(bottomHeight > 0) { + box.height = bottomHeight; + box.y = bottomStart; + } + + sp = Allocate(sizeof(Strut)); + sp->client = np; + sp->box = box; + sp->prev = NULL; + sp->next = struts; + if(struts) { + struts->prev = sp; + } else { + strutsTail = sp; + } + struts = sp; + + } + JXFree(value); + return; + } + + /* Next try to read _NET_WM_STRUT */ + /* Format is: left_width, right_width, top_width, bottom_width */ + status = JXGetWindowProperty(display, np->window, + atoms[ATOM_NET_WM_STRUT], 0, 4, False, XA_CARDINAL, + &actualType, &actualFormat, &count, &bytesLeft, &value); + if(status == Success) { + if(count == 4) { + lvalue = (long*)value; + leftWidth = lvalue[0]; + rightWidth = lvalue[1]; + topHeight = lvalue[2]; + bottomHeight = lvalue[3]; + + if(leftWidth > 0) { + box.x = 0; + box.width = leftWidth; + } + + if(rightWidth > 0) { + box.x = rootWidth - rightWidth; + box.width = rightWidth; + } + + if(topHeight > 0) { + box.y = 0; + box.height = topHeight; + } + + if(bottomHeight > 0) { + box.y = rootHeight - bottomHeight; + box.height = bottomHeight; + } + + sp = Allocate(sizeof(Strut)); + sp->client = np; + sp->box = box; + sp->prev = NULL; + sp->next = struts; + if(struts) { + struts->prev = sp; + } else { + strutsTail = sp; + } + struts = sp; + + } + JXFree(value); + return; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void GetScreenBounds(const ScreenType *sp, BoundingBox *box) { + + box->x = sp->x; + box->y = sp->y; + box->width = sp->width; + box->height = sp->height; + +} + +/**************************************************************************** + * Shrink dest such that it does not intersect with src. + ****************************************************************************/ +void SubtractBounds(const BoundingBox *src, BoundingBox *dest) { + + BoundingBox boxes[4]; + + if(src->x + src->width <= dest->x) { + return; + } + if(src->y + src->height <= dest->y) { + return; + } + if(dest->x + dest->width <= src->x) { + return; + } + if(dest->y + dest->height <= src->y) { + return; + } + + /* There are four ways to do this: + * 0. Increase the x-coordinate and decrease the width of dest. + * 1. Increase the y-coordinate and decrease the height of dest. + * 2. Decrease the width of dest. + * 3. Decrease the height of dest. + * We will chose the option which leaves the greatest area. + * Note that negative areas are possible. + */ + + /* 0 */ + boxes[0] = *dest; + boxes[0].x = src->x + src->width; + boxes[0].width = dest->x + dest->width - boxes[0].x; + + /* 1 */ + boxes[1] = *dest; + boxes[1].y = src->y + src->height; + boxes[1].height = dest->y + dest->height - boxes[1].y; + + /* 2 */ + boxes[2] = *dest; + boxes[2].width = src->x - dest->x; + + /* 3 */ + boxes[3] = *dest; + boxes[3].height = src->y - dest->y; + + /* 0 and 1, winner in 0. */ + if(boxes[0].width * boxes[0].height < boxes[1].width * boxes[1].height) { + boxes[0] = boxes[1]; + } + + /* 2 and 3, winner in 2. */ + if(boxes[2].width * boxes[2].height < boxes[3].width * boxes[3].height) { + boxes[2] = boxes[3]; + } + + /* 0 and 2, winner in dest. */ + if(boxes[0].width * boxes[0].height < boxes[2].width * boxes[2].height) { + *dest = boxes[2]; + } else { + *dest = boxes[0]; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void UpdateTrayBounds(BoundingBox *box, unsigned int layer) { + + TrayType *tp; + BoundingBox src; + BoundingBox last; + + for(tp = GetTrays(); tp; tp = tp->next) { + + if(tp->layer > layer && !tp->autoHide) { + + src.x = tp->x; + src.y = tp->y; + src.width = tp->width; + src.height = tp->height; + + last = *box; + SubtractBounds(&src, box); + if(box->width * box->height <= 0) { + *box = last; + break; + } + + } + + } + +} + +/**************************************************************************** + ****************************************************************************/ +void UpdateStrutBounds(BoundingBox *box) { + + Strut *sp; + BoundingBox last; + + for(sp = struts; sp; sp = sp->next) { + if(sp->client->state.desktop == currentDesktop + || (sp->client->state.status & STAT_STICKY)) { + continue; + } + last = *box; + SubtractBounds(&sp->box, box); + if(box->width * box->height <= 0) { + *box = last; + break; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +void PlaceClient(ClientNode *np, int alreadyMapped) { + + BoundingBox box; + int north, south, east, west; + const ScreenType *sp; + int cascadeIndex; + int overflow; + + Assert(np); + + GetBorderSize(np, &north, &south, &east, &west); + + if(np->x + np->width > rootWidth || np->y + np->height > rootWidth) { + overflow = 1; + } else { + overflow = 0; + } + + sp = GetMouseScreen(); + GetScreenBounds(sp, &box); + + if(!overflow && (alreadyMapped + || (!(np->state.status & STAT_PIGNORE) + && (np->sizeFlags & (PPosition | USPosition))))) { + + GravitateClient(np, 0); + + } else { + + UpdateTrayBounds(&box, np->state.layer); + UpdateStrutBounds(&box); + + cascadeIndex = sp->index * desktopCount + currentDesktop; + + /* Set the cascaded location. */ + np->x = box.x + west + cascadeOffsets[cascadeIndex]; + np->y = box.y + north + cascadeOffsets[cascadeIndex]; + cascadeOffsets[cascadeIndex] += borderWidth + titleHeight; + + /* Check for cascade overflow. */ + overflow = 0; + if(np->x + np->width - box.x > box.width) { + overflow = 1; + } else if(np->y + np->height - box.y > box.height) { + overflow = 1; + } + + if(overflow) { + + cascadeOffsets[cascadeIndex] = borderWidth + titleHeight; + np->x = box.x + west + cascadeOffsets[cascadeIndex]; + np->y = box.y + north + cascadeOffsets[cascadeIndex]; + + /* Check for client overflow. */ + overflow = 0; + if(np->x + np->width - box.x > box.width) { + overflow = 1; + } else if(np->y + np->height - box.y > box.height) { + overflow = 1; + } + + /* Update cascade position or position client. */ + if(overflow) { + np->x = box.x + west; + np->y = box.y + north; + } else { + cascadeOffsets[cascadeIndex] += borderWidth + titleHeight; + } + + } + + } + + if(np->state.status & STAT_FULLSCREEN) { + JXMoveWindow(display, np->parent, sp->x, sp->y); + } else { + JXMoveWindow(display, np->parent, np->x - west, np->y - north); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void ConstrainSize(ClientNode *np) { + + BoundingBox box; + const ScreenType *sp; + int north, south, east, west; + float ratio, minr, maxr; + + Assert(np); + + /* Determine if the size needs to be constrained. */ + sp = GetCurrentScreen(np->x, np->y); + if(np->width < sp->width && np->height < sp->height) { + return; + } + + /* Constrain the size. */ + GetBorderSize(np, &north, &south, &east, &west); + + GetScreenBounds(sp, &box); + UpdateTrayBounds(&box, np->state.layer); + UpdateStrutBounds(&box); + + box.x += west; + box.y += north; + box.width -= east + west; + box.height -= north + south; + + if(box.width > np->maxWidth) { + box.width = np->maxWidth; + } + if(box.height > np->maxHeight) { + box.height = np->maxHeight; + } + + if(np->sizeFlags & PAspect) { + + ratio = (float)box.width / box.height; + + minr = (float)np->aspect.minx / np->aspect.miny; + if(ratio < minr) { + box.height = (int)((float)box.width / minr); + } + + maxr = (float)np->aspect.maxx / np->aspect.maxy; + if(ratio > maxr) { + box.width = (int)((float)box.height * maxr); + } + + } + + np->x = box.x; + np->y = box.y; + np->width = box.width - (box.width % np->xinc); + np->height = box.height - (box.height % np->yinc); + +} + +/**************************************************************************** + ****************************************************************************/ +void PlaceMaximizedClient(ClientNode *np) { + + BoundingBox box; + const ScreenType *sp; + int north, south, east, west; + float ratio, minr, maxr; + + np->oldx = np->x; + np->oldy = np->y; + np->oldWidth = np->width; + np->oldHeight = np->height; + + GetBorderSize(np, &north, &south, &east, &west); + + sp = GetCurrentScreen( + np->x + (east + west + np->width) / 2, + np->y + (north + south + np->height) / 2); + GetScreenBounds(sp, &box); + UpdateTrayBounds(&box, np->state.layer); + UpdateStrutBounds(&box); + + box.x += west; + box.y += north; + box.width -= east + west; + box.height -= north + south; + + if(box.width > np->maxWidth) { + box.width = np->maxWidth; + } + if(box.height > np->maxHeight) { + box.height = np->maxHeight; + } + + if(np->sizeFlags & PAspect) { + + ratio = (float)box.width / box.height; + + minr = (float)np->aspect.minx / np->aspect.miny; + if(ratio < minr) { + box.height = (int)((float)box.width / minr); + } + + maxr = (float)np->aspect.maxx / np->aspect.maxy; + if(ratio > maxr) { + box.width = (int)((float)box.height * maxr); + } + + } + + np->x = box.x; + np->y = box.y; + np->width = box.width - (box.width % np->xinc); + np->height = box.height - (box.height % np->yinc); + + np->state.status |= STAT_MAXIMIZED; + +} + +/**************************************************************************** + ****************************************************************************/ +void GetGravityDelta(const ClientNode *np, int *x, int *y) { + + int north, south, east, west; + + Assert(np); + Assert(x); + Assert(y); + + GetBorderSize(np, &north, &south, &east, &west); + + switch(np->gravity) { + case NorthWestGravity: + *y = -north; + *x = -west; + break; + case NorthGravity: + *y = -north; + break; + case NorthEastGravity: + *y = -north; + *x = west; + break; + case WestGravity: + *x = -west; + break; + case CenterGravity: + *y = (north + south) / 2; + *x = (east + west) / 2; + break; + case EastGravity: + *x = west; + break; + case SouthWestGravity: + *y = south; + *x = -west; + break; + case SouthGravity: + *y = south; + break; + case SouthEastGravity: + *y = south; + *x = west; + break; + default: /* Static */ + *x = 0; + *y = 0; + break; + } + +} + +/**************************************************************************** + * Move the window in the specified direction for reparenting. + ****************************************************************************/ +void GravitateClient(ClientNode *np, int negate) { + + int deltax, deltay; + + Assert(np); + + GetGravityDelta(np, &deltax, &deltay); + + if(negate) { + np->x += deltax; + np->y += deltay; + } else { + np->x -= deltax; + np->y -= deltay; + } + +} + diff --git a/src/place.h b/src/place.h new file mode 100644 index 0000000..7508881 --- /dev/null +++ b/src/place.h @@ -0,0 +1,34 @@ +/** + * @file place.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for client placement functions. + * + */ + +#ifndef PLACE_H +#define PLACE_H + +struct ClientNode; + +/*@{*/ +void InitializePlacement(); +void StartupPlacement(); +void ShutdownPlacement(); +void DestroyPlacement(); +/*@}*/ + +void RemoveClientStrut(struct ClientNode *np); +void ReadClientStrut(struct ClientNode *np); + +void PlaceClient(struct ClientNode *np, int alreadyMapped); +void PlaceMaximizedClient(struct ClientNode *np); +void GravitateClient(struct ClientNode *np, int negate); + +void GetGravityDelta(const struct ClientNode *np, int *x, int *y); + +void ConstrainSize(struct ClientNode *np); + +#endif + diff --git a/src/popup.c b/src/popup.c new file mode 100644 index 0000000..c9caafc --- /dev/null +++ b/src/popup.c @@ -0,0 +1,232 @@ +/**************************************************************************** + * Functions for displaying popup windows. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "popup.h" +#include "main.h" +#include "color.h" +#include "font.h" +#include "screen.h" +#include "cursor.h" +#include "error.h" +#include "timing.h" +#include "misc.h" + +#define DEFAULT_POPUP_DELAY 600 + +typedef struct PopupType { + int isActive; + int x, y; /* The coordinates of the upper-left corner of the popup. */ + int mx, my; /* The mouse position when the popup was created. */ + int width, height; + char *text; + Window window; +} PopupType; + +static PopupType popup; +static int popupEnabled; +int popupDelay; + +static void DrawPopup(); + +/**************************************************************************** + ****************************************************************************/ +void InitializePopup() { + popupDelay = DEFAULT_POPUP_DELAY; + popupEnabled = 1; +} + +/**************************************************************************** + ****************************************************************************/ +void StartupPopup() { + popup.isActive = 0; + popup.text = NULL; + popup.window = None; +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownPopup() { + if(popup.text) { + Release(popup.text); + popup.text = NULL; + } + if(popup.window != None) { + JXDestroyWindow(display, popup.window); + popup.window = None; + } +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyPopup() { +} + +/**************************************************************************** + * Show a popup window. + * x - The x coordinate of the popup window. + * y - The y coordinate of the popup window. + * text - The text to display in the popup. + ****************************************************************************/ +void ShowPopup(int x, int y, const char *text) { + + unsigned long attrMask; + XSetWindowAttributes attr; + const ScreenType *sp; + + Assert(text); + + if(!popupEnabled) { + return; + } + + if(popup.text) { + Release(popup.text); + popup.text = NULL; + } + + if(!strlen(text)) { + return; + } + + popup.text = CopyString(text); + + popup.height = GetStringHeight(FONT_POPUP); + popup.width = GetStringWidth(FONT_POPUP, popup.text); + + popup.height += 2; + popup.width += 8; + + sp = GetCurrentScreen(x, y); + + if(popup.width > sp->width) { + popup.width = sp->width; + } + + popup.x = x; + popup.y = y - popup.height - 2; + + if(popup.width + popup.x >= sp->width) { + popup.x = sp->width - popup.width - 2; + } + if(popup.height + popup.y >= sp->height) { + popup.y = sp->height - popup.height - 2; + } + + if(popup.window == None) { + + attrMask = 0; + + attrMask |= CWEventMask; + attr.event_mask + = ExposureMask + | PointerMotionMask | PointerMotionHintMask; + + attrMask |= CWSaveUnder; + attr.save_under = True; + + attrMask |= CWBackPixel; + attr.background_pixel = colors[COLOR_POPUP_BG]; + + attrMask |= CWBorderPixel; + attr.border_pixel = colors[COLOR_POPUP_OUTLINE]; + + attrMask |= CWDontPropagate; + attr.do_not_propagate_mask + = PointerMotionMask + | ButtonPressMask + | ButtonReleaseMask; + + popup.window = JXCreateWindow(display, rootWindow, popup.x, popup.y, + popup.width, popup.height, 1, CopyFromParent, + InputOutput, CopyFromParent, attrMask, &attr); + + } else { + JXMoveResizeWindow(display, popup.window, popup.x, popup.y, + popup.width, popup.height); + } + + popup.mx = x; + popup.my = y; + + if(!popup.isActive) { + JXMapRaised(display, popup.window); + popup.isActive = 1; + } else { + DrawPopup(); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void SetPopupEnabled(int e) { + popupEnabled = e; +} + +/**************************************************************************** + ****************************************************************************/ +void SetPopupDelay(const char *str) { + + int temp; + + if(str == NULL) { + return; + } + + temp = atoi(str); + + if(temp < 0) { + Warning("invalid popup delay specified: %s\n", str); + } else { + popupDelay = temp; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void SignalPopup(const TimeType *now, int x, int y) { + + if(popup.isActive) { + if(abs(popup.mx - x) > 2 || abs(popup.my - y) > 2) { + JXUnmapWindow(display, popup.window); + popup.isActive = 0; + } + } + +} + +/**************************************************************************** + ****************************************************************************/ +int ProcessPopupEvent(const XEvent *event) { + + if(popup.isActive && event->xany.window == popup.window) { + if(event->type == Expose) { + DrawPopup(); + return 1; + } else if(event->type == MotionNotify) { + JXUnmapWindow(display, popup.window); + popup.isActive = 0; + return 1; + } + } + + return 0; + +} + +/**************************************************************************** + ****************************************************************************/ +void DrawPopup() { + + Assert(popup.isActive); + + JXClearWindow(display, popup.window); + RenderString(popup.window, FONT_POPUP, COLOR_POPUP_FG, 4, 1, + popup.width, NULL, popup.text); + +} + diff --git a/src/popup.h b/src/popup.h new file mode 100644 index 0000000..21e84db --- /dev/null +++ b/src/popup.h @@ -0,0 +1,35 @@ +/** + * @file popup.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for popup functions. + * + */ + +#ifndef POPUP_H +#define POPUP_H + +#define POPUP_DELTA 2 + +struct TimeType; + +/*@{*/ +void InitializePopup(); +void StartupPopup(); +void ShutdownPopup(); +void DestroyPopup(); +/*@}*/ + +void ShowPopup(int x, int y, const char *text); + +void SetPopupEnabled(int e); +void SetPopupDelay(const char *str); + +void SignalPopup(const struct TimeType *now, int x, int y); +int ProcessPopupEvent(const XEvent *event); + +extern int popupDelay; + +#endif + diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..d2d747e --- /dev/null +++ b/src/render.c @@ -0,0 +1,226 @@ +/**************************************************************************** + * Functions to render icons using the XRender extension. + * Copyright (C) 2005 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "render.h" +#include "icon.h" +#include "image.h" +#include "main.h" +#include "color.h" +#include "error.h" + +#ifdef USE_XRENDER +static int haveRender = 0; +#endif + +/**************************************************************************** + ****************************************************************************/ +void QueryRenderExtension() +{ + +#ifdef USE_XRENDER + int event, error; + Bool rc; + + rc = JXRenderQueryExtension(display, &event, &error); + if(rc == True) { + haveRender = 1; + Debug("render extension enabled"); + } else { + haveRender = 0; + Debug("render extension disabled"); + } + + if(haveRender && rootDepth < 24) { + Warning("color depth is %d, disabling icon alpha channel", rootDepth); + haveRender = 0; + } + +#endif + +} + +/**************************************************************************** + ****************************************************************************/ +int PutScaledRenderIcon(IconNode *icon, ScaledIconNode *node, Drawable d, + int x, int y) +{ + +#ifdef USE_XRENDER + + Picture dest; + Picture source; + XRenderPictFormat *fp; + int width, height; + + Assert(icon); + + if(!haveRender) { + return 0; + } + + source = node->imagePicture; + if(source != None) { + + fp = JXRenderFindVisualFormat(display, rootVisual); + Assert(fp); + + dest = JXRenderCreatePicture(display, d, fp, 0, NULL); + + if(node->width == 0) { + width = icon->image->width; + } else { + width = node->width; + } + if(node->height == 0) { + height = icon->image->height; + } else { + height = node->height; + } + + JXRenderComposite(display, PictOpOver, source, None, dest, + 0, 0, 0, 0, x, y, width, height); + + JXRenderFreePicture(display, dest); + + } + + return 1; + +#else + + return 0; + +#endif + +} + +/**************************************************************************** + ****************************************************************************/ +ScaledIconNode *CreateScaledRenderIcon(IconNode *icon, + int width, int height) { + + ScaledIconNode *result = NULL; + +#ifdef USE_XRENDER + + XRenderPictureAttributes picAttributes; + XRenderPictFormat picFormat; + XRenderPictFormat *fp; + XColor color; + GC maskGC; + XImage *destImage; + XImage *destMask; + unsigned long alpha; + int index; + int x, y; + double scalex, scaley; + double srcx, srcy; + int imageLine; + int maskLine; + + Assert(icon); + + if(!haveRender) { + return NULL; + } + + result = Allocate(sizeof(ScaledIconNode)); + result->next = icon->nodes; + icon->nodes = result; + + if(width == 0) { + width = icon->image->width; + } + if(height == 0) { + height = icon->image->height; + } + result->width = width; + result->height = height; + + scalex = (double)icon->image->width / width; + scaley = (double)icon->image->height / height; + + result->mask = JXCreatePixmap(display, rootWindow, width, height, 8); + maskGC = JXCreateGC(display, result->mask, 0, NULL); + result->image = JXCreatePixmap(display, rootWindow, + width, height, rootDepth); + + destImage = JXCreateImage(display, rootVisual, rootDepth, ZPixmap, 0, + NULL, width, height, 8, 0); + destImage->data = Allocate(sizeof(unsigned long) * width * height); + + destMask = JXCreateImage(display, rootVisual, 8, ZPixmap, 0, + NULL, width, height, 8, 0); + destMask->data = Allocate(width * height); + + imageLine = 0; + maskLine = 0; + srcy = 0.0; + for(y = 0; y < height; y++) { + srcx = 0.0; + for(x = 0; x < width; x++) { + + index = (int)srcy * icon->image->width + (int)srcx; + alpha = (icon->image->data[index] >> 24) & 0xFFUL; + color.red = ((icon->image->data[index] >> 16) & 0xFFUL) * 257; + color.green = ((icon->image->data[index] >> 8) & 0xFFUL) * 257; + color.blue = (icon->image->data[index] & 0xFFUL) * 257; + + GetColor(&color); + XPutPixel(destImage, x, y, color.pixel); + destMask->data[maskLine + x] = alpha * 257; + + srcx += scalex; + + } + srcy += scaley; + imageLine += destImage->bytes_per_line; + maskLine += destMask->bytes_per_line; + } + + /* Render the image data to the image pixmap. */ + JXPutImage(display, result->image, rootGC, destImage, 0, 0, 0, 0, + width, height); + Release(destImage->data); + destImage->data = NULL; + JXDestroyImage(destImage); + + /* Render the alpha data to the mask pixmap. */ + JXPutImage(display, result->mask, maskGC, destMask, 0, 0, 0, 0, + width, height); + Release(destMask->data); + destMask->data = NULL; + JXDestroyImage(destMask); + JXFreeGC(display, maskGC); + + /* Create the render picture. */ + picFormat.type = PictTypeDirect; + picFormat.depth = 8; + picFormat.direct.alphaMask = 0xFF; + fp = JXRenderFindFormat(display, + PictFormatType | PictFormatDepth | PictFormatAlphaMask, + &picFormat, 0); + Assert(fp); + result->maskPicture = JXRenderCreatePicture(display, result->mask, + fp, 0, NULL); + picAttributes.alpha_map = result->maskPicture; + fp = JXRenderFindVisualFormat(display, rootVisual); + Assert(fp); + result->imagePicture = JXRenderCreatePicture(display, result->image, + fp, CPAlphaMap, &picAttributes); + + /* Free unneeded pixmaps. */ + JXFreePixmap(display, result->image); + result->image = None; + JXFreePixmap(display, result->mask); + result->mask = None; + +#endif + + return result; + +} + diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..1e7164b --- /dev/null +++ b/src/render.h @@ -0,0 +1,25 @@ +/** + * @file render.h + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Functions to render icons using the XRender extension. + * + */ + +#ifndef RENDER_H +#define RENDER_H + +struct IconNode; +struct ScaledIconNode; + +void QueryRenderExtension(); + +int PutScaledRenderIcon(struct IconNode *icon, struct ScaledIconNode *node, + Drawable d, int x, int y); + +struct ScaledIconNode *CreateScaledRenderIcon(struct IconNode *icon, + int width, int height); + +#endif + diff --git a/src/resize.c b/src/resize.c new file mode 100644 index 0000000..c7961f9 --- /dev/null +++ b/src/resize.c @@ -0,0 +1,491 @@ +/** + * @file resize.c + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Functions to handle resizing client windows. + * + */ + +#include "jwm.h" +#include "resize.h" +#include "client.h" +#include "outline.h" +#include "main.h" +#include "cursor.h" +#include "misc.h" +#include "pager.h" +#include "status.h" +#include "key.h" +#include "event.h" +#include "border.h" + +static ResizeModeType resizeMode = RESIZE_OPAQUE; + +static int shouldStopResize; + +static void StopResize(ClientNode *np); +static void ResizeController(int wasDestroyed); +static void FixWidth(ClientNode *np); +static void FixHeight(ClientNode *np); + +/** Set the resize mode to use. */ +void SetResizeMode(ResizeModeType mode) { + resizeMode = mode; +} + +/** Callback to stop a resize. */ +void ResizeController(int wasDestroyed) { + if(resizeMode == RESIZE_OUTLINE) { + ClearOutline(); + } + JXUngrabPointer(display, CurrentTime); + JXUngrabKeyboard(display, CurrentTime); + DestroyResizeWindow(); + shouldStopResize = 1; +} + +/** Resize a client window (mouse initiated). */ +void ResizeClient(ClientNode *np, BorderActionType action, + int startx, int starty) { + + XEvent event; + int oldx, oldy; + int oldw, oldh; + int gwidth, gheight; + int lastgwidth, lastgheight; + int delta; + int north, south, east, west; + float ratio, minr, maxr; + + Assert(np); + + if(!(np->state.border & BORDER_RESIZE)) { + return; + } + + if(!GrabMouseForResize(action)) { + Debug("ResizeClient: could not grab mouse"); + return; + } + + if(np->state.status & STAT_SHADED) { + action &= ~(BA_RESIZE_N | BA_RESIZE_S); + } + + np->controller = ResizeController; + shouldStopResize = 0; + + oldx = np->x; + oldy = np->y; + oldw = np->width; + oldh = np->height; + + gwidth = (np->width - np->baseWidth) / np->xinc; + gheight = (np->height - np->baseHeight) / np->yinc; + + GetBorderSize(np, &north, &south, &east, &west); + + startx += np->x - west; + starty += np->y - north; + + CreateResizeWindow(np); + UpdateResizeWindow(np, gwidth, gheight); + + if(!(GetMouseMask() & Button1Mask)) { + StopResize(np); + return; + } + + for(;;) { + + WaitForEvent(&event); + + if(shouldStopResize) { + np->controller = NULL; + return; + } + + switch(event.type) { + case ButtonRelease: + if(event.xbutton.button == Button1) { + StopResize(np); + return; + } + break; + case MotionNotify: + + SetMousePosition(event.xmotion.x_root, event.xmotion.y_root); + DiscardMotionEvents(&event, np->window); + + if(action & BA_RESIZE_N) { + delta = (event.xmotion.y - starty) / np->yinc; + delta *= np->yinc; + if(oldh - delta >= np->minHeight + && (oldh - delta <= np->maxHeight || delta > 0)) { + np->height = oldh - delta; + np->y = oldy + delta; + } + if(!(action & (BA_RESIZE_E | BA_RESIZE_W))) { + FixWidth(np); + } + } + if(action & BA_RESIZE_S) { + delta = (event.xmotion.y - starty) / np->yinc; + delta *= np->yinc; + np->height = oldh + delta; + np->height = Max(np->height, np->minHeight); + np->height = Min(np->height, np->maxHeight); + if(!(action & (BA_RESIZE_E | BA_RESIZE_W))) { + FixWidth(np); + } + } + if(action & BA_RESIZE_E) { + delta = (event.xmotion.x - startx) / np->xinc; + delta *= np->xinc; + np->width = oldw + delta; + np->width = Max(np->width, np->minWidth); + np->width = Min(np->width, np->maxWidth); + if(!(action & (BA_RESIZE_N | BA_RESIZE_S))) { + FixHeight(np); + } + } + if(action & BA_RESIZE_W) { + delta = (event.xmotion.x - startx) / np->xinc; + delta *= np->xinc; + if(oldw - delta >= np->minWidth + && (oldw - delta <= np->maxWidth || delta > 0)) { + np->width = oldw - delta; + np->x = oldx + delta; + } + if(!(action & (BA_RESIZE_N | BA_RESIZE_S))) { + FixHeight(np); + } + } + + if(np->sizeFlags & PAspect) { + if((action & (BA_RESIZE_N | BA_RESIZE_S)) && + (action & (BA_RESIZE_E | BA_RESIZE_W))) { + + ratio = (float)np->width / np->height; + + minr = (float)np->aspect.minx / np->aspect.miny; + if(ratio < minr) { + delta = np->width; + np->width = (int)((float)np->height * minr); + if(action & BA_RESIZE_W) { + np->x -= np->width - delta; + } + } + + maxr = (float)np->aspect.maxx / np->aspect.maxy; + if(ratio > maxr) { + delta = np->height; + np->height = (int)((float)np->width / maxr); + if(action & BA_RESIZE_N) { + np->y -= np->height - delta; + } + } + + } + } + + lastgwidth = gwidth; + lastgheight = gheight; + + gwidth = (np->width - np->baseWidth) / np->xinc; + gheight = (np->height - np->baseHeight) / np->yinc; + + if(lastgheight != gheight || lastgwidth != gwidth) { + + if(np->state.status & STAT_MAXIMIZED) { + np->state.status &= ~STAT_MAXIMIZED; + WriteState(np); + SendConfigureEvent(np); + } + + UpdateResizeWindow(np, gwidth, gheight); + + if(resizeMode == RESIZE_OUTLINE) { + ClearOutline(); + if(np->state.status & STAT_SHADED) { + DrawOutline(np->x - west, np->y - north, + np->width + west + east, north + south); + } else { + DrawOutline(np->x - west, np->y - north, + np->width + west + east, + np->height + north + south); + } + } else { + if(np->state.status & STAT_SHADED) { + JXMoveResizeWindow(display, np->parent, + np->x - west, np->y - north, + np->width + west + east, north + south); + } else { + JXMoveResizeWindow(display, np->parent, + np->x - west, np->y - north, + np->width + west + east, + np->height + north + south); + } + JXMoveResizeWindow(display, np->window, west, + north, np->width, np->height); + SendConfigureEvent(np); + } + + UpdatePager(); + + } + + break; + default: + break; + } + } + +} + +/** Resize a client window (keyboard or menu initiated). */ +void ResizeClientKeyboard(ClientNode *np) { + + XEvent event; + int gwidth, gheight; + int lastgwidth, lastgheight; + int north, south, east, west; + int deltax, deltay; + float ratio, minr, maxr; + + Assert(np); + + if(!(np->state.border & BORDER_RESIZE)) { + return; + } + + if(JXGrabKeyboard(display, np->window, True, GrabModeAsync, + GrabModeAsync, CurrentTime) != GrabSuccess) { + Debug("ResizeClientKeyboard: could not grab keyboard"); + return; + } + GrabMouseForResize(BA_RESIZE_S | BA_RESIZE_E | BA_RESIZE); + + np->controller = ResizeController; + shouldStopResize = 0; + + gwidth = (np->width - np->baseWidth) / np->xinc; + gheight = (np->height - np->baseHeight) / np->yinc; + + GetBorderSize(np, &north, &south, &east, &west); + + CreateResizeWindow(np); + UpdateResizeWindow(np, gwidth, gheight); + + MoveMouse(rootWindow, np->x + np->width, np->y + np->height); + DiscardMotionEvents(&event, np->window); + + for(;;) { + + WaitForEvent(&event); + + if(shouldStopResize) { + np->controller = NULL; + return; + } + + deltax = 0; + deltay = 0; + + if(event.type == KeyPress) { + + while(JXCheckTypedWindowEvent(display, np->window, KeyPress, &event)); + + switch(GetKey(&event.xkey) & 0xFF) { + case KEY_UP: + deltay = Min(-np->yinc, -10); + break; + case KEY_DOWN: + deltay = Max(np->yinc, 10); + break; + case KEY_RIGHT: + deltax = Max(np->xinc, 10); + break; + case KEY_LEFT: + deltax = Min(-np->xinc, -10); + break; + default: + StopResize(np); + return; + } + + } else if(event.type == MotionNotify) { + + SetMousePosition(event.xmotion.x_root, event.xmotion.y_root); + DiscardMotionEvents(&event, np->window); + + deltax = event.xmotion.x - (np->x + np->width); + deltay = event.xmotion.y - (np->y + np->height); + + } else if(event.type == ButtonRelease) { + + StopResize(np); + return; + + } + + if(abs(deltax) < np->xinc && abs(deltay) < np->yinc) { + continue; + } + + deltay -= deltay % np->yinc; + np->height += deltay; + np->height = Max(np->height, np->minHeight); + np->height = Min(np->height, np->maxHeight); + deltax -= deltax % np->xinc; + np->width += deltax; + np->width = Max(np->width, np->minWidth); + np->width = Min(np->width, np->maxWidth); + + if(np->sizeFlags & PAspect) { + + ratio = (float)np->width / np->height; + + minr = (float)np->aspect.minx / np->aspect.miny; + if(ratio < minr) { + np->width = (int)((float)np->height * minr); + } + + maxr = (float)np->aspect.maxx / np->aspect.maxy; + if(ratio > maxr) { + np->height = (int)((float)np->width / maxr); + } + + } + + lastgwidth = gwidth; + lastgheight = gheight; + gwidth = (np->width - np->baseWidth) / np->xinc; + gheight = (np->height - np->baseHeight) / np->yinc; + + if(lastgwidth != gwidth || lastgheight != gheight) { + + if(np->state.status & STAT_MAXIMIZED) { + np->state.status &= ~STAT_MAXIMIZED; + WriteState(np); + SendConfigureEvent(np); + } + + UpdateResizeWindow(np, gwidth, gheight); + + if(resizeMode == RESIZE_OUTLINE) { + ClearOutline(); + if(np->state.status & STAT_SHADED) { + DrawOutline(np->x - west, np->y - north, + np->width + west + east, + north + south); + } else { + DrawOutline(np->x - west, np->y - north, + np->width + west + east, + np->height + north + south); + } + } else { + if(np->state.status & STAT_SHADED) { + JXResizeWindow(display, np->parent, + np->width + west + east, north + south); + } else { + JXResizeWindow(display, np->parent, + np->width + west + east, np->height + north + south); + } + JXResizeWindow(display, np->window, np->width, np->height); + SendConfigureEvent(np); + } + + UpdatePager(); + + } + + } + +} + +/** Stop a resize action. */ +void StopResize(ClientNode *np) { + + int north, south, east, west; + + np->controller = NULL; + + if(resizeMode == RESIZE_OUTLINE) { + ClearOutline(); + } + + JXUngrabPointer(display, CurrentTime); + JXUngrabKeyboard(display, CurrentTime); + + DestroyResizeWindow(); + + GetBorderSize(np, &north, &south, &east, &west); + + if(np->state.status & STAT_SHADED) { + JXMoveResizeWindow(display, np->parent, + np->x - west, np->y - north, + np->width + east + west, north + south); + } else { + JXMoveResizeWindow(display, np->parent, + np->x - west, np->y - north, + np->width + east + west, + np->height + north + south); + } + JXMoveResizeWindow(display, np->window, west, + north, np->width, np->height); + SendConfigureEvent(np); + +} + +/** Fix the width to match the aspect ratio. */ +void FixWidth(ClientNode *np) { + + float ratio, minr, maxr; + + Assert(np); + + if((np->sizeFlags & PAspect) && np->height > 0) { + + ratio = (float)np->width / np->height; + + minr = (float)np->aspect.minx / np->aspect.miny; + if(ratio < minr) { + np->width = (int)((float)np->height * minr); + } + + maxr = (float)np->aspect.maxx / np->aspect.maxy; + if(ratio > maxr) { + np->width = (int)((float)np->height * maxr); + } + + } + +} + +/** Fix the height to match the aspect ratio. */ +void FixHeight(ClientNode *np) { + + float ratio, minr, maxr; + + Assert(np); + + if((np->sizeFlags & PAspect) && np->height > 0) { + + ratio = (float)np->width / np->height; + + minr = (float)np->aspect.minx / np->aspect.miny; + if(ratio < minr) { + np->height = (int)((float)np->width / minr); + } + + maxr = (float)np->aspect.maxx / np->aspect.maxy; + if(ratio > maxr) { + np->height = (int)((float)np->width / maxr); + } + + } + +} + diff --git a/src/resize.h b/src/resize.h new file mode 100644 index 0000000..92a407f --- /dev/null +++ b/src/resize.h @@ -0,0 +1,42 @@ +/** + * @file resize.h + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Header for client window resize functions. + * + */ + +#ifndef RESIZE_H +#define RESIZE_H + +#include "border.h" + +struct ClientNode; + +typedef enum { + RESIZE_OPAQUE, /**< Show window contents while resizing. */ + RESIZE_OUTLINE /**< Show an outline while resizing. */ +} ResizeModeType; + +/** Resize a client window. + * @param np The client to resize. + * @param action The location on the border where the move should take place. + * @param startx The starting mouse x-coordinate (window relative). + * @param starty The starting mouse y-coordinate (window relative). + */ +void ResizeClient(struct ClientNode *np, BorderActionType action, + int startx, int starty); + +/** Resize a client window using the keyboard (mouse optional). + * @param np The client to resize. + */ +void ResizeClientKeyboard(struct ClientNode *np); + +/** Set the resize mode to use. + * @param mode The resize mode to use. + */ +void SetResizeMode(ResizeModeType mode); + +#endif + diff --git a/src/root.c b/src/root.c new file mode 100644 index 0000000..b110018 --- /dev/null +++ b/src/root.c @@ -0,0 +1,315 @@ +/*************************************************************************** + * Functions to handle the root menu. + * Copyright (C) 2004 Joe Wingbermuehle + ***************************************************************************/ + +#include "jwm.h" +#include "root.h" +#include "menu.h" +#include "client.h" +#include "main.h" +#include "error.h" +#include "confirm.h" +#include "desktop.h" +#include "misc.h" +#include "winmenu.h" + +/* Allow for menus 0 to 9. */ +#define ROOT_MENU_COUNT 10 + +static Menu *rootMenu[ROOT_MENU_COUNT]; +static int showExitConfirmation = 1; + +static void ExitHandler(ClientNode *np); +static void PatchRootMenu(Menu *menu); +static void UnpatchRootMenu(Menu *menu); + +static void RunRootCommand(const MenuAction *action); + +/*************************************************************************** + ***************************************************************************/ +void InitializeRootMenu() { + + int x; + + for(x = 0; x < ROOT_MENU_COUNT; x++) { + rootMenu[x] = NULL; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void StartupRootMenu() { + + int x, y; + int found; + + for(x = 0; x < ROOT_MENU_COUNT; x++) { + if(rootMenu[x]) { + found = 0; + for(y = 0; y < x; y++) { + if(rootMenu[y] == rootMenu[x]) { + found = 1; + break; + } + } + if(!found) { + InitializeMenu(rootMenu[x]); + } + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ShutdownRootMenu() { +} + +/*************************************************************************** + ***************************************************************************/ +void DestroyRootMenu() { + + int x, y; + + for(x = 0; x < ROOT_MENU_COUNT; x++) { + if(rootMenu[x]) { + DestroyMenu(rootMenu[x]); + for(y = x + 1; y < ROOT_MENU_COUNT; y++) { + if(rootMenu[x] == rootMenu[y]) { + rootMenu[y] = NULL; + } + } + rootMenu[x] = NULL; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetRootMenu(const char *indexes, Menu *m) { + + int x, y; + int index; + int found; + + /* Loop over each index to consider. */ + for(x = 0; indexes[x]; x++) { + + /* Get the index and make sure it's in range. */ + index = indexes[x] - '0'; + if(index < 0 || index >= ROOT_MENU_COUNT) { + Warning("invalid root menu specified: \"%c\"", indexes[x]); + continue; + } + + if(rootMenu[index] && rootMenu[index] != m) { + + /* See if replacing this value will cause an orphan. */ + found = 0; + for(y = 0; y < ROOT_MENU_COUNT; y++) { + if(x != y && rootMenu[y] == rootMenu[x]) { + found = 1; + break; + } + } + + /* If we have an orphan, destroy it. */ + if(!found) { + DestroyMenu(rootMenu[index]); + } + + } + + rootMenu[index] = m; + + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetShowExitConfirmation(int v) { + showExitConfirmation = v; +} + +/*************************************************************************** + ***************************************************************************/ +int IsRootMenuDefined(int index) { + if(index >= 0 && index < ROOT_MENU_COUNT && rootMenu[index]) { + return 1; + } else { + return 0; + } +} + +/*************************************************************************** + ***************************************************************************/ +void GetRootMenuSize(int index, int *width, int *height) { + + if(!rootMenu[index]) { + *width = 0; + *height = 0; + return; + } + + PatchRootMenu(rootMenu[index]); + *width = rootMenu[index]->width; + *height = rootMenu[index]->height; + UnpatchRootMenu(rootMenu[index]); + +} + +/*************************************************************************** + ***************************************************************************/ +int ShowRootMenu(int index, int x, int y) { + + if(!rootMenu[index]) { + return 0; + } + + PatchRootMenu(rootMenu[index]); + ShowMenu(rootMenu[index], RunRootCommand, x, y); + UnpatchRootMenu(rootMenu[index]); + + return 1; + +} + +/*************************************************************************** + ***************************************************************************/ +void PatchRootMenu(Menu *menu) { + + MenuItem *item; + + for(item = menu->items; item; item = item->next) { + if(item->submenu) { + PatchRootMenu(item->submenu); + } + if(item->action.type == MA_DESKTOP) { + item->submenu = CreateDesktopMenu(1 << currentDesktop); + InitializeMenu(item->submenu); + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void UnpatchRootMenu(Menu *menu) { + + MenuItem *item; + + for(item = menu->items; item; item = item->next) { + if(item->action.type == MA_DESKTOP) { + DestroyMenu(item->submenu); + item->submenu = NULL; + } else if(item->submenu) { + UnpatchRootMenu(item->submenu); + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ExitHandler(ClientNode *np) { + shouldExit = 1; +} + +/*************************************************************************** + ***************************************************************************/ +void Restart() { + shouldRestart = 1; + shouldExit = 1; +} + +/*************************************************************************** + ***************************************************************************/ +void Exit() { + if(showExitConfirmation) { + ShowConfirmDialog(NULL, ExitHandler, + "Exit JWM", + "Are you sure?", + NULL); + } else { + ExitHandler(NULL); + } +} + +/*************************************************************************** + ***************************************************************************/ +void RunRootCommand(const MenuAction *action) { + + switch(action->type) { + + case MA_EXECUTE: + RunCommand(action->data.str); + break; + case MA_RESTART: + Restart(); + break; + case MA_EXIT: + if(exitCommand) { + Release(exitCommand); + } + exitCommand = CopyString(action->data.str); + Exit(); + break; + case MA_DESKTOP: + ChangeDesktop(action->data.i); + break; + + case MA_SENDTO: + case MA_LAYER: + case MA_MAXIMIZE: + case MA_MINIMIZE: + case MA_RESTORE: + case MA_SHADE: + case MA_MOVE: + case MA_RESIZE: + case MA_KILL: + case MA_CLOSE: + ChooseWindow(action); + break; + + default: + Debug("invalid RunRootCommand action: %d", action->type); + break; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void RunCommand(const char *command) { + char *displayString; + char *str; + + if(!command) { + return; + } + + displayString = DisplayString(display); + + if(!fork()) { + if(!fork()) { + close(ConnectionNumber(display)); + if(displayString && displayString[0]) { + str = malloc(strlen(displayString) + 9); + sprintf(str, "DISPLAY=%s", displayString); + putenv(str); + } + execl(SHELL_NAME, SHELL_NAME, "-c", command, NULL); + Warning("exec failed: (%s) %s", SHELL_NAME, command); + exit(1); + } + exit(0); + } + + wait(NULL); + +} + diff --git a/src/root.h b/src/root.h new file mode 100644 index 0000000..b546156 --- /dev/null +++ b/src/root.h @@ -0,0 +1,65 @@ +/** + * @file root.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the root menu functions. + * + */ + +#ifndef ROOT_H +#define ROOT_H + +struct Menu; + +/*@{*/ +void InitializeRootMenu(); +void StartupRootMenu(); +void ShutdownRootMenu(); +void DestroyRootMenu(); +/*@}*/ + +/** Set the root menu to be used for the specified indexes. + * @param indexes The indexes (ASCII string of '0' to '9'). + * @param m The menu to use for the specified indexes. + */ +void SetRootMenu(const char *indexes, struct Menu *m); + +/** Set whether a confirmation dialog is displayed on exit. + * @param v 1 to display confirmation, 0 to just exit. + */ +void SetShowExitConfirmation(int v); + +/** Determine if a root menu is defined for the specified index. + * @return 1 if it is defined, 0 if not. + */ +int IsRootMenuDefined(int index); + +/** Get the size of a root menu. + * @param index The root menu index. + * @param width The width output. + * @param height The height output. + */ +void GetRootMenuSize(int index, int *width, int *height); + +/** Show a root menu. + * @param index The root menu index. + * @param x The x-coordinate. + * @param y The y-coordinate. + * @return 1 if a menu was displayed, 0 if not. + */ +int ShowRootMenu(int index, int x, int y); + +/** Run a command. + * @param command The command to run (run in sh). + */ +void RunCommand(const char *command); + +/** Restart the window manager. */ +void Restart(); + +/** Exit the window manager. */ +void Exit(); + +#endif + diff --git a/src/screen.c b/src/screen.c new file mode 100644 index 0000000..cc2b8fd --- /dev/null +++ b/src/screen.c @@ -0,0 +1,138 @@ +/**************************************************************************** + * Screen functions. + * Copyright (C) 2005 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "screen.h" +#include "main.h" +#include "cursor.h" + +static ScreenType *screens; +static int screenCount; + +/**************************************************************************** + ****************************************************************************/ +void InitializeScreens() { + screens = NULL; +} + +/**************************************************************************** + ****************************************************************************/ +void StartupScreens() { +#ifdef USE_XINERAMA + + XineramaScreenInfo *info; + int x; + + if(XineramaIsActive(display)) { + + info = XineramaQueryScreens(display, &screenCount); + + screens = Allocate(sizeof(ScreenType) * screenCount); + for(x = 0; x < screenCount; x++) { + screens[x].index = x; + screens[x].x = info[x].x_org; + screens[x].y = info[x].y_org; + screens[x].width = info[x].width; + screens[x].height = info[x].height; + } + + JXFree(info); + + } else { + + screenCount = 1; + screens = Allocate(sizeof(ScreenType)); + screens->index = 0; + screens->x = 0; + screens->y = 0; + screens->width = rootWidth; + screens->height = rootHeight; + + } + +#else + + screenCount = 1; + screens = Allocate(sizeof(ScreenType)); + screens->index = 0; + screens->x = 0; + screens->y = 0; + screens->width = rootWidth; + screens->height = rootHeight; + +#endif /* USE_XINERAMA */ +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownScreens() { + if(screens) { + Release(screens); + screens = NULL; + } +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyScreens() { +} + +/**************************************************************************** + ****************************************************************************/ +const ScreenType *GetCurrentScreen(int x, int y) { + + ScreenType *sp; + int index; + + for(index = 1; index < screenCount; index++) { + sp = &screens[index]; + if(x >= sp->x && x < sp->x + sp->width) { + if(y >= sp->y && y <= sp->y + sp->height) { + return sp; + } + } + } + + return &screens[0]; + +} + +/**************************************************************************** + ****************************************************************************/ +const ScreenType *GetMouseScreen() { +#ifdef USE_XINERAMA + + int x, y; + + GetMousePosition(&x, &y); + return GetCurrentScreen(x, y); + +#else + + return &screens[0]; + +#endif +} + +/**************************************************************************** + ****************************************************************************/ +const ScreenType *GetScreen(int index) { + + Assert(index >= 0); + Assert(index < screenCount); + + return &screens[index]; + +} + +/**************************************************************************** + ****************************************************************************/ +int GetScreenCount() { + + return screenCount; + +} + + diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 0000000..2d9a351 --- /dev/null +++ b/src/screen.h @@ -0,0 +1,53 @@ +/** + * @file screen.h + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Header for screen functions. + * + * Note that screen here refers to physical monitors. Screens are + * determined using the xinerama extension (if available). There will + * always be at least one screen. + * + */ + +#ifndef SCREEN_H +#define SCREEN_H + +/** Structure to contain information about a screen. */ +typedef struct ScreenType { + int index; /**< The index of this screen. */ + int x, y; /**< The location of this screen. */ + int width, height; /**< The size of this screen. */ +} ScreenType; + +void InitializeScreens(); +void StartupScreens(); +void ShutdownScreens(); +void DestroyScreens(); + +/** Get the screen of the specified coordinates. + * @param x The x-coordinate. + * @param y The y-coordinate. + * @return The screen. + */ +const ScreenType *GetCurrentScreen(int x, int y); + +/** Get the screen containing the mouse. + * @return The screen containing the mouse. + */ +const ScreenType *GetMouseScreen(); + +/** Get the screen of the specified index. + * @param index The screen index (0 based). + * @return The screen. + */ +const ScreenType *GetScreen(int index); + +/** Get the number of screens. + * @return The number of screens. + */ +int GetScreenCount(); + +#endif + diff --git a/src/status.c b/src/status.c new file mode 100644 index 0000000..88c7ddb --- /dev/null +++ b/src/status.c @@ -0,0 +1,274 @@ +/************************************************************************* + * Functions for displaying window move/resize status. + * Copyright (C) 2004 Joe Wingbermuehle + *************************************************************************/ + +#include "jwm.h" +#include "status.h" +#include "font.h" +#include "screen.h" +#include "color.h" +#include "main.h" +#include "client.h" +#include "error.h" + +typedef enum { + SW_INVALID, + SW_OFF, + SW_SCREEN, + SW_WINDOW, + SW_CORNER +} StatusWindowType; + +static Window statusWindow; +static unsigned int statusWindowHeight; +static unsigned int statusWindowWidth; +static int statusWindowX, statusWindowY; +static StatusWindowType moveStatusType; +static StatusWindowType resizeStatusType; + +static void CreateMoveResizeWindow(const ClientNode *np, + StatusWindowType type); +static void DrawMoveResizeWindow(const ClientNode *np, StatusWindowType type); +static void DestroyMoveResizeWindow(); +static void GetMoveResizeCoordinates(const ClientNode *np, + StatusWindowType type, int *x, int *y); +static StatusWindowType ParseType(const char *str); + +/************************************************************************* + *************************************************************************/ +void GetMoveResizeCoordinates(const ClientNode *np, StatusWindowType type, + int *x, int *y) { + + const ScreenType *sp; + + if(type == SW_WINDOW) { + *x = np->x + np->width / 2 - statusWindowWidth / 2; + *y = np->y + np->height / 2 - statusWindowHeight / 2; + return; + } + + sp = GetCurrentScreen(np->x, np->y); + + if(type == SW_CORNER) { + *x = sp->x; + *y = sp->y; + return; + } + + /* SW_SCREEN */ + + *x = sp->x + sp->width / 2 - statusWindowWidth / 2; + *y = sp->y + sp->height / 2 - statusWindowHeight / 2; + +} + +/************************************************************************* + *************************************************************************/ +void CreateMoveResizeWindow(const ClientNode *np, StatusWindowType type) { + + XSetWindowAttributes attrs; + + if(type == SW_OFF) { + return; + } + + statusWindowHeight = GetStringHeight(FONT_MENU) + 8; + statusWindowWidth = GetStringWidth(FONT_MENU, " 00000 x 00000 "); + + GetMoveResizeCoordinates(np, type, &statusWindowX, &statusWindowY); + + attrs.background_pixel = colors[COLOR_MENU_BG]; + attrs.save_under = True; + attrs.override_redirect = True; + + statusWindow = JXCreateWindow(display, rootWindow, + statusWindowX, statusWindowY, + statusWindowWidth, statusWindowHeight, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWBackPixel | CWOverrideRedirect | CWSaveUnder, + &attrs); + + JXMapRaised(display, statusWindow); + +} + +/************************************************************************* + *************************************************************************/ +void DrawMoveResizeWindow(const ClientNode *np, StatusWindowType type) { + + int x, y; + + GetMoveResizeCoordinates(np, type, &x, &y); + if(x != statusWindowX || y != statusWindowX) { + statusWindowX = x; + statusWindowY = y; + JXMoveResizeWindow(display, statusWindow, x, y, + statusWindowWidth, statusWindowHeight); + } + + JXSetForeground(display, rootGC, colors[COLOR_MENU_BG]); + JXFillRectangle(display, statusWindow, rootGC, 2, 2, + statusWindowWidth - 3, statusWindowHeight - 3); + + JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]); + JXDrawLine(display, statusWindow, rootGC, + 0, 0, statusWindowWidth - 1, 0); + JXDrawLine(display, statusWindow, rootGC, + 0, 1, statusWindowWidth - 2, 1); + JXDrawLine(display, statusWindow, rootGC, + 0, 2, 0, statusWindowHeight - 1); + JXDrawLine(display, statusWindow, rootGC, + 1, 2, 1, statusWindowHeight - 2); + + JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]); + JXDrawLine(display, statusWindow, rootGC, + 1, statusWindowHeight - 1, statusWindowWidth - 1, + statusWindowHeight - 1); + JXDrawLine(display, statusWindow, rootGC, + 2, statusWindowHeight - 2, statusWindowWidth - 1, + statusWindowHeight - 2); + JXDrawLine(display, statusWindow, rootGC, + statusWindowWidth - 1, 1, statusWindowWidth - 1, + statusWindowHeight - 3); + JXDrawLine(display, statusWindow, rootGC, + statusWindowWidth - 2, 2, statusWindowWidth - 2, + statusWindowHeight - 3); + +} + +/************************************************************************* + *************************************************************************/ +void DestroyMoveResizeWindow() { + + if(statusWindow != None) { + JXDestroyWindow(display, statusWindow); + statusWindow = None; + } + +} + +/************************************************************************* + *************************************************************************/ +void CreateMoveWindow(ClientNode *np) { + + CreateMoveResizeWindow(np, moveStatusType); + +} + +/************************************************************************* + *************************************************************************/ +void UpdateMoveWindow(ClientNode *np) { + + char str[80]; + unsigned int width; + + if(moveStatusType == SW_OFF) { + return; + } + + DrawMoveResizeWindow(np, moveStatusType); + + snprintf(str, sizeof(str), "(%d, %d)", np->x, np->y); + width = GetStringWidth(FONT_MENU, str); + RenderString(statusWindow, FONT_MENU, COLOR_MENU_FG, + statusWindowWidth / 2 - width / 2, 4, rootWidth, NULL, str); + +} + +/************************************************************************* + *************************************************************************/ +void DestroyMoveWindow() { + + DestroyMoveResizeWindow(); + +} + +/************************************************************************* + *************************************************************************/ +void CreateResizeWindow(ClientNode *np) { + + CreateMoveResizeWindow(np, resizeStatusType); + +} + +/************************************************************************* + *************************************************************************/ +void UpdateResizeWindow(ClientNode *np, int gwidth, int gheight) { + + char str[80]; + unsigned int fontWidth; + + if(resizeStatusType == SW_OFF) { + return; + } + + DrawMoveResizeWindow(np, resizeStatusType); + + snprintf(str, sizeof(str), "%d x %d", gwidth, gheight); + fontWidth = GetStringWidth(FONT_MENU, str); + RenderString(statusWindow, FONT_MENU, COLOR_MENU_FG, + statusWindowWidth / 2 - fontWidth / 2, 4, rootWidth, NULL, str); + +} + +/************************************************************************* + *************************************************************************/ +void DestroyResizeWindow() { + + DestroyMoveResizeWindow(); + +} + +/************************************************************************* + *************************************************************************/ +StatusWindowType ParseType(const char *str) { + + if(!str) { + return SW_SCREEN; + } else if(!strcmp(str, "off")) { + return SW_OFF; + } else if(!strcmp(str, "screen")) { + return SW_SCREEN; + } else if(!strcmp(str, "window")) { + return SW_WINDOW; + } else if(!strcmp(str, "corner")) { + return SW_CORNER; + } else { + return SW_INVALID; + } + +} + +/************************************************************************* + *************************************************************************/ +void SetMoveStatusType(const char *str) { + + StatusWindowType type; + + type = ParseType(str); + if(type == SW_INVALID) { + moveStatusType = SW_SCREEN; + Warning("invalid MoveMode coordinates: \"%s\"", str); + } else { + moveStatusType = type; + } + +} + +/************************************************************************* + *************************************************************************/ +void SetResizeStatusType(const char *str) { + + StatusWindowType type; + + type = ParseType(str); + if(type == SW_INVALID) { + resizeStatusType = SW_SCREEN; + Warning("invalid ResizeMode coordinates: \"%s\"", str); + } else { + resizeStatusType = type; + } + +} + diff --git a/src/status.h b/src/status.h new file mode 100644 index 0000000..9fcabb8 --- /dev/null +++ b/src/status.h @@ -0,0 +1,55 @@ +/** + * @file status.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the status functions. + * + */ + +#ifndef STATUS_H +#define STATUS_H + +struct ClientNode; + +/** Create a move status window. + * @param np The client to be moved. + */ +void CreateMoveWindow(struct ClientNode *np); + +/** Update a move status window. + * @param np The client being moved. + */ +void UpdateMoveWindow(struct ClientNode *np); + +/** Destroy a move status window. */ +void DestroyMoveWindow(); + +/** Create a resize status window. + * @param np The client being resized. + */ +void CreateResizeWindow(struct ClientNode *np); + +/** Update a resize status window. + * @param np The client being resized. + * @param gwidth The width to display. + * @param gheight The height to display. + */ +void UpdateResizeWindow(struct ClientNode *np, int gwidth, int gheight); + +/** Destroy a resize status window. */ +void DestroyResizeWindow(); + +/** Set the location of move status windows. + * @param str The location (off, screen, window, or corner). + */ +void SetMoveStatusType(const char *str); + +/** Set the location of resize status windows. + * @param str The location (off, screen, window, or corner). + */ +void SetResizeStatusType(const char *str); + +#endif + + diff --git a/src/swallow.c b/src/swallow.c new file mode 100644 index 0000000..26836f1 --- /dev/null +++ b/src/swallow.c @@ -0,0 +1,274 @@ +/** + * @file swallow.c + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Swallow tray component. + * + */ + +#include "jwm.h" +#include "swallow.h" +#include "main.h" +#include "tray.h" +#include "error.h" +#include "root.h" +#include "color.h" +#include "client.h" +#include "event.h" +#include "misc.h" + +typedef struct SwallowNode { + + TrayComponentType *cp; + + char *name; + char *command; + int border; + int userWidth; + int userHeight; + + struct SwallowNode *next; + +} SwallowNode; + +static SwallowNode *swallowNodes; + +static void Destroy(TrayComponentType *cp); +static void Resize(TrayComponentType *cp); + +/** Initialize swallow data. */ +void InitializeSwallow() { + swallowNodes = NULL; +} + +/** Start swallow processing. */ +void StartupSwallow() { + + SwallowNode *np; + + for(np = swallowNodes; np; np = np->next) { + if(np->command) { + RunCommand(np->command); + } + } + +} + +/** Stop swallow processing. */ +void ShutdownSwallow() { +} + +/** Destroy swallow data. */ +void DestroySwallow() { + + SwallowNode *np; + + while(swallowNodes) { + + np = swallowNodes->next; + + Assert(swallowNodes->name); + Release(swallowNodes->name); + + if(swallowNodes->command) { + Release(swallowNodes->command); + } + + Release(swallowNodes); + swallowNodes = np; + + } + +} + +/** Create a swallowed application tray component. */ +TrayComponentType *CreateSwallow(const char *name, const char *command, + int width, int height) { + + TrayComponentType *cp; + SwallowNode *np; + + if(!name) { + Warning("cannot swallow a client with no name"); + return NULL; + } + + /* Make sure this name isn't already used. */ + for(np = swallowNodes; np; np = np->next) { + if(!strcmp(np->name, name)) { + Warning("cannot swallow the same client multiple times"); + return NULL; + } + } + + np = Allocate(sizeof(SwallowNode)); + np->name = CopyString(name); + np->command = CopyString(command); + + np->next = swallowNodes; + swallowNodes = np; + + cp = CreateTrayComponent(); + np->cp = cp; + cp->object = np; + cp->Destroy = Destroy; + cp->Resize = Resize; + + if(width) { + cp->requestedWidth = width; + np->userWidth = 1; + } else { + cp->requestedWidth = 1; + np->userWidth = 0; + } + if(height) { + cp->requestedHeight = height; + np->userHeight = 1; + } else { + cp->requestedHeight = 1; + np->userHeight = 0; + } + + return cp; + +} + +/** Process an event on a swallowed window. */ +int ProcessSwallowEvent(const XEvent *event) { + + SwallowNode *np; + int width, height; + + for(np = swallowNodes; np; np = np->next) { + if(event->xany.window == np->cp->window) { + switch(event->type) { + case DestroyNotify: + np->cp->window = None; + np->cp->requestedWidth = 1; + np->cp->requestedHeight = 1; + ResizeTray(np->cp->tray); + break; + case ResizeRequest: + np->cp->requestedWidth + = event->xresizerequest.width + np->border * 2; + np->cp->requestedHeight + = event->xresizerequest.height + np->border * 2; + ResizeTray(np->cp->tray); + break; + case ConfigureNotify: + /* I don't think this should be necessary, but somehow + * resize requests slip by sometimes... */ + width = event->xconfigure.width + np->border * 2; + height = event->xconfigure.height + np->border * 2; + if( width != np->cp->requestedWidth + && height != np->cp->requestedHeight) { + np->cp->requestedWidth = width; + np->cp->requestedHeight = height; + ResizeTray(np->cp->tray); + } + break; + default: + break; + } + return 1; + } + } + + return 0; + +} + +/** Handle a tray resize. */ +void Resize(TrayComponentType *cp) { + + int width, height; + + SwallowNode *np = (SwallowNode*)cp->object; + + if(cp->window != None) { + + width = cp->width - np->border * 2; + height = cp->height - np->border * 2; + + JXResizeWindow(display, cp->window, width, height); + + } + +} + +/** Destroy a swallow tray component. */ +void Destroy(TrayComponentType *cp) { + + ClientProtocolType protocols; + + if(cp->window) { + + JXReparentWindow(display, cp->window, rootWindow, 0, 0); + JXRemoveFromSaveSet(display, cp->window); + + protocols = ReadWMProtocols(cp->window); + if(protocols & PROT_DELETE) { + SendClientMessage(cp->window, ATOM_WM_PROTOCOLS, + ATOM_WM_DELETE_WINDOW); + } else { + JXKillClient(display, cp->window); + } + + } + +} + +/** Determine if this is a window to be swallowed, if it is, swallow it. */ +int CheckSwallowMap(const XMapEvent *event) { + + SwallowNode *np; + XClassHint hint; + XWindowAttributes attr; + + for(np = swallowNodes; np; np = np->next) { + + if(np->cp->window != None) { + continue; + } + + Assert(np->cp->tray->window != None); + + if(JXGetClassHint(display, event->window, &hint)) { + if(!strcmp(hint.res_name, np->name)) { + + /* Swallow the window. */ + JXSelectInput(display, event->window, + StructureNotifyMask | ResizeRedirectMask); + JXAddToSaveSet(display, event->window); + JXSetWindowBorder(display, event->window, colors[COLOR_TRAY_BG]); + JXReparentWindow(display, event->window, + np->cp->tray->window, 0, 0); + JXMapRaised(display, event->window); + JXFree(hint.res_name); + JXFree(hint.res_class); + np->cp->window = event->window; + + /* Update the size. */ + JXGetWindowAttributes(display, event->window, &attr); + np->border = attr.border_width; + if(!np->userWidth) { + np->cp->requestedWidth = attr.width + 2 * np->border; + } + if(!np->userHeight) { + np->cp->requestedHeight = attr.height + 2 * np->border; + } + + ResizeTray(np->cp->tray); + + return 1; + + } + } + + } + + return 0; + +} + diff --git a/src/swallow.h b/src/swallow.h new file mode 100644 index 0000000..148bea6 --- /dev/null +++ b/src/swallow.h @@ -0,0 +1,43 @@ +/** + * @file swallow.h + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Swallow tray component. + * + */ + +#ifndef SWALLOW_H +#define SWALLOW_H + +/*@{*/ +void InitializeSwallow(); +void StartupSwallow(); +void ShutdownSwallow(); +void DestroySwallow(); +/*@}*/ + +/** Create a swallowed application tray component. + * @param name The name of the application to swallow. + * @param command The command used to start the swallowed application. + * @param width The width to use (0 for default). + * @param height the height to use (0 for default). + */ +struct TrayComponentType *CreateSwallow( + const char *name, const char *command, + int width, int height); + +/** Determine if a map event was for a window that should be swallowed. + * @param event The map event. + * @return 1 if this window should be swallowed, 0 if not. + */ +int CheckSwallowMap(const XMapEvent *event); + +/** Process an event on a swallowed window. + * @param event The event to process. + * @return 1 if the event was for a swallowed window, 0 if not. + */ +int ProcessSwallowEvent(const XEvent *event); + +#endif + diff --git a/src/taskbar.c b/src/taskbar.c new file mode 100644 index 0000000..f8ab13c --- /dev/null +++ b/src/taskbar.c @@ -0,0 +1,936 @@ +/*************************************************************************** + ***************************************************************************/ + +#include "jwm.h" +#include "taskbar.h" +#include "tray.h" +#include "timing.h" +#include "main.h" +#include "client.h" +#include "color.h" +#include "popup.h" +#include "button.h" +#include "cursor.h" +#include "icon.h" +#include "error.h" +#include "font.h" +#include "winmenu.h" +#include "screen.h" + +typedef enum { + INSERT_LEFT, + INSERT_RIGHT +} InsertModeType; + +typedef struct TaskBarType { + + TrayComponentType *cp; + + int itemHeight; + LayoutType layout; + + Pixmap buffer; + + TimeType mouseTime; + int mousex, mousey; + + unsigned int maxItemWidth; + + struct TaskBarType *next; + +} TaskBarType; + +typedef struct Node { + ClientNode *client; + int y; + struct Node *next; + struct Node *prev; +} Node; + +static char minimized_bitmap[] = { + 0x01, 0x03, + 0x07, 0x0F +}; + +static const int TASK_SPACER = 2; + +static Pixmap minimizedPixmap; +static InsertModeType insertMode; + +static TaskBarType *bars; +static Node *taskBarNodes; +static Node *taskBarNodesTail; + +static Node *GetNode(TaskBarType *bar, int x); +static unsigned int GetItemCount(); +static int ShouldShowItem(const ClientNode *np); +static int ShouldFocusItem(const ClientNode *np); +static unsigned int GetItemWidth(const TaskBarType *bp, + unsigned int itemCount); +static void Render(const TaskBarType *bp); +static void ShowTaskWindowMenu(TaskBarType *bar, Node *np); + +static void SetSize(TrayComponentType *cp, int width, int height); +static void Create(TrayComponentType *cp); +static void Destroy(TrayComponentType *cp); +static void Resize(TrayComponentType *cp); +static void ProcessTaskButtonEvent(TrayComponentType *cp, + int x, int y, int mask); +static void ProcessTaskMotionEvent(TrayComponentType *cp, + int x, int y, int mask); + +/*************************************************************************** + ***************************************************************************/ +void InitializeTaskBar() { + bars = NULL; + taskBarNodes = NULL; + taskBarNodesTail = NULL; + insertMode = INSERT_RIGHT; +} + +/*************************************************************************** + ***************************************************************************/ +void StartupTaskBar() { + minimizedPixmap = JXCreatePixmapFromBitmapData(display, rootWindow, + minimized_bitmap, 4, 4, colors[COLOR_TASK_FG], + colors[COLOR_TASK_BG], rootDepth); +} + +/*************************************************************************** + ***************************************************************************/ +void ShutdownTaskBar() { + + TaskBarType *bp; + + for(bp = bars; bp; bp = bp->next) { + JXFreePixmap(display, bp->buffer); + } + + JXFreePixmap(display, minimizedPixmap); +} + +/*************************************************************************** + ***************************************************************************/ +void DestroyTaskBar() { + + TaskBarType *bp; + + while(bars) { + bp = bars->next; + Release(bars); + bars = bp; + } + +} + +/*************************************************************************** + ***************************************************************************/ +TrayComponentType *CreateTaskBar() { + + TrayComponentType *cp; + TaskBarType *tp; + + tp = Allocate(sizeof(TaskBarType)); + tp->next = bars; + bars = tp; + tp->itemHeight = 0; + tp->layout = LAYOUT_HORIZONTAL; + tp->mousex = -1; + tp->mousey = -1; + tp->mouseTime.seconds = 0; + tp->mouseTime.ms = 0; + tp->maxItemWidth = 0; + + cp = CreateTrayComponent(); + cp->object = tp; + tp->cp = cp; + + cp->SetSize = SetSize; + cp->Create = Create; + cp->Destroy = Destroy; + cp->Resize = Resize; + cp->ProcessButtonEvent = ProcessTaskButtonEvent; + cp->ProcessMotionEvent = ProcessTaskMotionEvent; + + return cp; + +} + +/*************************************************************************** + ***************************************************************************/ +void SetSize(TrayComponentType *cp, int width, int height) { + + TaskBarType *tp; + + Assert(cp); + + tp = (TaskBarType*)cp->object; + + Assert(tp); + + if(width == 0) { + tp->layout = LAYOUT_HORIZONTAL; + } else if(height == 0) { + tp->layout = LAYOUT_VERTICAL; + } else if(width > height) { + tp->layout = LAYOUT_HORIZONTAL; + } else { + tp->layout = LAYOUT_VERTICAL; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void Create(TrayComponentType *cp) { + + TaskBarType *tp; + + Assert(cp); + + tp = (TaskBarType*)cp->object; + + Assert(tp); + + if(tp->layout == LAYOUT_HORIZONTAL) { + tp->itemHeight = cp->height - TASK_SPACER; + } else { + tp->itemHeight = GetStringHeight(FONT_TASK) + 12; + } + + Assert(cp->width > 0); + Assert(cp->height > 0); + + cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height, + rootDepth); + tp->buffer = cp->pixmap; + + JXSetForeground(display, rootGC, colors[COLOR_TASK_BG]); + JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, cp->width, cp->height); + +} + +/*************************************************************************** + ***************************************************************************/ +void Resize(TrayComponentType *cp) { + + TaskBarType *tp; + + Assert(cp); + + tp = (TaskBarType*)cp->object; + + Assert(tp); + + if(tp->buffer != None) { + JXFreePixmap(display, tp->buffer); + } + + if(tp->layout == LAYOUT_HORIZONTAL) { + tp->itemHeight = cp->height - TASK_SPACER; + } else { + tp->itemHeight = GetStringHeight(FONT_TASK) + 12; + } + + Assert(cp->width > 0); + Assert(cp->height > 0); + + cp->pixmap = JXCreatePixmap(display, rootWindow, cp->width, cp->height, + rootDepth); + tp->buffer = cp->pixmap; + + JXSetForeground(display, rootGC, colors[COLOR_TASK_BG]); + JXFillRectangle(display, cp->pixmap, rootGC, + 0, 0, cp->width, cp->height); +} + +/*************************************************************************** + ***************************************************************************/ +void Destroy(TrayComponentType *cp) { + +} + +/*************************************************************************** + ***************************************************************************/ +void ProcessTaskButtonEvent(TrayComponentType *cp, int x, int y, int mask) { + + TaskBarType *bar = (TaskBarType*)cp->object; + Node *np; + + Assert(bar); + + if(bar->layout == LAYOUT_HORIZONTAL) { + np = GetNode(bar, x); + } else { + np = GetNode(bar, y); + } + + if(np) { + switch(mask) { + case Button1: + if(np->client->state.status & STAT_ACTIVE + && np->client == nodes[np->client->state.layer]) { + MinimizeClient(np->client); + } else { + RestoreClient(np->client, 1); + FocusClient(np->client); + } + break; + case Button3: + ShowTaskWindowMenu(bar, np); + break; + case Button4: + FocusPrevious(); + break; + case Button5: + FocusNext(); + break; + default: + break; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ProcessTaskMotionEvent(TrayComponentType *cp, int x, int y, int mask) { + + TaskBarType *bp = (TaskBarType*)cp->object; + + bp->mousex = cp->screenx + x; + bp->mousey = cp->screeny + y; + GetCurrentTime(&bp->mouseTime); + +} + +/*************************************************************************** + ***************************************************************************/ +void ShowTaskWindowMenu(TaskBarType *bar, Node *np) { + + int x, y; + int mwidth, mheight; + const ScreenType *sp; + + GetWindowMenuSize(np->client, &mwidth, &mheight); + + sp = GetCurrentScreen(x, y); + + if(bar->layout == LAYOUT_HORIZONTAL) { + GetMousePosition(&x, &y); + if(bar->cp->screeny + bar->cp->height / 2 < sp->y + sp->height / 2) { + y = bar->cp->screeny + bar->cp->height; + } else { + y = bar->cp->screeny - mheight; + } + x -= mwidth / 2; + } else { + if(bar->cp->screenx + bar->cp->width / 2 < sp->x + sp->width / 2) { + x = bar->cp->screenx + bar->cp->width; + } else { + x = bar->cp->screenx - mwidth; + } + y = bar->cp->screeny + np->y; + } + + ShowWindowMenu(np->client, x, y); + +} + +/*************************************************************************** + ***************************************************************************/ +void AddClientToTaskBar(ClientNode *np) { + + Node *tp; + + Assert(np); + + tp = Allocate(sizeof(Node)); + tp->client = np; + + if(insertMode == INSERT_RIGHT) { + tp->next = NULL; + tp->prev = taskBarNodesTail; + if(taskBarNodesTail) { + taskBarNodesTail->next = tp; + } else { + taskBarNodes = tp; + } + taskBarNodesTail = tp; + } else { + tp->prev = NULL; + tp->next = taskBarNodes; + if(taskBarNodes) { + taskBarNodes->prev = tp; + } + taskBarNodes = tp; + if(!taskBarNodesTail) { + taskBarNodesTail = tp; + } + } + + UpdateTaskBar(); + + UpdateNetClientList(); + +} + +/*************************************************************************** + ***************************************************************************/ +void RemoveClientFromTaskBar(ClientNode *np) { + + Node *tp; + + Assert(np); + + for(tp = taskBarNodes; tp; tp = tp->next) { + if(tp->client == np) { + if(tp->prev) { + tp->prev->next = tp->next; + } else { + taskBarNodes = tp->next; + } + if(tp->next) { + tp->next->prev = tp->prev; + } else { + taskBarNodesTail = tp->prev; + } + Release(tp); + break; + } + } + + UpdateTaskBar(); + + UpdateNetClientList(); + +} + +/*************************************************************************** + ***************************************************************************/ +void UpdateTaskBar() { + + TaskBarType *bp; + unsigned int count; + int lastHeight; + + if(shouldExit) { + return; + } + + for(bp = bars; bp; bp = bp->next) { + + if(bp->layout == LAYOUT_VERTICAL) { + lastHeight = bp->cp->requestedHeight; + count = GetItemCount(); + bp->cp->requestedHeight = GetStringHeight(FONT_TASK) + 12; + bp->cp->requestedHeight *= count; + bp->cp->requestedHeight += 2; + if(lastHeight != bp->cp->requestedHeight) { + ResizeTray(bp->cp->tray); + } + } + + Render(bp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SignalTaskbar(const TimeType *now, int x, int y) { + + TaskBarType *bp; + Node *np; + + for(bp = bars; bp; bp = bp->next) { + if(abs(bp->mousex - x) < POPUP_DELTA + && abs(bp->mousey - y) < POPUP_DELTA) { + if(GetTimeDifference(now, &bp->mouseTime) >= popupDelay) { + if(bp->layout == LAYOUT_HORIZONTAL) { + np = GetNode(bp, x - bp->cp->screenx); + } else { + np = GetNode(bp, y - bp->cp->screeny); + } + if(np) { + ShowPopup(x, y, np->client->name); + } + } + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void Render(const TaskBarType *bp) { + + Node *tp; + ButtonNode button; + int x, y; + int remainder; + int itemWidth, itemCount; + int width, height; + int iconSize; + Pixmap buffer; + GC gc; + char *minimizedName; + + if(shouldExit) { + return; + } + + Assert(bp); + Assert(bp->cp); + + width = bp->cp->width; + height = bp->cp->height; + buffer = bp->cp->pixmap; + gc = rootGC; + + x = TASK_SPACER; + width -= x; + y = 1; + + JXSetForeground(display, gc, colors[COLOR_TASK_BG]); + JXFillRectangle(display, buffer, gc, 0, 0, width, height); + + itemCount = GetItemCount(); + if(!itemCount) { + UpdateSpecificTray(bp->cp->tray, bp->cp); + return; + } + if(bp->layout == LAYOUT_HORIZONTAL) { + itemWidth = GetItemWidth(bp, itemCount); + remainder = width - itemWidth * itemCount; + } else { + itemWidth = width; + remainder = 0; + } + + iconSize = bp->itemHeight - 2 * TASK_SPACER - 4; + + ResetButton(&button, buffer, gc); + button.font = FONT_TASK; + + for(tp = taskBarNodes; tp; tp = tp->next) { + if(ShouldShowItem(tp->client)) { + + tp->y = y; + + if(tp->client->state.status & STAT_ACTIVE) { + button.type = BUTTON_TASK_ACTIVE; + } else { + button.type = BUTTON_TASK; + } + + if(remainder) { + button.width = itemWidth - TASK_SPACER; + } else { + button.width = itemWidth - TASK_SPACER - 1; + } + button.height = bp->itemHeight - 1; + button.x = x; + button.y = y; + button.icon = tp->client->icon; + + if(tp->client->state.status & STAT_MINIMIZED) { + minimizedName = AllocateStack(strlen(tp->client->name) + 3); + sprintf(minimizedName, "[%s]", tp->client->name); + button.text = minimizedName; + DrawButton(&button); + ReleaseStack(minimizedName); + } else { + button.text = tp->client->name; + DrawButton(&button); + } + + if(tp->client->state.status & STAT_MINIMIZED) { + JXCopyArea(display, minimizedPixmap, buffer, gc, + 0, 0, 4, 4, x + 3, y + bp->itemHeight - 7); + } + + if(bp->layout == LAYOUT_HORIZONTAL) { + x += itemWidth; + if(remainder) { + ++x; + --remainder; + } + } else { + y += bp->itemHeight; + if(remainder) { + ++y; + --remainder; + } + } + + } + } + + UpdateSpecificTray(bp->cp->tray, bp->cp); + +} + +/*************************************************************************** + ***************************************************************************/ +void FocusNext() { + + Node *tp; + + for(tp = taskBarNodes; tp; tp = tp->next) { + if(ShouldFocusItem(tp->client)) { + if(tp->client->state.status & STAT_ACTIVE) { + tp = tp->next; + break; + } + } + } + + if(!tp) { + tp = taskBarNodes; + } + + while(tp && !ShouldFocusItem(tp->client)) { + tp = tp->next; + } + + if(!tp) { + tp = taskBarNodes; + while(tp && !ShouldFocusItem(tp->client)) { + tp = tp->next; + } + } + + if(tp) { + RestoreClient(tp->client, 1); + FocusClient(tp->client); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void FocusPrevious() { + + Node *tp; + + for(tp = taskBarNodesTail; tp; tp = tp->prev) { + if(ShouldFocusItem(tp->client)) { + if(tp->client->state.status & STAT_ACTIVE) { + tp = tp->prev; + break; + } + } + } + + if(!tp) { + tp = taskBarNodesTail; + } + + while(tp && !ShouldFocusItem(tp->client)) { + tp = tp->prev; + } + + if(!tp) { + tp = taskBarNodesTail; + while(tp && !ShouldFocusItem(tp->client)) { + tp = tp->prev; + } + } + + if(tp) { + RestoreClient(tp->client, 1); + FocusClient(tp->client); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void FocusNextStackedCircular() { + + ClientNode *ac; + ClientNode *np; + int x; + + ac = GetActiveClient(); + np = NULL; + + /* Check for a valid client below this client in the same layer. */ + if(ac) { + for(np = ac->next; np; np = np->next) { + if(ShouldFocusItem(np)) { + break; + } + } + } + + /* Check for a valid client in lower layers. */ + if(ac && !np) { + for(x = ac->state.layer - 1; x >= LAYER_BOTTOM; x--) { + for(np = nodes[x]; np; np = np->next) { + if(ShouldFocusItem(np)) { + break; + } + } + if(np) { + break; + } + } + } + + /* Revert to the top-most valid client. */ + if(!np) { + for(x = LAYER_TOP; x >= LAYER_BOTTOM; x--) { + for(np = nodes[x]; np; np = np->next) { + if(ShouldFocusItem(np)) { + break; + } + } + if(np) { + break; + } + } + } + + if(np) { + FocusClient(np); + } + +} + +/*************************************************************************** + ***************************************************************************/ +Node *GetNode(TaskBarType *bar, int x) { + + Node *tp; + int remainder; + int itemCount; + int itemWidth; + int index, stop; + int width; + + index = TASK_SPACER; + + itemCount = GetItemCount(); + + if(bar->layout == LAYOUT_HORIZONTAL) { + + width = bar->cp->width - index; + itemWidth = GetItemWidth(bar, itemCount); + remainder = width - itemWidth * itemCount; + + for(tp = taskBarNodes; tp; tp = tp->next) { + if(ShouldShowItem(tp->client)) { + if(remainder) { + stop = index + itemWidth + 1; + --remainder; + } else { + stop = index + itemWidth; + } + if(x >= index && x < stop) { + return tp; + } + index = stop; + } + } + + } else { + + for(tp = taskBarNodes; tp; tp = tp->next) { + if(ShouldShowItem(tp->client)) { + stop = index + bar->itemHeight; + if(x >= index && x < stop) { + return tp; + } + index = stop; + } + } + + } + + return NULL; + +} + +/*************************************************************************** + ***************************************************************************/ +unsigned int GetItemCount() { + + Node *tp; + unsigned int count; + + count = 0; + for(tp = taskBarNodes; tp; tp = tp->next) { + if(ShouldShowItem(tp->client)) { + ++count; + } + } + + return count; + +} + +/*************************************************************************** + ***************************************************************************/ +int ShouldShowItem(const ClientNode *np) { + + if(np->state.desktop != currentDesktop + && !(np->state.status & STAT_STICKY)) { + return 0; + } + + if(np->state.status & STAT_NOLIST) { + return 0; + } + + if(np->owner != None) { + return 0; + } + + if(!(np->state.status & STAT_MAPPED) + && !(np->state.status & (STAT_MINIMIZED | STAT_SHADED))) { + return 0; + } + + return 1; + +} + +/*************************************************************************** + ***************************************************************************/ +int ShouldFocusItem(const ClientNode *np) { + + if(np->state.desktop != currentDesktop + && !(np->state.status & STAT_STICKY)) { + return 0; + } + + if(np->state.status & STAT_NOLIST) { + return 0; + } + + if(!(np->state.status & STAT_MAPPED)) { + return 0; + } + + if(np->owner != None) { + return 0; + } + + return 1; + +} + +/*************************************************************************** + ***************************************************************************/ +unsigned int GetItemWidth(const TaskBarType *bp, unsigned int itemCount) { + + unsigned int itemWidth; + + itemWidth = bp->cp->width - TASK_SPACER; + + if(!itemCount) { + return itemWidth; + } + + itemWidth /= itemCount; + if(!itemWidth) { + itemWidth = 1; + } + + if(bp->maxItemWidth > 0 && itemWidth > bp->maxItemWidth) { + itemWidth = bp->maxItemWidth; + } + + return itemWidth; + +} + +/*************************************************************************** + ***************************************************************************/ +void SetMaxTaskBarItemWidth(TrayComponentType *cp, const char *value) { + + int temp; + TaskBarType *bp; + + Assert(cp); + + if(value) { + temp = atoi(value); + if(temp < 0) { + Warning("invalid maxwidth for TaskList: %s", value); + return; + } + bp = (TaskBarType*)cp->object; + bp->maxItemWidth = temp; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetTaskBarInsertMode(const char *mode) { + + if(!mode) { + insertMode = INSERT_RIGHT; + return; + } + + if(!strcmp(mode, "right")) { + insertMode = INSERT_RIGHT; + } else if(!strcmp(mode, "left")) { + insertMode = INSERT_LEFT; + } else { + Warning("invalid insert mode: \"%s\"", mode); + insertMode = INSERT_RIGHT; + } + +} + +/*************************************************************************** + * Maintain the _NET_CLIENT_LIST[_STACKING] properties on the root window. + ***************************************************************************/ +void UpdateNetClientList() { + + Node *np; + ClientNode *client; + Window *windows; + int count; + int layer; + + count = 0; + for(np = taskBarNodes; np; np = np->next) { + ++count; + } + + if(count == 0) { + windows = NULL; + } else { + windows = AllocateStack(count * sizeof(Window)); + } + + /* Set _NET_CLIENT_LIST */ + count = 0; + for(np = taskBarNodes; np; np = np->next) { + windows[count++] = np->client->window; + } + JXChangeProperty(display, rootWindow, atoms[ATOM_NET_CLIENT_LIST], + XA_WINDOW, 32, PropModeReplace, (unsigned char*)windows, count); + + /* Set _NET_CLIENT_LIST_STACKING */ + count = 0; + for(layer = LAYER_BOTTOM; layer <= LAYER_TOP; layer++) { + for(client = nodes[layer]; client; client = client->next) { + windows[count++] = client->window; + } + } + JXChangeProperty(display, rootWindow, atoms[ATOM_NET_CLIENT_LIST_STACKING], + XA_WINDOW, 32, PropModeReplace, (unsigned char*)windows, count); + + if(windows != NULL) { + ReleaseStack(windows); + } + +} + diff --git a/src/taskbar.h b/src/taskbar.h new file mode 100644 index 0000000..e65600f --- /dev/null +++ b/src/taskbar.h @@ -0,0 +1,64 @@ +/** + * @file taskbar.h + * @author Joe Wingbermuehle + * @date 2005-2006 + * + * @brief Task list tray component. + * + */ + +#ifndef TASKBAR_H +#define TASKBAR_H + +struct ClientNode; +struct TimeType; + +/*@{*/ +void InitializeTaskBar(); +void StartupTaskBar(); +void ShutdownTaskBar(); +void DestroyTaskBar(); +/*@}*/ + +/** Create a new task bar tray component. */ +struct TrayComponentType *CreateTaskBar(); + +/** Add a client to the task bar(s). + * @param np The client to add. + */ +void AddClientToTaskBar(struct ClientNode *np); + +/** Remove a client from the task bar(s). + * @param np The client to remove. + */ +void RemoveClientFromTaskBar(struct ClientNode *np); + +void UpdateTaskBar(); + +void SignalTaskbar(const struct TimeType *now, int x, int y); + +/** Focus the next client in the task bar. */ +void FocusNext(); + +/** Focus the previous client in the task bar. */ +void FocusPrevious(); + +/** Focus the next stacked client. */ +void FocusNextStackedCircular(); + +/** Set the maximum width of task bar items. + * @param cp The task bar component. + * @param value The maximum width. + */ +void SetMaxTaskBarItemWidth(struct TrayComponentType *cp, const char *value); + +/** Set the insertion mode for task bars. + * @param mode The insertion mode (either right or left). + */ +void SetTaskBarInsertMode(const char *mode); + +/** Update the _NET_CLIENT_LIST property. */ +void UpdateNetClientList(); + +#endif + diff --git a/src/theme.c b/src/theme.c new file mode 100644 index 0000000..4560abc --- /dev/null +++ b/src/theme.c @@ -0,0 +1,88 @@ +/**************************************************************************** + * Theme functions. + * Copyright (C) 2006 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "theme.h" +#include "error.h" +#include "misc.h" + +typedef struct ThemePathNode { + char *path; + struct ThemePathNode *next; +} ThemePathNode; + +static ThemePathNode *themePaths; +static char *themeName; + +/**************************************************************************** + ****************************************************************************/ +void InitializeThemes() { + + themeName = NULL; + themePaths = NULL; + +} + +/**************************************************************************** + ****************************************************************************/ +void StartupThemes() { +} + +/**************************************************************************** + ****************************************************************************/ +void ShutdownThemes() { +} + +/**************************************************************************** + ****************************************************************************/ +void DestroyThemes() { + + ThemePathNode *tp; + + if(themeName) { + Release(themeName); + } + + while(themePaths) { + tp = themePaths->next; + Release(themePaths->path); + Release(themePaths); + themePaths = tp; + } + +} + +/**************************************************************************** + ****************************************************************************/ +void AddThemePath(const char *path) { + + ThemePathNode *tp; + + if(!path) { + return; + } + + tp = Allocate(sizeof(ThemePathNode)); + tp->path = CopyString(path); + Trim(tp->path); + + tp->next = themePaths; + themePaths = tp; + +} + +/**************************************************************************** + ****************************************************************************/ +void SetTheme(const char *name) { + + if(themeName) { + Warning("theme set multiple times"); + } + + themeName = CopyString(name); + Trim(themeName); + +} + diff --git a/src/theme.h b/src/theme.h new file mode 100644 index 0000000..2dd1400 --- /dev/null +++ b/src/theme.h @@ -0,0 +1,31 @@ +/** + * @file theme.h + * @author Joe Wingbermuehle + * @date 2006 + * + * @brief Header for the theme functions. + * + */ + +#ifndef THEME_H +#define THEME_H + +/*@{*/ +void InitializeThemes(); +void StartupThemes(); +void ShutdownThemes(); +void DestroyThemes(); +/*@}*/ + +/** Add a theme path. + * @param path The path to add. + */ +void AddThemePath(const char *path); + +/** Set the theme to use. + * @param name The name of the theme. + */ +void SetTheme(const char *name); + +#endif + diff --git a/src/timing.c b/src/timing.c new file mode 100644 index 0000000..047d919 --- /dev/null +++ b/src/timing.c @@ -0,0 +1,71 @@ +/**************************************************************************** + * Timing functions. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "timing.h" + +static const unsigned long MAX_TIME_SECONDS = 60; + +/**************************************************************************** + * Get the current time in milliseconds since midnight 1970-01-01 UTC. + ****************************************************************************/ +void GetCurrentTime(TimeType *t) { + struct timeval val; + gettimeofday(&val, NULL); + t->seconds = val.tv_sec; + t->ms = val.tv_usec / 1000; +} + +/**************************************************************************** + * Get the absolute difference between two times in milliseconds. + * If the difference is larger than a MAX_TIME_SECONDS, then + * MAX_TIME_SECONDS will be returned. + * Note that the times must be normalized. + ****************************************************************************/ +unsigned long GetTimeDifference(const TimeType *t1, const TimeType *t2) { + unsigned long deltaSeconds; + int deltaMs; + + if(t1->seconds > t2->seconds) { + deltaSeconds = t1->seconds - t2->seconds; + deltaMs = t1->ms - t2->ms; + } else if(t1->seconds < t2->seconds) { + deltaSeconds = t2->seconds - t1->seconds; + deltaMs = t2->ms - t1->ms; + } else if(t1->ms > t2->ms) { + deltaSeconds = 0; + deltaMs = t1->ms - t2->ms; + } else { + deltaSeconds = 0; + deltaMs = t2->ms - t1->ms; + } + + if(deltaSeconds > MAX_TIME_SECONDS) { + return MAX_TIME_SECONDS * 1000; + } else { + return deltaSeconds * 1000 + deltaMs; + } + +} + +/**************************************************************************** + * Get the current time. + * Not reenterent. + ****************************************************************************/ +const char *GetTimeString(const char *format) { + + static char str[80]; + time_t t; + + Assert(format); + + time(&t); + strftime(str, sizeof(str), format, localtime(&t)); + + return str; + +} + + diff --git a/src/timing.h b/src/timing.h new file mode 100644 index 0000000..25a8fca --- /dev/null +++ b/src/timing.h @@ -0,0 +1,43 @@ +/** + * @file timing.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Timing functions. + * + */ + +#ifndef TIMING_H +#define TIMING_H + +#define ZERO_TIME { 0, 0 } + +typedef struct TimeType { + + unsigned long seconds; + int ms; + +} TimeType; + +/** Get the current time. + * @param t The TimeType to fill. + */ +void GetCurrentTime(TimeType *t); + +/** Get the difference between two times. + * Note that the times must be normalized. + * @param t1 The first time. + * @param t2 The second time. + */ +unsigned long GetTimeDifference(const TimeType *t1, const TimeType *t2); + +/** Get a time string. + * Note that the string returned is a static value and should not be + * deleted. Therefore, this function is not thread safe. + * @param format The format to use for the string. + * @return The time string. + */ +const char *GetTimeString(const char *format); + +#endif + diff --git a/src/tray.c b/src/tray.c new file mode 100644 index 0000000..d30dd1e --- /dev/null +++ b/src/tray.c @@ -0,0 +1,1104 @@ +/*************************************************************************** + * Functions to handle the tray. + * Copyright (C) 2004 Joe Wingbermuehle + ***************************************************************************/ + +#include "jwm.h" +#include "tray.h" +#include "color.h" +#include "main.h" +#include "pager.h" +#include "cursor.h" +#include "error.h" +#include "taskbar.h" +#include "menu.h" +#include "timing.h" + +#define DEFAULT_TRAY_WIDTH 32 +#define DEFAULT_TRAY_HEIGHT 32 + +static TrayType *trays; +static Window supportingWindow; + +static void HandleTrayExpose(TrayType *tp, const XExposeEvent *event); +static void HandleTrayEnterNotify(TrayType *tp, const XCrossingEvent *event); + +static void HandleTrayButtonPress(TrayType *tp, const XButtonEvent *event); +static void HandleTrayMotionNotify(TrayType *tp, const XMotionEvent *event); + +static void ComputeTraySize(TrayType *tp); +static int ComputeMaxWidth(TrayType *tp); +static int ComputeTotalWidth(TrayType *tp); +static int ComputeMaxHeight(TrayType *tp); +static int ComputeTotalHeight(TrayType *tp); +static int CheckHorizontalFill(TrayType *tp); +static int CheckVerticalFill(TrayType *tp); +static void LayoutTray(TrayType *tp, int *variableSize, + int *variableRemainder); + +/*************************************************************************** + ***************************************************************************/ +void InitializeTray() { + trays = NULL; + supportingWindow = None; +} + +/*************************************************************************** + ***************************************************************************/ +void StartupTray() { + + XSetWindowAttributes attr; + unsigned long attrMask; + TrayType *tp; + TrayComponentType *cp; + int variableSize; + int variableRemainder; + int width, height; + int xoffset, yoffset; + + for(tp = trays; tp; tp = tp->next) { + + LayoutTray(tp, &variableSize, &variableRemainder); + + /* Create the tray window. */ + /* The window is created larger for a border. */ + attrMask = CWOverrideRedirect; + attr.override_redirect = True; + + /* We can't use PointerMotionHintMask since the exact position + * of the mouse on the tray is important for popups. */ + attrMask |= CWEventMask; + attr.event_mask + = ButtonPressMask + | SubstructureNotifyMask + | ExposureMask + | KeyPressMask + | EnterWindowMask + | PointerMotionMask; + + attrMask |= CWBackPixel; + attr.background_pixel = colors[COLOR_TRAY_BG]; + + tp->window = JXCreateWindow(display, rootWindow, + tp->x, tp->y, tp->width, tp->height, + 0, rootDepth, InputOutput, rootVisual, attrMask, &attr); + + SetDefaultCursor(tp->window); + + /* Create and layout items on the tray. */ + xoffset = tp->border; + yoffset = tp->border; + for(cp = tp->components; cp; cp = cp->next) { + + if(cp->Create) { + if(tp->layout == LAYOUT_HORIZONTAL) { + height = tp->height - 2 * tp->border; + width = cp->width; + if(width == 0) { + width = variableSize; + if(variableRemainder) { + ++width; + --variableRemainder; + } + } + } else { + width = tp->width - 2 * tp->border; + height = cp->height; + if(height == 0) { + height = variableSize; + if(variableRemainder) { + ++height; + --variableRemainder; + } + } + } + cp->width = width; + cp->height = height; + (cp->Create)(cp); + } + + cp->x = xoffset; + cp->y = yoffset; + cp->screenx = tp->x + xoffset; + cp->screeny = tp->y + yoffset; + + if(cp->window != None) { + JXReparentWindow(display, cp->window, tp->window, + xoffset, yoffset); + } + + if(tp->layout == LAYOUT_HORIZONTAL) { + xoffset += cp->width; + } else { + yoffset += cp->height; + } + } + + /* Show the tray. */ + JXMapWindow(display, tp->window); + + } + + UpdatePager(); + UpdateTaskBar(); + +} + +/*************************************************************************** + ***************************************************************************/ +void ShutdownTray() { + + TrayType *tp; + TrayComponentType *cp; + + for(tp = trays; tp; tp = tp->next) { + for(cp = tp->components; cp; cp = cp->next) { + if(cp->Destroy) { + (cp->Destroy)(cp); + } + } + JXDestroyWindow(display, tp->window); + } + + if(supportingWindow != None) { + XDestroyWindow(display, supportingWindow); + supportingWindow = None; + } + +} + + +/*************************************************************************** + ***************************************************************************/ +void DestroyTray() { + + TrayType *tp; + TrayComponentType *cp; + + while(trays) { + tp = trays->next; + + while(trays->components) { + cp = trays->components->next; + Release(trays->components); + trays->components = cp; + } + Release(trays); + + trays = tp; + } + +} + +/*************************************************************************** + ***************************************************************************/ +TrayType *CreateTray() { + + TrayType *tp; + + tp = Allocate(sizeof(TrayType)); + + tp->x = 0; + tp->y = -1; + tp->requestedWidth = 0; + tp->requestedHeight = 0; + tp->width = 0; + tp->height = 0; + tp->border = 1; + tp->layer = DEFAULT_TRAY_LAYER; + tp->layout = LAYOUT_HORIZONTAL; + tp->valign = TALIGN_FIXED; + tp->halign = TALIGN_FIXED; + + tp->autoHide = 0; + tp->hidden = 0; + + tp->window = None; + + tp->components = NULL; + tp->componentsTail = NULL; + + tp->next = trays; + trays = tp; + + return tp; + +} + +/*************************************************************************** + ***************************************************************************/ +TrayComponentType *CreateTrayComponent() { + + TrayComponentType *cp; + + cp = Allocate(sizeof(TrayComponentType)); + + cp->tray = NULL; + cp->object = NULL; + + cp->x = 0; + cp->y = 0; + cp->requestedWidth = 0; + cp->requestedHeight = 0; + cp->width = 0; + cp->height = 0; + + cp->window = None; + cp->pixmap = None; + + cp->Create = NULL; + cp->Destroy = NULL; + + cp->SetSize = NULL; + cp->Resize = NULL; + + cp->ProcessButtonEvent = NULL; + cp->ProcessMotionEvent = NULL; + + cp->next = NULL; + + return cp; + +} + +/*************************************************************************** + ***************************************************************************/ +void AddTrayComponent(TrayType *tp, TrayComponentType *cp) { + + Assert(tp); + Assert(cp); + + cp->tray = tp; + + if(tp->componentsTail) { + tp->componentsTail->next = cp; + } else { + tp->components = cp; + } + tp->componentsTail = cp; + cp->next = NULL; + +} + +/*************************************************************************** + * Compute the max component width. + ***************************************************************************/ +int ComputeMaxWidth(TrayType *tp) { + + TrayComponentType *cp; + int result; + int temp; + + result = 0; + for(cp = tp->components; cp; cp = cp->next) { + temp = cp->width; + if(temp > 0) { + temp += 2 * tp->border; + if(temp > result) { + result = temp; + } + } + } + + return result; + +} + +/*************************************************************************** + ***************************************************************************/ +int ComputeTotalWidth(TrayType *tp) { + + TrayComponentType *cp; + int result; + + result = 2 * tp->border; + for(cp = tp->components; cp; cp = cp->next) { + result += cp->width; + } + + return result; + +} + +/*************************************************************************** + * Compute the max component height. + ***************************************************************************/ +int ComputeMaxHeight(TrayType *tp) { + + TrayComponentType *cp; + int result; + int temp; + + result = 0; + for(cp = tp->components; cp; cp = cp->next) { + temp = cp->height; + if(temp > 0) { + temp += 2 * tp->border; + if(temp > result) { + result = temp; + } + } + } + + return result; + +} + +/*************************************************************************** + ***************************************************************************/ +int ComputeTotalHeight(TrayType *tp) { + + TrayComponentType *cp; + int result; + + result = 2 * tp->border; + for(cp = tp->components; cp; cp = cp->next) { + result += cp->height; + } + + return result; + +} + +/*************************************************************************** + ***************************************************************************/ +int CheckHorizontalFill(TrayType *tp) { + + TrayComponentType *cp; + + for(cp = tp->components; cp; cp = cp->next) { + if(cp->width == 0) { + return 1; + } + } + + return 0; + +} + +/*************************************************************************** + ***************************************************************************/ +int CheckVerticalFill(TrayType *tp) { + + TrayComponentType *cp; + + for(cp = tp->components; cp; cp = cp->next) { + if(cp->height == 0) { + return 1; + } + } + + return 0; + +} + +/*************************************************************************** + ***************************************************************************/ +void ComputeTraySize(TrayType *tp) { + + TrayComponentType *cp; + + /* Determine the first dimension. */ + if(tp->layout == LAYOUT_HORIZONTAL) { + + if(tp->height == 0) { + tp->height = ComputeMaxHeight(tp); + } + + if(tp->height == 0) { + tp->height = DEFAULT_TRAY_HEIGHT; + } + + } else { + + if(tp->width == 0) { + tp->width = ComputeMaxWidth(tp); + } + + if(tp->width == 0) { + tp->width = DEFAULT_TRAY_WIDTH; + } + + } + + /* Now at least one size is known. Inform the components. */ + for(cp = tp->components; cp; cp = cp->next) { + if(cp->SetSize) { + if(tp->layout == LAYOUT_HORIZONTAL) { + (cp->SetSize)(cp, 0, tp->height - 2 * tp->border); + } else { + (cp->SetSize)(cp, tp->width - 2 * tp->border, 0); + } + } + } + + /* Determine the missing dimension. */ + if(tp->layout == LAYOUT_HORIZONTAL) { + if(tp->width == 0) { + if(CheckHorizontalFill(tp)) { + tp->width = rootWidth; + } else { + tp->width = ComputeTotalWidth(tp); + } + if(tp->width == 0) { + tp->width = DEFAULT_TRAY_WIDTH; + } + } + } else { + if(tp->height == 0) { + if(CheckVerticalFill(tp)) { + tp->height = rootHeight; + } else { + tp->height = ComputeTotalHeight(tp); + } + if(tp->height == 0) { + tp->height = DEFAULT_TRAY_HEIGHT; + } + } + } + + /* Compute the tray location. */ + switch(tp->valign) { + case TALIGN_TOP: + tp->y = 0; + break; + case TALIGN_BOTTOM: + tp->y = rootHeight - tp->height + 1; + break; + case TALIGN_CENTER: + tp->y = rootHeight / 2 - tp->height / 2; + break; + default: + if(tp->y < 0) { + tp->y = rootHeight + tp->y - tp->height + 1; + } + break; + } + + switch(tp->halign) { + case TALIGN_LEFT: + tp->x = 0; + break; + case TALIGN_RIGHT: + tp->x = rootWidth - tp->width + 1; + break; + case TALIGN_CENTER: + tp->x = rootWidth / 2 - tp->width / 2; + break; + default: + if(tp->x < 0) { + tp->x = rootWidth + tp->x - tp->width + 1; + } + break; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ShowTray(TrayType *tp) { + + Window win1, win2; + int winx, winy; + unsigned int mask; + int mousex, mousey; + + if(tp->hidden) { + + tp->hidden = 0; + JXMoveWindow(display, tp->window, tp->x, tp->y); + + JXQueryPointer(display, rootWindow, &win1, &win2, + &mousex, &mousey, &winx, &winy, &mask); + SetMousePosition(mousex, mousey); + + } + +} + +/*************************************************************************** + ***************************************************************************/ +void HideTray(TrayType *tp) { + + int x, y; + + tp->hidden = 1; + + /* Determine where to move the tray. */ + if(tp->layout == LAYOUT_HORIZONTAL) { + + x = tp->x; + + if(tp->y >= rootHeight / 2) { + y = rootHeight - 1; + } else { + y = 1 - tp->height; + } + + } else { + + y = tp->y; + + if(tp->x >= rootWidth / 2) { + x = rootWidth - 1; + } else { + x = 1 - tp->width; + } + + } + + /* Move it. */ + JXMoveWindow(display, tp->window, x, y); + +} + +/*************************************************************************** + ***************************************************************************/ +int ProcessTrayEvent(const XEvent *event) { + + TrayType *tp; + + for(tp = trays; tp; tp = tp->next) { + if(event->xany.window == tp->window) { + switch(event->type) { + case Expose: + HandleTrayExpose(tp, &event->xexpose); + return 1; + case EnterNotify: + HandleTrayEnterNotify(tp, &event->xcrossing); + return 1; + case ButtonPress: + HandleTrayButtonPress(tp, &event->xbutton); + return 1; + case MotionNotify: + HandleTrayMotionNotify(tp, &event->xmotion); + return 1; + default: + return 0; + } + } + } + + return 0; + +} + +/*************************************************************************** + ***************************************************************************/ +void SignalTray(const TimeType *now, int x, int y) { + + TrayType *tp; + + for(tp = trays; tp; tp = tp->next) { + if(tp->autoHide && !tp->hidden && !menuShown) { + if(x < tp->x || x >= tp->x + tp->width + || y < tp->y || y >= tp->y + tp->height) { + HideTray(tp); + } + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void HandleTrayExpose(TrayType *tp, const XExposeEvent *event) { + + DrawSpecificTray(tp); + +} + +/*************************************************************************** + ***************************************************************************/ +void HandleTrayEnterNotify(TrayType *tp, const XCrossingEvent *event) { + + ShowTray(tp); + +} + +/*************************************************************************** + ***************************************************************************/ +void HandleTrayButtonPress(TrayType *tp, const XButtonEvent *event) { + + TrayComponentType *cp; + int xoffset, yoffset; + int width, height; + int x, y; + int mask; + + xoffset = tp->border; + yoffset = tp->border; + for(cp = tp->components; cp; cp = cp->next) { + width = cp->width; + height = cp->height; + if(event->x >= xoffset && event->x < xoffset + width) { + if(event->y >= yoffset && event->y < yoffset + height) { + if(cp->ProcessButtonEvent) { + x = event->x - xoffset; + y = event->y - yoffset; + mask = event->button; + (cp->ProcessButtonEvent)(cp, x, y, mask); + } + break; + } + } + if(tp->layout == LAYOUT_HORIZONTAL) { + xoffset += width; + } else { + yoffset += height; + } + } +} + +/*************************************************************************** + ***************************************************************************/ +void HandleTrayMotionNotify(TrayType *tp, const XMotionEvent *event) { + + TrayComponentType *cp; + int xoffset, yoffset; + int width, height; + int x, y; + int mask; + + xoffset = tp->border; + yoffset = tp->border; + for(cp = tp->components; cp; cp = cp->next) { + width = cp->width; + height = cp->height; + if(event->x >= xoffset && event->x < xoffset + width) { + if(event->y >= yoffset && event->y < yoffset + height) { + if(cp->ProcessMotionEvent) { + x = event->x - xoffset; + y = event->y - yoffset; + mask = event->state; + (cp->ProcessMotionEvent)(cp, x, y, mask); + } + break; + } + } + if(tp->layout == LAYOUT_HORIZONTAL) { + xoffset += width; + } else { + yoffset += height; + } + } +} + +/*************************************************************************** + ***************************************************************************/ +void DrawTray() { + + TrayType *tp; + + if(shouldExit) { + return; + } + + for(tp = trays; tp; tp = tp->next) { + DrawSpecificTray(tp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void DrawSpecificTray(const TrayType *tp) { + + TrayComponentType *cp; + int x; + + Assert(tp); + + /* Draw components. */ + for(cp = tp->components; cp; cp = cp->next) { + UpdateSpecificTray(tp, cp); + } + + /* Draw the border. */ + for(x = 0; x < tp->border; x++) { + + /* Top */ + JXSetForeground(display, rootGC, colors[COLOR_TRAY_UP]); + JXDrawLine(display, tp->window, rootGC, + 0, x, + tp->width - x - 1, x); + + /* Bottom */ + JXSetForeground(display, rootGC, colors[COLOR_TRAY_DOWN]); + JXDrawLine(display, tp->window, rootGC, + x + 1, tp->height - x - 1, + tp->width - x - 2, tp->height - x - 1); + + /* Left */ + JXSetForeground(display, rootGC, colors[COLOR_TRAY_UP]); + JXDrawLine(display, tp->window, rootGC, + x, x, + x, tp->height - x - 1); + + /* Right */ + JXSetForeground(display, rootGC, colors[COLOR_TRAY_DOWN]); + JXDrawLine(display, tp->window, rootGC, + tp->width - x - 1, x + 1, + tp->width - x - 1, tp->height - x - 1); + + } + +} + +/*************************************************************************** + ***************************************************************************/ +void UpdateSpecificTray(const TrayType *tp, const TrayComponentType *cp) { + + if(cp->pixmap != None && !shouldExit) { + JXCopyArea(display, cp->pixmap, tp->window, rootGC, 0, 0, + cp->width, cp->height, cp->x, cp->y); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void LayoutTray(TrayType *tp, int *variableSize, int *variableRemainder) { + + TrayComponentType *cp; + int variableCount; + int width, height; + int temp; + + tp->width = tp->requestedWidth; + tp->height = tp->requestedHeight; + + for(cp = tp->components; cp; cp = cp->next) { + cp->width = cp->requestedWidth; + cp->height = cp->requestedHeight; + } + + ComputeTraySize(tp); + + /* Get the remaining size after setting fixed size components. */ + /* Also, keep track of the number of variable size components. */ + width = tp->width - 2 * tp->border; + height = tp->height - 2 * tp->border; + variableCount = 0; + for(cp = tp->components; cp; cp = cp->next) { + if(tp->layout == LAYOUT_HORIZONTAL) { + temp = cp->width; + if(temp > 0) { + width -= temp; + } else { + ++variableCount; + } + } else { + temp = cp->height; + if(temp > 0) { + height -= temp; + } else { + ++variableCount; + } + } + } + + /* Distribute excess size among variable size components. + * If there are no variable size components, shrink the tray. + * If we are out of room, just give them a size of one. + */ + *variableSize = 1; + *variableRemainder = 0; + if(tp->layout == LAYOUT_HORIZONTAL) { + if(variableCount) { + if(width >= variableCount) { + *variableSize = width / variableCount; + *variableRemainder = width % variableCount; + } + } else if(width > 0) { + tp->width -= width; + } + } else { + if(variableCount) { + if(height >= variableCount) { + *variableSize = height / variableCount; + *variableRemainder = height % variableCount; + } + } else if(height > 0) { + tp->height -= height; + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ResizeTray(TrayType *tp) { + + TrayComponentType *cp; + int variableSize; + int variableRemainder; + int xoffset, yoffset; + int width, height; + + Assert(tp); + + LayoutTray(tp, &variableSize, &variableRemainder); + + /* Reposition items on the tray. */ + xoffset = tp->border; + yoffset = tp->border; + for(cp = tp->components; cp; cp = cp->next) { + + cp->x = xoffset; + cp->y = yoffset; + cp->screenx = tp->x + xoffset; + cp->screeny = tp->y + yoffset; + + if(cp->Resize) { + if(tp->layout == LAYOUT_HORIZONTAL) { + height = tp->height - 2 * tp->border; + width = cp->width; + if(width == 0) { + width = variableSize; + if(variableRemainder) { + ++width; + --variableRemainder; + } + } + } else { + width = tp->width - 2 * tp->border; + height = cp->height; + if(height == 0) { + height = variableSize; + if(variableRemainder) { + ++height; + --variableRemainder; + } + } + } + cp->width = width; + cp->height = height; + (cp->Resize)(cp); + } + + if(cp->window != None) { + JXMoveWindow(display, cp->window, xoffset, yoffset); + } + + if(tp->layout == LAYOUT_HORIZONTAL) { + xoffset += cp->width; + } else { + yoffset += cp->height; + } + } + + JXMoveResizeWindow(display, tp->window, tp->x, tp->y, + tp->width, tp->height); + + UpdateTaskBar(); + DrawSpecificTray(tp); + + if(tp->hidden) { + HideTray(tp); + } + +} + +/*************************************************************************** + ***************************************************************************/ +TrayType *GetTrays() { + return trays; +} + +/*************************************************************************** + ***************************************************************************/ +Window GetSupportingWindow() { + + if(trays) { + return trays->window; + } else if(supportingWindow != None) { + return supportingWindow; + } else { + supportingWindow = JXCreateSimpleWindow(display, rootWindow, + 0, 0, 1, 1, 0, 0, 0); + return supportingWindow; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetAutoHideTray(TrayType *tp, int v) { + Assert(tp); + tp->autoHide = v; +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayX(TrayType *tp, const char *str) { + Assert(tp); + Assert(str); + tp->x = atoi(str); +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayY(TrayType *tp, const char *str) { + Assert(tp); + Assert(str); + tp->y = atoi(str); +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayWidth(TrayType *tp, const char *str) { + + int width; + + Assert(tp); + Assert(str); + + width = atoi(str); + + if(width < 0) { + Warning("invalid tray width: %d", width); + } else { + tp->requestedWidth = width; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayHeight(TrayType *tp, const char *str) { + + int height; + + Assert(tp); + Assert(str); + + height = atoi(str); + + if(height < 0) { + Warning("invalid tray height: %d", height); + } else { + tp->requestedHeight = height; + } + +} + + +/*************************************************************************** + ***************************************************************************/ +void SetTrayLayout(TrayType *tp, const char *str) { + + Assert(tp); + + if(!str) { + + /* Compute based on requested size. */ + + } else if(!strcmp(str, "horizontal")) { + + tp->layout = LAYOUT_HORIZONTAL; + return; + + } else if(!strcmp(str, "vertical")) { + + tp->layout = LAYOUT_VERTICAL; + return; + + } else { + Warning("invalid tray layout: \"%s\"", str); + } + + /* Prefer horizontal layout, but use vertical if + * width is finite and height is larger than width or infinite. + */ + if(tp->requestedWidth > 0 + && (tp->requestedHeight == 0 + || tp->requestedHeight > tp->requestedWidth)) { + tp->layout = LAYOUT_VERTICAL; + } else { + tp->layout = LAYOUT_HORIZONTAL; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayLayer(TrayType *tp, const char *str) { + + int temp; + + Assert(tp); + Assert(str); + + temp = atoi(str); + if(temp < LAYER_BOTTOM || temp > LAYER_TOP) { + Warning("invalid tray layer: %d", temp); + tp->layer = DEFAULT_TRAY_LAYER; + } else { + tp->layer = temp; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayBorder(TrayType *tp, const char *str) { + + int temp; + + Assert(tp); + Assert(str); + + temp = atoi(str); + if(temp < MIN_TRAY_BORDER || temp > MAX_TRAY_BORDER) { + Warning("invalid tray border: %d", temp); + tp->border = DEFAULT_TRAY_BORDER; + } else { + tp->border = temp; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayHorizontalAlignment(TrayType *tp, const char *str) { + + Assert(tp); + + if(!str || !strcmp(str, "fixed")) { + tp->halign = TALIGN_FIXED; + } else if(!strcmp(str, "left")) { + tp->halign = TALIGN_LEFT; + } else if(!strcmp(str, "right")) { + tp->halign = TALIGN_RIGHT; + } else if(!strcmp(str, "center")) { + tp->halign = TALIGN_CENTER; + } else { + Warning("invalid tray horizontal alignment: \"%s\"", str); + tp->halign = TALIGN_FIXED; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void SetTrayVerticalAlignment(TrayType *tp, const char *str) { + + Assert(tp); + + if(!str || !strcmp(str, "fixed")) { + tp->valign = TALIGN_FIXED; + } else if(!strcmp(str, "top")) { + tp->valign = TALIGN_TOP; + } else if(!strcmp(str, "bottom")) { + tp->valign = TALIGN_BOTTOM; + } else if(!strcmp(str, "center")) { + tp->valign = TALIGN_CENTER; + } else { + Warning("invalid tray vertical alignment: \"%s\"", str); + tp->valign = TALIGN_FIXED; + } + +} + + diff --git a/src/tray.h b/src/tray.h new file mode 100644 index 0000000..bae38c5 --- /dev/null +++ b/src/tray.h @@ -0,0 +1,214 @@ +/** + * @file tray.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the tray functions. + * + */ + +#ifndef TRAY_H +#define TRAY_H + +#include "hint.h" + +struct TimeType; + +typedef enum { + LAYOUT_HORIZONTAL, + LAYOUT_VERTICAL +} LayoutType; + +typedef enum { + TALIGN_FIXED, + TALIGN_LEFT, + TALIGN_TOP, + TALIGN_CENTER, + TALIGN_RIGHT, + TALIGN_BOTTOM +} TrayAlignmentType; + +typedef struct TrayComponentType { + + /* The tray containing the component. + * UpdateSpecificTray(TrayType*, TrayComponentType*) should be called + * when content changes. + */ + struct TrayType *tray; + + /* Additional information needed for the component. */ + void *object; + + /* Coordinates on the tray (valid only after Create). */ + int x, y; + + /* Coordinates on the screen (valid only after Create). */ + int screenx, screeny; + + /* Sizing is handled as follows: + * - The component is created via a factory method. It sets its + * requested size (0 for no preference). + * - The SetSize callback is issued with size constraints + * (0 for no constraint). The component should update + * width and height in SetSize. + * - The Create callback is issued with finalized size information. + * Resizing is handled as follows: + * - A component determines that it needs to change size. It updates + * its requested size (0 for no preference). + * - The component calls ResizeTray. + * - The SetSize callback is issued with size constraints + * (0 for no constraint). The component should update + * width and height in SetSize. + * - The Resize callback is issued with finalized size information. + */ + + /* Requested component size. */ + int requestedWidth, requestedHeight; + + /* Actual component size. */ + int width, height; + + /* Content. */ + Window window; + Pixmap pixmap; + + /* Create the component. */ + void (*Create)(struct TrayComponentType *cp); + + /* Destroy the component. */ + void (*Destroy)(struct TrayComponentType *cp); + + /* Set the size known so far for items that need width/height ratios. + * Either width or height may be zero. + * This is called before Create. + */ + void (*SetSize)(struct TrayComponentType *cp, int width, int height); + + /* Resize the component. */ + void (*Resize)(struct TrayComponentType *cp); + + /* Callback for mouse clicks. */ + void (*ProcessButtonEvent)(struct TrayComponentType *cp, + int x, int y, int mask); + + /* Callback for mouse motion. */ + void (*ProcessMotionEvent)(struct TrayComponentType *cp, + int x, int y, int mask); + + /* The next component in the tray. */ + struct TrayComponentType *next; + +} TrayComponentType; + +typedef struct TrayType { + + int x, y; + int requestedWidth, requestedHeight; + int width, height; + int border; + WinLayerType layer; + LayoutType layout; + TrayAlignmentType valign, halign; + + int autoHide; + int hidden; + + Window window; + + struct TrayComponentType *components; + struct TrayComponentType *componentsTail; + + struct TrayType *next; + +} TrayType; + + +void InitializeTray(); +void StartupTray(); +void ShutdownTray(); +void DestroyTray(); + +/** Create a new tray. + * @return A new, empty tray. + */ +TrayType *CreateTray(); + +/** Create a tray component. + * @return A new tray component structure. + */ +TrayComponentType *CreateTrayComponent(); + +/** Add a tray component to a tray. + * @param tp The tray to update. + * @param cp The tray component to add. + */ +void AddTrayComponent(TrayType *tp, TrayComponentType *cp); + +/** Show a tray. + * @param tp The tray to show. + */ +void ShowTray(TrayType *tp); + +/** Hide a tray. + * @param tp The tray to hide. + */ +void HideTray(TrayType *tp); + +/** Draw all trays. */ +void DrawTray(); + +/** Draw a specific tray. + * @param tp The tray to draw. + */ +void DrawSpecificTray(const TrayType *tp); + +/** Update a component on a tray. + * @param tp The tray containing the component. + * @param cp The component that needs updating. + */ +void UpdateSpecificTray(const TrayType *tp, const TrayComponentType *cp); + +/** Resize a tray. + * @param tp The tray to resize containing the new requested size information. + */ +void ResizeTray(TrayType *tp); + +/** Get a linked list of trays. + * @return The trays. + */ +TrayType *GetTrays(); + +/** Get a window to use as the supporting window. + * This is used by clients to validate that compliant window manager is + * running. + * @return The supporting window. + */ +Window GetSupportingWindow(); + +/** Process an event that may be for a tray. + * @param event The event to process. + * @return 1 if this event was for a tray, 0 otherwise. + */ +int ProcessTrayEvent(const XEvent *event); + +/** Signal the trays. + * This function is called regularly so that autohide, etc. can take place. + * @param now The current time. + * @param x The mouse x-coordinate (root relative). + * @param y The mouse y-coordinate (root relative). + */ +void SignalTray(const struct TimeType *now, int x, int y); + +void SetAutoHideTray(TrayType *tp, int v); +void SetTrayX(TrayType *tp, const char *str); +void SetTrayY(TrayType *tp, const char *str); +void SetTrayWidth(TrayType *tp, const char *str); +void SetTrayHeight(TrayType *tp, const char *str); +void SetTrayLayout(TrayType *tp, const char *str); +void SetTrayLayer(TrayType *tp, const char *str); +void SetTrayBorder(TrayType *tp, const char *str); +void SetTrayHorizontalAlignment(TrayType *tp, const char *str); +void SetTrayVerticalAlignment(TrayType *tp, const char *str); + +#endif + diff --git a/src/traybutton.c b/src/traybutton.c new file mode 100644 index 0000000..b596ba0 --- /dev/null +++ b/src/traybutton.c @@ -0,0 +1,454 @@ +/*************************************************************************** + ***************************************************************************/ + +#include "jwm.h" +#include "traybutton.h" +#include "tray.h" +#include "icon.h" +#include "image.h" +#include "error.h" +#include "root.h" +#include "main.h" +#include "color.h" +#include "font.h" +#include "button.h" +#include "misc.h" +#include "screen.h" +#include "desktop.h" +#include "popup.h" +#include "timing.h" + +#define BUTTON_SIZE 4 + +typedef struct TrayButtonType { + + TrayComponentType *cp; + + char *label; + char *popup; + char *iconName; + IconNode *icon; + + char *action; + + int mousex; + int mousey; + TimeType mouseTime; + + struct TrayButtonType *next; + +} TrayButtonType; + +static TrayButtonType *buttons; + +static void CheckedCreate(TrayComponentType *cp); +static void Create(TrayComponentType *cp); +static void Destroy(TrayComponentType *cp); +static void SetSize(TrayComponentType *cp, int width, int height); +static void Resize(TrayComponentType *cp); + +static void ProcessButtonEvent(TrayComponentType *cp, + int x, int y, int mask); +static void ProcessMotionEvent(TrayComponentType *cp, + int x, int y, int mask); + +/*************************************************************************** + ***************************************************************************/ +void InitializeTrayButtons() { + buttons = NULL; +} + +/*************************************************************************** + ***************************************************************************/ +void StartupTrayButtons() { + + TrayButtonType *bp; + + for(bp = buttons; bp; bp = bp->next) { + if(bp->label) { + bp->cp->requestedWidth + = GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4; + bp->cp->requestedHeight + = GetStringHeight(FONT_TRAYBUTTON); + } else { + bp->cp->requestedWidth = 0; + bp->cp->requestedHeight = 0; + } + if(bp->iconName) { + bp->icon = LoadNamedIcon(bp->iconName); + if(bp->icon) { + bp->cp->requestedWidth += bp->icon->image->width; + bp->cp->requestedHeight += bp->icon->image->height; + } else { + Warning("could not load tray icon: \"%s\"", bp->iconName); + } + } + bp->cp->requestedWidth += 2 * BUTTON_SIZE; + bp->cp->requestedHeight += 2 * BUTTON_SIZE; + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ShutdownTrayButtons() { + +} + +/*************************************************************************** + ***************************************************************************/ +void DestroyTrayButtons() { + + TrayButtonType *bp; + + while(buttons) { + bp = buttons->next; + if(buttons->label) { + Release(buttons->label); + } + if(buttons->iconName) { + Release(buttons->iconName); + } + if(buttons->action) { + Release(buttons->action); + } + if(buttons->popup) { + Release(buttons->popup); + } + Release(buttons); + buttons = bp; + } + +} + +/*************************************************************************** + ***************************************************************************/ +TrayComponentType *CreateTrayButton(const char *iconName, + const char *label, const char *action, + const char *popup, int width, int height) { + + TrayButtonType *bp; + TrayComponentType *cp; + + if((label == NULL || strlen(label) == 0) + && (iconName == NULL || strlen(iconName) == 0)) { + Warning("no icon or label for TrayButton"); + return NULL; + } + + if(width < 0) { + Warning("invalid TrayButton width: %d", width); + width = 0; + } + if(height < 0) { + Warning("invalid TrayButton height: %d", height); + height = 0; + } + + bp = Allocate(sizeof(TrayButtonType)); + bp->next = buttons; + buttons = bp; + + bp->icon = NULL; + if(iconName) { + bp->iconName = Allocate(strlen(iconName) + 1); + strcpy(bp->iconName, iconName); + } else { + bp->iconName = NULL; + } + + if(label) { + bp->label = Allocate(strlen(label) + 1); + strcpy(bp->label, label); + } else { + bp->label = NULL; + } + + if(action) { + bp->action = Allocate(strlen(action) + 1); + strcpy(bp->action, action); + } else { + bp->action = NULL; + } + + if(popup) { + bp->popup = Allocate(strlen(popup) + 1); + strcpy(bp->popup, popup); + } else { + bp->popup = NULL; + } + + cp = CreateTrayComponent(); + cp->object = bp; + bp->cp = cp; + cp->requestedWidth = width; + cp->requestedHeight = height; + + cp->Create = CheckedCreate; + cp->Destroy = Destroy; + cp->SetSize = SetSize; + cp->Resize = Resize; + + cp->ProcessButtonEvent = ProcessButtonEvent; + if(popup || label) { + cp->ProcessMotionEvent = ProcessMotionEvent; + } + + return cp; + +} + +/*************************************************************************** + ***************************************************************************/ +void SetSize(TrayComponentType *cp, int width, int height) { + + TrayButtonType *bp; + int labelWidth, labelHeight; + int iconWidth, iconHeight; + double ratio; + + bp = (TrayButtonType*)cp->object; + + if(bp->icon) { + + if(bp->label) { + labelWidth = GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4; + labelHeight = GetStringHeight(FONT_TRAYBUTTON); + } else { + labelWidth = 0; + labelHeight = 0; + } + + iconWidth = bp->icon->image->width; + iconHeight = bp->icon->image->height; + ratio = (double)iconWidth / iconHeight; + + if(width > 0) { + + /* Compute height from width. */ + iconWidth = width - labelWidth - 2 * BUTTON_SIZE; + iconHeight = iconWidth / ratio; + height = Max(iconHeight, labelHeight) + 2 * BUTTON_SIZE; + + } else if(height > 0) { + + /* Compute width from height. */ + iconHeight = height - 2 * BUTTON_SIZE; + iconWidth = iconHeight * ratio; + width = iconWidth + labelWidth + 2 * BUTTON_SIZE; + + } + + cp->width = width; + cp->height = height; + + } + +} + +/*************************************************************************** + ***************************************************************************/ +void CheckedCreate(TrayComponentType *cp) { + + TrayButtonType *bp; + + bp = (TrayButtonType*)cp->object; + + /* Validate the action for this tray button. */ + if(bp->action && strlen(bp->action) > 0) { + if(!strncmp(bp->action, "exec:", 5)) { + /* Valid. */ + } else if(!strncmp(bp->action, "root:", 5)) { + /* Valid. However, the specified root menu may not exist. + * This case is handled in ValidateTrayButtons. + */ + } else if(!strcmp(bp->action, "showdesktop")) { + /* Valid. */ + } else { + Warning("invalid TrayButton action: \"%s\"", bp->action); + } + } else { + /* Valid. However, root menu 1 may not exist. + * This case is handled in ValidateTrayButtons. + */ + } + + Create(cp); + +} + +/*************************************************************************** + ***************************************************************************/ +void Create(TrayComponentType *cp) { + + ButtonNode button; + TrayButtonType *bp; + int labelx; + + bp = (TrayButtonType*)cp->object; + + cp->pixmap = JXCreatePixmap(display, rootWindow, + cp->width, cp->height, rootDepth); + + JXSetForeground(display, rootGC, colors[COLOR_TRAYBUTTON_BG]); + JXFillRectangle(display, cp->pixmap, rootGC, 0, 0, cp->width, cp->height); + + ResetButton(&button, cp->pixmap, rootGC); + button.type = BUTTON_TASK; + button.width = cp->width - 3; + button.height = cp->height - 3; + button.x = 1; + button.y = 1; + DrawButton(&button); + + /* Compute the offset of the text. */ + if(bp->label) { + if(!bp->icon) { + labelx = 2 + cp->width / 2; + labelx -= GetStringWidth(FONT_TRAYBUTTON, bp->label) / 2; + } else { + labelx = cp->width; + labelx -= GetStringWidth(FONT_TRAYBUTTON, bp->label) + 4; + } + } else { + labelx = cp->width; + } + labelx -= BUTTON_SIZE; + + if(bp->icon) { + PutIcon(bp->icon, cp->pixmap, BUTTON_SIZE, BUTTON_SIZE, + labelx - BUTTON_SIZE, cp->height - BUTTON_SIZE * 2); + } + + if(bp->label) { + RenderString(cp->pixmap, FONT_TRAYBUTTON, COLOR_TRAYBUTTON_FG, + labelx + 2, cp->height / 2 - GetStringHeight(FONT_TRAYBUTTON) / 2, + cp->width - labelx, NULL, bp->label); + } + +} + +/*************************************************************************** + ***************************************************************************/ +void Resize(TrayComponentType *cp) { + + Destroy(cp); + Create(cp); + +} + +/*************************************************************************** + ***************************************************************************/ +void Destroy(TrayComponentType *cp) { + if(cp->pixmap != None) { + JXFreePixmap(display, cp->pixmap); + } +} + +/*************************************************************************** + ***************************************************************************/ +void ProcessButtonEvent(TrayComponentType *cp, int x, int y, int mask) { + + const ScreenType *sp; + int mwidth, mheight; + int button; + + TrayButtonType *bp = (TrayButtonType*)cp->object; + + Assert(bp); + + if(bp->action && strlen(bp->action) > 0) { + if(!strncmp(bp->action, "exec:", 5)) { + RunCommand(bp->action + 5); + return; + } else if(!strncmp(bp->action, "root:", 5)) { + button = atoi(bp->action + 5); + } else if(!strcmp(bp->action, "showdesktop")) { + ShowDesktop(); + return; + } else { + return; + } + } else { + button = 1; + } + + GetRootMenuSize(button, &mwidth, &mheight); + + sp = GetCurrentScreen(cp->screenx, cp->screeny); + + if(cp->tray->layout == LAYOUT_HORIZONTAL) { + x = cp->screenx; + if(cp->screeny + cp->height / 2 < sp->y + sp->height / 2) { + y = cp->screeny + cp->height; + } else { + y = cp->screeny - mheight; + } + } else { + y = cp->screeny; + if(cp->screenx + cp->width / 2 < sp->x + sp->width / 2) { + x = cp->screenx + cp->width; + } else { + x = cp->screenx - mwidth; + } + } + + ShowRootMenu(button, x, y); + +} + +/*************************************************************************** + ***************************************************************************/ +void ProcessMotionEvent(TrayComponentType *cp, int x, int y, int mask) { + + TrayButtonType *bp = (TrayButtonType*)cp->object; + + bp->mousex = cp->screenx + x; + bp->mousey = cp->screeny + y; + GetCurrentTime(&bp->mouseTime); + +} + +/*************************************************************************** + ***************************************************************************/ +void SignalTrayButton(const TimeType *now, int x, int y) { + + TrayButtonType *bp; + const char *popup; + + for(bp = buttons; bp; bp = bp->next) { + if(bp->popup) { + popup = bp->popup; + } else if(bp->label) { + popup = bp->label; + } else { + continue; + } + if(abs(bp->mousex - x) < POPUP_DELTA + && abs(bp->mousey - y) < POPUP_DELTA) { + if(GetTimeDifference(now, &bp->mouseTime) >= popupDelay) { + ShowPopup(x, y, popup); + } + } + } + +} + +/*************************************************************************** + ***************************************************************************/ +void ValidateTrayButtons() { + + TrayButtonType *bp; + int bindex; + + for(bp = buttons; bp; bp = bp->next) { + if(bp->action && !strncmp(bp->action, "root:", 5)) { + bindex = atoi(bp->action + 5); + if(!IsRootMenuDefined(bindex)) { + Warning("tray button: root menu %d not defined", bindex); + } + } + } + +} + diff --git a/src/traybutton.h b/src/traybutton.h new file mode 100644 index 0000000..bd22494 --- /dev/null +++ b/src/traybutton.h @@ -0,0 +1,51 @@ +/** + * @file traybutton.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Tray button tray component. + * + */ + +#ifndef TRAY_BUTTON_H +#define TRAY_BUTTON_H + +struct TrayComponentType; +struct TimeType; + +/*@{*/ +void InitializeTrayButtons(); +void StartupTrayButtons(); +void ShutdownTrayButtons(); +void DestroyTrayButtons(); +/*@}*/ + +/** Create a tray button component. + * @param iconName The name of the icon to use for the button. + * @param label The label to use for the button. + * @param action The action to take when the button is clicked. + * @param popup Text to display in a popup window. + * @param width The width to use for the button (0 for default). + * @param height The height to use for the button (0 for default). + * @return A new tray button component. + */ +struct TrayComponentType *CreateTrayButton( + const char *iconName, const char *label, const char *action, + const char *popup, int width, int height); + +/** Signal a tray button. + * @param now The current time. + * @param x The x-coordinate of the mouse (root relative). + * @param y The y-coordinate of the mouse (root relative). + */ +void SignalTrayButton(const struct TimeType *now, int x, int y); + +/** Validate the tray buttons and print a warning if something is wrong. + * This is called after parsing the configuration file(s) to determine + * if a root menu is defined for each each tray button that specifies + * a root menu. + */ +void ValidateTrayButtons(); + +#endif + diff --git a/src/winmenu.c b/src/winmenu.c new file mode 100644 index 0000000..7b537cc --- /dev/null +++ b/src/winmenu.c @@ -0,0 +1,327 @@ +/**************************************************************************** + * Functions for handling window menus. + * Copyright (C) 2004 Joe Wingbermuehle + ****************************************************************************/ + +#include "jwm.h" +#include "winmenu.h" +#include "client.h" +#include "menu.h" +#include "main.h" +#include "desktop.h" +#include "move.h" +#include "resize.h" +#include "event.h" +#include "cursor.h" +#include "misc.h" + +static const char *SENDTO_TEXT = "Send To"; +static const char *LAYER_TEXT = "Layer"; + +static Menu *CreateWindowMenu(); +static void RunWindowCommand(const MenuAction *action); + +static void CreateWindowLayerMenu(Menu *menu); +static void CreateWindowSendToMenu(Menu *menu); +static void AddWindowMenuItem(Menu *menu, const char *name, + MenuActionType type, int value); + +static ClientNode *client = NULL; + +/**************************************************************************** + ****************************************************************************/ +void GetWindowMenuSize(ClientNode *np, int *width, int *height) { + + Menu *menu; + + client = np; + menu = CreateWindowMenu(); + InitializeMenu(menu); + *width = menu->width; + *height = menu->height; + DestroyMenu(menu); + +} + +/**************************************************************************** + ****************************************************************************/ +void ShowWindowMenu(ClientNode *np, int x, int y) { + + Menu *menu; + + client = np; + menu = CreateWindowMenu(); + + InitializeMenu(menu); + + ShowMenu(menu, RunWindowCommand, x, y); + + DestroyMenu(menu); + +} + +/**************************************************************************** + ****************************************************************************/ +Menu *CreateWindowMenu() { + + Menu *menu; + + menu = Allocate(sizeof(Menu)); + menu->itemHeight = 0; + menu->items = NULL; + menu->label = NULL; + + /* Note that items are added in reverse order of display. */ + + if(!(client->state.status & STAT_WMDIALOG)) { + AddWindowMenuItem(menu, "Close", MA_CLOSE, 0); + AddWindowMenuItem(menu, "Kill", MA_KILL, 0); + AddWindowMenuItem(menu, NULL, MA_NONE, 0); + } + + if(client->state.status & (STAT_MAPPED | STAT_SHADED)) { + if(client->state.border & BORDER_RESIZE) { + AddWindowMenuItem(menu, "Resize", MA_RESIZE, 0); + } + if(client->state.border & BORDER_MOVE) { + AddWindowMenuItem(menu, "Move", MA_MOVE, 0); + } + } + + if(client->state.border & BORDER_MIN) { + + if(client->state.status & STAT_MINIMIZED) { + AddWindowMenuItem(menu, "Restore", MA_RESTORE, 0); + } else { + if(client->state.status & STAT_SHADED) { + AddWindowMenuItem(menu, "Unshade", MA_SHADE, 0); + } else { + AddWindowMenuItem(menu, "Shade", MA_SHADE, 0); + } + AddWindowMenuItem(menu, "Minimize", MA_MINIMIZE, 0); + } + + } + + if((client->state.border & BORDER_MAX) + && (client->state.status & STAT_MAPPED)) { + + AddWindowMenuItem(menu, "Maximize", MA_MAXIMIZE, 0); + } + + if(!(client->state.status & STAT_WMDIALOG)) { + + if(client->state.status & STAT_STICKY) { + AddWindowMenuItem(menu, "Unstick", MA_STICK, 0); + } else { + AddWindowMenuItem(menu, "Stick", MA_STICK, 0); + } + + CreateWindowLayerMenu(menu); + + if(!(client->state.status & STAT_STICKY)) { + CreateWindowSendToMenu(menu); + } + + } + + return menu; +} + +/**************************************************************************** + ****************************************************************************/ +void CreateWindowLayerMenu(Menu *menu) { + + Menu *submenu; + MenuItem *item; + char str[10]; + unsigned int x; + + item = Allocate(sizeof(MenuItem)); + item->type = MENU_ITEM_SUBMENU; + item->name = CopyString(LAYER_TEXT); + item->action.type = MA_NONE; + item->action.data.str = NULL; + item->iconName = NULL; + + item->next = menu->items; + menu->items = item; + + submenu = Allocate(sizeof(Menu)); + item->submenu = submenu; + submenu->itemHeight = 0; + submenu->items = NULL; + submenu->label = NULL; + + if(client->state.layer == LAYER_TOP) { + AddWindowMenuItem(submenu, "[Top]", MA_LAYER, LAYER_TOP); + } else { + AddWindowMenuItem(submenu, "Top", MA_LAYER, LAYER_TOP); + } + + str[4] = 0; + for(x = LAYER_TOP - 1; x > LAYER_BOTTOM; x--) { + if(x == LAYER_NORMAL) { + if(client->state.layer == x) { + AddWindowMenuItem(submenu, "[Normal]", MA_LAYER, x); + } else { + AddWindowMenuItem(submenu, "Normal", MA_LAYER, x); + } + } else { + if(client->state.layer == x) { + str[0] = '['; + str[3] = ']'; + } else { + str[0] = ' '; + str[3] = ' '; + } + if(x < 10) { + str[1] = ' '; + } else { + str[1] = (x / 10) + '0'; + } + str[2] = (x % 10) + '0'; + AddWindowMenuItem(submenu, str, MA_LAYER, x); + } + } + + if(client->state.layer == LAYER_BOTTOM) { + AddWindowMenuItem(submenu, "[Bottom]", MA_LAYER, LAYER_BOTTOM); + } else { + AddWindowMenuItem(submenu, "Bottom", MA_LAYER, LAYER_BOTTOM); + } + +} + +/**************************************************************************** + ****************************************************************************/ +void CreateWindowSendToMenu(Menu *menu) { + + unsigned int mask; + unsigned int x; + + mask = 0; + for(x = 0; x < desktopCount; x++) { + if(client->state.desktop == x + || (client->state.status & STAT_STICKY)) { + mask |= 1 << x; + } + } + + AddWindowMenuItem(menu, SENDTO_TEXT, MA_NONE, 0); + + /* Now the first item in the menu is for the desktop list. */ + menu->items->submenu = CreateDesktopMenu(mask); + +} + +/**************************************************************************** + ****************************************************************************/ +void AddWindowMenuItem(Menu *menu, const char *name, + MenuActionType type, int value) { + + MenuItem *item; + + item = Allocate(sizeof(MenuItem)); + if(name) { + item->type = MENU_ITEM_NORMAL; + } else { + item->type = MENU_ITEM_SEPARATOR; + } + item->name = CopyString(name); + item->action.type = type; + item->action.data.i = value; + item->iconName = NULL; + item->submenu = NULL; + + item->next = menu->items; + menu->items = item; + +} + +/**************************************************************************** + ****************************************************************************/ +void ChooseWindow(const MenuAction *action) { + + XEvent event; + ClientNode *np; + + GrabMouseForChoose(); + + for(;;) { + + WaitForEvent(&event); + + if(event.type == ButtonPress) { + if(event.xbutton.button == Button1) { + np = FindClientByWindow(event.xbutton.subwindow); + if(np) { + client = np; + RunWindowCommand(action); + } + } + break; + } else if(event.type == KeyPress) { + break; + } + + } + + JXUngrabPointer(display, CurrentTime); + +} + +/**************************************************************************** + ****************************************************************************/ +void RunWindowCommand(const MenuAction *action) { + + switch(action->type) { + case MA_STICK: + if(client->state.status & STAT_STICKY) { + SetClientSticky(client, 0); + } else { + SetClientSticky(client, 1); + } + break; + case MA_MAXIMIZE: + MaximizeClient(client); + break; + case MA_MINIMIZE: + MinimizeClient(client); + break; + case MA_RESTORE: + RestoreClient(client, 1); + break; + case MA_CLOSE: + DeleteClient(client); + break; + case MA_SENDTO: + case MA_DESKTOP: + SetClientDesktop(client, action->data.i); + break; + case MA_SHADE: + if(client->state.status & STAT_SHADED) { + UnshadeClient(client); + } else { + ShadeClient(client); + } + break; + case MA_MOVE: + MoveClientKeyboard(client); + break; + case MA_RESIZE: + ResizeClientKeyboard(client); + break; + case MA_KILL: + KillClient(client); + break; + case MA_LAYER: + SetClientLayer(client, action->data.i); + break; + default: + Debug("unknown window command: %d", action->type); + break; + } + +} + diff --git a/src/winmenu.h b/src/winmenu.h new file mode 100644 index 0000000..a7b1f6e --- /dev/null +++ b/src/winmenu.h @@ -0,0 +1,37 @@ +/** + * @file tray.h + * @author Joe Wingbermuehle + * @date 2004-2006 + * + * @brief Header for the window menu functions. + * + */ + +#ifndef WINMENU_H +#define WINMENU_H + +#include "menu.h" + +struct ClientNode; + +/** Get the size of a window menu. + * @param np The client for the window menu. + * @param width The width return. + * @param heigth The height return. + */ +void GetWindowMenuSize(struct ClientNode *np, int *width, int *height); + +/** Show a window menu. + * @param np The client for the window menu. + * @param x The x-coordinate of the menu (root relative). + * @param y The y-coordinate of the menu (root relative). + */ +void ShowWindowMenu(struct ClientNode *np, int x, int y); + +/** Grab the mouse to select a window. + * @param action The action to perform when a window is selected. + */ +void ChooseWindow(const MenuAction *action); + +#endif + diff --git a/src/x.xpm b/src/x.xpm new file mode 100644 index 0000000..1e9376c --- /dev/null +++ b/src/x.xpm @@ -0,0 +1,32 @@ +static char *x_xpm[]={ +"28 28 2 1", +". c None", +"# c #ff0000", +"............................", +"............................", +"............................", +"............................", +".########..............##...", +"..########............##....", +"...########..........##.....", +"....########........##......", +".....########......##.......", +"......########....##........", +".......########..##.........", +"........######..##..........", +".........####..##...........", +"..........##..####..........", +".........##..######.........", +"........##..########........", +".......##....########.......", +"......##......########......", +".....##........########.....", +"....##..........########....", +"...##............########...", +"..##..............########..", +".##................########.", +"............................", +"............................", +"............................", +"............................", +"............................"}; diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..d4f7671 --- /dev/null +++ b/todo.txt @@ -0,0 +1,799 @@ + +Todo: + - Support multiple root menus. + - Add window list root menu item. + - Make the window menu configurable (other languages, order, etc.). + - Add mouse bindings. + - Add the ability to move windows via the pager. + - Add the ability to change desktops with the mouse pointer. + - Add the ability to drag windows to different desktops. + +20061222: + - Fix a bug where moving windows could cause the window to jump + to the upper left corner of the screen. + - Fix Dock when restarting. + - Fix a Swallow on startup. + - Focus transients of active windows when they appear. + - Make submenus appear on the left if there isn't enough room on + the right (mdsama). + +20061104: + - Improved _NET_WM_STATE_FULLSCREEN support. + - Fixed StartupCommands and Swallow items. + - Released v1.8rc4. + +20061027: + - Trim leading and trailing space from configuration options. + +20061023: + - Fix some seg faults with invalid configuration files. + +20061022: + - Fix an issue with auto-hide trays hidding when they shouldn't. + - Released v1.8rc3. + +20061019: + - Fix a bug that caused windows to be mapped incorrectly when started + at the same time as JWM. + +20061018: + - Fix a bug that caused a seg fault after a client was killed. + +20061008: + - Released v1.8rc2. + +20061001: + - More warning messages about incorrectly configured tray buttons and + key bindings that specify a root menu. + +20060826: + - Add support for _NET_WM_STATE_FULLSCREEN. + +20060819: + - Unmaximize maximized windows that resize themselves. + +20060817: + - Hide popups if the mouse moves over them. + - Fix alt+left click window moving so windows don't jump. + - Allow windows to be lowered with alt+right click. + - When maximizing, place the window on the screen of its center point. + +20060813: + - Fix key bindings to keycodes. + - Released v1.8rc1. + - Fix seg fault caused by tray buttons pointing to invalid root menus. + +20060810: + - Add RestartCommand. + +20060808: + - Allow window operations in root menus. + +20060807: + - Reduce flicker in the border by clipping the areas that need redrawing. + - Support for multiple root menus. + +20060805: + - Fix click propagation through popups. + +20060731: + - Improve aspect ratio resizing. + +20060728: + - Add the ability to move a window with alt+click (Jeremy Reed). + - Fix a bug with raising shaded windows from a task list. + - Preserve the shaded status when minimizing. + +20060704: + - Remove "Icons" tag. + - Add stack allocations. + - Use mouseClickDelta for checking menu selection mode. + - Support multiple startup/shutdown commands. + +20060427: + - Released v1.7. + +20060425: + - Fixed clock redrawing issue. + - Fixed Tray valign attribute. + - Don't un-maximize on a single click to the title bar. + +20060423: + - Released v1.6. + +20060422: + - Scroll menus that are too big for the screen. + - Moving a maximized window now un-maximizes it. + - Resizing a maximized window now un-maximizes it (lior2b). + - Left clicking on a window icon now shows the window menu (lior2b). + - Clock draw optimization (lior2b). + +20060418: + - New window buttons (lior2b). + - New minimized icon (lior2b). + - Put brackets around minimized items (lior2b). + +20060408: + - A second click on a "showdesktop" button will restore minimized windows. + +20060407: + - Scrollwheel now switches windows when over the task list. + - Scrollwheel now switches desktops over the root window. + - Scrollwheel now shades/unshades when over title bars. + - Double clicking now maximizes/restores when over title bars. + - Fix bug with key masks. + +20060402: + - Fix a key binding issue with keycode. + - Fix a bug involving maximized windows and the clock. + - Decreased the default popup delay to 600 ms. + - Added a 3 pixel border to popups. + +20060318: + - Fix key binding issue on shutdown/restart (lior2b). + +20060317: + - Fix a bug related to removing Dock and restarting. + - Don't pause waiting for Swallow items. + - Fix FriBidi UTF conversion (lior2b). + +20060315: + - Fix snap-to-screen with Xinerama. + - Make it possible to fix the width of the clock. + - Added "coordinates" attribute to MoveMode and ResizeMode. + +20060314: + - Fix a Xinerama window placement issue. + - Fix shading of shaped windows. + +20060313: + - Fix a bug causing JWM to lose the state of withdrawn windows. + +20060312: + - Ignore caps lock and num lock for key bindings. + - Released v1.5. + +20060311: + - Added popup for TrayButton. + - The clock now resizes itself as needed. + - Fixed window maximization with tray autohide. + - Fixed tray autohide with window menu and root menus. + - Added "enabled" and "delay" PopupStyle attributes. + - Fixed a bug in the way JWM handled shape events. + +20060310: + - Much faster color allocation. + - Constrain client requested resizes. + +20060305: + - Use ResizeRedirect instead of ConfigureNotify for resizing swallow items. + - Resize to 1 pixel in the tray when a swallow item dies. + - Support right-to-left text using FriBidi. + +20060304: + - Now to run a program from TrayButton, the program needs to be preceeded + with "exec:". + - Added a "showdesktop" action for TrayButton. This will minimize all + programs on the current desktop. + - Support for UTF-8. + - Added support for resizing of swallowed clients (lior2b). + - Allow "keycode" to be specified instead of "key" for key bindings. + +20060226: + - Fixed an issue with menu includes. + - Patch, v1.4p1. + +20060208: + - Added "valign" and "halign" attributes for Tray. + - Released v1.4. + +20060203: + - Make tray menus popup in a more natural location. + - Made menu sizes specified in terms of the size of icons. + - Make the tray figure out its layout from its size if the layout + isn't given explicitly. + - Fixed swallow items getting more space than requested. + - Made Swallow more sane when an error is encountered. + - Now supports windows without a border, but with a title bar. + - Fixed a key binding issue on restart. + - Don't show X errors unless in debug mode. + +20060114: + - Added support for _NET_WM_WINDOW_TYPE_DOCK. + - Added support for sending _NET_CURRENT_DESKTOP to root. + - Added the "pignore" group option. + - Added the "maximized", "minimized", and "shaded" group options. + - Released v1.3. + +20060110: + - Added more descriptive error messages for configuration parsing. + +20060109: + - Fixed the centering of icons in tray buttons. + - Fixed a bug where TrayButtonStyle was being used for task lists. + - Handle really small window borders/buttons in a more sane manner. + - Make vertical trays size-to-fit. + - Fixed size computation of fixed-size trays. + - Center tray button text when no icon is present. + +20060108: + - Added Dock item for Tray. This adds support for programs to dock + in the tray via _NET_SYSTEM_TRAY_Sn. + +20060107: + - Added support for _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING. + - Added support for _NET_WM_STRUT and _NET_WM_STRUT_PARTIAL. + - Added support for _NET_MOVERESIZE_WINDOW. + +20060101: + - Updates to configure.in to check if _XOPEN_SOURCE can safely be used. + - Fix some compiler warnings. + - Grab keys for trays. + - Fix a potential issue with bad PNG icons. + - Improved loading of swallowed clients. + - Don't use alpha blending for icons on color depths less than 24 bits. + - Fix resize when resizing a window that specifies an aspect ratio. + - Now sends WM_DELETE_WINDOW to swallowed clients before exiting. + - Fixed a problem with swallowing some programs (notably GTK+ programs). + - Released v1.2. + +20051120: + - Added "nextstacked" key binding. + - Released v1.1. + +20051119: + - Added ClockStyle, TrayButtonStyle, and TrayStyle options. + - Now icon aspect ratios are preserved when resized. + +20051116: + - Added button border to TrayButtons. + - Added Clock. + +20051114: + - Fixed task list overflow. + - Fixed minimization on restart. + +20051113: + - Released v1.0. + +20051112: + - Make maximization work in a more sane manner. + +20051111: + - Focus next client in the stacking order when the active client is closed. + - Added "Desktops" root menu item. + - Fixed key actions with click-to-focus. + - Improved window placement. + +20051110: + - Now desktops can be named, changes to the configuration for this. + +20051109: + - Send ClientMessage instead of PropertyNotify for _JWM commands. + - More EWMH support. + - Fix label attribute for RootMenu. + - Added menu includes (rarsa). + - Fix a minor menu bug. + +20051106: + - Restore maximization status on restart. + +20051027: + - Fix byte-order issue with PNG images. + +20051026: + - Fix lockup issue when restoring transient windows. + - Added a separator to the window menu before kill/close. + +20051024: + - Fix 64-bit X server issues. + +20051016: + - Tray button can now execute external programs (or show the root menu). + +20051013: + - Support for vertical trays, pagers, and task lists. + +20051012: + - Added the ability to swallow applications in the tray. + +20051010: + - Overhaul of the tray. Multiple trays now supported. + [The configuration file changed] + +20051007: + - Large windows are now handled in a more sane manner. + +20051003: + - Flush the X connection before closing it. + - Make status windows show on the screen with the mouse. + - Fixed an off-by-one error drawing the load. + +20051001: + - Can now use XRender for rendering icons. + - Added support for PNG icons (optional). + - Icons are now scaled independently for title bars and the task bar. + - Added "height" attribute to RootMenu and Menu. + +20050925: + - Now uses Xft for antialiasing, which can be disabled at compile time. + - Made drawing of border double-buffered. + +20050924: + - Added "enabled" option to Pager. + - Fixed a bug with loading icons. + - Attempted to fix color issues on 64-bit X-Servers. + +20050922: + - Added noborder, border, notitle, and title options to Group. + - Added "layer" attribute to Tray. + - Now restacks the clients after startup. + - Released v0.24. + +20050920: + - Added an "enabled" option to Load. + - Added an "enabled" option to Clock. + +20050915: + - Fixed the tray using the wrong colors for the button outlines. + - Fixed the confirm dialog using the wrong color for the background. + +20050913: + - Added the "tarball" option to make and made "distclean" do more cleaning. + +20050905: + - Fixed (?) mouse clicks going through some windows. + +20050904: + - "make install" no longer installs a .jwmrc to $HOME. + - Fixed a bug with moving shaded windows with snapping. + +20050901: + - Updated the man page with many changes from Joe Wiles. + - Made restarting and exiting more responsive. + +20050828: + - Fixed a bug related to stacking order with "click" focus. + - Added the ability to restart and exit JWM by sending the _JWM_RESTART + and _JWM_EXIT hints respectively. + - Added the ability to have "jwm" send _JWM_RESTART and _JWM_EXIT via + the -restart and -exit command line options respectively. + +20050826: + - Added the ability to have menu labels with labeled="true". + - Added the ability to disable clicking the root to show the + root menu with onroot="false". + - Added some Xinerama support. + - Added StartupCommand and ShutdownCommand to the configuration as + commands to be run when JWM starts and stops respectively. + - Added a slight border to the tray. + +20050803: + - Fixed memory leaks that happen when JWM is unable to start. + +20050524: + - Changed "VERISON=" to "VERSION=" in the slackware Makefile.in. + +20050522: + - Added key bindings for "exit" and "restart". + - Added the ability to parse environment variables within "Include" tags. + - Released v0.23. + +20050520: + - Fixed menu alignment problem on empty desktops (toomyem). + - Fixed --disable-confirm. + - Fixed problem with the entire tray not showing up when JWM is started + with no windows. + - Now menus that are too big to fit on the screen will go over the + task bar. + +20050423: + - Now maximizing a window takes advantage of the whole screen if + the tray is set to auto hide (Michael Rogers). + - Improved startup/shutdown order. This fixes a intermittent bug + that could cause a crash on restart or exit. + - Added the ability to specify a clock format. + +20050328: + - Fixed compile-time warning in border.c. + - Added the "nolist" group option. + - Fixed memory leak in icon.c. + +20050327: + - Fixed icon loading in menus when the icon is nonexistent. + +20050206: + - Changed menu icons so they are no longer scaled. + - Added the ability to specify a max width for tray items. + - Added the ability to specify how items are added to the tray. + +20050205: + - Added a configuration option to disable the exit confirm dialog. + - Fixed a minor error in the calculation of the load bars for the + load graph. + +20050117: + - Fixed menu offsets when submenus are below a separator. + - Added a compile-time option to disable confirm dialogs for + exiting and killing windows. + +20050112: + - Released v0.21. + +20050110: + - Improved the icon support to be more platform independent. + - Added a group option: "icon:". + +20050107: + - Now makes the directory for system.jwmrc if it doesn't already + exist for "make install". + +20050106: + - Released v0.20. + +20050103: + - Added "Width" and "Alignment" options for the tray. + - Added the ability to disable the "Start" button by specifying an + empty label without an icon. + +20041231: + - Added icon support. + +20041215: + - Released v0.19. + +20041214: + - Added support for _NET_WM_WINDOW_TYPE_DESKTOP. This allows graphical + file managers such as Nautilus to control the root. + +20041210: + - Added the option to move and/or resize with only an outline. + - Added the ability to start another window manager via the exit + menu item. + +20041207: + - Added group option for layer and desktop. + +20041203: + - Removed the dependence on Xm/MwmUtil.h. + - Exit and Restart menu items can now have different labels. + - "FocusNext" no longer focuses minimized or shaded windows. + - Tray is now one pixel when hidden instead of two. + +20041201: + - Added the ability to change the root menu button label. + - The clock is now the correct width. + - Minor fix to the snap-to-border algorithm. + - Clicking a tray button now only minimizes the client if it is + at the top level of its layer (as well as active). + +20041128: + - Added program groups based on title and class. Sticky option supported. + - Fixed the problem with long window titles running into the buttons. + +20041127: + - Released v0.18. + +20041126: + - Fixed font antialiasing with 8-bit color. + - Now skips out-dated mouse motion events. + - Added "exec:" key binding. + - No longer double-buffers drawing borders. + - The load status display's width is now proportional to the + tray height. + +20041125: + - Fixed the problem with text overflowing with 'antialias="false"' + +20041113: + - Added snap-to-border snap mode ("border" option). + - Fixed a problem with restarting JWM that caused borders to not + be redrawn. + - Fixed an error in the calculation of time differences. + +20041030: + - Fixes to click-to-focus model (Terry Loveall). + - FocusNext now skips transients. + +20041029: + - Added configuration options for snap mode and and snap distance. + +20041024: + - Snap to edge of screen implemented for moving windows (Terry Loveall). + +20041010: + - Fixed time format on the clock popup. + +20041009: + - Released v0.17. + +20041003: + - Added configurable popup status windows to the tray. + - Fixed an issue with key bindings. + +20040926: + - Added the ability to build IRIX tardists to configure. + - Fixed (?) an issue with minimizing windows with unmapped transients. + - Created a man page. + +20040923: + - Window placement for windows with an unspecified starting position + now attempts to cascade windows. + +20040922: + - Added the option to run a program when the load status is clicked + or when the clock is clicked. + +20040919: + - Released v0.16. + +20040918: + - Changed behavior of "FocusNext" so it no longer raises minimized + windows. (Suggested by Terry Loveall.) + +20040914: + - Added the option for "click to focus" (Terry Loveall). + - Added configuration option for focus model: "click" or "sloppy". + - Added "autohide" option for the tray (Terry Loveall). + +20040907: + - Fixed a bug in computing the colors for antialiasing. + +20040905: + - Fixed another layering issue. + +20040831: + - Fixed a layering issue that could crash JWM. + - Improved the configure script. + - Released v0.15. + +20040828: + - Fixed a potential bug in lex.c. + +20040823: + - Improved antialiasing to use fewer colormap entries. + +20040822: + - Improved configuration to be cleaner and allow more options. + +20040821: + - Added support for WM_COLORMAP_WINDOWS. + +20040820: + - Fixed the configure script to recognize platforms without GNU tr. + +20040803: + - Released v0.14. + +20040802: + - Added a configuration option for the height of the tray. + +20040801: + - Minimized windows now have a small icon instead of brackets. + - Improved drawing of borders. + - No longer shows marks on shaded window borders. + - Fixed behaviour of cursor over a shaded frame. + +20040731: + - Fixed mouse cursor issue with some applications (xpdf). + - Optimized drawing of border buttons. + +20040730: + - Mouse scroll wheel can now scroll through desktops when over the pager. + +20040718: + - Improved handling of Expose events. + +20040717: + - Released v0.13. + +20040716: + - Improved layer support. + +20040715: + - Improved the speed of interal window lookups. + +20040713: + - Fixed a few bugs related to Configure events. + - More hint support. + +20040709: + - Cleaned up/fixed hint stuff. This fixes many problems. + +20040705: + - Fixed reading of the _NET_WM_STATE hint. + +20040630: + - Improved the look of the move/resize window. + +20040626: + - Fixed loading of a default configuration file when a local one is + not found. + - Released v0.12 + +20040625: + - Fixed a bug which caused high CPU loads (PropertyNotify loop). + +20040611: + - Cleaned up window hints stuff. Still more to do. + - Changed the color of the "JWM" button. + +20040610: + - Now debug mode compiles with -pedantic and -ansi. + - Made the menus look 3d. + +20040609: + - Added debug checkpoints for Xlib functions. + +20040608: + - No longer displays title buttons if they won't fit. + +20040602: + - Improved resizing so windows aren't redrawn unnecessarily. + - Now accepts PropertyNotify for WM_PROTOCOLS hint. + - Now makes an extra attempt at sending WM_DELETE_WINDOW before + resorting to killing the client. + - Fix behavior for move on title bars without any buttons. + +20040530 + - Added a confirm dialog for killing a window and for closing windows + that don't listen for the WM_DELETE_WINDOW hint. + - Added a confirm dialog for exiting JWM. + +20040528: + - No longer shows marks on the edges of windows that can't be resized. + +20040525: + - Fixed "make install" + - Fixed an off-by-one-pixel problem when drawing the tray. + +20040519: + - Fixed a bug in the menu code. + - Added load status support for MacOS X. + - Released v0.11. + +20040518: + - Fixed a type consistancy issue in font.c. + - Improved menu selections over slow X11 connections. + +20040516: + - Fixed the load/time so that it no longer flickers. + +20040514: + - Made the separator on the menus look better. + - Improved shape extension support, still some issues. + - Now configure does a proper check for MwmUtils.h. + - Added a default configuration file in a standard location for + users that don't have a local configuration file. + - Released v0.10. + +20040513: + - Mouse now activates window buttons on release rather than press. + - Handle expose event on menus. + +20040511: + - Now correctly grabs the root window and tray. + - Can now change desktops with [modifiers]+[number] ('#'). + - Improved the way colors for the border outlines are calculated. + - Changed the look of the pager. + +20040510: + - Fixed a compiler warning in event.c + - Added option to enable antialiasing in the configuration file. + - Added the ability to change the height of the title bar. + - Added arrows to indicate submenus. + - Now menus listen for a button release rather than a button press. + - Fixed a stacking problem when a window was above the tray in + the stacking order. + - Can now use the scroll wheel to move through menus. + +20040509: + - Now restacks clients after changing desktops. + - Correctly updates the "sticky" desktop hint on client windows. + - Reads the current desktop hint from the root window at startup. + - Added text antialiasing. + +20040504: + - Can now use the mouse to move windows when using the keyboard. + - Can now use the mouse to resize windows when using the keyboard. + - Now hides the menu before executing a menu command. + - Released v0.9. + +20040502: + - Added "Kill" option to the window menu. + - Removed some unnecessary code. + - Improved memory usage for window stacking. + +20040427: + - Added the ability to map keys to window functions. + +20040424: + - Added the ability to resize/move windows with the keyboard. + +20040423: + - Added load status support for Solaris. + +20040420: + - Added the ability to shade/unshade windows (double click title). + - Added a configuration option for border size. + - Fixed a bug when a ConfigureRequest is sent to a shaped window. + +20040408: + - Now supports internal XML entities. + - Fixed window title overridding the title buttons. + - Fixed submenu behavior when mouse is on the edge of the parent menu. + - Released v0.8. + +20040329: + - Fixed(?) window gravity. + +20040325: + - Fixed a focus problem after displaying menus. + +20040323: + - Now restacks after a new window is mapped. + - Impoved move/resize/menu so that the time/load updates. + +20040304: + - Released v0.7. + +20040303: + - Fixed an off-by-one error when calculating the border action type. + - Fixed a potential error when a window becomes unmanaged. + - Fixed stacking order on startup/restart/exit. + - Fixed mouse cursor behavior with some programs (swmgr). + - Fixed startup/restart not focusing the window under the mouse. + - Should now be able to manage screens other than 0. + +20040229: + - Fixed a bug in the configuration lexer. + - Now only mouse buttons 1,2,3 will raise a window. + +20040228: + - Fixed a stacking bug related to transient windows. + +20040226: + - Resize now resizes the window as you move the mouse. + +20040225: + - Now supports aspect ratios for resizing windows. + +20040114: + - libXpm is no longer needed. + - Added load status support for Linux. + - Fixed a bug in the configuration lexer. + - Released v0.6. + +20040112: + - New window decorations. + - Fixed most XErrors. + - Improved shape extension support. + +20040111: + - Now JWM uses autoconf. + +20040110: + - Bug fixes. + - Released v0.5. + +20040109: + - Added some support for GNOME hints. + - Added support for window layers. + +20040106: + - Fixed a bug involving window stacking when switching desktops. + - Made desktop-switching "more" ICCCM compliant. + +20040105: + - Added a graphical pager. + +20040105: + - Minor bug fixes. + - Released v0.4. + +20040104: + - Added "Alt+Tab" shortcut to switch windows. + - Added support for virtual destops + - Added a simple pager to the tray. + - Added a window menu. + |