From a041d9898e6d699bd8c0c25482ec574feb03c547 Mon Sep 17 00:00:00 2001 From: "John Ankarstr\\xf6m" Date: Sat, 29 May 2021 12:54:47 +0200 Subject: 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. --- Doxyfile | 1237 +++++++++++++++++++++++++++++ LICENSE | 340 ++++++++ Makefile.in | 53 ++ README | 24 + config.h.in | 145 ++++ configure.in | 388 ++++++++++ example.jwmrc | 161 ++++ jwm.1.in | 1185 ++++++++++++++++++++++++++++ package/irix/Makefile.in | 18 + package/irix/jwm.idb.in | 3 + package/irix/jwm.spec.in | 23 + package/slackware/Makefile.in | 27 + package/slackware/slack-desc.in | 20 + package/slackware/slackware.jwmrc | 111 +++ package/solaris/Makefile.in | 28 + package/solaris/pkginfo.in | 9 + package/solaris/prototype.in | 4 + package/solaris/solaris.jwmrc | 111 +++ src/Makefile.in | 38 + src/border.c | 753 ++++++++++++++++++ src/border.h | 80 ++ src/button.c | 212 +++++ src/button.h | 63 ++ src/client.c | 1423 ++++++++++++++++++++++++++++++++++ src/client.h | 285 +++++++ src/clock.c | 332 ++++++++ src/clock.h | 41 + src/color.c | 606 +++++++++++++++ src/color.h | 91 +++ src/command.c | 124 +++ src/command.h | 36 + src/confirm.c | 412 ++++++++++ src/confirm.h | 36 + src/cursor.c | 313 ++++++++ src/cursor.h | 44 ++ src/debug.c | 396 ++++++++++ src/debug.h | 104 +++ src/desktop.c | 239 ++++++ src/desktop.h | 60 ++ src/dock.c | 602 ++++++++++++++ src/dock.h | 43 + src/error.c | 108 +++ src/error.h | 38 + src/event.c | 1138 +++++++++++++++++++++++++++ src/event.h | 28 + src/font.c | 295 +++++++ src/font.h | 43 + src/group.c | 299 +++++++ src/group.h | 48 ++ src/help.c | 84 ++ src/help.h | 26 + src/hint.c | 970 +++++++++++++++++++++++ src/hint.h | 173 +++++ src/icon.c | 726 +++++++++++++++++ src/icon.h | 110 +++ src/image.c | 362 +++++++++ src/image.h | 47 ++ src/jwm.h | 128 +++ src/jxlib.h | 420 ++++++++++ src/key.c | 452 +++++++++++ src/key.h | 57 ++ src/lex.c | 572 ++++++++++++++ src/lex.h | 115 +++ src/main.c | 507 ++++++++++++ src/main.h | 56 ++ src/match.c | 80 ++ src/match.h | 21 + src/menu.c | 799 +++++++++++++++++++ src/menu.h | 89 +++ src/misc.c | 196 +++++ src/misc.h | 37 + src/move.c | 729 +++++++++++++++++ src/move.h | 61 ++ src/outline.c | 73 ++ src/outline.h | 32 + src/pager.c | 331 ++++++++ src/pager.h | 27 + src/parse.c | 1551 +++++++++++++++++++++++++++++++++++++ src/parse.h | 19 + src/place.c | 647 ++++++++++++++++ src/place.h | 34 + src/popup.c | 232 ++++++ src/popup.h | 35 + src/render.c | 226 ++++++ src/render.h | 25 + src/resize.c | 491 ++++++++++++ src/resize.h | 42 + src/root.c | 315 ++++++++ src/root.h | 65 ++ src/screen.c | 138 ++++ src/screen.h | 53 ++ src/status.c | 274 +++++++ src/status.h | 55 ++ src/swallow.c | 274 +++++++ src/swallow.h | 43 + src/taskbar.c | 936 ++++++++++++++++++++++ src/taskbar.h | 64 ++ src/theme.c | 88 +++ src/theme.h | 31 + src/timing.c | 71 ++ src/timing.h | 43 + src/tray.c | 1104 ++++++++++++++++++++++++++ src/tray.h | 214 +++++ src/traybutton.c | 454 +++++++++++ src/traybutton.h | 51 ++ src/winmenu.c | 327 ++++++++ src/winmenu.h | 37 + src/x.xpm | 32 + todo.txt | 799 +++++++++++++++++++ 109 files changed, 28267 insertions(+) create mode 100644 Doxyfile create mode 100644 LICENSE create mode 100644 Makefile.in create mode 100644 README create mode 100644 config.h.in create mode 100644 configure.in create mode 100644 example.jwmrc create mode 100644 jwm.1.in create mode 100644 package/irix/Makefile.in create mode 100644 package/irix/jwm.idb.in create mode 100644 package/irix/jwm.spec.in create mode 100644 package/slackware/Makefile.in create mode 100644 package/slackware/slack-desc.in create mode 100644 package/slackware/slackware.jwmrc create mode 100644 package/solaris/Makefile.in create mode 100644 package/solaris/pkginfo.in create mode 100644 package/solaris/prototype.in create mode 100644 package/solaris/solaris.jwmrc create mode 100644 src/Makefile.in create mode 100644 src/border.c create mode 100644 src/border.h create mode 100644 src/button.c create mode 100644 src/button.h create mode 100644 src/client.c create mode 100644 src/client.h create mode 100644 src/clock.c create mode 100644 src/clock.h create mode 100644 src/color.c create mode 100644 src/color.h create mode 100644 src/command.c create mode 100644 src/command.h create mode 100644 src/confirm.c create mode 100644 src/confirm.h create mode 100644 src/cursor.c create mode 100644 src/cursor.h create mode 100644 src/debug.c create mode 100644 src/debug.h create mode 100644 src/desktop.c create mode 100644 src/desktop.h create mode 100644 src/dock.c create mode 100644 src/dock.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/event.c create mode 100644 src/event.h create mode 100644 src/font.c create mode 100644 src/font.h create mode 100644 src/group.c create mode 100644 src/group.h create mode 100644 src/help.c create mode 100644 src/help.h create mode 100644 src/hint.c create mode 100644 src/hint.h create mode 100644 src/icon.c create mode 100644 src/icon.h create mode 100644 src/image.c create mode 100644 src/image.h create mode 100644 src/jwm.h create mode 100644 src/jxlib.h create mode 100644 src/key.c create mode 100644 src/key.h create mode 100644 src/lex.c create mode 100644 src/lex.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/match.c create mode 100644 src/match.h create mode 100644 src/menu.c create mode 100644 src/menu.h create mode 100644 src/misc.c create mode 100644 src/misc.h create mode 100644 src/move.c create mode 100644 src/move.h create mode 100644 src/outline.c create mode 100644 src/outline.h create mode 100644 src/pager.c create mode 100644 src/pager.h create mode 100644 src/parse.c create mode 100644 src/parse.h create mode 100644 src/place.c create mode 100644 src/place.h create mode 100644 src/popup.c create mode 100644 src/popup.h create mode 100644 src/render.c create mode 100644 src/render.h create mode 100644 src/resize.c create mode 100644 src/resize.h create mode 100644 src/root.c create mode 100644 src/root.h create mode 100644 src/screen.c create mode 100644 src/screen.h create mode 100644 src/status.c create mode 100644 src/status.h create mode 100644 src/swallow.c create mode 100644 src/swallow.h create mode 100644 src/taskbar.c create mode 100644 src/taskbar.h create mode 100644 src/theme.c create mode 100644 src/theme.h create mode 100644 src/timing.c create mode 100644 src/timing.h create mode 100644 src/tray.c create mode 100644 src/tray.h create mode 100644 src/traybutton.c create mode 100644 src/traybutton.h create mode 100644 src/winmenu.c create mode 100644 src/winmenu.h create mode 100644 src/x.xpm create mode 100644 todo.txt 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 , where is the value of +# the FILE_VERSION_FILTER tag, and 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 , where +# is the value of the INPUT_FILTER tag, and 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/LICENSE @@ -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. + + + Copyright (C) + + 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. + + , 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 + diff --git a/README b/README new file mode 100644 index 0000000..ced403b --- /dev/null +++ b/README @@ -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 header file. */ +#undef HAVE_ALLOCA_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_CTYPE_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FT2BUILD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SIGNAL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDARG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDIO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_WAIT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_TIME_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_CURSORFONT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_EXTENSIONS_XRENDER_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_KEYSYM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_XATOM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_XLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_XPM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_XPROTO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_X11_XRESOURCE_H + +/* Define to 1 if you have the 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 +]])], [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 + ]) + +############################################################################ +# 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 +]) +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 @@ + + + + + + + + xterm + + + + dia + firefox + gaim + gftp + gimp + + gtk-gnutella + + gxine + xmms + + + xcalc + xfontsel + xmag + + xprop | xmessage -file - + + + + + + + + + + Gaim + + + + + xmms + + + + + + + + + + + + + + + + + + xload -nolabel -bg black -fg red -hl white + + xclock + + + + + + FreeSans-12:bold + 5 + 20 + black + gray90 + white + #4A5966 + + + + FreeSans-12:bold + white + #8899A6 + + + + + FreeSans-12:bold + gray90 + black + + + + black + gray90 + #888888 + #8899AA + #3A4956 + + + + FreeSans-12:bold + black + gray90 + white + #3A4956 + + + + FreeSans-10 + black + black + yellow + + + $HOME/.icons + + + xli -onroot /export0/images/formulae.jpg + + + + + + + + 400 + + + 2 + + + sloppy + + + border + + + opaque + + + opaque + + + up + down + right + left + left + down + up + right + select + escape + + next + close + desktop# + root:1 + window + + + 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 + +.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 @@ + + + + + + + xterm + + mozilla + $HOME/bin/fm + gaim + gimp + xmms + + + xfontsel + xmag + + + /usr/games/doom/doom + /usr/games/doom2/doom2 + /usr/games/hacknoid/run + /usr/games/mines/run + /usr/games/quake2/quake2 + + + + + + + + -sgi-rock-*-*-*-*-18-*-*-*-*-*-*-* + 5 + 20 + black + #DCDAD5 + white + #3A4956 + + + + -sgi-rock-*-*-*-*-18-*-*-*-*-*-*-* + 28 + black + #DCDAD5 + black + #8899AA + + + + black + #DCDAD5 + #888888 + #8899AA + #3A4956 + + + + xload + black + red + #DCDAD5 + + + + xclock + + + + -sgi-screen-*-r-*-*-15-*-*-*-*-*-*-* + black + black + yellow + + + + -sgi-screen-*-r-*-*-15-*-*-*-*-*-*-* + black + #DCDAD5 + white + #3A4956 + + + + 4 + + + 400 + + + 2 + + + sloppy + + + up + down + right + left + select + escape + + next + close + desktop# + root + window + + + 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 @@ + + + + + + + xterm + + mozilla + $HOME/bin/fm + gaim + gimp + xmms + + + xfontsel + xmag + + + /usr/games/doom/doom + /usr/games/doom2/doom2 + /usr/games/hacknoid/run + /usr/games/mines/run + /usr/games/quake2/quake2 + + + + + + + + -sgi-rock-*-*-*-*-18-*-*-*-*-*-*-* + 5 + 20 + black + #DCDAD5 + white + #3A4956 + + + + -sgi-rock-*-*-*-*-18-*-*-*-*-*-*-* + 28 + black + #DCDAD5 + black + #8899AA + + + + black + #DCDAD5 + #888888 + #8899AA + #3A4956 + + + + xload + black + red + #DCDAD5 + + + + xclock + + + + -sgi-screen-*-r-*-*-15-*-*-*-*-*-*-* + black + black + yellow + + + + -sgi-screen-*-r-*-*-15-*-*-*-*-*-*-* + black + #DCDAD5 + white + #3A4956 + + + + 4 + + + 400 + + + 2 + + + sloppy + + + up + down + right + left + select + escape + + next + close + desktop# + root + window + + + 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 = 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 +#include +#include +#include +#ifdef HAVE_ALLOCA_H +# include +#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 +#include +#include +#include + +#ifdef HAVE_STDARG_H +# include +#endif +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_TIME_H +# include +#endif +#ifdef HAVE_SYS_WAIT_H +# include +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_SELECT_H +# include +#endif + +#include +#ifdef HAVE_X11_XUTIL_H +# include +#endif +#ifdef HAVE_X11_XRESOURCE_H +# include +#endif +#ifdef HAVE_X11_CURSORFONT_H +# include +#endif +#ifdef HAVE_X11_XPROTO_H +# include +#endif +#ifdef HAVE_X11_XATOM_H +# include +#endif +#ifdef HAVE_X11_KEYSYM_H +# include +#endif + +#ifdef USE_XPM +# include +#endif +#ifdef USE_PNG +# include +#endif +#ifdef USE_SHAPE +# include +#endif +#ifdef USE_XINERAMA +# include +#endif +#ifdef USE_XFT +# ifdef HAVE_FT2BUILD_H +# include +# endif +# include +#endif +#ifdef USE_XRENDER +# include +#endif +#ifdef USE_FRIBIDI +# include +# include +#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)) { + x += 2; + break; + } + ++x; + } + } + + while(line[x]) { + + do { + + while(IsSpace(line[x], &lineNumber)) ++x; + + /* Skip comments */ + found = 0; + 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. + -- cgit v1.2.3